workspace-maxxing 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/workspace-maxxing/.workspace-templates/CONTEXT.md +44 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/SYSTEM.md +44 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/references/anti-patterns.md +16 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/references/iron-laws.md +26 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/references/reporting-format.md +52 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/benchmark.ts +171 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/dispatch.ts +473 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/generate-tests.ts +158 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/install-tool.ts +82 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/iterate.ts +265 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/orchestrator.ts +539 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/scaffold.ts +282 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/scripts/validate.ts +452 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/architecture/SKILL.md +95 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/fixer/SKILL.md +109 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/iteration/SKILL.md +89 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/prompt-engineering/SKILL.md +87 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/research/SKILL.md +94 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/testing/SKILL.md +89 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/tooling/SKILL.md +87 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/validation/SKILL.md +103 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/skills/worker/SKILL.md +79 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/workspace/00-meta/CONTEXT.md +6 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/workspace/00-meta/execution-log.md +27 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/workspace/01-input/CONTEXT.md +29 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/workspace/02-process/CONTEXT.md +29 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/workspace/03-output/CONTEXT.md +29 -0
- package/.agents/skills/workspace-maxxing/.workspace-templates/workspace/README.md +14 -0
- package/.agents/skills/workspace-maxxing/SKILL.md +312 -0
- package/.agents/skills/workspace-maxxing/scripts/benchmark.ts +171 -0
- package/.agents/skills/workspace-maxxing/scripts/dispatch.ts +473 -0
- package/.agents/skills/workspace-maxxing/scripts/generate-tests.ts +158 -0
- package/.agents/skills/workspace-maxxing/scripts/install-tool.ts +82 -0
- package/.agents/skills/workspace-maxxing/scripts/iterate.ts +265 -0
- package/.agents/skills/workspace-maxxing/scripts/orchestrator.ts +539 -0
- package/.agents/skills/workspace-maxxing/scripts/scaffold.ts +282 -0
- package/.agents/skills/workspace-maxxing/scripts/validate.ts +452 -0
- package/README.md +144 -0
- package/dist/agent-creator.d.ts +9 -0
- package/dist/agent-creator.d.ts.map +1 -0
- package/dist/agent-creator.js +199 -0
- package/dist/agent-creator.js.map +1 -0
- package/dist/agent-iterator.d.ts +38 -0
- package/dist/agent-iterator.d.ts.map +1 -0
- package/dist/agent-iterator.js +327 -0
- package/dist/agent-iterator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +18 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +117 -0
- package/dist/install.js.map +1 -0
- package/dist/platforms/claude.d.ts +7 -0
- package/dist/platforms/claude.d.ts.map +1 -0
- package/dist/platforms/claude.js +70 -0
- package/dist/platforms/claude.js.map +1 -0
- package/dist/platforms/copilot.d.ts +7 -0
- package/dist/platforms/copilot.d.ts.map +1 -0
- package/dist/platforms/copilot.js +75 -0
- package/dist/platforms/copilot.js.map +1 -0
- package/dist/platforms/gemini.d.ts +7 -0
- package/dist/platforms/gemini.d.ts.map +1 -0
- package/dist/platforms/gemini.js +81 -0
- package/dist/platforms/gemini.js.map +1 -0
- package/dist/platforms/index.d.ts +8 -0
- package/dist/platforms/index.d.ts.map +1 -0
- package/dist/platforms/index.js +41 -0
- package/dist/platforms/index.js.map +1 -0
- package/dist/platforms/opencode.d.ts +7 -0
- package/dist/platforms/opencode.d.ts.map +1 -0
- package/dist/platforms/opencode.js +70 -0
- package/dist/platforms/opencode.js.map +1 -0
- package/dist/scripts/benchmark.d.ts +20 -0
- package/dist/scripts/benchmark.d.ts.map +1 -0
- package/dist/scripts/benchmark.js +170 -0
- package/dist/scripts/benchmark.js.map +1 -0
- package/dist/scripts/dispatch.d.ts +32 -0
- package/dist/scripts/dispatch.d.ts.map +1 -0
- package/dist/scripts/dispatch.js +386 -0
- package/dist/scripts/dispatch.js.map +1 -0
- package/dist/scripts/generate-tests.d.ts +11 -0
- package/dist/scripts/generate-tests.d.ts.map +1 -0
- package/dist/scripts/generate-tests.js +118 -0
- package/dist/scripts/generate-tests.js.map +1 -0
- package/dist/scripts/install-tool.d.ts +8 -0
- package/dist/scripts/install-tool.d.ts.map +1 -0
- package/dist/scripts/install-tool.js +98 -0
- package/dist/scripts/install-tool.js.map +1 -0
- package/dist/scripts/iterate.d.ts +44 -0
- package/dist/scripts/iterate.d.ts.map +1 -0
- package/dist/scripts/iterate.js +260 -0
- package/dist/scripts/iterate.js.map +1 -0
- package/dist/scripts/orchestrator.d.ts +40 -0
- package/dist/scripts/orchestrator.d.ts.map +1 -0
- package/dist/scripts/orchestrator.js +378 -0
- package/dist/scripts/orchestrator.js.map +1 -0
- package/dist/scripts/scaffold.d.ts +8 -0
- package/dist/scripts/scaffold.d.ts.map +1 -0
- package/dist/scripts/scaffold.js +279 -0
- package/dist/scripts/scaffold.js.map +1 -0
- package/dist/scripts/validate.d.ts +11 -0
- package/dist/scripts/validate.d.ts.map +1 -0
- package/dist/scripts/validate.js +472 -0
- package/dist/scripts/validate.js.map +1 -0
- package/docs/superpowers/plans/2026-04-07-autonomous-iteration-plan.md +1123 -0
- package/docs/superpowers/plans/2026-04-07-autonomous-iteration-sub-agent-batches.md +1923 -0
- package/docs/superpowers/plans/2026-04-07-autonomous-workflow-sub-skill-plan.md +1505 -0
- package/docs/superpowers/plans/2026-04-07-benchmarking-multi-agent-plan.md +854 -0
- package/docs/superpowers/plans/2026-04-07-workspace-builder-logic-plan.md +1426 -0
- package/docs/superpowers/plans/2026-04-07-workspace-maxxing-plan.md +1299 -0
- package/docs/superpowers/plans/2026-04-08-session-294c-subagent-invocation-plan.md +320 -0
- package/docs/superpowers/plans/2026-04-08-workflow-prompt-hardening-plan.md +1025 -0
- package/docs/superpowers/plans/2026-04-12-workspace-agent-creation-plan.md +992 -0
- package/docs/superpowers/specs/2026-04-07-autonomous-iteration-design.md +214 -0
- package/docs/superpowers/specs/2026-04-07-autonomous-iteration-sub-agent-batches-design.md +188 -0
- package/docs/superpowers/specs/2026-04-07-autonomous-workflow-sub-skill-design.md +137 -0
- package/docs/superpowers/specs/2026-04-07-benchmarking-multi-agent-design.md +105 -0
- package/docs/superpowers/specs/2026-04-07-workspace-builder-logic-design.md +179 -0
- package/docs/superpowers/specs/2026-04-07-workspace-maxxing-design.md +227 -0
- package/docs/superpowers/specs/2026-04-08-session-294c-subagent-invocation-design.md +265 -0
- package/docs/superpowers/specs/2026-04-08-workflow-prompt-hardening-design.md +146 -0
- package/docs/superpowers/specs/2026-04-12-workspace-agent-creation-design.md +239 -0
- package/jest.config.js +8 -0
- package/package.json +32 -0
- package/src/agent-creator.ts +180 -0
- package/src/agent-iterator.ts +397 -0
- package/src/index.ts +189 -0
- package/src/install.ts +105 -0
- package/src/platforms/claude.ts +40 -0
- package/src/platforms/copilot.ts +50 -0
- package/src/platforms/gemini.ts +55 -0
- package/src/platforms/index.ts +45 -0
- package/src/platforms/opencode.ts +41 -0
- package/src/scripts/benchmark.ts +171 -0
- package/src/scripts/dispatch.ts +473 -0
- package/src/scripts/generate-tests.ts +112 -0
- package/src/scripts/install-tool.ts +82 -0
- package/src/scripts/iterate.ts +271 -0
- package/src/scripts/orchestrator.ts +539 -0
- package/src/scripts/scaffold.ts +282 -0
- package/src/scripts/validate.ts +516 -0
- package/templates/.workspace-templates/CONTEXT.md +44 -0
- package/templates/.workspace-templates/SYSTEM.md +44 -0
- package/templates/.workspace-templates/references/anti-patterns.md +16 -0
- package/templates/.workspace-templates/references/iron-laws.md +26 -0
- package/templates/.workspace-templates/references/reporting-format.md +52 -0
- package/templates/.workspace-templates/scripts/benchmark.ts +171 -0
- package/templates/.workspace-templates/scripts/dispatch.ts +473 -0
- package/templates/.workspace-templates/scripts/generate-tests.ts +158 -0
- package/templates/.workspace-templates/scripts/install-tool.ts +82 -0
- package/templates/.workspace-templates/scripts/iterate.ts +265 -0
- package/templates/.workspace-templates/scripts/orchestrator.ts +539 -0
- package/templates/.workspace-templates/scripts/scaffold.ts +282 -0
- package/templates/.workspace-templates/scripts/validate.ts +452 -0
- package/templates/.workspace-templates/skills/architecture/SKILL.md +95 -0
- package/templates/.workspace-templates/skills/fixer/SKILL.md +109 -0
- package/templates/.workspace-templates/skills/iteration/SKILL.md +89 -0
- package/templates/.workspace-templates/skills/prompt-engineering/SKILL.md +87 -0
- package/templates/.workspace-templates/skills/research/SKILL.md +94 -0
- package/templates/.workspace-templates/skills/testing/SKILL.md +89 -0
- package/templates/.workspace-templates/skills/tooling/SKILL.md +87 -0
- package/templates/.workspace-templates/skills/validation/SKILL.md +103 -0
- package/templates/.workspace-templates/skills/worker/SKILL.md +79 -0
- package/templates/.workspace-templates/workspace/00-meta/CONTEXT.md +6 -0
- package/templates/.workspace-templates/workspace/00-meta/execution-log.md +27 -0
- package/templates/.workspace-templates/workspace/01-input/CONTEXT.md +29 -0
- package/templates/.workspace-templates/workspace/02-process/CONTEXT.md +29 -0
- package/templates/.workspace-templates/workspace/03-output/CONTEXT.md +29 -0
- package/templates/.workspace-templates/workspace/README.md +14 -0
- package/templates/SKILL.md +347 -0
- package/tests/benchmark.test.ts +158 -0
- package/tests/cli.test.ts +109 -0
- package/tests/dispatch-parallel.test.ts +124 -0
- package/tests/dispatch.test.ts +218 -0
- package/tests/fixer-skill.test.ts +203 -0
- package/tests/generate-tests.test.ts +101 -0
- package/tests/install-tool.test.ts +141 -0
- package/tests/install.test.ts +144 -0
- package/tests/integration.test.ts +324 -0
- package/tests/iterate.test.ts +219 -0
- package/tests/orchestrator.test.ts +710 -0
- package/tests/scaffold.test.ts +238 -0
- package/tests/templates-enhanced.test.ts +208 -0
- package/tests/templates.test.ts +219 -0
- package/tests/validate.test.ts +421 -0
- package/tests/validation-enhanced.test.ts +303 -0
- package/tests/worker-skill.test.ts +88 -0
- package/tsconfig.json +19 -0
- package/workspace/00-meta/CONTEXT.md +3 -0
- package/workspace/00-meta/execution-log.md +17 -0
- package/workspace/00-meta/tools.md +11 -0
- package/workspace/01-input/CONTEXT.md +27 -0
- package/workspace/CONTEXT.md +35 -0
- package/workspace/README.md +14 -0
- package/workspace/SYSTEM.md +36 -0
- package/workspace-maxxing-0.1.0.tgz +0 -0
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
# Workspace Builder Logic Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Implement three helper scripts (scaffold, validate, install-tool) that programmatically generate, validate, and equip ICM-compliant workspaces, plus update the installer and SKILL.md to integrate them.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Three zero-dependency TypeScript scripts in `src/scripts/` that export both CLI entry points and testable functions. Scripts are compiled to `dist/scripts/` and copied to `templates/.workspace-templates/scripts/` during install. The installer (`src/install.ts`) is modified to include the scripts directory in its copy list.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js builtins only (`fs`, `path`, `process`, `child_process`), Jest for testing
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Structure
|
|
14
|
+
|
|
15
|
+
**New files:**
|
|
16
|
+
- `src/scripts/scaffold.ts` — Generates ICM workspace from JSON plan
|
|
17
|
+
- `src/scripts/validate.ts` — Checks workspace for ICM compliance
|
|
18
|
+
- `src/scripts/install-tool.ts` — Installs packages and updates tool inventory
|
|
19
|
+
- `templates/.workspace-templates/scripts/scaffold.ts` — Copy for distribution
|
|
20
|
+
- `templates/.workspace-templates/scripts/validate.ts` — Copy for distribution
|
|
21
|
+
- `templates/.workspace-templates/scripts/install-tool.ts` — Copy for distribution
|
|
22
|
+
- `tests/scaffold.test.ts` — Tests for scaffold
|
|
23
|
+
- `tests/validate.test.ts` — Tests for validate
|
|
24
|
+
- `tests/install-tool.test.ts` — Tests for install-tool
|
|
25
|
+
|
|
26
|
+
**Modified files:**
|
|
27
|
+
- `src/install.ts` — Add scripts directory to copy list
|
|
28
|
+
- `templates/SKILL.md` — Add "## Available Scripts" section
|
|
29
|
+
- `package.json` — Add `scripts:build` command to compile scripts
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### Task 1: Scaffold Script — Tests
|
|
34
|
+
|
|
35
|
+
**Files:**
|
|
36
|
+
- Test: `tests/scaffold.test.ts`
|
|
37
|
+
|
|
38
|
+
- [ ] **Step 1: Write scaffold tests**
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// tests/scaffold.test.ts
|
|
42
|
+
import * as fs from 'fs';
|
|
43
|
+
import * as path from 'path';
|
|
44
|
+
import * as os from 'os';
|
|
45
|
+
import { scaffoldWorkspace, ScaffoldOptions } from '../src/scripts/scaffold';
|
|
46
|
+
|
|
47
|
+
describe('scaffold', () => {
|
|
48
|
+
let tempDir: string;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'scaffold-test-'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('scaffoldWorkspace', () => {
|
|
59
|
+
it('creates output directory', () => {
|
|
60
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
61
|
+
const options: ScaffoldOptions = {
|
|
62
|
+
name: 'research',
|
|
63
|
+
stages: ['01-research', '02-analysis', '03-report'],
|
|
64
|
+
output: outputDir,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
scaffoldWorkspace(options);
|
|
68
|
+
|
|
69
|
+
expect(fs.existsSync(outputDir)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('creates SYSTEM.md with folder map', () => {
|
|
73
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
74
|
+
const options: ScaffoldOptions = {
|
|
75
|
+
name: 'research',
|
|
76
|
+
stages: ['01-research', '02-analysis', '03-report'],
|
|
77
|
+
output: outputDir,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
scaffoldWorkspace(options);
|
|
81
|
+
|
|
82
|
+
const systemMd = fs.readFileSync(path.join(outputDir, 'SYSTEM.md'), 'utf-8');
|
|
83
|
+
expect(systemMd).toContain('## Folder Map');
|
|
84
|
+
expect(systemMd).toContain('01-research');
|
|
85
|
+
expect(systemMd).toContain('02-analysis');
|
|
86
|
+
expect(systemMd).toContain('03-report');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('creates CONTEXT.md at root level', () => {
|
|
90
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
91
|
+
const options: ScaffoldOptions = {
|
|
92
|
+
name: 'research',
|
|
93
|
+
stages: ['01-research', '02-analysis', '03-report'],
|
|
94
|
+
output: outputDir,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
scaffoldWorkspace(options);
|
|
98
|
+
|
|
99
|
+
const contextMd = fs.readFileSync(path.join(outputDir, 'CONTEXT.md'), 'utf-8');
|
|
100
|
+
expect(contextMd).toContain('## Routing Table');
|
|
101
|
+
expect(contextMd).toContain('01-research');
|
|
102
|
+
expect(contextMd).toContain('02-analysis');
|
|
103
|
+
expect(contextMd).toContain('03-report');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('creates numbered stage folders with CONTEXT.md', () => {
|
|
107
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
108
|
+
const options: ScaffoldOptions = {
|
|
109
|
+
name: 'research',
|
|
110
|
+
stages: ['01-research', '02-analysis', '03-report'],
|
|
111
|
+
output: outputDir,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
scaffoldWorkspace(options);
|
|
115
|
+
|
|
116
|
+
for (const stage of options.stages) {
|
|
117
|
+
const stageDir = path.join(outputDir, stage);
|
|
118
|
+
expect(fs.existsSync(stageDir)).toBe(true);
|
|
119
|
+
const contextMd = fs.readFileSync(path.join(stageDir, 'CONTEXT.md'), 'utf-8');
|
|
120
|
+
expect(contextMd.trim().length).toBeGreaterThan(0);
|
|
121
|
+
expect(contextMd).toContain(stage);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('creates 00-meta folder with tools.md', () => {
|
|
126
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
127
|
+
const options: ScaffoldOptions = {
|
|
128
|
+
name: 'research',
|
|
129
|
+
stages: ['01-research'],
|
|
130
|
+
output: outputDir,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
scaffoldWorkspace(options);
|
|
134
|
+
|
|
135
|
+
const toolsMd = path.join(outputDir, '00-meta', 'tools.md');
|
|
136
|
+
expect(fs.existsSync(toolsMd)).toBe(true);
|
|
137
|
+
const content = fs.readFileSync(toolsMd, 'utf-8');
|
|
138
|
+
expect(content).toContain('## Tool Inventory');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('creates README.md', () => {
|
|
142
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
143
|
+
const options: ScaffoldOptions = {
|
|
144
|
+
name: 'research',
|
|
145
|
+
stages: ['01-research'],
|
|
146
|
+
output: outputDir,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
scaffoldWorkspace(options);
|
|
150
|
+
|
|
151
|
+
const readme = fs.readFileSync(path.join(outputDir, 'README.md'), 'utf-8');
|
|
152
|
+
expect(readme.trim().length).toBeGreaterThan(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('throws if output directory already exists', () => {
|
|
156
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
157
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
158
|
+
|
|
159
|
+
const options: ScaffoldOptions = {
|
|
160
|
+
name: 'research',
|
|
161
|
+
stages: ['01-research'],
|
|
162
|
+
output: outputDir,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
expect(() => scaffoldWorkspace(options)).toThrow('already exists');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('overwrites if force is true', () => {
|
|
169
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
170
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
171
|
+
fs.writeFileSync(path.join(outputDir, 'existing.txt'), 'data');
|
|
172
|
+
|
|
173
|
+
const options: ScaffoldOptions = {
|
|
174
|
+
name: 'research',
|
|
175
|
+
stages: ['01-research'],
|
|
176
|
+
output: outputDir,
|
|
177
|
+
force: true,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
scaffoldWorkspace(options);
|
|
181
|
+
|
|
182
|
+
const systemMd = path.join(outputDir, 'SYSTEM.md');
|
|
183
|
+
expect(fs.existsSync(systemMd)).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('throws if stages list is empty', () => {
|
|
187
|
+
const outputDir = path.join(tempDir, 'workspace');
|
|
188
|
+
const options: ScaffoldOptions = {
|
|
189
|
+
name: 'research',
|
|
190
|
+
stages: [],
|
|
191
|
+
output: outputDir,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
expect(() => scaffoldWorkspace(options)).toThrow('stages');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm test -- tests/scaffold.test.ts
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Expected: FAIL — module not found or function not defined
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### Task 2: Scaffold Script — Implementation
|
|
211
|
+
|
|
212
|
+
**Files:**
|
|
213
|
+
- Create: `src/scripts/scaffold.ts`
|
|
214
|
+
|
|
215
|
+
- [ ] **Step 1: Implement scaffold script**
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// src/scripts/scaffold.ts
|
|
219
|
+
import * as fs from 'fs';
|
|
220
|
+
import * as path from 'path';
|
|
221
|
+
|
|
222
|
+
export interface ScaffoldOptions {
|
|
223
|
+
name: string;
|
|
224
|
+
stages: string[];
|
|
225
|
+
output: string;
|
|
226
|
+
force?: boolean;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function scaffoldWorkspace(options: ScaffoldOptions): void {
|
|
230
|
+
const { name, stages, output, force = false } = options;
|
|
231
|
+
|
|
232
|
+
// Validate inputs
|
|
233
|
+
if (!stages || stages.length === 0) {
|
|
234
|
+
throw new Error('Stages list cannot be empty');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const outputDir = path.resolve(output);
|
|
238
|
+
|
|
239
|
+
// Check if output directory exists
|
|
240
|
+
if (fs.existsSync(outputDir)) {
|
|
241
|
+
if (!force) {
|
|
242
|
+
throw new Error(`Output directory already exists: ${outputDir} (use --force to overwrite)`);
|
|
243
|
+
}
|
|
244
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Create directory structure
|
|
248
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
249
|
+
|
|
250
|
+
// Create SYSTEM.md
|
|
251
|
+
const systemMd = generateSystemMd(name, stages);
|
|
252
|
+
fs.writeFileSync(path.join(outputDir, 'SYSTEM.md'), systemMd);
|
|
253
|
+
|
|
254
|
+
// Create CONTEXT.md
|
|
255
|
+
const contextMd = generateContextMd(name, stages);
|
|
256
|
+
fs.writeFileSync(path.join(outputDir, 'CONTEXT.md'), contextMd);
|
|
257
|
+
|
|
258
|
+
// Create 00-meta folder with tools.md
|
|
259
|
+
const metaDir = path.join(outputDir, '00-meta');
|
|
260
|
+
fs.mkdirSync(metaDir, { recursive: true });
|
|
261
|
+
fs.writeFileSync(path.join(metaDir, 'tools.md'), generateToolsMd());
|
|
262
|
+
fs.writeFileSync(path.join(metaDir, 'CONTEXT.md'), `# 00-meta Context\n\nMetadata and tool inventory for the ${name} workspace.\n`);
|
|
263
|
+
|
|
264
|
+
// Create stage folders
|
|
265
|
+
for (const stage of stages) {
|
|
266
|
+
const stageDir = path.join(outputDir, stage);
|
|
267
|
+
fs.mkdirSync(stageDir, { recursive: true });
|
|
268
|
+
fs.writeFileSync(
|
|
269
|
+
path.join(stageDir, 'CONTEXT.md'),
|
|
270
|
+
generateStageContextMd(name, stage),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Create README.md
|
|
275
|
+
fs.writeFileSync(path.join(outputDir, 'README.md'), generateReadmeMd(name, stages));
|
|
276
|
+
|
|
277
|
+
console.log(`Workspace "${name}" scaffolded at: ${outputDir}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function generateSystemMd(name: string, stages: string[]): string {
|
|
281
|
+
return `# ${name} — System Prompt
|
|
282
|
+
|
|
283
|
+
## Role
|
|
284
|
+
You are an AI assistant working within the ${name} workspace.
|
|
285
|
+
|
|
286
|
+
## Folder Map
|
|
287
|
+
|
|
288
|
+
${stages.map((s) => `- \`${s}/\` — ${stageDescription(s)}`).join('\n')}
|
|
289
|
+
- \`00-meta/\` — Metadata and tool inventory
|
|
290
|
+
|
|
291
|
+
## Rules
|
|
292
|
+
- Follow ICM methodology: canonical sources, one-way dependencies, selective loading
|
|
293
|
+
- Each numbered folder is a workflow stage with its own CONTEXT.md for routing
|
|
294
|
+
- Do not create content outside the defined structure
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function generateContextMd(name: string, stages: string[]): string {
|
|
299
|
+
return `# ${name} — Context Router
|
|
300
|
+
|
|
301
|
+
## Routing Table
|
|
302
|
+
|
|
303
|
+
${stages.map((s) => `- \`${s}/\` → \`${s}/CONTEXT.md\``).join('\n')}
|
|
304
|
+
- \`00-meta/\` → \`00-meta/tools.md\`
|
|
305
|
+
|
|
306
|
+
## How to Use
|
|
307
|
+
When working on a task, load only the CONTEXT.md for the relevant stage.
|
|
308
|
+
Do not load the entire workspace. Route to specific sections.
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function generateStageContextMd(name: string, stage: string): string {
|
|
313
|
+
return `# ${stage} — Context
|
|
314
|
+
|
|
315
|
+
## Purpose
|
|
316
|
+
This folder handles the ${stage} stage of the ${name} workflow.
|
|
317
|
+
|
|
318
|
+
## Inputs
|
|
319
|
+
- Define what inputs this stage expects
|
|
320
|
+
|
|
321
|
+
## Outputs
|
|
322
|
+
- Define what outputs this stage produces
|
|
323
|
+
|
|
324
|
+
## Dependencies
|
|
325
|
+
- List upstream stages this stage depends on
|
|
326
|
+
`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function generateToolsMd(): string {
|
|
330
|
+
return `# Tool Inventory
|
|
331
|
+
|
|
332
|
+
## Installed Tools
|
|
333
|
+
|
|
334
|
+
| Tool | Version | Manager | Installed |
|
|
335
|
+
|------|---------|---------|-----------|
|
|
336
|
+
| — | — | — | — |
|
|
337
|
+
|
|
338
|
+
## Pending Tools
|
|
339
|
+
|
|
340
|
+
List tools that are proposed but not yet approved.
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function generateReadmeMd(name: string, stages: string[]): string {
|
|
345
|
+
return `# ${name} Workspace
|
|
346
|
+
|
|
347
|
+
## Structure
|
|
348
|
+
|
|
349
|
+
${stages.map((s) => `- \`${s}/\``).join('\n')}
|
|
350
|
+
- \`00-meta/\`
|
|
351
|
+
|
|
352
|
+
## Usage
|
|
353
|
+
|
|
354
|
+
1. Follow the workflow stages in order
|
|
355
|
+
2. Load CONTEXT.md files selectively — only what you need
|
|
356
|
+
3. Update tools.md when installing new tools
|
|
357
|
+
4. Run validate.ts to check ICM compliance
|
|
358
|
+
`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function stageDescription(stage: string): string {
|
|
362
|
+
const descriptions: Record<string, string> = {
|
|
363
|
+
'01-input': 'Input collection and validation',
|
|
364
|
+
'02-process': 'Processing and transformation',
|
|
365
|
+
'03-output': 'Output generation and delivery',
|
|
366
|
+
};
|
|
367
|
+
return descriptions[stage] || `Stage: ${stage}`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// CLI entry point
|
|
371
|
+
if (require.main === module) {
|
|
372
|
+
const args = process.argv.slice(2);
|
|
373
|
+
const parseArg = (flag: string): string | undefined => {
|
|
374
|
+
const idx = args.indexOf(flag);
|
|
375
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const hasFlag = (flag: string): boolean => args.includes(flag);
|
|
379
|
+
|
|
380
|
+
const name = parseArg('--name');
|
|
381
|
+
const stagesStr = parseArg('--stages');
|
|
382
|
+
const output = parseArg('--output');
|
|
383
|
+
|
|
384
|
+
if (!name || !stagesStr || !output) {
|
|
385
|
+
console.error('Usage: node scaffold.ts --name <name> --stages <s1,s2,...> --output <path> [--force]');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const stages = stagesStr.split(',').map((s) => s.trim()).filter(Boolean);
|
|
390
|
+
|
|
391
|
+
scaffoldWorkspace({
|
|
392
|
+
name,
|
|
393
|
+
stages,
|
|
394
|
+
output,
|
|
395
|
+
force: hasFlag('--force'),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
- [ ] **Step 2: Run tests to verify they pass**
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npm test -- tests/scaffold.test.ts
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Expected: All 9 tests PASS
|
|
407
|
+
|
|
408
|
+
- [ ] **Step 3: Commit**
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
git add src/scripts/scaffold.ts tests/scaffold.test.ts
|
|
412
|
+
git commit -m "feat: add scaffold script with tests"
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
### Task 3: Validate Script — Tests
|
|
418
|
+
|
|
419
|
+
**Files:**
|
|
420
|
+
- Test: `tests/validate.test.ts`
|
|
421
|
+
|
|
422
|
+
- [ ] **Step 1: Write validate tests**
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// tests/validate.test.ts
|
|
426
|
+
import * as fs from 'fs';
|
|
427
|
+
import * as path from 'path';
|
|
428
|
+
import * as os from 'os';
|
|
429
|
+
import { validateWorkspace, ValidationResult } from '../src/scripts/validate';
|
|
430
|
+
|
|
431
|
+
describe('validate', () => {
|
|
432
|
+
let tempDir: string;
|
|
433
|
+
|
|
434
|
+
beforeEach(() => {
|
|
435
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'validate-test-'));
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
afterEach(() => {
|
|
439
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
function createValidWorkspace(): string {
|
|
443
|
+
const ws = path.join(tempDir, 'workspace');
|
|
444
|
+
fs.mkdirSync(ws, { recursive: true });
|
|
445
|
+
|
|
446
|
+
fs.writeFileSync(path.join(ws, 'SYSTEM.md'), '# Test\n\n## Folder Map\n\n- `01-input/`\n- `02-output/`\n');
|
|
447
|
+
fs.writeFileSync(path.join(ws, 'CONTEXT.md'), '# Router\n\n## Routing Table\n\n- `01-input/`\n');
|
|
448
|
+
fs.mkdirSync(path.join(ws, '01-input'), { recursive: true });
|
|
449
|
+
fs.writeFileSync(path.join(ws, '01-input', 'CONTEXT.md'), '# 01-input\n\nContent here.\n');
|
|
450
|
+
fs.mkdirSync(path.join(ws, '02-output'), { recursive: true });
|
|
451
|
+
fs.writeFileSync(path.join(ws, '02-output', 'CONTEXT.md'), '# 02-output\n\nContent here.\n');
|
|
452
|
+
fs.mkdirSync(path.join(ws, '00-meta'), { recursive: true });
|
|
453
|
+
fs.writeFileSync(path.join(ws, '00-meta', 'tools.md'), '# Tools\n\n| Tool | Version |\n|------|---------|\n');
|
|
454
|
+
|
|
455
|
+
return ws;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
describe('validateWorkspace', () => {
|
|
459
|
+
it('passes for a valid workspace', () => {
|
|
460
|
+
const ws = createValidWorkspace();
|
|
461
|
+
const result = validateWorkspace(ws);
|
|
462
|
+
|
|
463
|
+
expect(result.passed).toBe(true);
|
|
464
|
+
expect(result.checks.every((c) => c.passed)).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('fails if SYSTEM.md is missing', () => {
|
|
468
|
+
const ws = createValidWorkspace();
|
|
469
|
+
fs.unlinkSync(path.join(ws, 'SYSTEM.md'));
|
|
470
|
+
|
|
471
|
+
const result = validateWorkspace(ws);
|
|
472
|
+
|
|
473
|
+
expect(result.passed).toBe(false);
|
|
474
|
+
const systemCheck = result.checks.find((c) => c.name === 'SYSTEM.md exists');
|
|
475
|
+
expect(systemCheck?.passed).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('fails if SYSTEM.md has no folder map', () => {
|
|
479
|
+
const ws = createValidWorkspace();
|
|
480
|
+
fs.writeFileSync(path.join(ws, 'SYSTEM.md'), '# Test\n\nNo folder map here.\n');
|
|
481
|
+
|
|
482
|
+
const result = validateWorkspace(ws);
|
|
483
|
+
|
|
484
|
+
expect(result.passed).toBe(false);
|
|
485
|
+
const check = result.checks.find((c) => c.name.includes('folder map'));
|
|
486
|
+
expect(check?.passed).toBe(false);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('fails if CONTEXT.md is missing at root', () => {
|
|
490
|
+
const ws = createValidWorkspace();
|
|
491
|
+
fs.unlinkSync(path.join(ws, 'CONTEXT.md'));
|
|
492
|
+
|
|
493
|
+
const result = validateWorkspace(ws);
|
|
494
|
+
|
|
495
|
+
expect(result.passed).toBe(false);
|
|
496
|
+
const check = result.checks.find((c) => c.name.includes('CONTEXT.md exists'));
|
|
497
|
+
expect(check?.passed).toBe(false);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('fails if a numbered folder is missing CONTEXT.md', () => {
|
|
501
|
+
const ws = createValidWorkspace();
|
|
502
|
+
fs.unlinkSync(path.join(ws, '01-input', 'CONTEXT.md'));
|
|
503
|
+
|
|
504
|
+
const result = validateWorkspace(ws);
|
|
505
|
+
|
|
506
|
+
expect(result.passed).toBe(false);
|
|
507
|
+
const check = result.checks.find((c) => c.name.includes('CONTEXT.md'));
|
|
508
|
+
expect(check?.passed).toBe(false);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('fails if a CONTEXT.md is empty', () => {
|
|
512
|
+
const ws = createValidWorkspace();
|
|
513
|
+
fs.writeFileSync(path.join(ws, '01-input', 'CONTEXT.md'), '');
|
|
514
|
+
|
|
515
|
+
const result = validateWorkspace(ws);
|
|
516
|
+
|
|
517
|
+
expect(result.passed).toBe(false);
|
|
518
|
+
const check = result.checks.find((c) => c.name.includes('empty'));
|
|
519
|
+
expect(check?.passed).toBe(false);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('fails if duplicate content exists across files', () => {
|
|
523
|
+
const ws = createValidWorkspace();
|
|
524
|
+
const duplicateText = 'This is a long duplicate text block that appears in multiple files and should be flagged as a potential duplicate content issue for testing purposes.';
|
|
525
|
+
fs.appendFileSync(path.join(ws, '01-input', 'CONTEXT.md'), duplicateText);
|
|
526
|
+
fs.appendFileSync(path.join(ws, '02-output', 'CONTEXT.md'), duplicateText);
|
|
527
|
+
|
|
528
|
+
const result = validateWorkspace(ws);
|
|
529
|
+
|
|
530
|
+
expect(result.passed).toBe(false);
|
|
531
|
+
const check = result.checks.find((c) => c.name.includes('duplicate'));
|
|
532
|
+
expect(check?.passed).toBe(false);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('returns structured output with all check names', () => {
|
|
536
|
+
const ws = createValidWorkspace();
|
|
537
|
+
const result = validateWorkspace(ws);
|
|
538
|
+
|
|
539
|
+
const checkNames = result.checks.map((c) => c.name);
|
|
540
|
+
expect(checkNames).toContain('SYSTEM.md exists');
|
|
541
|
+
expect(checkNames).toContain('CONTEXT.md exists at root');
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
npm test -- tests/validate.test.ts
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Expected: FAIL — module not found
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
### Task 4: Validate Script — Implementation
|
|
558
|
+
|
|
559
|
+
**Files:**
|
|
560
|
+
- Create: `src/scripts/validate.ts`
|
|
561
|
+
|
|
562
|
+
- [ ] **Step 1: Implement validate script**
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// src/scripts/validate.ts
|
|
566
|
+
import * as fs from 'fs';
|
|
567
|
+
import * as path from 'path';
|
|
568
|
+
|
|
569
|
+
export interface CheckResult {
|
|
570
|
+
name: string;
|
|
571
|
+
passed: boolean;
|
|
572
|
+
message: string;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export interface ValidationResult {
|
|
576
|
+
passed: boolean;
|
|
577
|
+
checks: CheckResult[];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export function validateWorkspace(workspacePath: string): ValidationResult {
|
|
581
|
+
const ws = path.resolve(workspacePath);
|
|
582
|
+
const checks: CheckResult[] = [];
|
|
583
|
+
|
|
584
|
+
// Check 1: SYSTEM.md exists
|
|
585
|
+
const systemMdPath = path.join(ws, 'SYSTEM.md');
|
|
586
|
+
const systemExists = fs.existsSync(systemMdPath);
|
|
587
|
+
checks.push({
|
|
588
|
+
name: 'SYSTEM.md exists',
|
|
589
|
+
passed: systemExists,
|
|
590
|
+
message: systemExists ? 'Found' : 'Missing',
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Check 2: SYSTEM.md contains folder map
|
|
594
|
+
if (systemExists) {
|
|
595
|
+
const systemContent = fs.readFileSync(systemMdPath, 'utf-8');
|
|
596
|
+
const hasFolderMap = systemContent.toLowerCase().includes('folder map');
|
|
597
|
+
checks.push({
|
|
598
|
+
name: 'SYSTEM.md contains folder map',
|
|
599
|
+
passed: hasFolderMap,
|
|
600
|
+
message: hasFolderMap ? 'Found' : 'Missing "folder map" reference',
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
checks.push({
|
|
604
|
+
name: 'SYSTEM.md contains folder map',
|
|
605
|
+
passed: false,
|
|
606
|
+
message: 'Cannot check — SYSTEM.md missing',
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Check 3: CONTEXT.md exists at root
|
|
611
|
+
const contextMdPath = path.join(ws, 'CONTEXT.md');
|
|
612
|
+
const contextExists = fs.existsSync(contextMdPath);
|
|
613
|
+
checks.push({
|
|
614
|
+
name: 'CONTEXT.md exists at root',
|
|
615
|
+
passed: contextExists,
|
|
616
|
+
message: contextExists ? 'Found' : 'Missing',
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Check 4: Every numbered folder has CONTEXT.md
|
|
620
|
+
const entries = fs.readdirSync(ws, { withFileTypes: true });
|
|
621
|
+
const numberedFolders = entries
|
|
622
|
+
.filter((e) => e.isDirectory() && /^\d/.test(e.name))
|
|
623
|
+
.map((e) => e.name);
|
|
624
|
+
|
|
625
|
+
for (const folder of numberedFolders) {
|
|
626
|
+
const contextPath = path.join(ws, folder, 'CONTEXT.md');
|
|
627
|
+
const exists = fs.existsSync(contextPath);
|
|
628
|
+
checks.push({
|
|
629
|
+
name: `${folder}/CONTEXT.md exists`,
|
|
630
|
+
passed: exists,
|
|
631
|
+
message: exists ? 'Found' : 'Missing',
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Check 5: No empty CONTEXT.md files
|
|
635
|
+
if (exists) {
|
|
636
|
+
const content = fs.readFileSync(contextPath, 'utf-8');
|
|
637
|
+
const notEmpty = content.trim().length > 0;
|
|
638
|
+
checks.push({
|
|
639
|
+
name: `${folder}/CONTEXT.md is not empty`,
|
|
640
|
+
passed: notEmpty,
|
|
641
|
+
message: notEmpty ? `${content.trim().length} chars` : 'File is empty',
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Check 6: No duplicate content across files
|
|
647
|
+
const allFiles = getAllMarkdownFiles(ws);
|
|
648
|
+
const duplicateCheck = checkDuplicateContent(allFiles);
|
|
649
|
+
checks.push(duplicateCheck);
|
|
650
|
+
|
|
651
|
+
const passed = checks.every((c) => c.passed);
|
|
652
|
+
|
|
653
|
+
// Print results
|
|
654
|
+
console.log(`\nValidation: ${ws}`);
|
|
655
|
+
console.log('='.repeat(50));
|
|
656
|
+
for (const check of checks) {
|
|
657
|
+
const icon = check.passed ? '✓' : '✗';
|
|
658
|
+
console.log(` ${icon} ${check.name}: ${check.message}`);
|
|
659
|
+
}
|
|
660
|
+
console.log('='.repeat(50));
|
|
661
|
+
console.log(passed ? '✓ All checks passed' : '✗ Some checks failed');
|
|
662
|
+
|
|
663
|
+
return { passed, checks };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function getAllMarkdownFiles(dir: string): string[] {
|
|
667
|
+
const results: string[] = [];
|
|
668
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
669
|
+
|
|
670
|
+
for (const entry of entries) {
|
|
671
|
+
const fullPath = path.join(dir, entry.name);
|
|
672
|
+
if (entry.isDirectory()) {
|
|
673
|
+
results.push(...getAllMarkdownFiles(fullPath));
|
|
674
|
+
} else if (entry.name.endsWith('.md')) {
|
|
675
|
+
results.push(fullPath);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return results;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function checkDuplicateContent(files: string[]): CheckResult {
|
|
683
|
+
const MIN_DUPLICATE_LENGTH = 50;
|
|
684
|
+
const duplicates: string[] = [];
|
|
685
|
+
|
|
686
|
+
for (let i = 0; i < files.length; i++) {
|
|
687
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
688
|
+
const contentA = fs.readFileSync(files[i], 'utf-8');
|
|
689
|
+
const contentB = fs.readFileSync(files[j], 'utf-8');
|
|
690
|
+
|
|
691
|
+
// Check for identical text blocks > 50 characters
|
|
692
|
+
const linesA = contentA.split('\n');
|
|
693
|
+
const linesB = contentB.split('\n');
|
|
694
|
+
|
|
695
|
+
for (const lineA of linesA) {
|
|
696
|
+
const trimmed = lineA.trim();
|
|
697
|
+
if (trimmed.length > MIN_DUPLICATE_LENGTH) {
|
|
698
|
+
for (const lineB of linesB) {
|
|
699
|
+
if (lineB.trim() === trimmed) {
|
|
700
|
+
duplicates.push(trimmed.substring(0, 60) + '...');
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (duplicates.length > 0) {
|
|
710
|
+
return {
|
|
711
|
+
name: 'No duplicate content across files',
|
|
712
|
+
passed: false,
|
|
713
|
+
message: `Found ${duplicates.length} duplicate text block(s)`,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
name: 'No duplicate content across files',
|
|
719
|
+
passed: true,
|
|
720
|
+
message: 'No duplicates found',
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// CLI entry point
|
|
725
|
+
if (require.main === module) {
|
|
726
|
+
const args = process.argv.slice(2);
|
|
727
|
+
const workspaceIdx = args.indexOf('--workspace');
|
|
728
|
+
const workspace = workspaceIdx !== -1 ? args[workspaceIdx + 1] : undefined;
|
|
729
|
+
|
|
730
|
+
if (!workspace) {
|
|
731
|
+
console.error('Usage: node validate.ts --workspace <path>');
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const result = validateWorkspace(workspace);
|
|
736
|
+
process.exit(result.passed ? 0 : 1);
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
- [ ] **Step 2: Run tests to verify they pass**
|
|
741
|
+
|
|
742
|
+
```bash
|
|
743
|
+
npm test -- tests/validate.test.ts
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
Expected: All 8 tests PASS
|
|
747
|
+
|
|
748
|
+
- [ ] **Step 3: Commit**
|
|
749
|
+
|
|
750
|
+
```bash
|
|
751
|
+
git add src/scripts/validate.ts tests/validate.test.ts
|
|
752
|
+
git commit -m "feat: add validate script with tests"
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
### Task 5: Install-Tool Script — Tests
|
|
758
|
+
|
|
759
|
+
**Files:**
|
|
760
|
+
- Test: `tests/install-tool.test.ts`
|
|
761
|
+
|
|
762
|
+
- [ ] **Step 1: Write install-tool tests**
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
// tests/install-tool.test.ts
|
|
766
|
+
import * as fs from 'fs';
|
|
767
|
+
import * as path from 'path';
|
|
768
|
+
import * as os from 'os';
|
|
769
|
+
import { installTool, InstallToolOptions } from '../src/scripts/install-tool';
|
|
770
|
+
|
|
771
|
+
jest.mock('child_process', () => ({
|
|
772
|
+
execSync: jest.fn(),
|
|
773
|
+
}));
|
|
774
|
+
|
|
775
|
+
const { execSync } = require('child_process');
|
|
776
|
+
|
|
777
|
+
describe('install-tool', () => {
|
|
778
|
+
let tempDir: string;
|
|
779
|
+
|
|
780
|
+
beforeEach(() => {
|
|
781
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'install-tool-test-'));
|
|
782
|
+
// Create minimal workspace
|
|
783
|
+
const metaDir = path.join(tempDir, '00-meta');
|
|
784
|
+
fs.mkdirSync(metaDir, { recursive: true });
|
|
785
|
+
fs.writeFileSync(
|
|
786
|
+
path.join(metaDir, 'tools.md'),
|
|
787
|
+
'# Tool Inventory\n\n## Installed Tools\n\n| Tool | Version | Manager | Installed |\n|------|---------|---------|-----------|\n| — | — | — | — |\n',
|
|
788
|
+
);
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
afterEach(() => {
|
|
792
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
793
|
+
jest.clearAllMocks();
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
describe('installTool', () => {
|
|
797
|
+
it('runs correct npm install command', () => {
|
|
798
|
+
(execSync as jest.Mock).mockReturnValue('');
|
|
799
|
+
|
|
800
|
+
const options: InstallToolOptions = {
|
|
801
|
+
tool: 'pdf-lib',
|
|
802
|
+
manager: 'npm',
|
|
803
|
+
workspace: tempDir,
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
installTool(options);
|
|
807
|
+
|
|
808
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
809
|
+
'npm install pdf-lib',
|
|
810
|
+
expect.objectContaining({ cwd: tempDir, stdio: 'inherit' }),
|
|
811
|
+
);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it('runs correct pip install command', () => {
|
|
815
|
+
(execSync as jest.Mock).mockReturnValue('');
|
|
816
|
+
|
|
817
|
+
const options: InstallToolOptions = {
|
|
818
|
+
tool: 'requests',
|
|
819
|
+
manager: 'pip',
|
|
820
|
+
workspace: tempDir,
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
installTool(options);
|
|
824
|
+
|
|
825
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
826
|
+
'pip install requests',
|
|
827
|
+
expect.objectContaining({ cwd: tempDir, stdio: 'inherit' }),
|
|
828
|
+
);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
it('runs correct npx install command', () => {
|
|
832
|
+
(execSync as jest.Mock).mockReturnValue('');
|
|
833
|
+
|
|
834
|
+
const options: InstallToolOptions = {
|
|
835
|
+
tool: 'create-next-app',
|
|
836
|
+
manager: 'npx',
|
|
837
|
+
workspace: tempDir,
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
installTool(options);
|
|
841
|
+
|
|
842
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
843
|
+
'npx create-next-app',
|
|
844
|
+
expect.objectContaining({ cwd: tempDir, stdio: 'inherit' }),
|
|
845
|
+
);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it('runs correct brew install command', () => {
|
|
849
|
+
(execSync as jest.Mock).mockReturnValue('');
|
|
850
|
+
|
|
851
|
+
const options: InstallToolOptions = {
|
|
852
|
+
tool: 'ffmpeg',
|
|
853
|
+
manager: 'brew',
|
|
854
|
+
workspace: tempDir,
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
installTool(options);
|
|
858
|
+
|
|
859
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
860
|
+
'brew install ffmpeg',
|
|
861
|
+
expect.objectContaining({ cwd: tempDir, stdio: 'inherit' }),
|
|
862
|
+
);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('updates tools.md with installed tool', () => {
|
|
866
|
+
(execSync as jest.Mock).mockReturnValue('');
|
|
867
|
+
|
|
868
|
+
const options: InstallToolOptions = {
|
|
869
|
+
tool: 'pdf-lib',
|
|
870
|
+
manager: 'npm',
|
|
871
|
+
workspace: tempDir,
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
installTool(options);
|
|
875
|
+
|
|
876
|
+
const toolsMd = fs.readFileSync(path.join(tempDir, '00-meta', 'tools.md'), 'utf-8');
|
|
877
|
+
expect(toolsMd).toContain('pdf-lib');
|
|
878
|
+
expect(toolsMd).toContain('npm');
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('throws if install command fails', () => {
|
|
882
|
+
(execSync as jest.Mock).mockImplementation(() => {
|
|
883
|
+
const err = new Error('Command failed');
|
|
884
|
+
(err as any).status = 1;
|
|
885
|
+
throw err;
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const options: InstallToolOptions = {
|
|
889
|
+
tool: 'nonexistent-package',
|
|
890
|
+
manager: 'npm',
|
|
891
|
+
workspace: tempDir,
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
expect(() => installTool(options)).toThrow('Command failed');
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it('throws for unsupported manager', () => {
|
|
898
|
+
const options: InstallToolOptions = {
|
|
899
|
+
tool: 'something',
|
|
900
|
+
manager: 'unknown' as any,
|
|
901
|
+
workspace: tempDir,
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
expect(() => installTool(options)).toThrow('Unsupported package manager');
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
911
|
+
|
|
912
|
+
```bash
|
|
913
|
+
npm test -- tests/install-tool.test.ts
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
Expected: FAIL — module not found
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
### Task 6: Install-Tool Script — Implementation
|
|
921
|
+
|
|
922
|
+
**Files:**
|
|
923
|
+
- Create: `src/scripts/install-tool.ts`
|
|
924
|
+
|
|
925
|
+
- [ ] **Step 1: Implement install-tool script**
|
|
926
|
+
|
|
927
|
+
```typescript
|
|
928
|
+
// src/scripts/install-tool.ts
|
|
929
|
+
import * as fs from 'fs';
|
|
930
|
+
import * as path from 'path';
|
|
931
|
+
import { execSync } from 'child_process';
|
|
932
|
+
|
|
933
|
+
export type PackageManager = 'npm' | 'pip' | 'npx' | 'brew';
|
|
934
|
+
|
|
935
|
+
export interface InstallToolOptions {
|
|
936
|
+
tool: string;
|
|
937
|
+
manager: PackageManager;
|
|
938
|
+
workspace: string;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export function installTool(options: InstallToolOptions): void {
|
|
942
|
+
const { tool, manager, workspace } = options;
|
|
943
|
+
const ws = path.resolve(workspace);
|
|
944
|
+
|
|
945
|
+
// Validate package manager
|
|
946
|
+
const validManagers: PackageManager[] = ['npm', 'pip', 'npx', 'brew'];
|
|
947
|
+
if (!validManagers.includes(manager)) {
|
|
948
|
+
throw new Error(`Unsupported package manager: ${manager}. Supported: ${validManagers.join(', ')}`);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Build install command
|
|
952
|
+
const command = `${manager} install ${tool}`;
|
|
953
|
+
|
|
954
|
+
console.log(`Installing ${tool} via ${manager}...`);
|
|
955
|
+
try {
|
|
956
|
+
execSync(command, { cwd: ws, stdio: 'inherit' });
|
|
957
|
+
} catch (error) {
|
|
958
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
959
|
+
throw new Error(`Failed to install ${tool}: ${message}`);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Update tools.md
|
|
963
|
+
updateToolsMd(ws, tool, manager);
|
|
964
|
+
|
|
965
|
+
console.log(`✓ ${tool} installed and added to tool inventory`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function updateToolsMd(workspace: string, tool: string, manager: string): void {
|
|
969
|
+
const toolsMdPath = path.join(workspace, '00-meta', 'tools.md');
|
|
970
|
+
|
|
971
|
+
if (!fs.existsSync(toolsMdPath)) {
|
|
972
|
+
throw new Error(`tools.md not found at: ${toolsMdPath}`);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const content = fs.readFileSync(toolsMdPath, 'utf-8');
|
|
976
|
+
const now = new Date().toISOString().split('T')[0];
|
|
977
|
+
|
|
978
|
+
// Replace the placeholder row or add new row
|
|
979
|
+
const placeholderRow = '| — | — | — | — |';
|
|
980
|
+
const newRow = `| ${tool} | latest | ${manager} | ${now} |`;
|
|
981
|
+
|
|
982
|
+
let updated: string;
|
|
983
|
+
if (content.includes(placeholderRow)) {
|
|
984
|
+
updated = content.replace(placeholderRow, `${placeholderRow}\n${newRow}`);
|
|
985
|
+
} else {
|
|
986
|
+
// Insert before the "## Pending Tools" section or at end
|
|
987
|
+
const pendingIdx = content.indexOf('## Pending Tools');
|
|
988
|
+
if (pendingIdx !== -1) {
|
|
989
|
+
updated = content.slice(0, pendingIdx) + newRow + '\n\n' + content.slice(pendingIdx);
|
|
990
|
+
} else {
|
|
991
|
+
updated = content + '\n' + newRow + '\n';
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
fs.writeFileSync(toolsMdPath, updated);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// CLI entry point
|
|
999
|
+
if (require.main === module) {
|
|
1000
|
+
const args = process.argv.slice(2);
|
|
1001
|
+
const parseArg = (flag: string): string | undefined => {
|
|
1002
|
+
const idx = args.indexOf(flag);
|
|
1003
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
const tool = parseArg('--tool');
|
|
1007
|
+
const manager = parseArg('--manager') as PackageManager;
|
|
1008
|
+
const workspace = parseArg('--workspace');
|
|
1009
|
+
|
|
1010
|
+
if (!tool || !manager || !workspace) {
|
|
1011
|
+
console.error('Usage: node install-tool.ts --tool <name> --manager <npm|pip|npx|brew> --workspace <path>');
|
|
1012
|
+
process.exit(1);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
installTool({ tool, manager, workspace });
|
|
1016
|
+
}
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
- [ ] **Step 2: Run tests to verify they pass**
|
|
1020
|
+
|
|
1021
|
+
```bash
|
|
1022
|
+
npm test -- tests/install-tool.test.ts
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
Expected: All 7 tests PASS
|
|
1026
|
+
|
|
1027
|
+
- [ ] **Step 3: Commit**
|
|
1028
|
+
|
|
1029
|
+
```bash
|
|
1030
|
+
git add src/scripts/install-tool.ts tests/install-tool.test.ts
|
|
1031
|
+
git commit -m "feat: add install-tool script with tests"
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
---
|
|
1035
|
+
|
|
1036
|
+
### Task 7: Update Installer to Copy Scripts
|
|
1037
|
+
|
|
1038
|
+
**Files:**
|
|
1039
|
+
- Modify: `src/install.ts`
|
|
1040
|
+
|
|
1041
|
+
- [ ] **Step 1: Modify install.ts to also copy scripts directory**
|
|
1042
|
+
|
|
1043
|
+
The current `installSkill` function copies SKILL.md and `.workspace-templates/`. We need to ensure the scripts directory inside `.workspace-templates/` is also copied. Since the existing code already does a recursive copy of `.workspace-templates/` via `copyDirSync`, scripts will be included automatically once we add them to `templates/.workspace-templates/scripts/`.
|
|
1044
|
+
|
|
1045
|
+
However, we also need to copy the scripts to the skill directory root so agents can invoke them directly. Add a new copy operation:
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
// src/install.ts — add after the .workspace-templates copy block (before the return statement)
|
|
1049
|
+
|
|
1050
|
+
// Copy scripts to skill root for direct invocation
|
|
1051
|
+
const scriptsSrc = path.join(templatesDir, '.workspace-templates', 'scripts');
|
|
1052
|
+
if (fs.existsSync(scriptsSrc)) {
|
|
1053
|
+
const scriptsDest = path.join(skillDir, 'scripts');
|
|
1054
|
+
copyDirSync(scriptsSrc, scriptsDest);
|
|
1055
|
+
}
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
Full modified file:
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
import * as fs from 'fs';
|
|
1062
|
+
import * as path from 'path';
|
|
1063
|
+
|
|
1064
|
+
export interface InstallResult {
|
|
1065
|
+
success: boolean;
|
|
1066
|
+
skillPath: string;
|
|
1067
|
+
error?: string;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Walk up from startDir looking for package.json or .git directory.
|
|
1072
|
+
* Returns the first parent containing a marker, or startDir if none found.
|
|
1073
|
+
*/
|
|
1074
|
+
export function detectProjectRoot(startDir: string): string {
|
|
1075
|
+
let current = path.resolve(startDir);
|
|
1076
|
+
const root = path.parse(current).root;
|
|
1077
|
+
|
|
1078
|
+
while (current !== root) {
|
|
1079
|
+
const hasPackageJson = fs.existsSync(path.join(current, 'package.json'));
|
|
1080
|
+
const hasGit = fs.existsSync(path.join(current, '.git'));
|
|
1081
|
+
|
|
1082
|
+
if (hasPackageJson || hasGit) {
|
|
1083
|
+
return current;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
current = path.dirname(current);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
return startDir;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Recursively copy a directory, overwriting existing files.
|
|
1094
|
+
*/
|
|
1095
|
+
function copyDirSync(src: string, dest: string): void {
|
|
1096
|
+
if (!fs.existsSync(src)) {
|
|
1097
|
+
throw new Error(`Source directory not found: ${src}`);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
1101
|
+
|
|
1102
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
1103
|
+
|
|
1104
|
+
for (const entry of entries) {
|
|
1105
|
+
const srcPath = path.join(src, entry.name);
|
|
1106
|
+
const destPath = path.join(dest, entry.name);
|
|
1107
|
+
|
|
1108
|
+
if (entry.isDirectory()) {
|
|
1109
|
+
copyDirSync(srcPath, destPath);
|
|
1110
|
+
} else {
|
|
1111
|
+
fs.copyFileSync(srcPath, destPath);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Install the workspace-maxxing skill into a project.
|
|
1118
|
+
* Copies SKILL.md, .workspace-templates/, and scripts/ to .agents/skills/workspace-maxxing/
|
|
1119
|
+
*/
|
|
1120
|
+
export async function installSkill(
|
|
1121
|
+
projectRoot: string,
|
|
1122
|
+
templatesDir: string,
|
|
1123
|
+
): Promise<InstallResult> {
|
|
1124
|
+
const skillDir = path.join(projectRoot, '.agents', 'skills', 'workspace-maxxing');
|
|
1125
|
+
|
|
1126
|
+
try {
|
|
1127
|
+
// Copy SKILL.md
|
|
1128
|
+
const skillMdSrc = path.join(templatesDir, 'SKILL.md');
|
|
1129
|
+
const skillMdDest = path.join(skillDir, 'SKILL.md');
|
|
1130
|
+
fs.mkdirSync(path.dirname(skillMdDest), { recursive: true });
|
|
1131
|
+
fs.copyFileSync(skillMdSrc, skillMdDest);
|
|
1132
|
+
|
|
1133
|
+
// Copy .workspace-templates/
|
|
1134
|
+
const workspaceTemplatesSrc = path.join(templatesDir, '.workspace-templates');
|
|
1135
|
+
const workspaceTemplatesDest = path.join(skillDir, '.workspace-templates');
|
|
1136
|
+
copyDirSync(workspaceTemplatesSrc, workspaceTemplatesDest);
|
|
1137
|
+
|
|
1138
|
+
// Copy scripts to skill root for direct invocation
|
|
1139
|
+
const scriptsSrc = path.join(templatesDir, '.workspace-templates', 'scripts');
|
|
1140
|
+
if (fs.existsSync(scriptsSrc)) {
|
|
1141
|
+
const scriptsDest = path.join(skillDir, 'scripts');
|
|
1142
|
+
copyDirSync(scriptsSrc, scriptsDest);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
return { success: true, skillPath: skillDir };
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1148
|
+
return { success: false, skillPath: skillDir, error: message };
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
- [ ] **Step 2: Update integration test to verify scripts are copied**
|
|
1154
|
+
|
|
1155
|
+
Modify `tests/integration.test.ts` — add scripts to the expected files list:
|
|
1156
|
+
|
|
1157
|
+
```typescript
|
|
1158
|
+
// In the first integration test, add to expectedFiles array:
|
|
1159
|
+
const expectedFiles = [
|
|
1160
|
+
'SKILL.md',
|
|
1161
|
+
'.workspace-templates/SYSTEM.md',
|
|
1162
|
+
'.workspace-templates/CONTEXT.md',
|
|
1163
|
+
'.workspace-templates/workspace/00-meta/CONTEXT.md',
|
|
1164
|
+
'.workspace-templates/workspace/01-input/CONTEXT.md',
|
|
1165
|
+
'.workspace-templates/workspace/02-process/CONTEXT.md',
|
|
1166
|
+
'.workspace-templates/workspace/03-output/CONTEXT.md',
|
|
1167
|
+
'.workspace-templates/workspace/README.md',
|
|
1168
|
+
'scripts/scaffold.ts',
|
|
1169
|
+
'scripts/validate.ts',
|
|
1170
|
+
'scripts/install-tool.ts',
|
|
1171
|
+
];
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
- [ ] **Step 3: Run all tests to verify**
|
|
1175
|
+
|
|
1176
|
+
```bash
|
|
1177
|
+
npm test
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
Expected: All tests PASS (existing + new)
|
|
1181
|
+
|
|
1182
|
+
- [ ] **Step 4: Commit**
|
|
1183
|
+
|
|
1184
|
+
```bash
|
|
1185
|
+
git add src/install.ts tests/integration.test.ts
|
|
1186
|
+
git commit -m "feat: update installer to copy scripts directory"
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
### Task 8: Copy Scripts to Templates Directory
|
|
1192
|
+
|
|
1193
|
+
**Files:**
|
|
1194
|
+
- Create: `templates/.workspace-templates/scripts/scaffold.ts`
|
|
1195
|
+
- Create: `templates/.workspace-templates/scripts/validate.ts`
|
|
1196
|
+
- Create: `templates/.workspace-templates/scripts/install-tool.ts`
|
|
1197
|
+
|
|
1198
|
+
- [ ] **Step 1: Copy compiled script content to templates**
|
|
1199
|
+
|
|
1200
|
+
The templates directory contains the files that get distributed. Copy the content from `src/scripts/` to `templates/.workspace-templates/scripts/`. These should be identical copies — the same TypeScript source that gets compiled and tested.
|
|
1201
|
+
|
|
1202
|
+
Create the scripts directory:
|
|
1203
|
+
```bash
|
|
1204
|
+
mkdir -p templates/.workspace-templates/scripts
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
Copy each file:
|
|
1208
|
+
```bash
|
|
1209
|
+
cp src/scripts/scaffold.ts templates/.workspace-templates/scripts/scaffold.ts
|
|
1210
|
+
cp src/scripts/validate.ts templates/.workspace-templates/scripts/validate.ts
|
|
1211
|
+
cp src/scripts/install-tool.ts templates/.workspace-templates/scripts/install-tool.ts
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
- [ ] **Step 2: Run all tests to verify**
|
|
1215
|
+
|
|
1216
|
+
```bash
|
|
1217
|
+
npm test
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
Expected: All tests PASS (integration test now verifies scripts are copied)
|
|
1221
|
+
|
|
1222
|
+
- [ ] **Step 3: Commit**
|
|
1223
|
+
|
|
1224
|
+
```bash
|
|
1225
|
+
git add templates/.workspace-templates/scripts/
|
|
1226
|
+
git commit -m "feat: add scripts to workspace templates for distribution"
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
---
|
|
1230
|
+
|
|
1231
|
+
### Task 9: Enhance SKILL.md with Available Scripts Section
|
|
1232
|
+
|
|
1233
|
+
**Files:**
|
|
1234
|
+
- Modify: `templates/SKILL.md`
|
|
1235
|
+
|
|
1236
|
+
- [ ] **Step 1: Update SKILL.md with scripts documentation**
|
|
1237
|
+
|
|
1238
|
+
```markdown
|
|
1239
|
+
# Workspace-Maxxing Skill
|
|
1240
|
+
|
|
1241
|
+
## Role
|
|
1242
|
+
You are a workspace architect. You create structured, ICM-compliant workspaces.
|
|
1243
|
+
|
|
1244
|
+
## Available Scripts
|
|
1245
|
+
|
|
1246
|
+
Use these scripts to programmatically build, validate, and equip workspaces. Invoke them via shell commands from the skill directory.
|
|
1247
|
+
|
|
1248
|
+
### scaffold.ts — Generate ICM Workspace
|
|
1249
|
+
|
|
1250
|
+
Creates a complete ICM workspace structure from a plan.
|
|
1251
|
+
|
|
1252
|
+
```bash
|
|
1253
|
+
node scripts/scaffold.ts --name "research" --stages "01-research,02-analysis,03-report" --output ./workspace
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
Options:
|
|
1257
|
+
- `--name <name>` — Workspace name
|
|
1258
|
+
- `--stages <s1,s2,...>` — Comma-separated stage folder names
|
|
1259
|
+
- `--output <path>` — Where to create the workspace
|
|
1260
|
+
- `--force` — Overwrite if output directory already exists
|
|
1261
|
+
|
|
1262
|
+
### validate.ts — Check ICM Compliance
|
|
1263
|
+
|
|
1264
|
+
Validates a workspace against ICM rules.
|
|
1265
|
+
|
|
1266
|
+
```bash
|
|
1267
|
+
node scripts/validate.ts --workspace ./workspace
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
Checks:
|
|
1271
|
+
- SYSTEM.md exists and contains a folder map
|
|
1272
|
+
- CONTEXT.md exists at root level
|
|
1273
|
+
- Every numbered folder has a CONTEXT.md
|
|
1274
|
+
- No empty CONTEXT.md files
|
|
1275
|
+
- No duplicate content across files
|
|
1276
|
+
|
|
1277
|
+
Exit code: 0 = all pass, 1 = some failed
|
|
1278
|
+
|
|
1279
|
+
### install-tool.ts — Install Packages
|
|
1280
|
+
|
|
1281
|
+
Installs a tool and updates the workspace inventory.
|
|
1282
|
+
|
|
1283
|
+
```bash
|
|
1284
|
+
node scripts/install-tool.ts --tool "pdf-lib" --manager npm --workspace ./workspace
|
|
1285
|
+
```
|
|
1286
|
+
|
|
1287
|
+
Supported managers: `npm`, `pip`, `npx`, `brew`
|
|
1288
|
+
|
|
1289
|
+
## Process
|
|
1290
|
+
|
|
1291
|
+
1. CAPTURE INTENT — Ask: "What workflow do you want to automate?"
|
|
1292
|
+
2. PROPOSE STRUCTURE — Design workspace with numbered folders, CONTEXT.md routing files, canonical sources
|
|
1293
|
+
3. GET APPROVAL — Present plan. Wait. Do not build until approved.
|
|
1294
|
+
4. BUILD WORKSPACE — Run: `node scripts/scaffold.ts --name "<name>" --stages "<stages>" --output ./workspace`
|
|
1295
|
+
5. VALIDATE — Run: `node scripts/validate.ts --workspace ./workspace`. Fix any failures.
|
|
1296
|
+
6. ASSESS TOOLS — Scan environment. List available tools. Propose missing tools needed. Get approval.
|
|
1297
|
+
7. INSTALL TOOLS — For each approved tool: `node scripts/install-tool.ts --tool "<name>" --manager <mgr> --workspace ./workspace`
|
|
1298
|
+
8. FINAL VALIDATE — Run validate.ts one more time to confirm compliance.
|
|
1299
|
+
9. DELIVER — Output: workspace folder + skill package + usage guide
|
|
1300
|
+
|
|
1301
|
+
## When to Use Scripts vs Manual
|
|
1302
|
+
|
|
1303
|
+
- **Scripts:** For structure creation, validation, and tool installation
|
|
1304
|
+
- **Manual:** For writing content inside CONTEXT.md files, customizing stage descriptions, adding domain-specific instructions
|
|
1305
|
+
|
|
1306
|
+
## ICM Rules
|
|
1307
|
+
- Canonical sources: each fact lives in exactly one file
|
|
1308
|
+
- One-way dependencies only: A → B, never B → A
|
|
1309
|
+
- Selective loading: route to sections, not whole files
|
|
1310
|
+
- Numbered folders for workflow stages
|
|
1311
|
+
|
|
1312
|
+
## Output Format
|
|
1313
|
+
- workspace/ — the built workspace
|
|
1314
|
+
- .agents/skills/<workspace-name>/ — installable skill
|
|
1315
|
+
- USAGE.md — how to use this workspace in future sessions
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
- [ ] **Step 2: Update integration test for new SKILL.md sections**
|
|
1319
|
+
|
|
1320
|
+
Add assertions to `tests/integration.test.ts` for the new SKILL.md sections:
|
|
1321
|
+
|
|
1322
|
+
```typescript
|
|
1323
|
+
// Add to the SKILL.md assertions in the first integration test:
|
|
1324
|
+
expect(skillContent).toContain('## Available Scripts');
|
|
1325
|
+
expect(skillContent).toContain('scaffold.ts');
|
|
1326
|
+
expect(skillContent).toContain('validate.ts');
|
|
1327
|
+
expect(skillContent).toContain('install-tool.ts');
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
- [ ] **Step 3: Run all tests to verify**
|
|
1331
|
+
|
|
1332
|
+
```bash
|
|
1333
|
+
npm test
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
Expected: All tests PASS
|
|
1337
|
+
|
|
1338
|
+
- [ ] **Step 4: Commit**
|
|
1339
|
+
|
|
1340
|
+
```bash
|
|
1341
|
+
git add templates/SKILL.md tests/integration.test.ts
|
|
1342
|
+
git commit -m "feat: enhance SKILL.md with Available Scripts section"
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
---
|
|
1346
|
+
|
|
1347
|
+
### Task 10: Final Verification & All Tests
|
|
1348
|
+
|
|
1349
|
+
**Files:**
|
|
1350
|
+
- All files
|
|
1351
|
+
|
|
1352
|
+
- [ ] **Step 1: Run full test suite**
|
|
1353
|
+
|
|
1354
|
+
```bash
|
|
1355
|
+
npm test
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
Expected output: All tests pass (Phase 1: 33 tests + Phase 2: ~24 new tests = ~57 total)
|
|
1359
|
+
|
|
1360
|
+
- [ ] **Step 2: Build and verify no TypeScript errors**
|
|
1361
|
+
|
|
1362
|
+
```bash
|
|
1363
|
+
npm run build
|
|
1364
|
+
```
|
|
1365
|
+
|
|
1366
|
+
Expected: No errors, output in `dist/`
|
|
1367
|
+
|
|
1368
|
+
- [ ] **Step 3: Verify dist/scripts/ exists**
|
|
1369
|
+
|
|
1370
|
+
```bash
|
|
1371
|
+
ls dist/scripts/
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
Expected: `scaffold.js`, `validate.js`, `install-tool.js`
|
|
1375
|
+
|
|
1376
|
+
- [ ] **Step 4: Commit final state**
|
|
1377
|
+
|
|
1378
|
+
```bash
|
|
1379
|
+
git add -A
|
|
1380
|
+
git commit -m "feat: workspace builder logic complete — scaffold, validate, install-tool"
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
---
|
|
1384
|
+
|
|
1385
|
+
## Self-Review
|
|
1386
|
+
|
|
1387
|
+
### 1. Spec Coverage Check
|
|
1388
|
+
|
|
1389
|
+
| Spec Requirement | Task |
|
|
1390
|
+
|---|---|
|
|
1391
|
+
| scaffold.ts generates ICM workspace from JSON plan | Task 1-2 |
|
|
1392
|
+
| scaffold.ts CLI: --name, --stages, --output, --force | Task 2 |
|
|
1393
|
+
| scaffold.ts creates SYSTEM.md, CONTEXT.md, stage folders, 00-meta, README.md | Task 1-2 |
|
|
1394
|
+
| scaffold.ts dependencies: Node.js builtins only | Task 2 |
|
|
1395
|
+
| validate.ts checks SYSTEM.md exists with folder map | Task 3-4 |
|
|
1396
|
+
| validate.ts checks CONTEXT.md at root | Task 3-4 |
|
|
1397
|
+
| validate.ts checks numbered folders have CONTEXT.md | Task 3-4 |
|
|
1398
|
+
| validate.ts checks no empty CONTEXT.md | Task 3-4 |
|
|
1399
|
+
| validate.ts checks no duplicate content (>50 chars) | Task 3-4 |
|
|
1400
|
+
| validate.ts exit code 0/1, structured output | Task 4 |
|
|
1401
|
+
| install-tool.ts runs install command | Task 5-6 |
|
|
1402
|
+
| install-tool.ts updates 00-meta/tools.md | Task 5-6 |
|
|
1403
|
+
| install-tool.ts supports npm, pip, npx, brew | Task 5-6 |
|
|
1404
|
+
| install-tool.ts fails on non-zero exit | Task 5-6 |
|
|
1405
|
+
| Enhanced SKILL.md with Available Scripts | Task 9 |
|
|
1406
|
+
| Installer copies scripts | Task 7-8 |
|
|
1407
|
+
| Tests for all three scripts | Tasks 1, 3, 5 |
|
|
1408
|
+
| Integration: scaffold → validate → pass | Covered by scaffold tests + integration test |
|
|
1409
|
+
|
|
1410
|
+
All requirements covered. ✓
|
|
1411
|
+
|
|
1412
|
+
### 2. Placeholder Scan
|
|
1413
|
+
|
|
1414
|
+
No TBD, TODO, "add tests for the above", "handle edge cases", or "similar to Task N" patterns found. All steps contain complete code. ✓
|
|
1415
|
+
|
|
1416
|
+
### 3. Type Consistency
|
|
1417
|
+
|
|
1418
|
+
- `ScaffoldOptions` interface defined in Task 2, used in Task 1 tests ✓
|
|
1419
|
+
- `ValidationResult` and `CheckResult` interfaces defined in Task 4, used in Task 3 tests ✓
|
|
1420
|
+
- `InstallToolOptions` and `PackageManager` type defined in Task 6, used in Task 5 tests ✓
|
|
1421
|
+
- All scripts use `require.main === module` pattern for CLI entry ✓
|
|
1422
|
+
- All exported functions match test imports ✓
|
|
1423
|
+
|
|
1424
|
+
---
|
|
1425
|
+
|
|
1426
|
+
Plan complete. Ready for execution.
|