agentxchain 0.8.8 → 2.2.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/README.md +136 -136
- package/bin/agentxchain.js +186 -5
- package/dashboard/app.js +305 -0
- package/dashboard/components/blocked.js +145 -0
- package/dashboard/components/cross-repo.js +126 -0
- package/dashboard/components/gate.js +311 -0
- package/dashboard/components/hooks.js +177 -0
- package/dashboard/components/initiative.js +147 -0
- package/dashboard/components/ledger.js +165 -0
- package/dashboard/components/timeline.js +222 -0
- package/dashboard/index.html +352 -0
- package/package.json +14 -6
- package/scripts/live-api-proxy-preflight-smoke.sh +531 -0
- package/scripts/publish-from-tag.sh +88 -0
- package/scripts/release-postflight.sh +231 -0
- package/scripts/release-preflight.sh +167 -0
- package/src/commands/accept-turn.js +160 -0
- package/src/commands/approve-completion.js +80 -0
- package/src/commands/approve-transition.js +85 -0
- package/src/commands/dashboard.js +70 -0
- package/src/commands/init.js +516 -0
- package/src/commands/migrate.js +348 -0
- package/src/commands/multi.js +549 -0
- package/src/commands/plugin.js +157 -0
- package/src/commands/reject-turn.js +204 -0
- package/src/commands/resume.js +389 -0
- package/src/commands/status.js +196 -3
- package/src/commands/step.js +947 -0
- package/src/commands/template-list.js +33 -0
- package/src/commands/template-set.js +279 -0
- package/src/commands/validate.js +20 -11
- package/src/commands/verify.js +71 -0
- package/src/lib/adapters/api-proxy-adapter.js +1076 -0
- package/src/lib/adapters/local-cli-adapter.js +337 -0
- package/src/lib/adapters/manual-adapter.js +169 -0
- package/src/lib/blocked-state.js +94 -0
- package/src/lib/config.js +97 -1
- package/src/lib/context-compressor.js +121 -0
- package/src/lib/context-section-parser.js +220 -0
- package/src/lib/coordinator-acceptance.js +428 -0
- package/src/lib/coordinator-config.js +461 -0
- package/src/lib/coordinator-dispatch.js +276 -0
- package/src/lib/coordinator-gates.js +487 -0
- package/src/lib/coordinator-hooks.js +239 -0
- package/src/lib/coordinator-recovery.js +523 -0
- package/src/lib/coordinator-state.js +365 -0
- package/src/lib/cross-repo-context.js +247 -0
- package/src/lib/dashboard/bridge-server.js +284 -0
- package/src/lib/dashboard/file-watcher.js +93 -0
- package/src/lib/dashboard/state-reader.js +96 -0
- package/src/lib/dispatch-bundle.js +568 -0
- package/src/lib/dispatch-manifest.js +252 -0
- package/src/lib/gate-evaluator.js +285 -0
- package/src/lib/governed-state.js +2139 -0
- package/src/lib/governed-templates.js +145 -0
- package/src/lib/hook-runner.js +788 -0
- package/src/lib/normalized-config.js +539 -0
- package/src/lib/plugin-config-schema.js +192 -0
- package/src/lib/plugins.js +692 -0
- package/src/lib/protocol-conformance.js +291 -0
- package/src/lib/reference-conformance-adapter.js +858 -0
- package/src/lib/repo-observer.js +597 -0
- package/src/lib/repo.js +0 -31
- package/src/lib/schema.js +121 -0
- package/src/lib/schemas/turn-result.schema.json +205 -0
- package/src/lib/token-budget.js +206 -0
- package/src/lib/token-counter.js +27 -0
- package/src/lib/turn-paths.js +67 -0
- package/src/lib/turn-result-validator.js +496 -0
- package/src/lib/validation.js +137 -0
- package/src/templates/governed/api-service.json +31 -0
- package/src/templates/governed/cli-tool.json +30 -0
- package/src/templates/governed/generic.json +10 -0
- package/src/templates/governed/web-app.json +30 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadProjectContext, loadProjectState } from '../lib/config.js';
|
|
3
|
+
import { approvePhaseTransition } from '../lib/governed-state.js';
|
|
4
|
+
import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
5
|
+
|
|
6
|
+
export async function approveTransitionCommand(opts) {
|
|
7
|
+
const context = loadProjectContext();
|
|
8
|
+
if (!context) {
|
|
9
|
+
console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (context.config.protocol_mode !== 'governed') {
|
|
14
|
+
console.log(chalk.red('approve-transition is only available in governed mode.'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { root, config } = context;
|
|
19
|
+
const state = loadProjectState(root, config);
|
|
20
|
+
|
|
21
|
+
if (!state?.pending_phase_transition) {
|
|
22
|
+
console.log(chalk.yellow('No pending phase transition to approve.'));
|
|
23
|
+
if (state?.phase) {
|
|
24
|
+
console.log(chalk.dim(` Current phase: ${state.phase}`));
|
|
25
|
+
}
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const pt = state.pending_phase_transition;
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(chalk.bold(' Approving Phase Transition'));
|
|
32
|
+
console.log(chalk.dim(' ' + '─'.repeat(44)));
|
|
33
|
+
console.log(` ${chalk.dim('From:')} ${pt.from}`);
|
|
34
|
+
console.log(` ${chalk.dim('To:')} ${pt.to}`);
|
|
35
|
+
console.log(` ${chalk.dim('Gate:')} ${pt.gate}`);
|
|
36
|
+
console.log(` ${chalk.dim('Turn:')} ${pt.requested_by_turn}`);
|
|
37
|
+
console.log('');
|
|
38
|
+
|
|
39
|
+
const result = approvePhaseTransition(root, config);
|
|
40
|
+
|
|
41
|
+
if (!result.ok) {
|
|
42
|
+
if (result.error_code?.startsWith('hook_') || result.error_code === 'hook_blocked') {
|
|
43
|
+
printGateHookFailure(result, 'phase_transition', pt);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(chalk.red(` Failed: ${result.error}`));
|
|
46
|
+
}
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(chalk.green(` ✓ Phase advanced: ${pt.from} → ${pt.to}`));
|
|
51
|
+
console.log(chalk.dim(` Run status: ${result.state.status}`));
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log(chalk.dim(` Next: agentxchain step (to run the first turn in ${pt.to} phase)`));
|
|
54
|
+
console.log('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function printGateHookFailure(result, gateType, gateInfo) {
|
|
58
|
+
const recovery = deriveRecoveryDescriptor(result.state);
|
|
59
|
+
const hookName = result.hookResults?.blocker?.hook_name
|
|
60
|
+
|| result.hookResults?.results?.find((entry) => entry.hook_name)?.hook_name
|
|
61
|
+
|| '(unknown)';
|
|
62
|
+
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log(chalk.yellow(` ${gateType === 'phase_transition' ? 'Phase Transition' : 'Run Completion'} Blocked By Hook`));
|
|
65
|
+
console.log(chalk.dim(' ' + '-'.repeat(44)));
|
|
66
|
+
console.log('');
|
|
67
|
+
if (gateType === 'phase_transition') {
|
|
68
|
+
console.log(` ${chalk.dim('From:')} ${gateInfo.from}`);
|
|
69
|
+
console.log(` ${chalk.dim('To:')} ${gateInfo.to}`);
|
|
70
|
+
}
|
|
71
|
+
console.log(` ${chalk.dim('Gate:')} ${gateInfo.gate}`);
|
|
72
|
+
console.log(` ${chalk.dim('Hook:')} ${hookName}`);
|
|
73
|
+
console.log(` ${chalk.dim('Error:')} ${result.error}`);
|
|
74
|
+
if (recovery) {
|
|
75
|
+
console.log(` ${chalk.dim('Reason:')} ${recovery.typed_reason}`);
|
|
76
|
+
console.log(` ${chalk.dim('Owner:')} ${recovery.owner}`);
|
|
77
|
+
console.log(` ${chalk.dim('Action:')} ${recovery.recovery_action}`);
|
|
78
|
+
if (recovery.detail) {
|
|
79
|
+
console.log(` ${chalk.dim('Detail:')} ${recovery.detail}`);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
console.log(` ${chalk.dim('Action:')} Fix or reconfigure hook "${hookName}", then rerun agentxchain approve-transition`);
|
|
83
|
+
}
|
|
84
|
+
console.log('');
|
|
85
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: agentxchain dashboard
|
|
3
|
+
*
|
|
4
|
+
* Starts the read-only dashboard bridge server and opens a browser.
|
|
5
|
+
* See: V2_DASHBOARD_SPEC.md, DEC-DASH-002 (read-only in v2.0).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { createBridgeServer } from '../lib/dashboard/bridge-server.js';
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const DEFAULT_PORT = 3847;
|
|
15
|
+
|
|
16
|
+
export async function dashboardCommand(options) {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const agentxchainDir = join(cwd, '.agentxchain');
|
|
19
|
+
const dashboardDir = join(__dirname, '..', '..', 'dashboard');
|
|
20
|
+
|
|
21
|
+
if (!existsSync(agentxchainDir)) {
|
|
22
|
+
console.error('Error: No .agentxchain/ directory found in the current directory.');
|
|
23
|
+
console.error('Run "agentxchain init --governed" first to create a governed project.');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!existsSync(dashboardDir)) {
|
|
28
|
+
console.error('Error: Dashboard assets not found at', dashboardDir);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const port = parseInt(options.port, 10) || DEFAULT_PORT;
|
|
33
|
+
const bridge = createBridgeServer({ agentxchainDir, dashboardDir, port });
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const { port: actualPort } = await bridge.start();
|
|
37
|
+
const url = `http://localhost:${actualPort}`;
|
|
38
|
+
|
|
39
|
+
console.log(`Dashboard running at ${url}`);
|
|
40
|
+
console.log('Press Ctrl+C to stop.\n');
|
|
41
|
+
|
|
42
|
+
if (options.open !== false) {
|
|
43
|
+
try {
|
|
44
|
+
const { exec } = await import('child_process');
|
|
45
|
+
const openCmd = process.platform === 'darwin' ? 'open'
|
|
46
|
+
: process.platform === 'win32' ? 'start'
|
|
47
|
+
: 'xdg-open';
|
|
48
|
+
exec(`${openCmd} ${url}`);
|
|
49
|
+
} catch {
|
|
50
|
+
// Browser open is best-effort
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Keep running until interrupted
|
|
55
|
+
const shutdown = async () => {
|
|
56
|
+
console.log('\nShutting down dashboard...');
|
|
57
|
+
await bridge.stop();
|
|
58
|
+
process.exit(0);
|
|
59
|
+
};
|
|
60
|
+
process.on('SIGINT', shutdown);
|
|
61
|
+
process.on('SIGTERM', shutdown);
|
|
62
|
+
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (err.code === 'EADDRINUSE') {
|
|
65
|
+
console.error(`Error: Port ${port} is already in use. Try --port <number>.`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -5,6 +5,7 @@ import chalk from 'chalk';
|
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import { CONFIG_FILE, LOCK_FILE, STATE_FILE } from '../lib/config.js';
|
|
7
7
|
import { generateVSCodeFiles } from '../lib/generate-vscode.js';
|
|
8
|
+
import { loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS } from '../lib/governed-templates.js';
|
|
8
9
|
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const TEMPLATES_DIR = join(__dirname, '../templates');
|
|
@@ -45,7 +46,522 @@ function loadTemplates() {
|
|
|
45
46
|
return templates;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function interpolateTemplateContent(contentTemplate, projectName) {
|
|
50
|
+
return contentTemplate.replaceAll('{{project_name}}', projectName);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function appendPromptOverride(basePrompt, override) {
|
|
54
|
+
if (!override || !override.trim()) return basePrompt;
|
|
55
|
+
return `${basePrompt}\n\n---\n\n## Project-Type-Specific Guidance\n\n${override.trim()}\n`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function appendAcceptanceHints(baseMatrix, acceptanceHints) {
|
|
59
|
+
if (!Array.isArray(acceptanceHints) || acceptanceHints.length === 0) {
|
|
60
|
+
return baseMatrix;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const hintLines = acceptanceHints.map((hint) => `- [ ] ${hint}`).join('\n');
|
|
64
|
+
return `${baseMatrix}\n\n## Template Guidance\n${hintLines}\n`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Governed init ───────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
const GOVERNED_ROLES = {
|
|
70
|
+
pm: {
|
|
71
|
+
title: 'Product Manager',
|
|
72
|
+
mandate: 'Protect user value, scope clarity, and acceptance criteria.',
|
|
73
|
+
write_authority: 'review_only',
|
|
74
|
+
runtime: 'manual-pm'
|
|
75
|
+
},
|
|
76
|
+
dev: {
|
|
77
|
+
title: 'Developer',
|
|
78
|
+
mandate: 'Implement approved work safely and verify behavior.',
|
|
79
|
+
write_authority: 'authoritative',
|
|
80
|
+
runtime: 'local-dev'
|
|
81
|
+
},
|
|
82
|
+
qa: {
|
|
83
|
+
title: 'QA',
|
|
84
|
+
mandate: 'Challenge correctness, acceptance coverage, and ship readiness.',
|
|
85
|
+
write_authority: 'review_only',
|
|
86
|
+
runtime: 'api-qa'
|
|
87
|
+
},
|
|
88
|
+
eng_director: {
|
|
89
|
+
title: 'Engineering Director',
|
|
90
|
+
mandate: 'Resolve tactical deadlocks and enforce technical coherence.',
|
|
91
|
+
write_authority: 'review_only',
|
|
92
|
+
runtime: 'manual-director'
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const GOVERNED_RUNTIMES = {
|
|
97
|
+
'manual-pm': { type: 'manual' },
|
|
98
|
+
'local-dev': { type: 'local_cli', command: ['claude', '--print', '-p', '{prompt}'], cwd: '.', prompt_transport: 'argv' },
|
|
99
|
+
'api-qa': { type: 'api_proxy', provider: 'anthropic', model: 'claude-sonnet-4-6', auth_env: 'ANTHROPIC_API_KEY' },
|
|
100
|
+
'manual-director': { type: 'manual' }
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const GOVERNED_ROUTING = {
|
|
104
|
+
planning: {
|
|
105
|
+
entry_role: 'pm',
|
|
106
|
+
allowed_next_roles: ['pm', 'eng_director', 'human'],
|
|
107
|
+
exit_gate: 'planning_signoff'
|
|
108
|
+
},
|
|
109
|
+
implementation: {
|
|
110
|
+
entry_role: 'dev',
|
|
111
|
+
allowed_next_roles: ['dev', 'qa', 'eng_director', 'human'],
|
|
112
|
+
exit_gate: 'implementation_complete'
|
|
113
|
+
},
|
|
114
|
+
qa: {
|
|
115
|
+
entry_role: 'qa',
|
|
116
|
+
allowed_next_roles: ['dev', 'qa', 'eng_director', 'human'],
|
|
117
|
+
exit_gate: 'qa_ship_verdict'
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const GOVERNED_GATES = {
|
|
122
|
+
planning_signoff: {
|
|
123
|
+
requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md'],
|
|
124
|
+
requires_human_approval: true
|
|
125
|
+
},
|
|
126
|
+
implementation_complete: {
|
|
127
|
+
requires_verification_pass: true
|
|
128
|
+
},
|
|
129
|
+
qa_ship_verdict: {
|
|
130
|
+
requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md'],
|
|
131
|
+
requires_human_approval: true
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function buildGovernedPrompt(roleId, role) {
|
|
136
|
+
const rolePrompts = {
|
|
137
|
+
pm: buildPmPrompt,
|
|
138
|
+
dev: buildDevPrompt,
|
|
139
|
+
qa: buildQaPrompt,
|
|
140
|
+
eng_director: buildEngDirectorPrompt,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const builder = rolePrompts[roleId];
|
|
144
|
+
if (builder) return builder(role);
|
|
145
|
+
|
|
146
|
+
// Fallback for custom roles
|
|
147
|
+
return buildGenericPrompt(roleId, role);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildPmPrompt(role) {
|
|
151
|
+
return `# Product Manager — Role Prompt
|
|
152
|
+
|
|
153
|
+
You are the Product Manager. Your mandate: **${role.mandate}**
|
|
154
|
+
|
|
155
|
+
## What You Do Each Turn
|
|
156
|
+
|
|
157
|
+
1. **Read the previous turn** (from CONTEXT.md). Understand what was done and what decisions were made.
|
|
158
|
+
2. **Challenge it.** Even if the work looks correct, identify at least one risk, scope gap, or assumption worth questioning. Rubber-stamping violates the protocol.
|
|
159
|
+
3. **Create or refine planning artifacts:**
|
|
160
|
+
- \`.planning/ROADMAP.md\` — what will be built, in what order, with acceptance criteria
|
|
161
|
+
- \`.planning/PM_SIGNOFF.md\` — your formal sign-off when planning is complete
|
|
162
|
+
- \`.planning/acceptance-matrix.md\` — the acceptance criteria checklist for QA
|
|
163
|
+
4. **Propose the next role.** Typically \`dev\` after planning is complete, or \`eng_director\` if there's a technical deadlock.
|
|
164
|
+
|
|
165
|
+
## Planning Phase Exit
|
|
166
|
+
|
|
167
|
+
To exit the planning phase, you must:
|
|
168
|
+
- Ensure \`.planning/PM_SIGNOFF.md\` exists with your explicit sign-off
|
|
169
|
+
- Ensure \`.planning/ROADMAP.md\` exists with clear acceptance criteria
|
|
170
|
+
- Set \`phase_transition_request: "implementation"\` in your turn result
|
|
171
|
+
|
|
172
|
+
The orchestrator will evaluate the gate and may require human approval.
|
|
173
|
+
|
|
174
|
+
## Scope Authority
|
|
175
|
+
|
|
176
|
+
You define **what** gets built and **why**. You do not define **how** — that's the developer's domain. If you disagree with a technical approach, raise an objection with rationale, but do not override implementation decisions.
|
|
177
|
+
|
|
178
|
+
## Acceptance Criteria Quality
|
|
179
|
+
|
|
180
|
+
Every roadmap item must have acceptance criteria that are:
|
|
181
|
+
- **Observable** — can be verified by running code or inspecting output
|
|
182
|
+
- **Specific** — not "works well" but "returns 200 for GET /api/users with valid token"
|
|
183
|
+
- **Complete** — covers happy path, error cases, and edge cases worth testing
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildDevPrompt(role) {
|
|
188
|
+
return `# Developer — Role Prompt
|
|
189
|
+
|
|
190
|
+
You are the Developer. Your mandate: **${role.mandate}**
|
|
191
|
+
|
|
192
|
+
## What You Do Each Turn
|
|
193
|
+
|
|
194
|
+
1. **Read the previous turn and ROADMAP.md.** Understand what you're building and what the acceptance criteria are.
|
|
195
|
+
2. **Challenge the previous turn.** If the PM's acceptance criteria are ambiguous, flag it. If QA's objections are unfounded, explain why. Never skip this.
|
|
196
|
+
3. **Implement the work.**
|
|
197
|
+
- Write clean, correct code that meets the acceptance criteria
|
|
198
|
+
- Run tests and include the results as verification evidence
|
|
199
|
+
- Accurately list every file you changed in \`files_changed\`
|
|
200
|
+
4. **Verify your work.** Run the test suite, linter, or build command. Record the commands and exit codes in \`verification.machine_evidence\`.
|
|
201
|
+
|
|
202
|
+
## Implementation Rules
|
|
203
|
+
|
|
204
|
+
- Only implement what the roadmap and acceptance criteria require. Do not add unrequested features.
|
|
205
|
+
- If acceptance criteria are unclear, set \`status: "needs_human"\` and explain what needs clarification in \`needs_human_reason\`.
|
|
206
|
+
- If you encounter a technical blocker, set \`status: "blocked"\` and describe it.
|
|
207
|
+
|
|
208
|
+
## Verification Is Mandatory
|
|
209
|
+
|
|
210
|
+
You must run verification commands and report them honestly:
|
|
211
|
+
- \`verification.status\` must be \`"pass"\` only if all commands exited with code 0
|
|
212
|
+
- \`verification.machine_evidence\` must list every command you ran with its actual exit code
|
|
213
|
+
- Do NOT claim \`"pass"\` if you did not run the tests
|
|
214
|
+
|
|
215
|
+
## Phase Transition
|
|
216
|
+
|
|
217
|
+
When your implementation is complete and verified:
|
|
218
|
+
- If the implementation phase gate requires verification pass: ensure tests pass
|
|
219
|
+
- Set \`phase_transition_request: "qa"\` to advance to QA
|
|
220
|
+
- The gate may auto-advance or require human approval depending on config
|
|
221
|
+
|
|
222
|
+
## Artifact Type
|
|
223
|
+
|
|
224
|
+
Your artifact type is \`workspace\` (direct file modifications). The orchestrator will diff your changes against the pre-turn snapshot to verify \`files_changed\` accuracy.
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function buildQaPrompt(role) {
|
|
229
|
+
return `# QA — Role Prompt
|
|
230
|
+
|
|
231
|
+
You are QA. Your mandate: **${role.mandate}**
|
|
232
|
+
|
|
233
|
+
## What You Do Each Turn
|
|
234
|
+
|
|
235
|
+
1. **Read the previous turn, the ROADMAP, and the acceptance matrix.** Understand what was built and what the acceptance criteria are.
|
|
236
|
+
2. **Challenge the implementation.** You MUST raise at least one objection — this is a protocol requirement for review_only roles. If the code is perfect, challenge the test coverage, the edge cases, or the documentation.
|
|
237
|
+
3. **Evaluate against acceptance criteria.** Go through each criterion and determine pass/fail.
|
|
238
|
+
4. **Create review artifacts:**
|
|
239
|
+
- \`.planning/acceptance-matrix.md\` — updated with pass/fail verdicts per criterion
|
|
240
|
+
- \`.planning/ship-verdict.md\` — your overall ship/no-ship recommendation
|
|
241
|
+
|
|
242
|
+
## You Cannot Modify Code
|
|
243
|
+
|
|
244
|
+
You have \`review_only\` write authority. You may NOT modify product files. You may only create/modify files under \`.planning/\` and \`.agentxchain/reviews/\`. Your artifact type must be \`review\`.
|
|
245
|
+
|
|
246
|
+
## Objection Requirement
|
|
247
|
+
|
|
248
|
+
You MUST raise at least one objection in your turn result. An empty \`objections\` array is a protocol violation and will be rejected by the validator. If the work is genuinely excellent, raise a low-severity observation about test coverage, documentation, or future risk.
|
|
249
|
+
|
|
250
|
+
Each objection must have:
|
|
251
|
+
- \`id\`: pattern \`OBJ-NNN\`
|
|
252
|
+
- \`severity\`: \`low\`, \`medium\`, \`high\`, or \`blocking\`
|
|
253
|
+
- \`against_turn_id\`: the turn you're challenging
|
|
254
|
+
- \`statement\`: clear description of the issue
|
|
255
|
+
- \`status\`: \`"raised"\`
|
|
256
|
+
|
|
257
|
+
## Blocking vs. Non-Blocking
|
|
258
|
+
|
|
259
|
+
- \`blocking\` severity means the work cannot ship. Use sparingly and only for real defects.
|
|
260
|
+
- \`high\` severity means significant risk but potentially shippable with mitigation.
|
|
261
|
+
- \`medium\` and \`low\` are observations that improve quality but don't block.
|
|
262
|
+
|
|
263
|
+
## Ship Verdict & Run Completion
|
|
264
|
+
|
|
265
|
+
When you are satisfied the work meets acceptance criteria:
|
|
266
|
+
1. Create \`.planning/ship-verdict.md\` with your verdict
|
|
267
|
+
2. Create/update \`.planning/acceptance-matrix.md\` with all criteria checked
|
|
268
|
+
3. Set \`run_completion_request: true\` in your turn result
|
|
269
|
+
|
|
270
|
+
**Only set \`run_completion_request: true\` when:**
|
|
271
|
+
- All blocking objections from prior turns are resolved
|
|
272
|
+
- The acceptance matrix shows all critical criteria passing
|
|
273
|
+
- \`.planning/ship-verdict.md\` exists with an affirmative verdict
|
|
274
|
+
|
|
275
|
+
**Do NOT set \`run_completion_request: true\` if:**
|
|
276
|
+
- You have unresolved blocking objections
|
|
277
|
+
- Critical acceptance criteria are failing
|
|
278
|
+
- You need the developer to fix issues first (propose \`dev\` as next role instead)
|
|
279
|
+
|
|
280
|
+
## Routing After QA
|
|
281
|
+
|
|
282
|
+
- If issues found → propose \`dev\` as next role (they fix, then you re-review)
|
|
283
|
+
- If ship-ready → set \`run_completion_request: true\`
|
|
284
|
+
- If deadlocked → propose \`eng_director\` or \`human\`
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function buildEngDirectorPrompt(role) {
|
|
289
|
+
return `# Engineering Director — Role Prompt
|
|
290
|
+
|
|
291
|
+
You are the Engineering Director. Your mandate: **${role.mandate}**
|
|
292
|
+
|
|
293
|
+
## When You Are Called
|
|
294
|
+
|
|
295
|
+
You are invoked when the normal PM → Dev → QA loop is stuck:
|
|
296
|
+
- Repeated QA/Dev cycles without convergence
|
|
297
|
+
- Technical disagreement between roles
|
|
298
|
+
- Scope dispute that the PM cannot resolve
|
|
299
|
+
- Budget or timeline pressure requiring trade-offs
|
|
300
|
+
|
|
301
|
+
## What You Do Each Turn
|
|
302
|
+
|
|
303
|
+
1. **Read the full context.** Review the escalation reason, unresolved objections, and the decision history.
|
|
304
|
+
2. **Make a binding decision.** Your role is to break deadlocks, not to add more opinions. State your decision clearly with rationale.
|
|
305
|
+
3. **Challenge what led to the deadlock.** Identify the root cause — unclear acceptance criteria? Wrong technical approach? Scope creep?
|
|
306
|
+
4. **Route back to the appropriate role.** After your decision, the normal loop should resume.
|
|
307
|
+
|
|
308
|
+
## Decision Authority
|
|
309
|
+
|
|
310
|
+
- You may override QA objections if they are unreasonable or out of scope
|
|
311
|
+
- You may override PM scope decisions if they create technical impossibility
|
|
312
|
+
- You may NOT override human decisions — escalate to \`human\` if needed
|
|
313
|
+
- Every override must be recorded as a decision with clear rationale
|
|
314
|
+
|
|
315
|
+
## You Cannot Modify Code
|
|
316
|
+
|
|
317
|
+
You have \`review_only\` write authority. Like QA, you must raise at least one objection (protocol requirement). Your artifact type is \`review\`.
|
|
318
|
+
|
|
319
|
+
## Objection Requirement
|
|
320
|
+
|
|
321
|
+
You MUST raise at least one objection. Typically this will be about the process failure that led to your involvement — why did the loop deadlock? What should be done differently next time?
|
|
322
|
+
|
|
323
|
+
## Escalation to Human
|
|
324
|
+
|
|
325
|
+
If you cannot resolve the deadlock:
|
|
326
|
+
- Set \`status: "needs_human"\`
|
|
327
|
+
- Explain the situation in \`needs_human_reason\`
|
|
328
|
+
- The orchestrator will pause the run for human input
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function buildGenericPrompt(roleId, role) {
|
|
333
|
+
return `# ${role.title} — Role Prompt
|
|
334
|
+
|
|
335
|
+
You are the **${role.title}** on this project.
|
|
336
|
+
|
|
337
|
+
**Mandate:** ${role.mandate}
|
|
338
|
+
**Write authority:** ${role.write_authority}
|
|
339
|
+
|
|
340
|
+
## What You Do Each Turn
|
|
341
|
+
|
|
342
|
+
1. Read the previous turn and challenge it explicitly.
|
|
343
|
+
2. Do your work according to your mandate.
|
|
344
|
+
3. Write your structured turn result to the turn-scoped staging path printed by the orchestrator (\`.agentxchain/staging/<turn_id>/turn-result.json\`).
|
|
345
|
+
|
|
346
|
+
## File Access
|
|
347
|
+
|
|
348
|
+
${role.write_authority === 'authoritative'
|
|
349
|
+
? 'You may modify product files directly.'
|
|
350
|
+
: role.write_authority === 'proposed'
|
|
351
|
+
? 'You may propose changes via patches.'
|
|
352
|
+
: 'You may NOT modify product files. Only create review artifacts under `.planning/` and `.agentxchain/reviews/`.'}
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function scaffoldGoverned(dir, projectName, projectId, templateId = 'generic') {
|
|
357
|
+
const template = loadGovernedTemplate(templateId);
|
|
358
|
+
const config = {
|
|
359
|
+
schema_version: '1.0',
|
|
360
|
+
template: template.id,
|
|
361
|
+
project: {
|
|
362
|
+
id: projectId,
|
|
363
|
+
name: projectName,
|
|
364
|
+
default_branch: 'main'
|
|
365
|
+
},
|
|
366
|
+
roles: GOVERNED_ROLES,
|
|
367
|
+
runtimes: GOVERNED_RUNTIMES,
|
|
368
|
+
routing: GOVERNED_ROUTING,
|
|
369
|
+
gates: GOVERNED_GATES,
|
|
370
|
+
budget: {
|
|
371
|
+
per_turn_max_usd: 2.0,
|
|
372
|
+
per_run_max_usd: 50.0,
|
|
373
|
+
on_exceed: 'pause_and_escalate'
|
|
374
|
+
},
|
|
375
|
+
retention: {
|
|
376
|
+
talk_strategy: 'append_only',
|
|
377
|
+
history_strategy: 'jsonl_append_only'
|
|
378
|
+
},
|
|
379
|
+
prompts: {
|
|
380
|
+
pm: '.agentxchain/prompts/pm.md',
|
|
381
|
+
dev: '.agentxchain/prompts/dev.md',
|
|
382
|
+
qa: '.agentxchain/prompts/qa.md',
|
|
383
|
+
eng_director: '.agentxchain/prompts/eng_director.md'
|
|
384
|
+
},
|
|
385
|
+
rules: {
|
|
386
|
+
challenge_required: true,
|
|
387
|
+
max_turn_retries: 2,
|
|
388
|
+
max_deadlock_cycles: 2
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const state = {
|
|
393
|
+
schema_version: '1.1',
|
|
394
|
+
run_id: null,
|
|
395
|
+
project_id: projectId,
|
|
396
|
+
status: 'idle',
|
|
397
|
+
phase: 'planning',
|
|
398
|
+
accepted_integration_ref: null,
|
|
399
|
+
active_turns: {},
|
|
400
|
+
turn_sequence: 0,
|
|
401
|
+
last_completed_turn_id: null,
|
|
402
|
+
blocked_on: null,
|
|
403
|
+
blocked_reason: null,
|
|
404
|
+
escalation: null,
|
|
405
|
+
queued_phase_transition: null,
|
|
406
|
+
queued_run_completion: null,
|
|
407
|
+
phase_gate_status: {
|
|
408
|
+
planning_signoff: 'pending',
|
|
409
|
+
implementation_complete: 'pending',
|
|
410
|
+
qa_ship_verdict: 'pending'
|
|
411
|
+
},
|
|
412
|
+
budget_reservations: {},
|
|
413
|
+
budget_status: {
|
|
414
|
+
spent_usd: 0,
|
|
415
|
+
remaining_usd: config.budget.per_run_max_usd
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Create directories
|
|
420
|
+
mkdirSync(join(dir, '.agentxchain', 'staging'), { recursive: true });
|
|
421
|
+
mkdirSync(join(dir, '.agentxchain', 'prompts'), { recursive: true });
|
|
422
|
+
mkdirSync(join(dir, '.agentxchain', 'reviews'), { recursive: true });
|
|
423
|
+
mkdirSync(join(dir, '.agentxchain', 'dispatch'), { recursive: true });
|
|
424
|
+
mkdirSync(join(dir, '.planning'), { recursive: true });
|
|
425
|
+
|
|
426
|
+
// Core governed files
|
|
427
|
+
writeFileSync(join(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + '\n');
|
|
428
|
+
writeFileSync(join(dir, '.agentxchain', 'state.json'), JSON.stringify(state, null, 2) + '\n');
|
|
429
|
+
writeFileSync(join(dir, '.agentxchain', 'history.jsonl'), '');
|
|
430
|
+
writeFileSync(join(dir, '.agentxchain', 'decision-ledger.jsonl'), '');
|
|
431
|
+
|
|
432
|
+
// Prompt templates
|
|
433
|
+
for (const [roleId, role] of Object.entries(GOVERNED_ROLES)) {
|
|
434
|
+
const basePrompt = buildGovernedPrompt(roleId, role);
|
|
435
|
+
const prompt = appendPromptOverride(basePrompt, template.prompt_overrides?.[roleId]);
|
|
436
|
+
writeFileSync(join(dir, '.agentxchain', 'prompts', `${roleId}.md`), prompt);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Planning artifacts
|
|
440
|
+
writeFileSync(join(dir, '.planning', 'PM_SIGNOFF.md'), `# PM Signoff — ${projectName}\n\nApproved: NO\n\n## Discovery Checklist\n- [ ] Target user defined\n- [ ] Core pain point defined\n- [ ] Core workflow defined\n- [ ] MVP scope defined\n- [ ] Out-of-scope list defined\n- [ ] Success metric defined\n\n## Notes for team\n(PM and human add final kickoff notes here.)\n`);
|
|
441
|
+
writeFileSync(join(dir, '.planning', 'ROADMAP.md'), `# Roadmap — ${projectName}\n\n## Phases\n\n| Phase | Goal | Status |\n|-------|------|--------|\n| Planning | Align scope, requirements, acceptance criteria | In progress |\n| Implementation | Build and verify | Pending |\n| QA | Challenge correctness and ship readiness | Pending |\n`);
|
|
442
|
+
const baseAcceptanceMatrix = `# Acceptance Matrix — ${projectName}\n\n| Req # | Requirement | Acceptance criteria | Test status | Last tested | Status |\n|-------|-------------|-------------------|-------------|-------------|--------|\n| (QA fills this from ROADMAP.md) | | | | | |\n`;
|
|
443
|
+
writeFileSync(
|
|
444
|
+
join(dir, '.planning', 'acceptance-matrix.md'),
|
|
445
|
+
appendAcceptanceHints(baseAcceptanceMatrix, template.acceptance_hints)
|
|
446
|
+
);
|
|
447
|
+
writeFileSync(join(dir, '.planning', 'ship-verdict.md'), `# Ship Verdict — ${projectName}\n\n## Verdict: PENDING\n\n## QA Summary\n\n(QA writes the final ship/no-ship assessment here.)\n\n## Open Blockers\n\n(List any blocking issues.)\n\n## Conditions\n\n(List any conditions for shipping.)\n`);
|
|
448
|
+
for (const artifact of template.planning_artifacts) {
|
|
449
|
+
writeFileSync(
|
|
450
|
+
join(dir, '.planning', artifact.filename),
|
|
451
|
+
interpolateTemplateContent(artifact.content_template, projectName)
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// TALK.md
|
|
456
|
+
writeFileSync(join(dir, 'TALK.md'), `# ${projectName} — Team Talk File\n\nCanonical human-readable handoff log for all agents.\n\n---\n\n`);
|
|
457
|
+
|
|
458
|
+
// .gitignore additions
|
|
459
|
+
const gitignorePath = join(dir, '.gitignore');
|
|
460
|
+
const requiredIgnores = ['.env', '.agentxchain/staging/', '.agentxchain/dispatch/'];
|
|
461
|
+
if (!existsSync(gitignorePath)) {
|
|
462
|
+
writeFileSync(gitignorePath, requiredIgnores.join('\n') + '\n');
|
|
463
|
+
} else {
|
|
464
|
+
const existingIgnore = readFileSync(gitignorePath, 'utf8');
|
|
465
|
+
const missing = requiredIgnores.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
|
|
466
|
+
if (missing.length > 0) {
|
|
467
|
+
const prefix = existingIgnore.endsWith('\n') ? '' : '\n';
|
|
468
|
+
writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return { config, state };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function initGoverned(opts) {
|
|
476
|
+
let projectName, folderName;
|
|
477
|
+
const templateId = opts.template || 'generic';
|
|
478
|
+
|
|
479
|
+
if (!VALID_GOVERNED_TEMPLATE_IDS.includes(templateId)) {
|
|
480
|
+
console.error(chalk.red(` Error: Unknown template "${templateId}".`));
|
|
481
|
+
console.error('');
|
|
482
|
+
console.error(' Available templates:');
|
|
483
|
+
console.error(' generic Default governed scaffold');
|
|
484
|
+
console.error(' api-service Governed scaffold for a backend service');
|
|
485
|
+
console.error(' cli-tool Governed scaffold for a CLI tool');
|
|
486
|
+
console.error(' web-app Governed scaffold for a web application');
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (opts.yes) {
|
|
491
|
+
projectName = 'My AgentXchain Project';
|
|
492
|
+
folderName = slugify(projectName);
|
|
493
|
+
} else {
|
|
494
|
+
const { name } = await inquirer.prompt([{
|
|
495
|
+
type: 'input',
|
|
496
|
+
name: 'name',
|
|
497
|
+
message: 'Project name:',
|
|
498
|
+
default: 'My AgentXchain Project'
|
|
499
|
+
}]);
|
|
500
|
+
projectName = name;
|
|
501
|
+
folderName = slugify(projectName);
|
|
502
|
+
|
|
503
|
+
const { folder } = await inquirer.prompt([{
|
|
504
|
+
type: 'input',
|
|
505
|
+
name: 'folder',
|
|
506
|
+
message: 'Folder name:',
|
|
507
|
+
default: folderName
|
|
508
|
+
}]);
|
|
509
|
+
folderName = folder;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const dir = resolve(process.cwd(), folderName);
|
|
513
|
+
const projectId = slugify(projectName);
|
|
514
|
+
|
|
515
|
+
if (existsSync(dir) && existsSync(join(dir, CONFIG_FILE))) {
|
|
516
|
+
if (!opts.yes) {
|
|
517
|
+
const { overwrite } = await inquirer.prompt([{
|
|
518
|
+
type: 'confirm',
|
|
519
|
+
name: 'overwrite',
|
|
520
|
+
message: `${folderName}/ already has agentxchain.json. Overwrite?`,
|
|
521
|
+
default: false
|
|
522
|
+
}]);
|
|
523
|
+
if (!overwrite) {
|
|
524
|
+
console.log(chalk.yellow(' Aborted.'));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
531
|
+
|
|
532
|
+
scaffoldGoverned(dir, projectName, projectId, templateId);
|
|
533
|
+
|
|
534
|
+
console.log('');
|
|
535
|
+
console.log(chalk.green(` ✓ Created governed project ${chalk.bold(folderName)}/`));
|
|
536
|
+
console.log('');
|
|
537
|
+
console.log(` ${chalk.dim('├──')} agentxchain.json ${chalk.dim('(governed)')}`);
|
|
538
|
+
console.log(` ${chalk.dim('├──')} .agentxchain/`);
|
|
539
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} state.json / history.jsonl / decision-ledger.jsonl`);
|
|
540
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} staging/`);
|
|
541
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} prompts/ ${chalk.dim('(pm, dev, qa, eng_director)')}`);
|
|
542
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} reviews/`);
|
|
543
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} dispatch/`);
|
|
544
|
+
console.log(` ${chalk.dim('├──')} .planning/`);
|
|
545
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} PM_SIGNOFF.md / ROADMAP.md`);
|
|
546
|
+
console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} acceptance-matrix.md / ship-verdict.md`);
|
|
547
|
+
console.log(` ${chalk.dim('└──')} TALK.md`);
|
|
548
|
+
console.log('');
|
|
549
|
+
console.log(` ${chalk.dim('Roles:')} pm, dev, qa, eng_director`);
|
|
550
|
+
console.log(` ${chalk.dim('Template:')} ${templateId}`);
|
|
551
|
+
console.log(` ${chalk.dim('Protocol:')} governed convergence`);
|
|
552
|
+
console.log('');
|
|
553
|
+
console.log(` ${chalk.cyan('Next:')}`);
|
|
554
|
+
console.log(` ${chalk.bold(`cd ${folderName}`)}`);
|
|
555
|
+
console.log(` ${chalk.bold('agentxchain step')} ${chalk.dim('# run the first governed turn')}`);
|
|
556
|
+
console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# inspect phase, gate, and turn state')}`);
|
|
557
|
+
console.log('');
|
|
558
|
+
}
|
|
559
|
+
|
|
48
560
|
export async function initCommand(opts) {
|
|
561
|
+
if (opts.governed || opts.schemaVersion === '4') {
|
|
562
|
+
return initGoverned(opts);
|
|
563
|
+
}
|
|
564
|
+
|
|
49
565
|
let project, agents, folderName, rules;
|
|
50
566
|
|
|
51
567
|
if (opts.yes) {
|