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,109 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
describe('CLI', () => {
|
|
7
|
+
let tempDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'workspace-maxxing-cli-'));
|
|
11
|
+
fs.writeFileSync(path.join(tempDir, 'package.json'), '{}');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('shows help when no args provided', () => {
|
|
19
|
+
const output = execSync('node dist/index.js', {
|
|
20
|
+
cwd: path.join(__dirname, '..'),
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(output).toContain('workspace-maxxing');
|
|
25
|
+
expect(output).toContain('--opencode');
|
|
26
|
+
expect(output).toContain('--claude');
|
|
27
|
+
expect(output).toContain('--copilot');
|
|
28
|
+
expect(output).toContain('--gemini');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('shows help when --help flag is provided', () => {
|
|
32
|
+
const output = execSync('node dist/index.js --help', {
|
|
33
|
+
cwd: path.join(__dirname, '..'),
|
|
34
|
+
encoding: 'utf-8',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(output).toContain('workspace-maxxing');
|
|
38
|
+
expect(output).toContain('--opencode');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('installs skill when --opencode flag is provided', () => {
|
|
42
|
+
const cliPath = path.join(__dirname, '..', 'dist', 'index.js');
|
|
43
|
+
const output = execSync(`node "${cliPath}" --opencode`, {
|
|
44
|
+
cwd: tempDir,
|
|
45
|
+
encoding: 'utf-8',
|
|
46
|
+
env: { ...process.env, WORKSPACE_MAXXING_TEMPLATES: path.join(__dirname, '..', 'templates') },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(output).toContain('installed');
|
|
50
|
+
expect(
|
|
51
|
+
fs.existsSync(path.join(tempDir, '.agents', 'skills', 'workspace-maxxing', 'SKILL.md')),
|
|
52
|
+
).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('installs to claude path when --claude flag is provided', () => {
|
|
56
|
+
const cliPath = path.join(__dirname, '..', 'dist', 'index.js');
|
|
57
|
+
const output = execSync(`node "${cliPath}" --claude`, {
|
|
58
|
+
cwd: tempDir,
|
|
59
|
+
encoding: 'utf-8',
|
|
60
|
+
env: { ...process.env, WORKSPACE_MAXXING_TEMPLATES: path.join(__dirname, '..', 'templates') },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(output).toContain('claude');
|
|
64
|
+
expect(
|
|
65
|
+
fs.existsSync(path.join(tempDir, '.claude', 'skills', 'SKILL.md')),
|
|
66
|
+
).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('installs to copilot path when --copilot flag is provided', () => {
|
|
70
|
+
const cliPath = path.join(__dirname, '..', 'dist', 'index.js');
|
|
71
|
+
const output = execSync(`node "${cliPath}" --copilot`, {
|
|
72
|
+
cwd: tempDir,
|
|
73
|
+
encoding: 'utf-8',
|
|
74
|
+
env: { ...process.env, WORKSPACE_MAXXING_TEMPLATES: path.join(__dirname, '..', 'templates') },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(output).toContain('copilot');
|
|
78
|
+
expect(
|
|
79
|
+
fs.existsSync(path.join(tempDir, '.github', 'copilot-instructions', 'SKILL.md')),
|
|
80
|
+
).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('installs to gemini path when --gemini flag is provided', () => {
|
|
84
|
+
const cliPath = path.join(__dirname, '..', 'dist', 'index.js');
|
|
85
|
+
const output = execSync(`node "${cliPath}" --gemini`, {
|
|
86
|
+
cwd: tempDir,
|
|
87
|
+
encoding: 'utf-8',
|
|
88
|
+
env: { ...process.env, WORKSPACE_MAXXING_TEMPLATES: path.join(__dirname, '..', 'templates') },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(output).toContain('gemini');
|
|
92
|
+
expect(
|
|
93
|
+
fs.existsSync(path.join(tempDir, '.gemini', 'skills', 'SKILL.md')),
|
|
94
|
+
).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('errors on unsupported flag', () => {
|
|
98
|
+
try {
|
|
99
|
+
execSync('node dist/index.js --unknown-flag', {
|
|
100
|
+
cwd: path.join(__dirname, '..'),
|
|
101
|
+
encoding: 'utf-8',
|
|
102
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
103
|
+
});
|
|
104
|
+
fail('Should have thrown');
|
|
105
|
+
} catch (error) {
|
|
106
|
+
expect(true).toBe(true);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { dispatchSkill, dispatchParallel, ParallelDispatchResult, ParallelInvocation } from '../src/scripts/dispatch';
|
|
5
|
+
|
|
6
|
+
describe('parallel dispatch', () => {
|
|
7
|
+
let tempDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dispatch-parallel-'));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('dispatches multiple skills in parallel and aggregates results', () => {
|
|
18
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
19
|
+
fs.mkdirSync(path.join(skillsDir, 'validation'), { recursive: true });
|
|
20
|
+
fs.writeFileSync(path.join(skillsDir, 'validation', 'SKILL.md'), '---\nname: validation\n---\n\nTest');
|
|
21
|
+
|
|
22
|
+
const invocations: ParallelInvocation[] = [
|
|
23
|
+
{ skill: 'validation', batchId: 1, testCaseId: 'tc-001' },
|
|
24
|
+
{ skill: 'validation', batchId: 1, testCaseId: 'tc-002' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const results = dispatchParallel(invocations, skillsDir);
|
|
28
|
+
|
|
29
|
+
expect(results).toHaveLength(2);
|
|
30
|
+
expect(results[0].status).toBe('passed');
|
|
31
|
+
expect(results[1].status).toBe('passed');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('fails worker invocations when external runner is not configured', () => {
|
|
35
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
36
|
+
fs.mkdirSync(path.join(skillsDir, 'worker'), { recursive: true });
|
|
37
|
+
fs.writeFileSync(path.join(skillsDir, 'worker', 'SKILL.md'), '---\nname: worker\n---\n\nTest');
|
|
38
|
+
|
|
39
|
+
const invocations: ParallelInvocation[] = [
|
|
40
|
+
{ skill: 'worker', batchId: 1, testCaseId: 'tc-001' },
|
|
41
|
+
{ skill: 'worker', batchId: 1, testCaseId: 'tc-002' },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const results = dispatchParallel(invocations, skillsDir);
|
|
45
|
+
|
|
46
|
+
expect(results).toHaveLength(2);
|
|
47
|
+
expect(results[0].status).toBe('failed');
|
|
48
|
+
expect(results[1].status).toBe('failed');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('includes batchId and testCaseId in results', () => {
|
|
52
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
53
|
+
fs.mkdirSync(path.join(skillsDir, 'validation'), { recursive: true });
|
|
54
|
+
fs.writeFileSync(path.join(skillsDir, 'validation', 'SKILL.md'), '---\nname: validation\n---\n\nTest');
|
|
55
|
+
|
|
56
|
+
const invocations: ParallelInvocation[] = [
|
|
57
|
+
{ skill: 'validation', batchId: 2, testCaseId: 'tc-003' },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const results = dispatchParallel(invocations, skillsDir);
|
|
61
|
+
|
|
62
|
+
expect(results[0].batchId).toBe(2);
|
|
63
|
+
expect(results[0].testCaseId).toBe('tc-003');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('handles missing skill gracefully in parallel mode', () => {
|
|
67
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
68
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
69
|
+
|
|
70
|
+
const invocations: ParallelInvocation[] = [
|
|
71
|
+
{ skill: 'nonexistent', batchId: 1, testCaseId: 'tc-001' },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const results = dispatchParallel(invocations, skillsDir);
|
|
75
|
+
|
|
76
|
+
expect(results[0].status).toBe('failed');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('supports external runner mode for worker invocations', () => {
|
|
80
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
81
|
+
fs.mkdirSync(path.join(skillsDir, 'worker'), { recursive: true });
|
|
82
|
+
fs.writeFileSync(path.join(skillsDir, 'worker', 'SKILL.md'), '---\nname: worker\n---\n\nTest');
|
|
83
|
+
|
|
84
|
+
const runnerPath = path.join(tempDir, 'parallel-runner.js');
|
|
85
|
+
fs.writeFileSync(
|
|
86
|
+
runnerPath,
|
|
87
|
+
[
|
|
88
|
+
'const args = process.argv.slice(2);',
|
|
89
|
+
'const skill = args[0];',
|
|
90
|
+
'const batchId = Number(args[1]);',
|
|
91
|
+
'const testCaseId = args[2];',
|
|
92
|
+
'console.log(JSON.stringify({',
|
|
93
|
+
' skill,',
|
|
94
|
+
' status: "passed",',
|
|
95
|
+
' timestamp: "2026-04-08T00:00:00.000Z",',
|
|
96
|
+
' findings: ["parallel runner executed"],',
|
|
97
|
+
' recommendations: ["continue"],',
|
|
98
|
+
' metrics: { executionTimeMs: 8 },',
|
|
99
|
+
' nextSkill: "validation",',
|
|
100
|
+
' batchId,',
|
|
101
|
+
' testCaseId,',
|
|
102
|
+
'}));',
|
|
103
|
+
].join('\n'),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const invocations: ParallelInvocation[] = [
|
|
107
|
+
{ skill: 'worker', batchId: 3, testCaseId: 'tc-010' },
|
|
108
|
+
{ skill: 'worker', batchId: 3, testCaseId: 'tc-011' },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const runnerCommand = `"${process.execPath}" "${runnerPath}" {skill} {batchId} {testCaseId}`;
|
|
112
|
+
const results = dispatchParallel(invocations, skillsDir, {
|
|
113
|
+
workspacePath: tempDir,
|
|
114
|
+
runnerCommand,
|
|
115
|
+
runnerTimeoutSeconds: 10,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(results).toHaveLength(2);
|
|
119
|
+
expect(results[0].status).toBe('passed');
|
|
120
|
+
expect(results[1].status).toBe('passed');
|
|
121
|
+
expect(results[0].findings).toContain('parallel runner executed');
|
|
122
|
+
expect(results[1].testCaseId).toBe('tc-011');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { dispatchSkill, DispatchReport } from '../src/scripts/dispatch';
|
|
5
|
+
|
|
6
|
+
describe('dispatchSkill', () => {
|
|
7
|
+
let tempDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dispatch-test-'));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('loads and returns report for a valid sub-skill', () => {
|
|
18
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
19
|
+
fs.mkdirSync(path.join(skillsDir, 'validation'), { recursive: true });
|
|
20
|
+
fs.writeFileSync(path.join(skillsDir, 'validation', 'SKILL.md'), '---\nname: validation\ndescription: test\n---\n\n## Overview\nTest');
|
|
21
|
+
|
|
22
|
+
const result = dispatchSkill('validation', skillsDir);
|
|
23
|
+
|
|
24
|
+
expect(result.skill).toBe('validation');
|
|
25
|
+
expect(result.status).toBeDefined();
|
|
26
|
+
expect(result.timestamp).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns failed status for non-existent skill', () => {
|
|
30
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
31
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
const result = dispatchSkill('nonexistent', skillsDir);
|
|
34
|
+
|
|
35
|
+
expect(result.skill).toBe('nonexistent');
|
|
36
|
+
expect(result.status).toBe('failed');
|
|
37
|
+
expect(result.findings.length).toBeGreaterThan(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('includes nextSkill recommendation in report', () => {
|
|
41
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
42
|
+
fs.mkdirSync(path.join(skillsDir, 'validation'), { recursive: true });
|
|
43
|
+
fs.writeFileSync(path.join(skillsDir, 'validation', 'SKILL.md'), '---\nname: validation\ndescription: test\n---\n\n## Overview\nTest');
|
|
44
|
+
|
|
45
|
+
const result = dispatchSkill('validation', skillsDir);
|
|
46
|
+
|
|
47
|
+
expect(result.nextSkill).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('maps validation to prompt-engineering as next skill', () => {
|
|
51
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
52
|
+
fs.mkdirSync(path.join(skillsDir, 'validation'), { recursive: true });
|
|
53
|
+
fs.writeFileSync(path.join(skillsDir, 'validation', 'SKILL.md'), '---\nname: validation\ndescription: test\n---\n\n## Overview\nTest');
|
|
54
|
+
|
|
55
|
+
const result = dispatchSkill('validation', skillsDir);
|
|
56
|
+
|
|
57
|
+
expect(result.nextSkill).toBe('prompt-engineering');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('maps research to architecture as next skill', () => {
|
|
61
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
62
|
+
fs.mkdirSync(path.join(skillsDir, 'research'), { recursive: true });
|
|
63
|
+
fs.writeFileSync(path.join(skillsDir, 'research', 'SKILL.md'), '---\nname: research\ndescription: test\n---\n\n## Overview\nTest');
|
|
64
|
+
|
|
65
|
+
const result = dispatchSkill('research', skillsDir);
|
|
66
|
+
|
|
67
|
+
expect(result.nextSkill).toBe('architecture');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('fails worker dispatch when external runner is not configured', () => {
|
|
71
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
72
|
+
fs.mkdirSync(path.join(skillsDir, 'worker'), { recursive: true });
|
|
73
|
+
fs.writeFileSync(path.join(skillsDir, 'worker', 'SKILL.md'), '---\nname: worker\ndescription: test\n---\n\n## Overview\nTest');
|
|
74
|
+
|
|
75
|
+
const result = dispatchSkill('worker', skillsDir);
|
|
76
|
+
|
|
77
|
+
expect(result.status).toBe('failed');
|
|
78
|
+
expect(result.findings.join(' ')).toContain('External sub-agent runner is required');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('fails fixer dispatch when external runner is not configured', () => {
|
|
82
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
83
|
+
fs.mkdirSync(path.join(skillsDir, 'fixer'), { recursive: true });
|
|
84
|
+
fs.writeFileSync(path.join(skillsDir, 'fixer', 'SKILL.md'), '---\nname: fixer\ndescription: test\n---\n\n## Overview\nTest');
|
|
85
|
+
|
|
86
|
+
const result = dispatchSkill('fixer', skillsDir);
|
|
87
|
+
|
|
88
|
+
expect(result.status).toBe('failed');
|
|
89
|
+
expect(result.findings.join(' ')).toContain('External sub-agent runner is required');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('uses external runner command for worker skill when configured', () => {
|
|
93
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
94
|
+
fs.mkdirSync(path.join(skillsDir, 'worker'), { recursive: true });
|
|
95
|
+
fs.writeFileSync(path.join(skillsDir, 'worker', 'SKILL.md'), '---\nname: worker\ndescription: test\n---\n\n## Overview\nTest');
|
|
96
|
+
|
|
97
|
+
const runnerPath = path.join(tempDir, 'runner.js');
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
runnerPath,
|
|
100
|
+
[
|
|
101
|
+
'const skill = process.argv[2];',
|
|
102
|
+
'const batchId = Number(process.argv[3]);',
|
|
103
|
+
'const testCaseId = process.argv[4];',
|
|
104
|
+
'console.log(JSON.stringify({',
|
|
105
|
+
' skill,',
|
|
106
|
+
' status: "passed",',
|
|
107
|
+
' timestamp: "2026-04-08T00:00:00.000Z",',
|
|
108
|
+
' findings: ["external runner executed"],',
|
|
109
|
+
' recommendations: ["continue"],',
|
|
110
|
+
' metrics: { executionTimeMs: 12 },',
|
|
111
|
+
' nextSkill: "validation",',
|
|
112
|
+
' batchId,',
|
|
113
|
+
' testCaseId,',
|
|
114
|
+
'}));',
|
|
115
|
+
].join('\n'),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const runnerCommand = `"${process.execPath}" "${runnerPath}" {skill} {batchId} {testCaseId}`;
|
|
119
|
+
|
|
120
|
+
const result = dispatchSkill('worker', skillsDir, {
|
|
121
|
+
workspacePath: tempDir,
|
|
122
|
+
runnerCommand,
|
|
123
|
+
invocation: {
|
|
124
|
+
skill: 'worker',
|
|
125
|
+
batchId: 1,
|
|
126
|
+
testCaseId: 'tc-001',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(result.status).toBe('passed');
|
|
131
|
+
expect(result.findings).toContain('external runner executed');
|
|
132
|
+
expect(result.nextSkill).toBe('validation');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('writes runner telemetry artifact for external worker dispatch', () => {
|
|
136
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
137
|
+
fs.mkdirSync(path.join(skillsDir, 'worker'), { recursive: true });
|
|
138
|
+
fs.writeFileSync(path.join(skillsDir, 'worker', 'SKILL.md'), '---\nname: worker\ndescription: test\n---\n\n## Overview\nTest');
|
|
139
|
+
|
|
140
|
+
const runnerPath = path.join(tempDir, 'runner-telemetry.js');
|
|
141
|
+
fs.writeFileSync(
|
|
142
|
+
runnerPath,
|
|
143
|
+
[
|
|
144
|
+
'const skill = process.argv[2];',
|
|
145
|
+
'const batchId = Number(process.argv[3]);',
|
|
146
|
+
'const testCaseId = process.argv[4];',
|
|
147
|
+
'console.log(JSON.stringify({',
|
|
148
|
+
' skill,',
|
|
149
|
+
' status: "passed",',
|
|
150
|
+
' timestamp: "2026-04-08T00:00:00.000Z",',
|
|
151
|
+
' findings: ["runner telemetry"],',
|
|
152
|
+
' recommendations: ["continue"],',
|
|
153
|
+
' metrics: { executionTimeMs: 7 },',
|
|
154
|
+
' nextSkill: "validation",',
|
|
155
|
+
' batchId,',
|
|
156
|
+
' testCaseId,',
|
|
157
|
+
'}));',
|
|
158
|
+
].join('\n'),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const runnerCommand = `"${process.execPath}" "${runnerPath}" {skill} {batchId} {testCaseId}`;
|
|
162
|
+
|
|
163
|
+
const result = dispatchSkill('worker', skillsDir, {
|
|
164
|
+
workspacePath: tempDir,
|
|
165
|
+
runnerCommand,
|
|
166
|
+
invocation: {
|
|
167
|
+
skill: 'worker',
|
|
168
|
+
batchId: 1,
|
|
169
|
+
testCaseId: 'tc-telemetry',
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(result.status).toBe('passed');
|
|
174
|
+
|
|
175
|
+
const runsDir = path.join(tempDir, '.agents', 'iteration', 'runs');
|
|
176
|
+
expect(fs.existsSync(runsDir)).toBe(true);
|
|
177
|
+
const telemetryFiles = fs.readdirSync(runsDir).filter((f) => f.endsWith('.json'));
|
|
178
|
+
expect(telemetryFiles.length).toBeGreaterThan(0);
|
|
179
|
+
|
|
180
|
+
const telemetry = JSON.parse(fs.readFileSync(path.join(runsDir, telemetryFiles[0]), 'utf-8'));
|
|
181
|
+
expect(telemetry.skill).toBe('worker');
|
|
182
|
+
expect(telemetry.status).toBe('passed');
|
|
183
|
+
expect(telemetry.batchId).toBe(1);
|
|
184
|
+
expect(telemetry.testCaseId).toBe('tc-telemetry');
|
|
185
|
+
expect(telemetry.commandTemplate).toContain('{skill}');
|
|
186
|
+
expect(typeof telemetry.durationMs).toBe('number');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('returns failed status when external runner command fails', () => {
|
|
190
|
+
const skillsDir = path.join(tempDir, 'skills');
|
|
191
|
+
fs.mkdirSync(path.join(skillsDir, 'worker'), { recursive: true });
|
|
192
|
+
fs.writeFileSync(path.join(skillsDir, 'worker', 'SKILL.md'), '---\nname: worker\ndescription: test\n---\n\n## Overview\nTest');
|
|
193
|
+
|
|
194
|
+
const runnerPath = path.join(tempDir, 'failing-runner.js');
|
|
195
|
+
fs.writeFileSync(
|
|
196
|
+
runnerPath,
|
|
197
|
+
[
|
|
198
|
+
'console.error("runner failed");',
|
|
199
|
+
'process.exit(2);',
|
|
200
|
+
].join('\n'),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const runnerCommand = `"${process.execPath}" "${runnerPath}"`;
|
|
204
|
+
|
|
205
|
+
const result = dispatchSkill('worker', skillsDir, {
|
|
206
|
+
workspacePath: tempDir,
|
|
207
|
+
runnerCommand,
|
|
208
|
+
invocation: {
|
|
209
|
+
skill: 'worker',
|
|
210
|
+
batchId: 2,
|
|
211
|
+
testCaseId: 'tc-002',
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(result.status).toBe('failed');
|
|
216
|
+
expect(result.findings.join(' ')).toContain('runner failed');
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
describe('fixer sub-skill', () => {
|
|
5
|
+
const skillPath = path.join(__dirname, '..', 'templates', '.workspace-templates', 'skills', 'fixer', 'SKILL.md');
|
|
6
|
+
const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
|
+
const parseInlineYamlArray = (value: string): string[] =>
|
|
8
|
+
value
|
|
9
|
+
.replace(/^\[/, '')
|
|
10
|
+
.replace(/\]$/, '')
|
|
11
|
+
.split(',')
|
|
12
|
+
.map((item) => item.trim().replace(/^['"]|['"]$/g, ''))
|
|
13
|
+
.filter((item) => item.length > 0);
|
|
14
|
+
const getSectionBody = (content: string, heading: string): string | null => {
|
|
15
|
+
const headingPattern = escapeRegExp(heading);
|
|
16
|
+
const sectionMatch = content.match(
|
|
17
|
+
new RegExp(`##\\s+${headingPattern}\\s*\\r?\\n([\\s\\S]*?)(?=\\r?\\n##\\s+|$)`, 'i'),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return sectionMatch ? sectionMatch[1] : null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
it('exists', () => {
|
|
24
|
+
expect(fs.existsSync(skillPath)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('has YAML frontmatter at the top with required keys', () => {
|
|
28
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
29
|
+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
30
|
+
|
|
31
|
+
expect(frontmatterMatch).not.toBeNull();
|
|
32
|
+
|
|
33
|
+
if (!frontmatterMatch) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const frontmatter = frontmatterMatch[1];
|
|
38
|
+
|
|
39
|
+
expect(frontmatter).toMatch(/^name:\s*fixer\s*$/m);
|
|
40
|
+
|
|
41
|
+
const descriptionMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
42
|
+
expect(descriptionMatch).not.toBeNull();
|
|
43
|
+
expect(descriptionMatch?.[1].trim()).not.toBe('');
|
|
44
|
+
|
|
45
|
+
const triggersLineMatch = frontmatter.match(/^triggers:\s*(.*)$/m);
|
|
46
|
+
expect(triggersLineMatch).not.toBeNull();
|
|
47
|
+
|
|
48
|
+
const triggersInlineValue = triggersLineMatch?.[1].trim() ?? '';
|
|
49
|
+
if (/^\[.*\]$/.test(triggersInlineValue)) {
|
|
50
|
+
const triggerItems = parseInlineYamlArray(triggersInlineValue);
|
|
51
|
+
expect(triggerItems.length).toBeGreaterThan(0);
|
|
52
|
+
} else {
|
|
53
|
+
expect(triggersInlineValue).toBe('');
|
|
54
|
+
|
|
55
|
+
const triggersBlockMatch = frontmatter.match(/^triggers:\s*\r?\n((?:\s*-\s+.+\r?\n?)*)/m);
|
|
56
|
+
expect(triggersBlockMatch).not.toBeNull();
|
|
57
|
+
|
|
58
|
+
const triggerItems = (triggersBlockMatch?.[1] ?? '')
|
|
59
|
+
.split(/\r?\n/)
|
|
60
|
+
.map((line) => line.trim())
|
|
61
|
+
.filter((line) => line.startsWith('- '))
|
|
62
|
+
.map((line) => line.replace(/^-\s+/, '').trim())
|
|
63
|
+
.filter((line) => line.length > 0);
|
|
64
|
+
|
|
65
|
+
expect(triggerItems.length).toBeGreaterThan(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const firstHeadingIndex = content.indexOf('\n## ');
|
|
69
|
+
expect(firstHeadingIndex).toBeGreaterThan(frontmatterMatch[0].length - 1);
|
|
70
|
+
|
|
71
|
+
const betweenFrontmatterAndFirstHeading = content
|
|
72
|
+
.slice(frontmatterMatch[0].length, firstHeadingIndex)
|
|
73
|
+
.trim();
|
|
74
|
+
expect(betweenFrontmatterAndFirstHeading).toBe('');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('has required sections', () => {
|
|
78
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
79
|
+
|
|
80
|
+
expect(content).toContain('## Overview');
|
|
81
|
+
expect(content).toContain('## When to Use');
|
|
82
|
+
expect(content).toContain('## When Not to Use');
|
|
83
|
+
expect(content).toContain('## The Iron Law');
|
|
84
|
+
expect(content).toContain('## The Process');
|
|
85
|
+
expect(content).toContain('## Anti-Rationalization Table');
|
|
86
|
+
expect(content).toContain('## Sub-Skill Dispatch');
|
|
87
|
+
expect(content).toContain('## Report Format');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('has Iron Law section', () => {
|
|
91
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
92
|
+
const ironLawSection = getSectionBody(content, 'The Iron Law');
|
|
93
|
+
|
|
94
|
+
expect(ironLawSection).not.toBeNull();
|
|
95
|
+
|
|
96
|
+
if (!ironLawSection) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
expect(ironLawSection).toMatch(/NO BLIND RETRIES/i);
|
|
101
|
+
expect(ironLawSection).toMatch(/NO COSMETIC FIXES/i);
|
|
102
|
+
expect(ironLawSection).toMatch(/NO FIXING WHAT IS NOT BROKEN/i);
|
|
103
|
+
expect(ironLawSection).toMatch(/NO CLAIMING FIX WITHOUT RE-?VALIDATION/i);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('captures core fixer process semantics without brittle paragraph matching', () => {
|
|
107
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
108
|
+
const processSection = getSectionBody(content, 'The Process');
|
|
109
|
+
|
|
110
|
+
expect(processSection).not.toBeNull();
|
|
111
|
+
|
|
112
|
+
if (!processSection) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
expect(processSection).toMatch(/root\s+cause[\s\S]{0,160}map[\s\S]{0,120}finding[\s\S]{0,120}defect/i);
|
|
117
|
+
expect(processSection).toMatch(/minimal\s+fix[\s\S]{0,160}change\s+only[\s\S]{0,120}needed/i);
|
|
118
|
+
expect(processSection).toMatch(/dispatch\s+validation[\s\S]{0,200}(after\s+fix|re-?validation)/i);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('has Anti-Rationalization Table', () => {
|
|
122
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
123
|
+
const antiRationalizationSection = getSectionBody(content, 'Anti-Rationalization Table');
|
|
124
|
+
|
|
125
|
+
expect(antiRationalizationSection).not.toBeNull();
|
|
126
|
+
|
|
127
|
+
if (!antiRationalizationSection) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
expect(antiRationalizationSection).toContain('| Thought | Reality |');
|
|
132
|
+
expect(antiRationalizationSection).toMatch(/re-?run the worker logic/i);
|
|
133
|
+
expect(antiRationalizationSection).toMatch(/blind retries/i);
|
|
134
|
+
expect(antiRationalizationSection).toMatch(/fix other things while i am here/i);
|
|
135
|
+
expect(antiRationalizationSection).toMatch(/scope creep/i);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('has Report Format section with required fixer report fields', () => {
|
|
139
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
140
|
+
expect(content.toLowerCase()).toContain('report format');
|
|
141
|
+
|
|
142
|
+
const reportJsonBlockMatch = content.match(/## Report Format[\s\S]*?```json\r?\n([\s\S]*?)\r?\n```/i);
|
|
143
|
+
expect(reportJsonBlockMatch).not.toBeNull();
|
|
144
|
+
|
|
145
|
+
if (!reportJsonBlockMatch) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const reportSample = JSON.parse(reportJsonBlockMatch[1]);
|
|
150
|
+
|
|
151
|
+
expect(reportSample).toEqual(
|
|
152
|
+
expect.objectContaining({
|
|
153
|
+
skill: 'fixer',
|
|
154
|
+
status: expect.any(String),
|
|
155
|
+
timestamp: expect.any(String),
|
|
156
|
+
testCaseId: expect.any(String),
|
|
157
|
+
batchId: expect.any(Number),
|
|
158
|
+
findings: expect.any(Array),
|
|
159
|
+
fixesApplied: expect.any(Array),
|
|
160
|
+
recommendations: expect.any(Array),
|
|
161
|
+
metrics: expect.any(Object),
|
|
162
|
+
nextSkill: 'validation',
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(reportSample.metrics).toEqual(
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
findingsAddressed: expect.any(Number),
|
|
169
|
+
fixesApplied: expect.any(Number),
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('keeps Process report shape aligned with Report Format fields', () => {
|
|
175
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
176
|
+
const processSection = getSectionBody(content, 'The Process');
|
|
177
|
+
|
|
178
|
+
expect(processSection).not.toBeNull();
|
|
179
|
+
|
|
180
|
+
if (!processSection) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
expect(processSection).toMatch(/Write\s+report\.json/i);
|
|
185
|
+
|
|
186
|
+
const requiredFields = [
|
|
187
|
+
'skill',
|
|
188
|
+
'status',
|
|
189
|
+
'timestamp',
|
|
190
|
+
'testCaseId',
|
|
191
|
+
'batchId',
|
|
192
|
+
'findings',
|
|
193
|
+
'fixesApplied',
|
|
194
|
+
'recommendations',
|
|
195
|
+
'metrics',
|
|
196
|
+
'nextSkill',
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
for (const field of requiredFields) {
|
|
200
|
+
expect(processSection).toMatch(new RegExp(`\\b${escapeRegExp(field)}\\b`, 'i'));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|