agentxchain 2.14.0 → 2.16.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 +9 -3
- package/bin/agentxchain.js +20 -2
- package/package.json +3 -1
- package/scripts/release-downstream-truth.sh +15 -14
- package/scripts/release-postflight.sh +21 -5
- package/scripts/sync-homebrew.sh +225 -0
- package/src/commands/init.js +16 -6
- package/src/commands/intake-approve.js +2 -10
- package/src/commands/intake-handoff.js +58 -0
- package/src/commands/intake-plan.js +2 -11
- package/src/commands/intake-record.js +2 -10
- package/src/commands/intake-resolve.js +2 -10
- package/src/commands/intake-scan.js +2 -10
- package/src/commands/intake-start.js +2 -10
- package/src/commands/intake-status.js +6 -10
- package/src/commands/intake-triage.js +2 -10
- package/src/commands/intake-workspace.js +58 -0
- package/src/commands/migrate.js +7 -3
- package/src/commands/multi.js +58 -2
- package/src/commands/run.js +29 -1
- package/src/commands/template-set.js +51 -2
- package/src/lib/adapter-interface.js +31 -0
- package/src/lib/coordinator-acceptance.js +24 -98
- package/src/lib/coordinator-barriers.js +116 -0
- package/src/lib/coordinator-config.js +124 -0
- package/src/lib/coordinator-dispatch.js +10 -1
- package/src/lib/coordinator-gates.js +28 -1
- package/src/lib/coordinator-recovery.js +133 -68
- package/src/lib/coordinator-state.js +74 -0
- package/src/lib/cross-repo-context.js +68 -1
- package/src/lib/governed-templates.js +60 -0
- package/src/lib/intake-handoff.js +58 -0
- package/src/lib/intake.js +300 -11
- package/src/lib/report.js +759 -27
- package/src/lib/workflow-gate-semantics.js +209 -0
- package/src/templates/governed/api-service.json +8 -1
- package/src/templates/governed/cli-tool.json +8 -1
- package/src/templates/governed/library.json +8 -1
- package/src/templates/governed/web-app.json +8 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { handoffIntent } from '../lib/intake.js';
|
|
3
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
4
|
+
|
|
5
|
+
export async function intakeHandoffCommand(opts) {
|
|
6
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
7
|
+
|
|
8
|
+
if (!opts.intent) {
|
|
9
|
+
const msg = '--intent <id> is required';
|
|
10
|
+
if (opts.json) {
|
|
11
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
12
|
+
} else {
|
|
13
|
+
console.log(chalk.red(msg));
|
|
14
|
+
}
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!opts.coordinatorRoot) {
|
|
19
|
+
const msg = '--coordinator-root <path> is required';
|
|
20
|
+
if (opts.json) {
|
|
21
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
22
|
+
} else {
|
|
23
|
+
console.log(chalk.red(msg));
|
|
24
|
+
}
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!opts.workstream) {
|
|
29
|
+
const msg = '--workstream <id> is required';
|
|
30
|
+
if (opts.json) {
|
|
31
|
+
console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
|
|
32
|
+
} else {
|
|
33
|
+
console.log(chalk.red(msg));
|
|
34
|
+
}
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const result = handoffIntent(root, opts.intent, {
|
|
39
|
+
coordinatorRoot: opts.coordinatorRoot,
|
|
40
|
+
workstreamId: opts.workstream,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (opts.json) {
|
|
44
|
+
console.log(JSON.stringify(result, null, 2));
|
|
45
|
+
} else if (result.ok) {
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(chalk.green(` Handed off intent ${result.intent.intent_id}`));
|
|
48
|
+
console.log(` Workstream: ${result.intent.target_workstream.workstream_id}`);
|
|
49
|
+
console.log(` Super Run: ${result.super_run_id}`);
|
|
50
|
+
console.log(` Handoff: ${result.handoff_path}`);
|
|
51
|
+
console.log(chalk.dim(' Status: planned → executing (coordinator-managed)'));
|
|
52
|
+
console.log('');
|
|
53
|
+
} else {
|
|
54
|
+
console.log(chalk.red(` ${result.error}`));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
process.exit(result.exitCode);
|
|
58
|
+
}
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
3
2
|
import { planIntent } from '../lib/intake.js';
|
|
4
|
-
import {
|
|
3
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
5
4
|
|
|
6
5
|
export async function intakePlanCommand(opts) {
|
|
7
|
-
const root =
|
|
8
|
-
if (!root) {
|
|
9
|
-
if (opts.json) {
|
|
10
|
-
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
11
|
-
} else {
|
|
12
|
-
console.log(chalk.red('agentxchain.json not found'));
|
|
13
|
-
}
|
|
14
|
-
process.exit(2);
|
|
15
|
-
}
|
|
6
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
16
7
|
|
|
17
8
|
if (!opts.intent) {
|
|
18
9
|
const msg = '--intent <id> is required';
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
5
4
|
import { recordEvent } from '../lib/intake.js';
|
|
5
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
6
6
|
|
|
7
7
|
export async function intakeRecordCommand(opts) {
|
|
8
|
-
const root =
|
|
9
|
-
if (!root) {
|
|
10
|
-
if (opts.json) {
|
|
11
|
-
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
12
|
-
} else {
|
|
13
|
-
console.log(chalk.red('agentxchain.json not found'));
|
|
14
|
-
}
|
|
15
|
-
process.exit(2);
|
|
16
|
-
}
|
|
8
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
17
9
|
|
|
18
10
|
let payload;
|
|
19
11
|
try {
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
3
2
|
import { resolveIntent } from '../lib/intake.js';
|
|
3
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
4
4
|
|
|
5
5
|
export async function intakeResolveCommand(opts) {
|
|
6
|
-
const root =
|
|
7
|
-
if (!root) {
|
|
8
|
-
if (opts.json) {
|
|
9
|
-
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
10
|
-
} else {
|
|
11
|
-
console.log(chalk.red('agentxchain.json not found'));
|
|
12
|
-
}
|
|
13
|
-
process.exit(2);
|
|
14
|
-
}
|
|
6
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
15
7
|
|
|
16
8
|
if (!opts.intent) {
|
|
17
9
|
const msg = '--intent <id> is required';
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
5
4
|
import { scanSource, SCAN_SOURCES } from '../lib/intake.js';
|
|
5
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
6
6
|
|
|
7
7
|
export async function intakeScanCommand(opts) {
|
|
8
|
-
const root =
|
|
9
|
-
if (!root) {
|
|
10
|
-
if (opts.json) {
|
|
11
|
-
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
12
|
-
} else {
|
|
13
|
-
console.log(chalk.red('agentxchain.json not found'));
|
|
14
|
-
}
|
|
15
|
-
process.exit(2);
|
|
16
|
-
}
|
|
8
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
17
9
|
|
|
18
10
|
if (!opts.source) {
|
|
19
11
|
const msg = `--source is required. Supported scan sources: ${SCAN_SOURCES.join(', ')}`;
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
3
2
|
import { startIntent } from '../lib/intake.js';
|
|
3
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
4
4
|
|
|
5
5
|
export async function intakeStartCommand(opts) {
|
|
6
|
-
const root =
|
|
7
|
-
if (!root) {
|
|
8
|
-
if (opts.json) {
|
|
9
|
-
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
10
|
-
} else {
|
|
11
|
-
console.log(chalk.red('agentxchain.json not found'));
|
|
12
|
-
}
|
|
13
|
-
process.exit(2);
|
|
14
|
-
}
|
|
6
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
15
7
|
|
|
16
8
|
if (!opts.intent) {
|
|
17
9
|
const msg = '--intent <id> is required';
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
3
2
|
import { intakeStatus } from '../lib/intake.js';
|
|
3
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
4
4
|
|
|
5
5
|
export async function intakeStatusCommand(opts) {
|
|
6
|
-
const root =
|
|
7
|
-
if (!root) {
|
|
8
|
-
if (opts.json) {
|
|
9
|
-
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
10
|
-
} else {
|
|
11
|
-
console.log(chalk.red('agentxchain.json not found'));
|
|
12
|
-
}
|
|
13
|
-
process.exit(2);
|
|
14
|
-
}
|
|
6
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
15
7
|
|
|
16
8
|
const result = intakeStatus(root, opts.intent || null);
|
|
17
9
|
|
|
@@ -72,6 +64,10 @@ function printIntentDetail(intent, event) {
|
|
|
72
64
|
console.log(` ${chalk.dim('Charter:')} ${intent.charter || '—'}`);
|
|
73
65
|
console.log(` ${chalk.dim('Created:')} ${intent.created_at}`);
|
|
74
66
|
console.log(` ${chalk.dim('Updated:')} ${intent.updated_at}`);
|
|
67
|
+
if (intent.target_workstream) {
|
|
68
|
+
console.log(` ${chalk.dim('Workstream:')} ${intent.target_workstream.workstream_id}`);
|
|
69
|
+
console.log(` ${chalk.dim('Super Run:')} ${intent.target_workstream.super_run_id}`);
|
|
70
|
+
}
|
|
75
71
|
|
|
76
72
|
if (intent.acceptance_contract && intent.acceptance_contract.length > 0) {
|
|
77
73
|
console.log('');
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
3
2
|
import { triageIntent } from '../lib/intake.js';
|
|
3
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
4
4
|
|
|
5
5
|
export async function intakeTriageCommand(opts) {
|
|
6
|
-
const root =
|
|
7
|
-
if (!root) {
|
|
8
|
-
if (opts.json) {
|
|
9
|
-
console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
|
|
10
|
-
} else {
|
|
11
|
-
console.log(chalk.red('agentxchain.json not found'));
|
|
12
|
-
}
|
|
13
|
-
process.exit(2);
|
|
14
|
-
}
|
|
6
|
+
const root = requireIntakeWorkspaceOrExit(opts);
|
|
15
7
|
|
|
16
8
|
if (!opts.intent) {
|
|
17
9
|
const msg = '--intent <id> is required';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join, parse as pathParse, resolve } from 'node:path';
|
|
4
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
5
|
+
import { COORDINATOR_CONFIG_FILE } from '../lib/coordinator-config.js';
|
|
6
|
+
|
|
7
|
+
function findCoordinatorWorkspaceRoot(startDir = process.cwd()) {
|
|
8
|
+
let dir = resolve(startDir);
|
|
9
|
+
const { root: fsRoot } = pathParse(dir);
|
|
10
|
+
|
|
11
|
+
while (true) {
|
|
12
|
+
if (existsSync(join(dir, COORDINATOR_CONFIG_FILE))) {
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
if (dir === fsRoot) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
dir = join(dir, '..');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function listCoordinatorChildRepos(coordinatorRoot) {
|
|
23
|
+
const configPath = join(coordinatorRoot, COORDINATOR_CONFIG_FILE);
|
|
24
|
+
if (!existsSync(configPath)) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const raw = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
30
|
+
return Object.keys(raw?.repos || {});
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function requireIntakeWorkspaceOrExit(opts, startDir = process.cwd()) {
|
|
37
|
+
const projectRoot = findProjectRoot(startDir);
|
|
38
|
+
if (projectRoot) {
|
|
39
|
+
return projectRoot;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const coordinatorRoot = findCoordinatorWorkspaceRoot(startDir);
|
|
43
|
+
const childRepos = coordinatorRoot ? listCoordinatorChildRepos(coordinatorRoot) : [];
|
|
44
|
+
const repoHint = childRepos.length > 0
|
|
45
|
+
? ` Available child repos: ${childRepos.join(', ')}.`
|
|
46
|
+
: '';
|
|
47
|
+
const error = coordinatorRoot
|
|
48
|
+
? `intake commands are repo-local only. Found coordinator workspace at ${coordinatorRoot} (${COORDINATOR_CONFIG_FILE}). Run intake inside a child governed repo (agentxchain.json).${repoHint} Then use \`agentxchain multi step\` for cross-repo coordination.`
|
|
49
|
+
: 'agentxchain.json not found';
|
|
50
|
+
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
console.log(JSON.stringify({ ok: false, error }, null, 2));
|
|
53
|
+
} else {
|
|
54
|
+
console.log(chalk.red(error));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
package/src/commands/migrate.js
CHANGED
|
@@ -165,14 +165,15 @@ export async function migrateCommand(opts) {
|
|
|
165
165
|
routing,
|
|
166
166
|
gates: {
|
|
167
167
|
planning_signoff: {
|
|
168
|
-
requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md'],
|
|
168
|
+
requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md', '.planning/SYSTEM_SPEC.md'],
|
|
169
169
|
requires_human_approval: true
|
|
170
170
|
},
|
|
171
171
|
implementation_complete: {
|
|
172
|
+
requires_files: ['.planning/IMPLEMENTATION_NOTES.md'],
|
|
172
173
|
requires_verification_pass: true
|
|
173
174
|
},
|
|
174
175
|
qa_ship_verdict: {
|
|
175
|
-
requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md'],
|
|
176
|
+
requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md', '.planning/RELEASE_NOTES.md'],
|
|
176
177
|
requires_human_approval: true
|
|
177
178
|
}
|
|
178
179
|
},
|
|
@@ -311,8 +312,11 @@ ${report.requires_human_review.map((r, i) => `${i + 1}. ${r}`).join('\n')}
|
|
|
311
312
|
const planningFiles = {
|
|
312
313
|
'PM_SIGNOFF.md': `# PM Signoff — ${projectName}\n\nApproved: NO\n`,
|
|
313
314
|
'ROADMAP.md': `# Roadmap — ${projectName}\n\n(Migrated from v3. Review and update.)\n`,
|
|
315
|
+
'SYSTEM_SPEC.md': `# System Spec — ${projectName}\n\n## Purpose\n\n(Describe the migrated subsystem purpose.)\n\n## Interface\n\n(List the commands, files, APIs, or contracts this repo owns.)\n\n## Behavior\n\n(Describe the expected runtime behavior.)\n\n## Error Cases\n\n(List the important failure modes.)\n\n## Acceptance Tests\n\n- [ ] Name the executable checks required before implementation resumes.\n\n## Open Questions\n\n- (Capture migration-specific gaps here.)\n`,
|
|
316
|
+
'IMPLEMENTATION_NOTES.md': `# Implementation Notes — ${projectName}\n\n## Changes\n\n(Dev fills this during implementation)\n\n## Verification\n\n(Dev fills this during implementation)\n\n## Unresolved Follow-ups\n\n(Dev lists any known gaps, tech debt, or follow-up items here.)\n`,
|
|
314
317
|
'acceptance-matrix.md': `# Acceptance Matrix — ${projectName}\n\n(QA fills this.)\n`,
|
|
315
|
-
'ship-verdict.md': `# Ship Verdict — ${projectName}\n\n## Verdict: PENDING\n
|
|
318
|
+
'ship-verdict.md': `# Ship Verdict — ${projectName}\n\n## Verdict: PENDING\n`,
|
|
319
|
+
'RELEASE_NOTES.md': `# Release Notes — ${projectName}\n\n## User Impact\n\n(QA fills this during the QA phase)\n\n## Verification Summary\n\n(QA fills this during the QA phase)\n\n## Upgrade Notes\n\n(QA fills this during the QA phase)\n\n## Known Issues\n\n(QA fills this during the QA phase)\n`
|
|
316
320
|
};
|
|
317
321
|
for (const [file, content] of Object.entries(planningFiles)) {
|
|
318
322
|
const path = join(root, '.planning', file);
|
package/src/commands/multi.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* multi init — bootstrap a multi-repo coordinator run
|
|
6
6
|
* multi status — show coordinator status and repo-run snapshots
|
|
7
7
|
* multi step — reconcile repo truth, then dispatch or request the next coordinator gate
|
|
8
|
+
* multi resume — clear coordinator blocked state after operator recovery
|
|
8
9
|
* multi approve-gate — approve a pending phase transition or completion gate
|
|
9
10
|
* multi resync — detect divergence and rebuild coordinator state from repo authority
|
|
10
11
|
*/
|
|
@@ -26,7 +27,11 @@ import {
|
|
|
26
27
|
requestCoordinatorCompletion,
|
|
27
28
|
requestPhaseTransition,
|
|
28
29
|
} from '../lib/coordinator-gates.js';
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
detectDivergence,
|
|
32
|
+
resyncFromRepoAuthority,
|
|
33
|
+
resumeCoordinatorFromBlockedState,
|
|
34
|
+
} from '../lib/coordinator-recovery.js';
|
|
30
35
|
import {
|
|
31
36
|
fireCoordinatorHook,
|
|
32
37
|
buildAssignmentPayload,
|
|
@@ -154,7 +159,7 @@ export async function multiStepCommand(options) {
|
|
|
154
159
|
// Fire on_escalation hook (advisory — cannot block, only notifies)
|
|
155
160
|
fireEscalationHook(workspacePath, configResult.config, state, state.blocked_reason || 'unknown reason');
|
|
156
161
|
console.error(`Coordinator is blocked: ${state.blocked_reason || 'unknown reason'}`);
|
|
157
|
-
console.error('Resolve the blocked state before stepping.');
|
|
162
|
+
console.error('Resolve the blocked state, then run `agentxchain multi resume` before stepping again.');
|
|
158
163
|
process.exitCode = 1;
|
|
159
164
|
return;
|
|
160
165
|
}
|
|
@@ -362,6 +367,57 @@ function maybeRequestCoordinatorGate(workspacePath, state, config) {
|
|
|
362
367
|
return { ok: false, type: 'run_completion', blockers: completionEvaluation.blockers };
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
// ── multi resume ───────────────────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
export async function multiResumeCommand(options) {
|
|
373
|
+
const workspacePath = process.cwd();
|
|
374
|
+
const configResult = loadCoordinatorConfig(workspacePath);
|
|
375
|
+
|
|
376
|
+
if (!configResult.ok) {
|
|
377
|
+
console.error('Coordinator config error:');
|
|
378
|
+
for (const err of configResult.errors || []) {
|
|
379
|
+
console.error(` - ${typeof err === 'string' ? err : err.message || JSON.stringify(err)}`);
|
|
380
|
+
}
|
|
381
|
+
process.exitCode = 1;
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const state = loadCoordinatorState(workspacePath);
|
|
386
|
+
if (!state) {
|
|
387
|
+
console.error('No coordinator state found. Run `agentxchain multi init` first.');
|
|
388
|
+
process.exitCode = 1;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const result = resumeCoordinatorFromBlockedState(workspacePath, state, configResult.config);
|
|
393
|
+
|
|
394
|
+
if (!result.ok) {
|
|
395
|
+
console.error(result.error || 'Coordinator recovery failed.');
|
|
396
|
+
process.exitCode = 1;
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (options.json) {
|
|
401
|
+
console.log(JSON.stringify({
|
|
402
|
+
ok: true,
|
|
403
|
+
previous_status: 'blocked',
|
|
404
|
+
resumed_status: result.resumed_status,
|
|
405
|
+
blocked_reason: result.blocked_reason,
|
|
406
|
+
pending_gate: result.state?.pending_gate || null,
|
|
407
|
+
resync: result.resync,
|
|
408
|
+
}, null, 2));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log(`Coordinator resumed: ${result.resumed_status}`);
|
|
413
|
+
console.log(`Previous block: ${result.blocked_reason}`);
|
|
414
|
+
if (result.resumed_status === 'paused' && result.state?.pending_gate) {
|
|
415
|
+
console.log(`Next action: agentxchain multi approve-gate (${result.state.pending_gate.gate})`);
|
|
416
|
+
} else {
|
|
417
|
+
console.log('Next action: agentxchain multi step');
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
365
421
|
// ── multi approve-gate ─────────────────────────────────────────────────────
|
|
366
422
|
|
|
367
423
|
export async function multiApproveGateCommand(options) {
|
package/src/commands/run.js
CHANGED
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { createInterface } from 'readline';
|
|
17
|
-
import { readFileSync, existsSync } from 'fs';
|
|
17
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
18
18
|
import { join } from 'path';
|
|
19
19
|
import { loadProjectContext, loadProjectState } from '../lib/config.js';
|
|
20
20
|
import { runLoop } from '../lib/run-loop.js';
|
|
21
|
+
import { buildRunExport } from '../lib/export.js';
|
|
22
|
+
import { buildGovernanceReport, formatGovernanceReportMarkdown } from '../lib/report.js';
|
|
21
23
|
import { dispatchApiProxy } from '../lib/adapters/api-proxy-adapter.js';
|
|
22
24
|
import {
|
|
23
25
|
dispatchLocalCli,
|
|
@@ -328,6 +330,32 @@ export async function runCommand(opts) {
|
|
|
328
330
|
}
|
|
329
331
|
}
|
|
330
332
|
|
|
333
|
+
// ── Auto governance report ──────────────────────────────────────────────
|
|
334
|
+
if (opts.report !== false && result.state) {
|
|
335
|
+
try {
|
|
336
|
+
const reportsDir = join(root, '.agentxchain', 'reports');
|
|
337
|
+
mkdirSync(reportsDir, { recursive: true });
|
|
338
|
+
|
|
339
|
+
const exportResult = buildRunExport(root);
|
|
340
|
+
if (exportResult.ok) {
|
|
341
|
+
const runId = result.state.run_id || 'unknown';
|
|
342
|
+
const exportPath = join(reportsDir, `export-${runId}.json`);
|
|
343
|
+
writeFileSync(exportPath, JSON.stringify(exportResult.export, null, 2));
|
|
344
|
+
|
|
345
|
+
const reportResult = buildGovernanceReport(exportResult.export, { input: exportPath });
|
|
346
|
+
const reportPath = join(reportsDir, `report-${runId}.md`);
|
|
347
|
+
writeFileSync(reportPath, formatGovernanceReportMarkdown(reportResult.report));
|
|
348
|
+
|
|
349
|
+
console.log('');
|
|
350
|
+
console.log(chalk.dim(` Governance report: .agentxchain/reports/report-${runId}.md`));
|
|
351
|
+
} else {
|
|
352
|
+
console.log(chalk.dim(` Governance report skipped: ${exportResult.error}`));
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
console.log(chalk.dim(` Governance report failed: ${err.message}`));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
331
359
|
// ── Exit code ───────────────────────────────────────────────────────────
|
|
332
360
|
const successReasons = new Set(['completed', 'gate_held', 'caller_stopped', 'max_turns_reached']);
|
|
333
361
|
if (result.ok || successReasons.has(result.stop_reason)) {
|
|
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { CONFIG_FILE } from '../lib/config.js';
|
|
5
|
-
import { loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS } from '../lib/governed-templates.js';
|
|
5
|
+
import { loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS, SYSTEM_SPEC_OVERLAY_SEPARATOR } from '../lib/governed-templates.js';
|
|
6
6
|
|
|
7
7
|
const LEDGER_PATH = '.agentxchain/decision-ledger.jsonl';
|
|
8
8
|
const PROMPT_OVERRIDE_SEPARATOR = '## Project-Type-Specific Guidance';
|
|
@@ -76,6 +76,7 @@ export async function templateSetCommand(templateId, opts) {
|
|
|
76
76
|
prompts_missing_paths: [],
|
|
77
77
|
prompts_missing_files: [],
|
|
78
78
|
acceptance_hints_status: 'none',
|
|
79
|
+
system_spec_overlay_status: 'none',
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
// Planning artifacts
|
|
@@ -127,6 +128,22 @@ export async function templateSetCommand(templateId, opts) {
|
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
// System spec overlay
|
|
132
|
+
const systemSpecOverlay = manifest.system_spec_overlay;
|
|
133
|
+
const systemSpecPath = join(root, '.planning', 'SYSTEM_SPEC.md');
|
|
134
|
+
if (systemSpecOverlay && Object.keys(systemSpecOverlay).length > 0) {
|
|
135
|
+
if (!existsSync(systemSpecPath)) {
|
|
136
|
+
plan.system_spec_overlay_status = 'missing_file';
|
|
137
|
+
} else {
|
|
138
|
+
const specContent = readFileSync(systemSpecPath, 'utf8');
|
|
139
|
+
if (specContent.includes(SYSTEM_SPEC_OVERLAY_SEPARATOR)) {
|
|
140
|
+
plan.system_spec_overlay_status = 'existing_guidance';
|
|
141
|
+
} else {
|
|
142
|
+
plan.system_spec_overlay_status = 'append';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
130
147
|
// ── Dry run: print plan and exit ──────────────────────────────────────
|
|
131
148
|
if (opts.dryRun) {
|
|
132
149
|
console.log(chalk.bold(`\n Template: ${previousTemplate} → ${templateId}\n`));
|
|
@@ -175,6 +192,16 @@ export async function templateSetCommand(templateId, opts) {
|
|
|
175
192
|
} else {
|
|
176
193
|
console.log(` ${chalk.dim('(none)')}`);
|
|
177
194
|
}
|
|
195
|
+
console.log('\n System spec overlay:');
|
|
196
|
+
if (plan.system_spec_overlay_status === 'append') {
|
|
197
|
+
console.log(` .planning/SYSTEM_SPEC.md: ${chalk.green('WILL APPEND template guidance')}`);
|
|
198
|
+
} else if (plan.system_spec_overlay_status === 'existing_guidance') {
|
|
199
|
+
console.log(` .planning/SYSTEM_SPEC.md: ${chalk.dim('ALREADY HAS guidance (skip)')}`);
|
|
200
|
+
} else if (plan.system_spec_overlay_status === 'missing_file') {
|
|
201
|
+
console.log(` .planning/SYSTEM_SPEC.md: ${chalk.yellow('MISSING FILE (skip)')}`);
|
|
202
|
+
} else {
|
|
203
|
+
console.log(` ${chalk.dim('(none)')}`);
|
|
204
|
+
}
|
|
178
205
|
console.log(chalk.dim('\n No changes written. Use without --dry-run to apply.\n'));
|
|
179
206
|
process.exit(0);
|
|
180
207
|
}
|
|
@@ -242,7 +269,24 @@ export async function templateSetCommand(templateId, opts) {
|
|
|
242
269
|
console.log(chalk.yellow(' Warning: .planning/acceptance-matrix.md not found. Skipping template guidance hints.'));
|
|
243
270
|
}
|
|
244
271
|
|
|
245
|
-
// 5.
|
|
272
|
+
// 5. Append system spec overlay
|
|
273
|
+
if (plan.system_spec_overlay_status === 'append' && existsSync(systemSpecPath)) {
|
|
274
|
+
const specContent = readFileSync(systemSpecPath, 'utf8');
|
|
275
|
+
const guidanceLines = [];
|
|
276
|
+
if (systemSpecOverlay.purpose_guidance) guidanceLines.push(`**Purpose:** ${systemSpecOverlay.purpose_guidance}`);
|
|
277
|
+
if (systemSpecOverlay.interface_guidance) guidanceLines.push(`**Interface:** ${systemSpecOverlay.interface_guidance}`);
|
|
278
|
+
if (systemSpecOverlay.behavior_guidance) guidanceLines.push(`**Behavior:** ${systemSpecOverlay.behavior_guidance}`);
|
|
279
|
+
if (systemSpecOverlay.error_cases_guidance) guidanceLines.push(`**Error Cases:** ${systemSpecOverlay.error_cases_guidance}`);
|
|
280
|
+
if (systemSpecOverlay.acceptance_tests_guidance) guidanceLines.push(`**Acceptance Tests:**\n${systemSpecOverlay.acceptance_tests_guidance}`);
|
|
281
|
+
if (systemSpecOverlay.extra_sections) guidanceLines.push(systemSpecOverlay.extra_sections);
|
|
282
|
+
const guidanceBlock = guidanceLines.join('\n\n');
|
|
283
|
+
const appended = `${specContent}\n\n${SYSTEM_SPEC_OVERLAY_SEPARATOR}\n\n${guidanceBlock}\n`;
|
|
284
|
+
writeFileSync(systemSpecPath, appended);
|
|
285
|
+
} else if (plan.system_spec_overlay_status === 'missing_file') {
|
|
286
|
+
console.log(chalk.yellow(' Warning: .planning/SYSTEM_SPEC.md not found. Skipping template spec overlay.'));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 6. Decision ledger
|
|
246
290
|
const ledgerEntry = {
|
|
247
291
|
type: 'template_set',
|
|
248
292
|
timestamp: new Date().toISOString(),
|
|
@@ -260,6 +304,8 @@ export async function templateSetCommand(templateId, opts) {
|
|
|
260
304
|
prompt_missing_files: plan.prompts_missing_files,
|
|
261
305
|
acceptance_hints_appended: plan.acceptance_hints_status === 'append',
|
|
262
306
|
acceptance_hints_skipped_reason: plan.acceptance_hints_status === 'append' ? null : plan.acceptance_hints_status,
|
|
307
|
+
system_spec_overlay_appended: plan.system_spec_overlay_status === 'append',
|
|
308
|
+
system_spec_overlay_skipped_reason: plan.system_spec_overlay_status === 'append' ? null : plan.system_spec_overlay_status,
|
|
263
309
|
operator: 'human',
|
|
264
310
|
};
|
|
265
311
|
appendJsonl(root, LEDGER_PATH, ledgerEntry);
|
|
@@ -275,5 +321,8 @@ export async function templateSetCommand(templateId, opts) {
|
|
|
275
321
|
if (plan.acceptance_hints_status === 'append') {
|
|
276
322
|
console.log(` Appended template guidance to acceptance-matrix.md`);
|
|
277
323
|
}
|
|
324
|
+
if (plan.system_spec_overlay_status === 'append') {
|
|
325
|
+
console.log(` Appended template-specific guidance to SYSTEM_SPEC.md`);
|
|
326
|
+
}
|
|
278
327
|
console.log('');
|
|
279
328
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter Interface — declared public boundary for shipped adapter dispatch.
|
|
3
|
+
*
|
|
4
|
+
* This module gives external runners a stable way to reuse the first-party
|
|
5
|
+
* adapters without importing deep internal source paths.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
printManualDispatchInstructions,
|
|
10
|
+
waitForStagedResult,
|
|
11
|
+
readStagedResult,
|
|
12
|
+
} from './adapters/manual-adapter.js';
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
dispatchLocalCli,
|
|
16
|
+
saveDispatchLogs,
|
|
17
|
+
resolvePromptTransport,
|
|
18
|
+
} from './adapters/local-cli-adapter.js';
|
|
19
|
+
|
|
20
|
+
export { dispatchApiProxy } from './adapters/api-proxy-adapter.js';
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
DEFAULT_MCP_TOOL_NAME,
|
|
24
|
+
DEFAULT_MCP_TRANSPORT,
|
|
25
|
+
dispatchMcp,
|
|
26
|
+
resolveMcpToolName,
|
|
27
|
+
resolveMcpTransport,
|
|
28
|
+
describeMcpRuntimeTarget,
|
|
29
|
+
} from './adapters/mcp-adapter.js';
|
|
30
|
+
|
|
31
|
+
export const ADAPTER_INTERFACE_VERSION = '0.1';
|