install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/gof-composite-pattern" ~/.claude/skills/intense-visions-harness-engineering-gof-composite-pattern-62a44f && rm -rf "$T"
manifest:
agents/skills/codex/gof-composite-pattern/SKILL.mdsource content
GOF Composite Pattern
Compose objects into tree structures and treat individual and composite objects uniformly.
When to Use
- You need to represent a part-whole hierarchy (file system, org chart, menu tree, AST)
- Client code should be able to treat leaf nodes and composite nodes identically
- You need to apply an operation recursively across an entire tree structure
- You want to build flexible tree structures without client code knowing whether it's a leaf or branch
Instructions
Core structure — Component interface, Leaf, Composite:
// Component — uniform interface for both leaves and composites interface FileSystemItem { name: string; size(): number; print(indent?: string): void; } // Leaf — no children class File implements FileSystemItem { constructor( public readonly name: string, private readonly bytes: number ) {} size(): number { return this.bytes; } print(indent = ''): void { console.log(`${indent}📄 ${this.name} (${this.bytes}B)`); } } // Composite — has children, delegates to them class Directory implements FileSystemItem { private children: FileSystemItem[] = []; constructor(public readonly name: string) {} add(item: FileSystemItem): this { this.children.push(item); return this; } remove(item: FileSystemItem): void { this.children = this.children.filter((c) => c !== item); } size(): number { // Recursion handled uniformly — leaves and composites both have size() return this.children.reduce((total, child) => total + child.size(), 0); } print(indent = ''): void { console.log(`${indent}📁 ${this.name}`); for (const child of this.children) { child.print(indent + ' '); } } } // Client code treats everything as FileSystemItem function printSummary(item: FileSystemItem): void { item.print(); console.log(`Total size: ${item.size()}B`); } // Build the tree const root = new Directory('project') .add(new File('package.json', 512)) .add(new File('tsconfig.json', 256)) .add( new Directory('src') .add(new File('index.ts', 1024)) .add(new Directory('utils').add(new File('logger.ts', 768))) ); printSummary(root);
Permission tree (practical RBAC example):
interface Permission { name: string; check(userId: string, action: string): boolean; } class AtomicPermission implements Permission { constructor( public readonly name: string, private readonly resource: string ) {} check(userId: string, action: string): boolean { // Real implementation would check DB return action === this.resource; } } class PermissionGroup implements Permission { private permissions: Permission[] = []; constructor(public readonly name: string) {} add(permission: Permission): this { this.permissions.push(permission); return this; } // OR semantics: granted if any child grants it check(userId: string, action: string): boolean { return this.permissions.some((p) => p.check(userId, action)); } } const adminRole = new PermissionGroup('admin') .add(new AtomicPermission('read-users', 'users')) .add(new AtomicPermission('write-users', 'users')) .add( new PermissionGroup('billing') .add(new AtomicPermission('read-invoices', 'invoices')) .add(new AtomicPermission('write-invoices', 'invoices')) );
Details
TypeScript discriminated union alternative: For simpler cases, a discriminated union often beats a class hierarchy:
type TreeNode = | { kind: 'leaf'; name: string; value: number } | { kind: 'branch'; name: string; children: TreeNode[] }; function sum(node: TreeNode): number { if (node.kind === 'leaf') return node.value; return node.children.reduce((acc, child) => acc + sum(child), 0); }
Anti-patterns:
- Composite that exposes
/add
on the Component interface — leaves can't implement these; use optional methods or a type guardremove - Circular references in the tree — add parent tracking and validate on
add() - Excessive depth causing stack overflow in recursive operations — add iteration-based traversal for very deep trees
Traversal strategies:
function* depthFirst(item: FileSystemItem): Generator<FileSystemItem> { yield item; if (item instanceof Directory) { for (const child of item.getChildren()) { yield* depthFirst(child); } } } // Count all files const fileCount = [...depthFirst(root)].filter((item) => item instanceof File).length;
Source
refactoring.guru/design-patterns/composite
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.