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,171 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export interface StageBenchmark {
|
|
5
|
+
name: string;
|
|
6
|
+
raw: number;
|
|
7
|
+
weight: number;
|
|
8
|
+
weighted: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BenchmarkResult {
|
|
12
|
+
workspace: string;
|
|
13
|
+
agent: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
rawScore: number;
|
|
16
|
+
weightedScore: number;
|
|
17
|
+
stages: StageBenchmark[];
|
|
18
|
+
fixSuggestions: string[];
|
|
19
|
+
improvementPotential: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const STAGE_WEIGHTS: Record<string, number> = {
|
|
23
|
+
'01-ideation': 1.5,
|
|
24
|
+
'02-research': 1.3,
|
|
25
|
+
'03-architecture': 1.2,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const DEFAULT_WEIGHT = 1.0;
|
|
29
|
+
const MAX_RAW_SCORE = 45;
|
|
30
|
+
|
|
31
|
+
export function calculateBenchmark(workspacePath: string): BenchmarkResult {
|
|
32
|
+
const ws = path.resolve(workspacePath);
|
|
33
|
+
const stageFolders = getNumberedFolders(ws);
|
|
34
|
+
|
|
35
|
+
const stages: StageBenchmark[] = [];
|
|
36
|
+
let totalWeighted = 0;
|
|
37
|
+
let totalWeight = 0;
|
|
38
|
+
|
|
39
|
+
for (const folder of stageFolders) {
|
|
40
|
+
const weight = STAGE_WEIGHTS[folder] ?? DEFAULT_WEIGHT;
|
|
41
|
+
const raw = calculateStageRawScore(ws, folder);
|
|
42
|
+
const weighted = (raw / MAX_RAW_SCORE) * 100 * weight;
|
|
43
|
+
|
|
44
|
+
stages.push({ name: folder, raw, weight, weighted });
|
|
45
|
+
totalWeighted += weighted;
|
|
46
|
+
totalWeight += weight;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const weightedScore = totalWeight > 0 ? totalWeighted / totalWeight : 0;
|
|
50
|
+
const rawScore = stages.reduce((sum, s) => sum + s.raw, 0);
|
|
51
|
+
|
|
52
|
+
const fixSuggestions = stages
|
|
53
|
+
.filter((s) => s.raw < MAX_RAW_SCORE)
|
|
54
|
+
.map((s) => `Improve ${s.name}: current score ${s.raw}/${MAX_RAW_SCORE}`);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
workspace: path.basename(ws),
|
|
58
|
+
agent: 'unknown',
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
rawScore,
|
|
61
|
+
weightedScore: Math.min(Math.round(weightedScore), 100),
|
|
62
|
+
stages,
|
|
63
|
+
fixSuggestions,
|
|
64
|
+
improvementPotential: stages.some((s) => s.raw < MAX_RAW_SCORE),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatBenchmarkTable(data: BenchmarkResult): string {
|
|
69
|
+
const lines: string[] = [];
|
|
70
|
+
|
|
71
|
+
lines.push(`\nBenchmark Report: ${data.workspace}`);
|
|
72
|
+
lines.push(`Agent: ${data.agent} | Timestamp: ${data.timestamp}`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push(
|
|
75
|
+
padRight('Stage', 20) +
|
|
76
|
+
padRight('Raw', 8) +
|
|
77
|
+
padRight('Weight', 10) +
|
|
78
|
+
padRight('Weighted', 12)
|
|
79
|
+
);
|
|
80
|
+
lines.push('-'.repeat(50));
|
|
81
|
+
|
|
82
|
+
for (const stage of data.stages) {
|
|
83
|
+
lines.push(
|
|
84
|
+
padRight(stage.name, 20) +
|
|
85
|
+
padRight(String(stage.raw), 8) +
|
|
86
|
+
padRight(stage.weight.toFixed(1) + 'x', 10) +
|
|
87
|
+
padRight(stage.weighted.toFixed(1), 12)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
lines.push('-'.repeat(50));
|
|
92
|
+
lines.push(
|
|
93
|
+
padRight('TOTAL', 20) +
|
|
94
|
+
padRight(String(data.rawScore), 8) +
|
|
95
|
+
padRight('', 10) +
|
|
96
|
+
padRight(data.weightedScore.toFixed(1), 12)
|
|
97
|
+
);
|
|
98
|
+
lines.push('');
|
|
99
|
+
|
|
100
|
+
if (data.fixSuggestions.length > 0) {
|
|
101
|
+
lines.push('Suggestions:');
|
|
102
|
+
for (const suggestion of data.fixSuggestions) {
|
|
103
|
+
lines.push(` - ${suggestion}`);
|
|
104
|
+
}
|
|
105
|
+
lines.push('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function saveBenchmarkReport(workspacePath: string, data: BenchmarkResult): string {
|
|
112
|
+
const reportDir = path.join(workspacePath, '.workspace-benchmarks');
|
|
113
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
const filename = `${data.workspace}-${data.timestamp.replace(/[:.]/g, '-')}.json`;
|
|
116
|
+
const filePath = path.join(reportDir, filename);
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
119
|
+
return filePath;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function calculateStageRawScore(ws: string, folder: string): number {
|
|
123
|
+
const stageContextPath = path.join(ws, folder, 'CONTEXT.md');
|
|
124
|
+
let score = 0;
|
|
125
|
+
|
|
126
|
+
if (fs.existsSync(stageContextPath)) {
|
|
127
|
+
const content = fs.readFileSync(stageContextPath, 'utf-8');
|
|
128
|
+
if (content.toLowerCase().includes('purpose') || content.toLowerCase().includes('## purpose')) score += 4;
|
|
129
|
+
if (content.toLowerCase().includes('input')) score += 4;
|
|
130
|
+
if (content.toLowerCase().includes('output')) score += 4;
|
|
131
|
+
if (content.toLowerCase().includes('dependenc')) score += 3;
|
|
132
|
+
if (content.toLowerCase().includes('## success criteria') || content.toLowerCase().includes('success criteria')) score += 5;
|
|
133
|
+
if (content.toLowerCase().includes('## approach') || content.toLowerCase().includes('approach')) score += 5;
|
|
134
|
+
if (content.toLowerCase().includes('## risks') || content.toLowerCase().includes('risks')) score += 5;
|
|
135
|
+
if (content.toLowerCase().includes('## timeline') || content.toLowerCase().includes('timeline')) score += 5;
|
|
136
|
+
if (content.toLowerCase().includes('## resources') || content.toLowerCase().includes('resources')) score += 5;
|
|
137
|
+
if (content.toLowerCase().includes('## validation') || content.toLowerCase().includes('validation')) score += 5;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return Math.min(score, MAX_RAW_SCORE);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getNumberedFolders(workspacePath: string): string[] {
|
|
144
|
+
if (!fs.existsSync(workspacePath)) return [];
|
|
145
|
+
const entries = fs.readdirSync(workspacePath, { withFileTypes: true });
|
|
146
|
+
return entries
|
|
147
|
+
.filter((e) => e.isDirectory() && /^\d{2}-/.test(e.name) && e.name !== '00-meta')
|
|
148
|
+
.map((e) => e.name);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function padRight(str: string, length: number): string {
|
|
152
|
+
return str.padEnd(length);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (require.main === module) {
|
|
156
|
+
const args = process.argv.slice(2);
|
|
157
|
+
const parseArg = (flag: string): string | undefined => {
|
|
158
|
+
const idx = args.indexOf(flag);
|
|
159
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const workspace = parseArg('--workspace');
|
|
163
|
+
|
|
164
|
+
if (!workspace) {
|
|
165
|
+
console.error('Usage: node benchmark.ts --workspace <path>');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const result = calculateBenchmark(workspace);
|
|
170
|
+
console.log(formatBenchmarkTable(result));
|
|
171
|
+
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
export interface DispatchReport {
|
|
6
|
+
skill: string;
|
|
7
|
+
status: 'passed' | 'failed' | 'escalated';
|
|
8
|
+
timestamp: string;
|
|
9
|
+
findings: string[];
|
|
10
|
+
recommendations: string[];
|
|
11
|
+
metrics: Record<string, number>;
|
|
12
|
+
nextSkill: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ParallelInvocation {
|
|
16
|
+
skill: string;
|
|
17
|
+
batchId: number;
|
|
18
|
+
testCaseId: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ParallelDispatchResult extends DispatchReport {
|
|
22
|
+
batchId: number;
|
|
23
|
+
testCaseId: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DispatchOptions {
|
|
27
|
+
workspacePath?: string;
|
|
28
|
+
runnerCommand?: string;
|
|
29
|
+
runnerTimeoutSeconds?: number;
|
|
30
|
+
invocation?: ParallelInvocation;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ParallelDispatchOptions {
|
|
34
|
+
workspacePath?: string;
|
|
35
|
+
runnerCommand?: string;
|
|
36
|
+
runnerTimeoutSeconds?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const SKILL_NEXT_MAP: Record<string, string> = {
|
|
40
|
+
research: 'architecture',
|
|
41
|
+
architecture: 'none',
|
|
42
|
+
validation: 'prompt-engineering',
|
|
43
|
+
'prompt-engineering': 'testing',
|
|
44
|
+
testing: 'iteration',
|
|
45
|
+
iteration: 'none',
|
|
46
|
+
tooling: 'none',
|
|
47
|
+
worker: 'validation',
|
|
48
|
+
fixer: 'validation',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function isValidStatus(value: unknown): value is DispatchReport['status'] {
|
|
52
|
+
return value === 'passed' || value === 'failed' || value === 'escalated';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeStringArray(value: unknown): string[] {
|
|
56
|
+
if (!Array.isArray(value)) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return value
|
|
61
|
+
.map((item) => String(item))
|
|
62
|
+
.filter((item) => item.trim().length > 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractJsonPayload(output: string): Record<string, unknown> | null {
|
|
66
|
+
const trimmed = output.trim();
|
|
67
|
+
if (!trimmed) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(trimmed);
|
|
73
|
+
return typeof parsed === 'object' && parsed !== null
|
|
74
|
+
? parsed as Record<string, unknown>
|
|
75
|
+
: null;
|
|
76
|
+
} catch {
|
|
77
|
+
// Continue trying to parse trailing JSON from mixed runner logs.
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const firstBrace = trimmed.indexOf('{');
|
|
81
|
+
const lastBrace = trimmed.lastIndexOf('}');
|
|
82
|
+
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const candidate = trimmed.slice(firstBrace, lastBrace + 1);
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(candidate);
|
|
89
|
+
return typeof parsed === 'object' && parsed !== null
|
|
90
|
+
? parsed as Record<string, unknown>
|
|
91
|
+
: null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function renderRunnerCommand(
|
|
98
|
+
template: string,
|
|
99
|
+
skillName: string,
|
|
100
|
+
options: DispatchOptions,
|
|
101
|
+
): string {
|
|
102
|
+
const invocation = options.invocation;
|
|
103
|
+
const replacements: Record<string, string> = {
|
|
104
|
+
'{skill}': skillName,
|
|
105
|
+
'{workspace}': options.workspacePath ?? process.cwd(),
|
|
106
|
+
'{batchId}': invocation ? String(invocation.batchId) : '',
|
|
107
|
+
'{testCaseId}': invocation?.testCaseId ?? '',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return template.replace(/\{skill\}|\{workspace\}|\{batchId\}|\{testCaseId\}/g, (token) => {
|
|
111
|
+
return replacements[token] ?? token;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createRunnerFailureReport(
|
|
116
|
+
skillName: string,
|
|
117
|
+
message: string,
|
|
118
|
+
nextSkill: string,
|
|
119
|
+
metrics: Record<string, number> = {},
|
|
120
|
+
): DispatchReport {
|
|
121
|
+
return {
|
|
122
|
+
skill: skillName,
|
|
123
|
+
status: 'failed',
|
|
124
|
+
timestamp: new Date().toISOString(),
|
|
125
|
+
findings: [message],
|
|
126
|
+
recommendations: ['Inspect runner command and ensure it outputs a valid JSON report'],
|
|
127
|
+
metrics,
|
|
128
|
+
nextSkill,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function truncateTelemetryText(value: string, maxLength: number = 2000): string {
|
|
133
|
+
if (value.length <= maxLength) {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
return `${value.slice(0, maxLength)}...`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function writeRunnerTelemetry(
|
|
140
|
+
skillName: string,
|
|
141
|
+
options: DispatchOptions,
|
|
142
|
+
details: {
|
|
143
|
+
commandTemplate: string;
|
|
144
|
+
renderedCommand: string;
|
|
145
|
+
status: DispatchReport['status'];
|
|
146
|
+
timestamp: string;
|
|
147
|
+
durationMs: number;
|
|
148
|
+
exitCode: number;
|
|
149
|
+
timedOut: number;
|
|
150
|
+
stdout: string;
|
|
151
|
+
stderr: string;
|
|
152
|
+
parsedPayload: number;
|
|
153
|
+
},
|
|
154
|
+
): void {
|
|
155
|
+
try {
|
|
156
|
+
const runsDir = path.join(options.workspacePath ?? process.cwd(), '.agents', 'iteration', 'runs');
|
|
157
|
+
fs.mkdirSync(runsDir, { recursive: true });
|
|
158
|
+
|
|
159
|
+
const batchToken = options.invocation ? String(options.invocation.batchId) : 'na';
|
|
160
|
+
const testCaseToken = options.invocation?.testCaseId ? options.invocation.testCaseId.replace(/[^a-zA-Z0-9-_]/g, '_') : 'na';
|
|
161
|
+
const fileName = `${Date.now()}-${skillName}-${batchToken}-${testCaseToken}.json`;
|
|
162
|
+
const filePath = path.join(runsDir, fileName);
|
|
163
|
+
|
|
164
|
+
const telemetry = {
|
|
165
|
+
skill: skillName,
|
|
166
|
+
status: details.status,
|
|
167
|
+
timestamp: details.timestamp,
|
|
168
|
+
batchId: options.invocation?.batchId,
|
|
169
|
+
testCaseId: options.invocation?.testCaseId,
|
|
170
|
+
commandTemplate: details.commandTemplate,
|
|
171
|
+
renderedCommand: details.renderedCommand,
|
|
172
|
+
durationMs: details.durationMs,
|
|
173
|
+
exitCode: details.exitCode,
|
|
174
|
+
timedOut: details.timedOut,
|
|
175
|
+
parsedPayload: details.parsedPayload,
|
|
176
|
+
stdoutLength: details.stdout.length,
|
|
177
|
+
stderrLength: details.stderr.length,
|
|
178
|
+
stdoutPreview: truncateTelemetryText(details.stdout),
|
|
179
|
+
stderrPreview: truncateTelemetryText(details.stderr),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
fs.writeFileSync(filePath, JSON.stringify(telemetry, null, 2));
|
|
183
|
+
} catch {
|
|
184
|
+
// Best-effort telemetry; dispatch result should not fail due to telemetry write issues.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function runExternalRunner(
|
|
189
|
+
skillName: string,
|
|
190
|
+
options: DispatchOptions,
|
|
191
|
+
): DispatchReport {
|
|
192
|
+
if (!options.runnerCommand) {
|
|
193
|
+
return createRunnerFailureReport(skillName, 'Runner command is required for external dispatch mode', 'none');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const command = renderRunnerCommand(options.runnerCommand, skillName, options);
|
|
197
|
+
const startedAt = Date.now();
|
|
198
|
+
const timeoutMs = (options.runnerTimeoutSeconds ?? 300) * 1000;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const stdout = execFileSync(command, {
|
|
202
|
+
cwd: options.workspacePath ?? process.cwd(),
|
|
203
|
+
encoding: 'utf-8',
|
|
204
|
+
shell: true,
|
|
205
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
206
|
+
timeout: timeoutMs,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const parsed = extractJsonPayload(stdout);
|
|
210
|
+
const elapsedMs = Date.now() - startedAt;
|
|
211
|
+
const nextSkill = SKILL_NEXT_MAP[skillName] ?? 'none';
|
|
212
|
+
|
|
213
|
+
const commandTemplate = options.runnerCommand ?? '';
|
|
214
|
+
|
|
215
|
+
if (!parsed) {
|
|
216
|
+
const report: DispatchReport = {
|
|
217
|
+
skill: skillName,
|
|
218
|
+
status: 'passed',
|
|
219
|
+
timestamp: new Date().toISOString(),
|
|
220
|
+
findings: ['External runner completed without JSON payload'],
|
|
221
|
+
recommendations: ['Emit JSON report for richer metrics and routing decisions'],
|
|
222
|
+
metrics: {
|
|
223
|
+
executionTimeMs: elapsedMs,
|
|
224
|
+
outputLength: stdout.length,
|
|
225
|
+
},
|
|
226
|
+
nextSkill,
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
writeRunnerTelemetry(skillName, options, {
|
|
230
|
+
commandTemplate,
|
|
231
|
+
renderedCommand: command,
|
|
232
|
+
status: report.status,
|
|
233
|
+
timestamp: report.timestamp,
|
|
234
|
+
durationMs: elapsedMs,
|
|
235
|
+
exitCode: 0,
|
|
236
|
+
timedOut: 0,
|
|
237
|
+
stdout,
|
|
238
|
+
stderr: '',
|
|
239
|
+
parsedPayload: 0,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return report;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const findings = normalizeStringArray(parsed.findings);
|
|
246
|
+
const recommendations = normalizeStringArray(parsed.recommendations);
|
|
247
|
+
const rawMetrics = parsed.metrics;
|
|
248
|
+
const metrics = typeof rawMetrics === 'object' && rawMetrics !== null
|
|
249
|
+
? rawMetrics as Record<string, number>
|
|
250
|
+
: {};
|
|
251
|
+
|
|
252
|
+
const report: DispatchReport = {
|
|
253
|
+
skill: typeof parsed.skill === 'string' && parsed.skill.trim()
|
|
254
|
+
? parsed.skill
|
|
255
|
+
: skillName,
|
|
256
|
+
status: isValidStatus(parsed.status) ? parsed.status : 'passed',
|
|
257
|
+
timestamp: typeof parsed.timestamp === 'string' && parsed.timestamp.trim()
|
|
258
|
+
? parsed.timestamp
|
|
259
|
+
: new Date().toISOString(),
|
|
260
|
+
findings: findings.length > 0 ? findings : ['External runner completed'],
|
|
261
|
+
recommendations: recommendations.length > 0
|
|
262
|
+
? recommendations
|
|
263
|
+
: ['Proceed using the returned runner output'],
|
|
264
|
+
metrics: {
|
|
265
|
+
...metrics,
|
|
266
|
+
executionTimeMs: typeof metrics.executionTimeMs === 'number'
|
|
267
|
+
? metrics.executionTimeMs
|
|
268
|
+
: elapsedMs,
|
|
269
|
+
},
|
|
270
|
+
nextSkill: typeof parsed.nextSkill === 'string' && parsed.nextSkill.trim()
|
|
271
|
+
? parsed.nextSkill
|
|
272
|
+
: nextSkill,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
writeRunnerTelemetry(skillName, options, {
|
|
276
|
+
commandTemplate,
|
|
277
|
+
renderedCommand: command,
|
|
278
|
+
status: report.status,
|
|
279
|
+
timestamp: report.timestamp,
|
|
280
|
+
durationMs: elapsedMs,
|
|
281
|
+
exitCode: 0,
|
|
282
|
+
timedOut: 0,
|
|
283
|
+
stdout,
|
|
284
|
+
stderr: '',
|
|
285
|
+
parsedPayload: 1,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return report;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
const err = error as {
|
|
291
|
+
status?: number;
|
|
292
|
+
signal?: string;
|
|
293
|
+
stdout?: string | Buffer;
|
|
294
|
+
stderr?: string | Buffer;
|
|
295
|
+
message?: string;
|
|
296
|
+
killed?: boolean;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const stderr = err.stderr ? String(err.stderr).trim() : '';
|
|
300
|
+
const stdout = err.stdout ? String(err.stdout).trim() : '';
|
|
301
|
+
const baseMessage = err.message ?? 'External runner failed';
|
|
302
|
+
const detailMessage = [stderr, stdout].find((value) => value.length > 0) ?? baseMessage;
|
|
303
|
+
|
|
304
|
+
const failureReport = createRunnerFailureReport(
|
|
305
|
+
skillName,
|
|
306
|
+
detailMessage,
|
|
307
|
+
'none',
|
|
308
|
+
{
|
|
309
|
+
exitCode: typeof err.status === 'number' ? err.status : -1,
|
|
310
|
+
timedOut: err.signal === 'SIGTERM' || err.killed ? 1 : 0,
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
writeRunnerTelemetry(skillName, options, {
|
|
315
|
+
commandTemplate: options.runnerCommand ?? '',
|
|
316
|
+
renderedCommand: command,
|
|
317
|
+
status: failureReport.status,
|
|
318
|
+
timestamp: failureReport.timestamp,
|
|
319
|
+
durationMs: Date.now() - startedAt,
|
|
320
|
+
exitCode: typeof err.status === 'number' ? err.status : -1,
|
|
321
|
+
timedOut: err.signal === 'SIGTERM' || err.killed ? 1 : 0,
|
|
322
|
+
stdout,
|
|
323
|
+
stderr,
|
|
324
|
+
parsedPayload: 0,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return failureReport;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function dispatchSkill(skillName: string, skillsDir: string, options: DispatchOptions = {}): DispatchReport {
|
|
332
|
+
const skillPath = path.join(skillsDir, skillName, 'SKILL.md');
|
|
333
|
+
|
|
334
|
+
if (!fs.existsSync(skillPath)) {
|
|
335
|
+
return {
|
|
336
|
+
skill: skillName,
|
|
337
|
+
status: 'failed',
|
|
338
|
+
timestamp: new Date().toISOString(),
|
|
339
|
+
findings: [`Sub-skill SKILL.md not found: ${skillPath}`],
|
|
340
|
+
recommendations: ['Ensure the sub-skill directory and SKILL.md exist'],
|
|
341
|
+
metrics: {},
|
|
342
|
+
nextSkill: 'none',
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
347
|
+
const nameMatch = content.match(/^---\nname:\s*(.+)$/m);
|
|
348
|
+
const skill = nameMatch ? nameMatch[1].trim() : skillName;
|
|
349
|
+
|
|
350
|
+
const requiresExternalRunner = skillName === 'worker' || skillName === 'fixer';
|
|
351
|
+
if (requiresExternalRunner && !options.runnerCommand) {
|
|
352
|
+
return createRunnerFailureReport(
|
|
353
|
+
skillName,
|
|
354
|
+
'External sub-agent runner is required for worker/fixer',
|
|
355
|
+
'none',
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const usesExternalRunner = Boolean(options.runnerCommand && (skillName === 'worker' || skillName === 'fixer'));
|
|
360
|
+
if (usesExternalRunner) {
|
|
361
|
+
return runExternalRunner(skillName, options);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const fallbackRecommendations = ['Follow the sub-skill instructions to complete the task'];
|
|
365
|
+
if (skillName === 'worker' || skillName === 'fixer') {
|
|
366
|
+
fallbackRecommendations.push('Configure --runner-command or WORKSPACE_MAXXING_SUBAGENT_RUNNER for true sub-agent execution');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
skill,
|
|
371
|
+
status: 'passed',
|
|
372
|
+
timestamp: new Date().toISOString(),
|
|
373
|
+
findings: [`Sub-skill "${skill}" loaded successfully`],
|
|
374
|
+
recommendations: fallbackRecommendations,
|
|
375
|
+
metrics: {
|
|
376
|
+
contentLength: content.length,
|
|
377
|
+
simulatedDispatch: skillName === 'worker' || skillName === 'fixer' ? 1 : 0,
|
|
378
|
+
},
|
|
379
|
+
nextSkill: SKILL_NEXT_MAP[skillName] ?? 'none',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function dispatchParallel(
|
|
384
|
+
invocations: ParallelInvocation[],
|
|
385
|
+
skillsDir: string,
|
|
386
|
+
options: ParallelDispatchOptions = {},
|
|
387
|
+
): ParallelDispatchResult[] {
|
|
388
|
+
return invocations.map((inv) => {
|
|
389
|
+
const report = dispatchSkill(inv.skill, skillsDir, {
|
|
390
|
+
...options,
|
|
391
|
+
invocation: inv,
|
|
392
|
+
});
|
|
393
|
+
return {
|
|
394
|
+
...report,
|
|
395
|
+
batchId: inv.batchId,
|
|
396
|
+
testCaseId: inv.testCaseId,
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (require.main === module) {
|
|
402
|
+
const args = process.argv.slice(2);
|
|
403
|
+
const parseArg = (flag: string): string | undefined => {
|
|
404
|
+
const idx = args.indexOf(flag);
|
|
405
|
+
return idx !== -1 ? args[idx + 1] : undefined;
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const skill = parseArg('--skill');
|
|
409
|
+
const workspace = parseArg('--workspace');
|
|
410
|
+
const batchId = parseArg('--batch-id');
|
|
411
|
+
const testCaseId = parseArg('--test-case-id');
|
|
412
|
+
const parallel = args.includes('--parallel');
|
|
413
|
+
const invocationsPath = parseArg('--invocations');
|
|
414
|
+
const runnerCommand = parseArg('--runner-command') ?? process.env.WORKSPACE_MAXXING_SUBAGENT_RUNNER;
|
|
415
|
+
const runnerTimeoutRaw = parseArg('--runner-timeout');
|
|
416
|
+
let runnerTimeoutSeconds: number | undefined;
|
|
417
|
+
if (runnerTimeoutRaw !== undefined) {
|
|
418
|
+
const parsedTimeout = Number(runnerTimeoutRaw);
|
|
419
|
+
if (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0) {
|
|
420
|
+
console.error('--runner-timeout must be a positive number of seconds');
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
runnerTimeoutSeconds = parsedTimeout;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const skillsDir = workspace
|
|
427
|
+
? path.join(workspace, '.agents', 'skills', 'workspace-maxxing', 'skills')
|
|
428
|
+
: path.join(process.cwd(), 'skills');
|
|
429
|
+
|
|
430
|
+
const dispatchOptions: ParallelDispatchOptions = {
|
|
431
|
+
workspacePath: workspace,
|
|
432
|
+
runnerCommand,
|
|
433
|
+
runnerTimeoutSeconds,
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
if (parallel) {
|
|
437
|
+
if (!invocationsPath) {
|
|
438
|
+
console.error('--parallel requires --invocations <path>');
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const parsed = JSON.parse(fs.readFileSync(invocationsPath, 'utf-8'));
|
|
443
|
+
if (!Array.isArray(parsed)) {
|
|
444
|
+
console.error('--invocations must point to a JSON array');
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const results = dispatchParallel(parsed as ParallelInvocation[], skillsDir, dispatchOptions);
|
|
449
|
+
console.log(JSON.stringify(results, null, 2));
|
|
450
|
+
} else {
|
|
451
|
+
if (!skill) {
|
|
452
|
+
console.error('Usage: node dispatch.ts --skill <name> --workspace <path> [--batch-id <n>] [--parallel --invocations <path>]');
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const singleInvocation = batchId
|
|
457
|
+
? {
|
|
458
|
+
skill,
|
|
459
|
+
batchId: parseInt(batchId, 10),
|
|
460
|
+
testCaseId: testCaseId ?? '',
|
|
461
|
+
}
|
|
462
|
+
: undefined;
|
|
463
|
+
|
|
464
|
+
const result = dispatchSkill(skill, skillsDir, {
|
|
465
|
+
...dispatchOptions,
|
|
466
|
+
invocation: singleInvocation,
|
|
467
|
+
});
|
|
468
|
+
const output = batchId
|
|
469
|
+
? { ...result, batchId: parseInt(batchId, 10) }
|
|
470
|
+
: result;
|
|
471
|
+
console.log(JSON.stringify(output, null, 2));
|
|
472
|
+
}
|
|
473
|
+
}
|