agents-templated 2.2.12 → 2.2.13
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/README.md +32 -5
- package/bin/cli.js +49 -0
- package/lib/orchestrator.js +562 -0
- package/lib/workflow.js +470 -1
- package/package.json +1 -1
- package/templates/.claude/agents/README.md +15 -1
- package/templates/.claude/agents/architect.md +79 -106
- package/templates/.claude/agents/backend-specialist.md +79 -0
- package/templates/.claude/agents/build-error-resolver.md +78 -119
- package/templates/.claude/agents/code-reviewer.md +79 -116
- package/templates/.claude/agents/compatibility-checker.md +79 -79
- package/templates/.claude/agents/configuration-validator.md +79 -85
- package/templates/.claude/agents/database-migrator.md +79 -83
- package/templates/.claude/agents/dependency-auditor.md +79 -92
- package/templates/.claude/agents/deployment-specialist.md +91 -0
- package/templates/.claude/agents/doc-updater.md +78 -130
- package/templates/.claude/agents/e2e-runner.md +78 -122
- package/templates/.claude/agents/frontend-specialist.md +79 -0
- package/templates/.claude/agents/load-tester.md +79 -80
- package/templates/.claude/agents/performance-profiler.md +79 -103
- package/templates/.claude/agents/performance-specialist.md +91 -0
- package/templates/.claude/agents/planner.md +81 -87
- package/templates/.claude/agents/qa-specialist.md +92 -0
- package/templates/.claude/agents/refactor-cleaner.md +79 -137
- package/templates/.claude/agents/release-ops-specialist.md +80 -0
- package/templates/.claude/agents/security-reviewer.md +80 -138
- package/templates/.claude/agents/tdd-guide.md +79 -98
- package/templates/.claude/agents/test-data-builder.md +79 -0
- package/templates/CLAUDE.md +7 -0
- package/templates/README.md +34 -5
- package/templates/agent-docs/ARCHITECTURE.md +6 -0
- package/templates/agents/commands/README.md +79 -0
- package/templates/agents/commands/SCHEMA.md +21 -1
- package/templates/agents/commands/test-data.md +56 -0
- package/agents/commands/README.md +0 -64
- package/agents/commands/SCHEMA.md +0 -22
- package/agents/commands/arch-check.md +0 -58
- package/agents/commands/audit.md +0 -58
- package/agents/commands/debug-track.md +0 -58
- package/agents/commands/docs.md +0 -58
- package/agents/commands/fix.md +0 -58
- package/agents/commands/learn-loop.md +0 -58
- package/agents/commands/perf.md +0 -58
- package/agents/commands/plan.md +0 -58
- package/agents/commands/pr.md +0 -58
- package/agents/commands/problem-map.md +0 -58
- package/agents/commands/release-ready.md +0 -58
- package/agents/commands/release.md +0 -58
- package/agents/commands/risk-review.md +0 -58
- package/agents/commands/scope-shape.md +0 -58
- package/agents/commands/task.md +0 -58
- package/agents/commands/test.md +0 -58
- package/agents/commands/ux-bar.md +0 -58
- package/agents/rules/planning.mdc +0 -69
package/README.md
CHANGED
|
@@ -237,6 +237,7 @@ These commands provide deterministic specialist guidance aligned to the sprint l
|
|
|
237
237
|
| `arch-check` | Architecture Reviewer | Lock architecture and edge-case coverage |
|
|
238
238
|
| `ux-bar` | Design Quality Lead | Raise UX quality before implementation |
|
|
239
239
|
| `debug-track` | Root-Cause Investigator | Reproduce and isolate root cause |
|
|
240
|
+
| `test-data` | Test Data Builder | Prepare deterministic fixtures/seeds for downstream validation |
|
|
240
241
|
| `risk-review` | Release Risk Reviewer | Surface production-risk issues before merge |
|
|
241
242
|
| `perf` | Performance Analyst | Optimize performance and guard against regressions |
|
|
242
243
|
| `release-ready` | Release Coordinator | Prepare release artifacts and final checks |
|
|
@@ -245,6 +246,23 @@ These commands provide deterministic specialist guidance aligned to the sprint l
|
|
|
245
246
|
|
|
246
247
|
Each command maps to deterministic contract files in `agents/commands/` and uses the schema in `agents/commands/SCHEMA.md`.
|
|
247
248
|
|
|
249
|
+
### Deprecated Workflow Aliases
|
|
250
|
+
|
|
251
|
+
The CLI keeps selected legacy names as non-breaking redirects with deterministic notices.
|
|
252
|
+
|
|
253
|
+
| Deprecated | Canonical |
|
|
254
|
+
|------------|-----------|
|
|
255
|
+
| `quality-gate` | `risk-review` |
|
|
256
|
+
| `perf-scan` | `perf` |
|
|
257
|
+
| `docs-sync` | `docs` |
|
|
258
|
+
|
|
259
|
+
Migration guidance:
|
|
260
|
+
|
|
261
|
+
- Existing scripts continue to work.
|
|
262
|
+
- Alias invocations print a deprecation warning and redirect deterministically.
|
|
263
|
+
- New automation should use canonical names only.
|
|
264
|
+
- Sunset guidance: deprecated aliases remain supported through v2.x and are scheduled for removal in v3.0.
|
|
265
|
+
|
|
248
266
|
|
|
249
267
|
---
|
|
250
268
|
|
|
@@ -361,11 +379,20 @@ Your AI will follow the enterprise patterns automatically!
|
|
|
361
379
|
|
|
362
380
|
| Agent | Responsibility |
|
|
363
381
|
|-------|---------------|
|
|
364
|
-
| **
|
|
365
|
-
| **
|
|
366
|
-
| **
|
|
367
|
-
| **
|
|
368
|
-
| **
|
|
382
|
+
| **backend-specialist** | API, business logic, auth middleware, persistence changes |
|
|
383
|
+
| **frontend-specialist** | UI/UX implementation, accessibility, interaction behavior |
|
|
384
|
+
| **qa-specialist** | Design-mode test planning and validation-mode regression gates |
|
|
385
|
+
| **performance-specialist** | Mode-locked performance profiling and load threshold validation |
|
|
386
|
+
| **test-data-builder** | Deterministic fixtures, seeds, and downstream handoff contracts |
|
|
387
|
+
| **security-reviewer** | Conditional security invocation based on trigger thresholds |
|
|
388
|
+
| **dependency-auditor** | CVE/dependency risk auditing and upgrade hygiene |
|
|
389
|
+
| **deployment-specialist** | Ordered deployment phase contract and rollback readiness |
|
|
390
|
+
|
|
391
|
+
#### Separation-Preservation Sequence Contracts
|
|
392
|
+
|
|
393
|
+
- Backend implementation lane: `backend-specialist -> build-error-resolver -> compatibility-checker`
|
|
394
|
+
- Review/governance lane: `code-reviewer -> dependency-auditor -> doc-updater`
|
|
395
|
+
- Test-data handoff lane: `qa-specialist(mode=design) -> test-data-builder -> qa-specialist(mode=validation) -> e2e-runner -> performance-specialist(mode=load)`
|
|
369
396
|
|
|
370
397
|
**Reference**: [AGENTS.MD](AGENTS.MD)
|
|
371
398
|
|
package/bin/cli.js
CHANGED
|
@@ -27,9 +27,14 @@ const {
|
|
|
27
27
|
WORKFLOW_COMMANDS,
|
|
28
28
|
CONTRACT_FILES,
|
|
29
29
|
SPECIALIST_CONTRACT_FILES,
|
|
30
|
+
DEPRECATED_COMMAND_ALIASES,
|
|
30
31
|
formatWorkflowOutput,
|
|
31
32
|
validateWorkflowDefinitions
|
|
32
33
|
} = require('../lib/workflow');
|
|
34
|
+
const {
|
|
35
|
+
WorkflowOrchestrator,
|
|
36
|
+
formatOrchestrationOutput
|
|
37
|
+
} = require('../lib/orchestrator');
|
|
33
38
|
|
|
34
39
|
// Resolve the templates directory - works in both dev and installed contexts
|
|
35
40
|
const getTemplatesDir = () => {
|
|
@@ -1109,6 +1114,32 @@ program
|
|
|
1109
1114
|
console.log(chalk.white(' agents-templated problem-map "daily briefing app for founders"\n'));
|
|
1110
1115
|
});
|
|
1111
1116
|
|
|
1117
|
+
program
|
|
1118
|
+
.command('orchestrate [objective...]')
|
|
1119
|
+
.description('Automatically route a high-level objective across specialist subagents')
|
|
1120
|
+
.option('--scenario <id>', 'Force a specific scenario id (feature-delivery, backend-api, frontend-feature, bug-fix, deployment)')
|
|
1121
|
+
.option('--mode <mode>', 'slash-command or slash-command-auto', 'slash-command-auto')
|
|
1122
|
+
.option('--retry-cycle <n>', 'Current retry cycle for refactor-cleaner/build-error-resolver loop control', '0')
|
|
1123
|
+
.option('--json', 'Emit structured JSON only')
|
|
1124
|
+
.action((objective, options) => {
|
|
1125
|
+
const objectiveText = Array.isArray(objective) ? objective.join(' ') : '';
|
|
1126
|
+
const retryCycle = Number.parseInt(options.retryCycle, 10);
|
|
1127
|
+
const orchestrator = new WorkflowOrchestrator();
|
|
1128
|
+
const result = orchestrator.orchestrate({
|
|
1129
|
+
objective: objectiveText,
|
|
1130
|
+
scenarioId: options.scenario,
|
|
1131
|
+
mode: options.mode,
|
|
1132
|
+
retryCycle: Number.isNaN(retryCycle) ? 0 : retryCycle
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
if (options.json) {
|
|
1136
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
process.stdout.write(formatOrchestrationOutput(result));
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1112
1143
|
for (const workflow of WORKFLOW_COMMANDS) {
|
|
1113
1144
|
program
|
|
1114
1145
|
.command(`${workflow.cli} [objective...]`)
|
|
@@ -1119,6 +1150,24 @@ for (const workflow of WORKFLOW_COMMANDS) {
|
|
|
1119
1150
|
});
|
|
1120
1151
|
}
|
|
1121
1152
|
|
|
1153
|
+
for (const deprecatedAlias of DEPRECATED_COMMAND_ALIASES) {
|
|
1154
|
+
program
|
|
1155
|
+
.command(`${deprecatedAlias.from} [objective...]`)
|
|
1156
|
+
.description(`Deprecated alias for ${deprecatedAlias.to}`)
|
|
1157
|
+
.action((objective) => {
|
|
1158
|
+
const objectiveText = Array.isArray(objective) ? objective.join(' ') : '';
|
|
1159
|
+
const redirectedWorkflow = WORKFLOW_COMMANDS.find((workflow) => workflow.cli === deprecatedAlias.to);
|
|
1160
|
+
|
|
1161
|
+
if (!redirectedWorkflow) {
|
|
1162
|
+
console.error(chalk.red(`Deprecated alias target not found: ${deprecatedAlias.to}`));
|
|
1163
|
+
process.exit(1);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
console.log(chalk.yellow(`[deprecated] ${deprecatedAlias.notice}`));
|
|
1167
|
+
process.stdout.write(formatWorkflowOutput(redirectedWorkflow, objectiveText));
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1122
1171
|
program
|
|
1123
1172
|
.command('new-skill <name>')
|
|
1124
1173
|
.description('Scaffold a new skill in .github/skills/<name>/SKILL.md')
|
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
const {
|
|
2
|
+
WORKFLOW_COMMANDS,
|
|
3
|
+
ORCHESTRATION_TRACKS,
|
|
4
|
+
OPTIONAL_SUBAGENT_RULES,
|
|
5
|
+
DEPRECATED_SUBAGENT_ALIASES,
|
|
6
|
+
NON_OVERLAP_ROUTE_BOUNDARIES,
|
|
7
|
+
MODE_LOCKED_SPECIALISTS,
|
|
8
|
+
SECURITY_REVIEW_POLICY,
|
|
9
|
+
REFACTOR_BUILD_RETRY_CAP,
|
|
10
|
+
REFACTOR_BUILD_HALT_AFTER,
|
|
11
|
+
findScenarioById,
|
|
12
|
+
resolveScenarioFromObjective
|
|
13
|
+
} = require('./workflow');
|
|
14
|
+
|
|
15
|
+
function createExecutionId(command) {
|
|
16
|
+
return `${command}-${Date.now().toString(36)}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeMode(mode) {
|
|
20
|
+
return mode === 'slash-command' ? 'slash-command' : 'slash-command-auto';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class WorkflowOrchestrator {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.workflowLookup = new Map(WORKFLOW_COMMANDS.map((workflow) => [workflow.cli, workflow]));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resolveScenario({ scenarioId, objective }) {
|
|
29
|
+
if (scenarioId) {
|
|
30
|
+
const explicitScenario = findScenarioById(scenarioId);
|
|
31
|
+
if (!explicitScenario) {
|
|
32
|
+
throw new Error(`Unknown scenario: ${scenarioId}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
scenario: explicitScenario,
|
|
37
|
+
reason: 'explicit scenario override'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return resolveScenarioFromObjective(objective);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
resolveOptionalSubagents({ scenarioId, phase, objective, routeState }) {
|
|
45
|
+
const objectiveLower = (objective || '').toLowerCase();
|
|
46
|
+
const activeSubagents = routeState.activeSubagents;
|
|
47
|
+
|
|
48
|
+
const matchesRule = (rule) => {
|
|
49
|
+
const when = rule.when || {};
|
|
50
|
+
|
|
51
|
+
if (Array.isArray(when.scenarios) && when.scenarios.length > 0 && !when.scenarios.includes(scenarioId)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(when.tracks) && when.tracks.length > 0 && !when.tracks.includes(phase.track)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(when.commands) && when.commands.length > 0 && !when.commands.includes(phase.command)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(when.keywords) && when.keywords.length > 0) {
|
|
64
|
+
const keywordMatched = when.keywords.some((keyword) => objectiveLower.includes(keyword.toLowerCase()));
|
|
65
|
+
if (!keywordMatched) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (Array.isArray(rule.dependsOnSubagents) && rule.dependsOnSubagents.length > 0) {
|
|
71
|
+
const isDependencySatisfied = rule.dependsOnSubagents.every((name) => activeSubagents.has(name));
|
|
72
|
+
if (!isDependencySatisfied) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let optionalSubagents = OPTIONAL_SUBAGENT_RULES
|
|
81
|
+
.filter(matchesRule)
|
|
82
|
+
.map((rule) => {
|
|
83
|
+
const alias = DEPRECATED_SUBAGENT_ALIASES[rule.subagent] || null;
|
|
84
|
+
const delegatedName = alias ? alias.to : rule.subagent;
|
|
85
|
+
const delegatedMode = rule.mode || (alias ? alias.mode : null);
|
|
86
|
+
|
|
87
|
+
if (alias) {
|
|
88
|
+
routeState.deprecationNotices.add(alias.notice);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
name: delegatedName,
|
|
93
|
+
required: false,
|
|
94
|
+
invocation_mode: delegatedMode,
|
|
95
|
+
reason: rule.reason || 'Scenario-specific optional delegation.',
|
|
96
|
+
deprecated_from: alias ? rule.subagent : null,
|
|
97
|
+
deprecation_notice: alias ? alias.notice : null
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const commandAllowlist = NON_OVERLAP_ROUTE_BOUNDARIES.commandAllowlist[phase.command];
|
|
102
|
+
if (Array.isArray(commandAllowlist) && commandAllowlist.length > 0) {
|
|
103
|
+
optionalSubagents = optionalSubagents.filter((entry) => commandAllowlist.includes(entry.name));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
phase.command === 'risk-review' &&
|
|
108
|
+
optionalSubagents.some((entry) => entry.name === 'code-reviewer') &&
|
|
109
|
+
!optionalSubagents.some((entry) => entry.name === 'dependency-auditor')
|
|
110
|
+
) {
|
|
111
|
+
optionalSubagents.push({
|
|
112
|
+
name: 'dependency-auditor',
|
|
113
|
+
required: false,
|
|
114
|
+
invocation_mode: null,
|
|
115
|
+
reason: 'Sequenced after code-reviewer to preserve review/dependency/docs ownership boundaries.',
|
|
116
|
+
deprecated_from: null,
|
|
117
|
+
deprecation_notice: null
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const mergedByIdentity = new Map();
|
|
122
|
+
for (const entry of optionalSubagents) {
|
|
123
|
+
const identity = `${entry.name}::${entry.invocation_mode || 'none'}`;
|
|
124
|
+
if (!mergedByIdentity.has(identity)) {
|
|
125
|
+
mergedByIdentity.set(identity, entry);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const existing = mergedByIdentity.get(identity);
|
|
130
|
+
const reasonParts = [existing.reason, entry.reason].filter(Boolean);
|
|
131
|
+
existing.reason = Array.from(new Set(reasonParts)).join(' ');
|
|
132
|
+
existing.required = existing.required || entry.required;
|
|
133
|
+
mergedByIdentity.set(identity, existing);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const reviewOrder = NON_OVERLAP_ROUTE_BOUNDARIES.reviewSequence;
|
|
137
|
+
return Array.from(mergedByIdentity.values()).sort((left, right) => {
|
|
138
|
+
const leftIndex = reviewOrder.indexOf(left.name);
|
|
139
|
+
const rightIndex = reviewOrder.indexOf(right.name);
|
|
140
|
+
|
|
141
|
+
if (leftIndex === -1 && rightIndex === -1) {
|
|
142
|
+
return left.name.localeCompare(right.name);
|
|
143
|
+
}
|
|
144
|
+
if (leftIndex === -1) {
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
if (rightIndex === -1) {
|
|
148
|
+
return -1;
|
|
149
|
+
}
|
|
150
|
+
return leftIndex - rightIndex;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
resolveInvocationMode({ routedSubagent, phase, objective, routeState }) {
|
|
155
|
+
const objectiveLower = (objective || '').toLowerCase();
|
|
156
|
+
|
|
157
|
+
if (routedSubagent === 'qa-specialist') {
|
|
158
|
+
if (phase.track === 'qa-design') {
|
|
159
|
+
return 'design';
|
|
160
|
+
}
|
|
161
|
+
if (phase.track === 'qa') {
|
|
162
|
+
return 'validation';
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (routedSubagent === 'performance-specialist') {
|
|
168
|
+
if (routeState.activeSubagents.has('test-data-builder')) {
|
|
169
|
+
return 'load';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const loadKeywords = ['load', 'throughput', 'stress', 'traffic'];
|
|
173
|
+
return loadKeywords.some((keyword) => objectiveLower.includes(keyword)) ? 'load' : 'profile';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
assertModeLock(subagent, invocationMode) {
|
|
180
|
+
const allowedModes = MODE_LOCKED_SPECIALISTS[subagent];
|
|
181
|
+
if (!allowedModes) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!invocationMode) {
|
|
186
|
+
const error = new Error(`Missing required mode for ${subagent}. Allowed: ${allowedModes.join('|')}`);
|
|
187
|
+
error.name = 'ModeLockError';
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!allowedModes.includes(invocationMode)) {
|
|
192
|
+
const error = new Error(`Unsupported mode ${invocationMode} for ${subagent}. Allowed: ${allowedModes.join('|')}`);
|
|
193
|
+
error.name = 'ModeLockError';
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
evaluateSecurityInvocationPolicy({ objective }) {
|
|
199
|
+
const objectiveLower = (objective || '').toLowerCase();
|
|
200
|
+
const mandatoryMatches = SECURITY_REVIEW_POLICY.mandatoryKeywords
|
|
201
|
+
.filter((keyword) => objectiveLower.includes(keyword));
|
|
202
|
+
const mediumMatches = SECURITY_REVIEW_POLICY.mediumKeywords
|
|
203
|
+
.filter((keyword) => objectiveLower.includes(keyword));
|
|
204
|
+
|
|
205
|
+
if (mandatoryMatches.length > 0) {
|
|
206
|
+
return {
|
|
207
|
+
required: true,
|
|
208
|
+
level: 'mandatory',
|
|
209
|
+
reason: `mandatory security trigger matched: ${mandatoryMatches.join(', ')}`,
|
|
210
|
+
mediumScore: mediumMatches.length,
|
|
211
|
+
skipReason: null
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (mediumMatches.length >= SECURITY_REVIEW_POLICY.mediumThreshold) {
|
|
216
|
+
return {
|
|
217
|
+
required: false,
|
|
218
|
+
level: 'optional',
|
|
219
|
+
reason: `optional security trigger score ${mediumMatches.length}/${SECURITY_REVIEW_POLICY.mediumThreshold}`,
|
|
220
|
+
mediumScore: mediumMatches.length,
|
|
221
|
+
skipReason: null
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
required: false,
|
|
227
|
+
level: 'skipped',
|
|
228
|
+
reason: null,
|
|
229
|
+
mediumScore: mediumMatches.length,
|
|
230
|
+
skipReason: `no mandatory trigger and medium score ${mediumMatches.length}/${SECURITY_REVIEW_POLICY.mediumThreshold}`
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
evaluateRefactorRepairPolicy({ objective, retryCycle }) {
|
|
235
|
+
const objectiveLower = (objective || '').toLowerCase();
|
|
236
|
+
const refactorDetected = ['refactor', 'cleanup', 'dead code', 'unused'].some((keyword) => objectiveLower.includes(keyword));
|
|
237
|
+
|
|
238
|
+
const normalizedRetryCycle = Number.isInteger(retryCycle) ? retryCycle : 0;
|
|
239
|
+
if (normalizedRetryCycle >= REFACTOR_BUILD_HALT_AFTER) {
|
|
240
|
+
const error = new Error(
|
|
241
|
+
`Refactor-repair retry cap exceeded at cycle ${normalizedRetryCycle}. Allowed max cycles: ${REFACTOR_BUILD_RETRY_CAP}`
|
|
242
|
+
);
|
|
243
|
+
error.name = 'RetryCapError';
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
active: refactorDetected,
|
|
249
|
+
retryCycle: normalizedRetryCycle,
|
|
250
|
+
retryCap: REFACTOR_BUILD_RETRY_CAP,
|
|
251
|
+
haltAfter: REFACTOR_BUILD_HALT_AFTER
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
buildPhases(scenario, objective, securityPolicy) {
|
|
256
|
+
let securityInjected = false;
|
|
257
|
+
const routeState = {
|
|
258
|
+
activeSubagents: new Set(),
|
|
259
|
+
deprecationNotices: new Set()
|
|
260
|
+
};
|
|
261
|
+
const phases = [];
|
|
262
|
+
|
|
263
|
+
for (let index = 0; index < scenario.phases.length; index += 1) {
|
|
264
|
+
const phase = scenario.phases[index];
|
|
265
|
+
const workflow = this.workflowLookup.get(phase.command);
|
|
266
|
+
const trackProfile = ORCHESTRATION_TRACKS[phase.track] || null;
|
|
267
|
+
const optionalSubagents = this.resolveOptionalSubagents({
|
|
268
|
+
scenarioId: scenario.id,
|
|
269
|
+
phase,
|
|
270
|
+
objective,
|
|
271
|
+
routeState
|
|
272
|
+
});
|
|
273
|
+
const routedSubagent = trackProfile ? trackProfile.subagent : null;
|
|
274
|
+
const invocationMode = this.resolveInvocationMode({
|
|
275
|
+
routedSubagent,
|
|
276
|
+
phase,
|
|
277
|
+
objective,
|
|
278
|
+
routeState
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
this.assertModeLock(routedSubagent, invocationMode);
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
!securityInjected &&
|
|
285
|
+
securityPolicy.required &&
|
|
286
|
+
['backend', 'frontend', 'release-ops', 'deployment'].includes(phase.track)
|
|
287
|
+
) {
|
|
288
|
+
optionalSubagents.push({
|
|
289
|
+
name: 'security-reviewer',
|
|
290
|
+
required: true,
|
|
291
|
+
invocation_mode: null,
|
|
292
|
+
reason: securityPolicy.reason
|
|
293
|
+
});
|
|
294
|
+
securityInjected = true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
for (const optionalSubagent of optionalSubagents) {
|
|
298
|
+
this.assertModeLock(optionalSubagent.name, optionalSubagent.invocation_mode);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const handoff_inputs = [];
|
|
302
|
+
if (routeState.activeSubagents.has('test-data-builder') && ['qa', 'frontend', 'performance'].includes(phase.track)) {
|
|
303
|
+
handoff_inputs.push('test-data-builder');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const phaseResult = {
|
|
307
|
+
phase_id: `phase-${index + 1}`,
|
|
308
|
+
orchestration_phase: phase.command,
|
|
309
|
+
command: workflow.cli,
|
|
310
|
+
slash_command: workflow.slash,
|
|
311
|
+
purpose: workflow.purpose,
|
|
312
|
+
specialist: workflow.specialist,
|
|
313
|
+
routed_track: phase.track,
|
|
314
|
+
routed_subagent: routedSubagent,
|
|
315
|
+
invocation_mode: invocationMode,
|
|
316
|
+
handoff_inputs,
|
|
317
|
+
routed_skills: trackProfile ? trackProfile.skills : [],
|
|
318
|
+
optional_subagents: optionalSubagents,
|
|
319
|
+
status: 'completed',
|
|
320
|
+
details: trackProfile ? trackProfile.goal : null
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
phases.push(phaseResult);
|
|
324
|
+
|
|
325
|
+
if (routedSubagent) {
|
|
326
|
+
routeState.activeSubagents.add(routedSubagent);
|
|
327
|
+
}
|
|
328
|
+
for (const optionalSubagent of optionalSubagents) {
|
|
329
|
+
routeState.activeSubagents.add(optionalSubagent.name);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
phases,
|
|
335
|
+
deprecationNotices: Array.from(routeState.deprecationNotices)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
orchestrate({ objective, scenarioId, mode, retryCycle = 0 }) {
|
|
340
|
+
const trimmedObjective = (objective || '').trim();
|
|
341
|
+
const selectedMode = normalizeMode(mode);
|
|
342
|
+
|
|
343
|
+
if (!trimmedObjective) {
|
|
344
|
+
return {
|
|
345
|
+
command: '/orchestrate',
|
|
346
|
+
execution_id: createExecutionId('orchestrate'),
|
|
347
|
+
mode: selectedMode,
|
|
348
|
+
status: 'blocked',
|
|
349
|
+
inputs: {
|
|
350
|
+
objective: objective || null,
|
|
351
|
+
scenario: scenarioId || null
|
|
352
|
+
},
|
|
353
|
+
prechecks: [
|
|
354
|
+
{
|
|
355
|
+
name: 'objective_non_empty',
|
|
356
|
+
status: 'failed',
|
|
357
|
+
details: 'Provide a non-empty orchestration objective.'
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
execution_log: [],
|
|
361
|
+
artifacts: null,
|
|
362
|
+
risks: [
|
|
363
|
+
{
|
|
364
|
+
level: 'high',
|
|
365
|
+
message: 'Orchestration was blocked because objective is missing.'
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
safety_checks: [
|
|
369
|
+
{
|
|
370
|
+
name: 'scope_guard',
|
|
371
|
+
status: 'passed',
|
|
372
|
+
details: 'No execution started.'
|
|
373
|
+
}
|
|
374
|
+
],
|
|
375
|
+
stop_condition: 'objective is required',
|
|
376
|
+
next_action: 'Provide an objective and rerun /orchestrate.'
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const refactorPolicy = this.evaluateRefactorRepairPolicy({
|
|
382
|
+
objective: trimmedObjective,
|
|
383
|
+
retryCycle
|
|
384
|
+
});
|
|
385
|
+
const { scenario, reason } = this.resolveScenario({ scenarioId, objective: trimmedObjective });
|
|
386
|
+
const securityPolicy = this.evaluateSecurityInvocationPolicy({
|
|
387
|
+
objective: trimmedObjective
|
|
388
|
+
});
|
|
389
|
+
const buildResult = this.buildPhases(scenario, trimmedObjective, securityPolicy);
|
|
390
|
+
const phases = buildResult.phases;
|
|
391
|
+
const executionLog = phases.map((phase) => ({
|
|
392
|
+
phase: phase.phase_id,
|
|
393
|
+
orchestration_phase: phase.orchestration_phase,
|
|
394
|
+
routed_subagent: phase.routed_subagent,
|
|
395
|
+
invocation_mode: phase.invocation_mode,
|
|
396
|
+
handoff_inputs: phase.handoff_inputs,
|
|
397
|
+
routed_track: phase.routed_track,
|
|
398
|
+
optional_subagents: phase.optional_subagents,
|
|
399
|
+
status: 'delegated',
|
|
400
|
+
details: `Delegated ${phase.command} to ${phase.routed_subagent}`
|
|
401
|
+
}));
|
|
402
|
+
|
|
403
|
+
const risks = [];
|
|
404
|
+
if (!scenarioId) {
|
|
405
|
+
risks.push({
|
|
406
|
+
level: 'low',
|
|
407
|
+
message: `Scenario auto-selection: ${reason}.`
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
command: '/orchestrate',
|
|
413
|
+
execution_id: createExecutionId('orchestrate'),
|
|
414
|
+
mode: selectedMode,
|
|
415
|
+
status: 'completed',
|
|
416
|
+
inputs: {
|
|
417
|
+
objective: trimmedObjective,
|
|
418
|
+
scenario: scenario.id
|
|
419
|
+
},
|
|
420
|
+
prechecks: [
|
|
421
|
+
{
|
|
422
|
+
name: 'objective_non_empty',
|
|
423
|
+
status: 'passed',
|
|
424
|
+
details: null
|
|
425
|
+
}
|
|
426
|
+
],
|
|
427
|
+
execution_log: executionLog,
|
|
428
|
+
artifacts: {
|
|
429
|
+
scenario: scenario.id,
|
|
430
|
+
scenario_reason: reason,
|
|
431
|
+
security_policy: securityPolicy,
|
|
432
|
+
refactor_repair_policy: refactorPolicy,
|
|
433
|
+
deprecation_notices: buildResult.deprecationNotices,
|
|
434
|
+
orchestration_summary: `Generated ${phases.length} phase handoffs for ${scenario.id}.`,
|
|
435
|
+
phases
|
|
436
|
+
},
|
|
437
|
+
risks,
|
|
438
|
+
safety_checks: [
|
|
439
|
+
{
|
|
440
|
+
name: 'scope_guard',
|
|
441
|
+
status: 'passed',
|
|
442
|
+
details: 'No hidden scope expansion detected in selected scenario.'
|
|
443
|
+
}
|
|
444
|
+
],
|
|
445
|
+
stop_condition: null,
|
|
446
|
+
next_action: null
|
|
447
|
+
};
|
|
448
|
+
} catch (error) {
|
|
449
|
+
const blockedByPolicy = error.name === 'ModeLockError' || error.name === 'RetryCapError';
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
command: '/orchestrate',
|
|
453
|
+
execution_id: createExecutionId('orchestrate'),
|
|
454
|
+
mode: selectedMode,
|
|
455
|
+
status: blockedByPolicy ? 'blocked' : 'failed',
|
|
456
|
+
inputs: {
|
|
457
|
+
objective: trimmedObjective,
|
|
458
|
+
scenario: scenarioId || null
|
|
459
|
+
},
|
|
460
|
+
prechecks: [
|
|
461
|
+
{
|
|
462
|
+
name: 'objective_non_empty',
|
|
463
|
+
status: 'passed',
|
|
464
|
+
details: null
|
|
465
|
+
}
|
|
466
|
+
],
|
|
467
|
+
execution_log: [],
|
|
468
|
+
artifacts: null,
|
|
469
|
+
risks: [
|
|
470
|
+
{
|
|
471
|
+
level: 'high',
|
|
472
|
+
message: error.message
|
|
473
|
+
}
|
|
474
|
+
],
|
|
475
|
+
safety_checks: [
|
|
476
|
+
{
|
|
477
|
+
name: 'scope_guard',
|
|
478
|
+
status: 'passed',
|
|
479
|
+
details: 'No mutation occurred.'
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
stop_condition: error.message,
|
|
483
|
+
next_action: 'Choose a valid scenario id or omit --scenario to use auto-routing.'
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function formatOrchestrationOutput(result) {
|
|
490
|
+
const lines = [];
|
|
491
|
+
lines.push('');
|
|
492
|
+
lines.push('Orchestration run');
|
|
493
|
+
lines.push(` Command: ${result.command}`);
|
|
494
|
+
lines.push(` Execution id: ${result.execution_id}`);
|
|
495
|
+
lines.push(` Mode: ${result.mode}`);
|
|
496
|
+
lines.push(` Status: ${result.status}`);
|
|
497
|
+
|
|
498
|
+
if (result.inputs) {
|
|
499
|
+
lines.push(` Objective: ${result.inputs.objective || 'n/a'}`);
|
|
500
|
+
lines.push(` Scenario: ${result.inputs.scenario || 'n/a'}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (result.artifacts && Array.isArray(result.artifacts.phases)) {
|
|
504
|
+
lines.push('');
|
|
505
|
+
lines.push('Auto-routed phases');
|
|
506
|
+
for (const phase of result.artifacts.phases) {
|
|
507
|
+
const modeText = phase.invocation_mode ? ` [mode=${phase.invocation_mode}]` : '';
|
|
508
|
+
lines.push(` - ${phase.command} (${phase.routed_track}) -> ${phase.routed_subagent}${modeText}`);
|
|
509
|
+
|
|
510
|
+
if (Array.isArray(phase.handoff_inputs) && phase.handoff_inputs.length > 0) {
|
|
511
|
+
lines.push(` handoff_inputs: ${phase.handoff_inputs.join(', ')}`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (Array.isArray(phase.optional_subagents) && phase.optional_subagents.length > 0) {
|
|
515
|
+
lines.push(` optional: ${phase.optional_subagents.map((entry) => entry.name).join(', ')}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (result.artifacts && Array.isArray(result.artifacts.deprecation_notices) && result.artifacts.deprecation_notices.length > 0) {
|
|
521
|
+
lines.push('');
|
|
522
|
+
lines.push('Deprecation notices');
|
|
523
|
+
for (const notice of result.artifacts.deprecation_notices) {
|
|
524
|
+
lines.push(` - ${notice}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (result.artifacts && result.artifacts.security_policy) {
|
|
529
|
+
const securityPolicy = result.artifacts.security_policy;
|
|
530
|
+
lines.push('');
|
|
531
|
+
lines.push(`Security policy: ${securityPolicy.level}`);
|
|
532
|
+
if (securityPolicy.reason) {
|
|
533
|
+
lines.push(` reason: ${securityPolicy.reason}`);
|
|
534
|
+
}
|
|
535
|
+
if (securityPolicy.skipReason) {
|
|
536
|
+
lines.push(` skip: ${securityPolicy.skipReason}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (result.artifacts && result.artifacts.refactor_repair_policy) {
|
|
541
|
+
const retryPolicy = result.artifacts.refactor_repair_policy;
|
|
542
|
+
lines.push('');
|
|
543
|
+
lines.push(`Refactor repair policy: retry_cycle=${retryPolicy.retryCycle}, cap=${retryPolicy.retryCap}, halt_after=${retryPolicy.haltAfter}`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (result.stop_condition) {
|
|
547
|
+
lines.push('');
|
|
548
|
+
lines.push(`Stop condition: ${result.stop_condition}`);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (result.next_action) {
|
|
552
|
+
lines.push(`Next action: ${result.next_action}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
lines.push('');
|
|
556
|
+
return `${lines.join('\n')}\n`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
module.exports = {
|
|
560
|
+
WorkflowOrchestrator,
|
|
561
|
+
formatOrchestrationOutput
|
|
562
|
+
};
|