agentxchain 2.128.0 → 2.129.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 +2 -0
- package/bin/agentxchain.js +28 -4
- package/package.json +1 -1
- package/scripts/verify-post-publish.sh +55 -5
- package/src/commands/connector.js +17 -2
- package/src/commands/doctor.js +122 -1
- package/src/commands/events.js +7 -1
- package/src/commands/init.js +42 -11
- package/src/commands/inject.js +1 -1
- package/src/commands/mission.js +142 -0
- package/src/commands/reissue-turn.js +122 -0
- package/src/commands/reject-turn.js +24 -4
- package/src/commands/restart.js +9 -2
- package/src/commands/resume.js +20 -9
- package/src/commands/status.js +46 -4
- package/src/commands/step.js +49 -10
- package/src/commands/validate.js +78 -20
- package/src/lib/cli-version.js +106 -0
- package/src/lib/connector-probe.js +146 -5
- package/src/lib/continuous-run.js +14 -86
- package/src/lib/dispatch-bundle.js +39 -0
- package/src/lib/governed-state.js +474 -10
- package/src/lib/governed-templates.js +1 -0
- package/src/lib/intake.js +221 -77
- package/src/lib/missions.js +56 -4
- package/src/lib/normalized-config.js +50 -15
- package/src/lib/repo-observer.js +7 -2
- package/src/lib/run-events.js +4 -0
- package/src/lib/run-loop.js +5 -0
- package/src/lib/runner-interface.js +2 -0
- package/src/lib/session-checkpoint.js +18 -2
- package/src/templates/governed/full-local-cli.json +71 -0
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Legacy IDE-window coordination is still shipped as a compatibility mode for team
|
|
|
15
15
|
- [CLI reference](https://agentxchain.dev/docs/cli/)
|
|
16
16
|
- [Lights-Out Scheduling](https://agentxchain.dev/docs/lights-out-scheduling/)
|
|
17
17
|
- [Templates](https://agentxchain.dev/docs/templates/)
|
|
18
|
+
- [Automation Patterns](https://agentxchain.dev/docs/automation-patterns/)
|
|
18
19
|
- [Export schema reference](https://agentxchain.dev/docs/export-schema/)
|
|
19
20
|
- [Adapter reference](https://agentxchain.dev/docs/adapters/)
|
|
20
21
|
- [Protocol v7](https://agentxchain.dev/docs/protocol/)
|
|
@@ -107,6 +108,7 @@ Built-in governed templates:
|
|
|
107
108
|
- `cli-tool`: command surface, platform support, distribution checklist
|
|
108
109
|
- `library`: public API, compatibility policy, release and adoption checklist
|
|
109
110
|
- `web-app`: user flows, UI acceptance, browser support
|
|
111
|
+
- `full-local-cli`: human-gated automation pattern with PM, Dev, QA, and Director all on authoritative `local_cli`
|
|
110
112
|
- `enterprise-app`: enterprise planning artifacts plus blueprint-backed `architect` and `security_reviewer` phases
|
|
111
113
|
|
|
112
114
|
Inspect the shipped template surfaces instead of inferring them from docs:
|
package/bin/agentxchain.js
CHANGED
|
@@ -72,6 +72,7 @@ import { injectCommand } from '../src/commands/inject.js';
|
|
|
72
72
|
import { escalateCommand } from '../src/commands/escalate.js';
|
|
73
73
|
import { acceptTurnCommand } from '../src/commands/accept-turn.js';
|
|
74
74
|
import { rejectTurnCommand } from '../src/commands/reject-turn.js';
|
|
75
|
+
import { reissueTurnCommand } from '../src/commands/reissue-turn.js';
|
|
75
76
|
import { proposalListCommand, proposalDiffCommand, proposalApplyCommand, proposalRejectCommand } from '../src/commands/proposal.js';
|
|
76
77
|
import { stepCommand } from '../src/commands/step.js';
|
|
77
78
|
import { runCommand } from '../src/commands/run.js';
|
|
@@ -124,7 +125,7 @@ import { eventsCommand } from '../src/commands/events.js';
|
|
|
124
125
|
import { connectorCheckCommand, connectorValidateCommand } from '../src/commands/connector.js';
|
|
125
126
|
import { scheduleDaemonCommand, scheduleListCommand, scheduleRunDueCommand, scheduleStatusCommand } from '../src/commands/schedule.js';
|
|
126
127
|
import { chainLatestCommand, chainListCommand, chainShowCommand } from '../src/commands/chain.js';
|
|
127
|
-
import { missionAttachChainCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
128
|
+
import { missionAttachChainCommand, missionBindCoordinatorCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
128
129
|
|
|
129
130
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
130
131
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -142,7 +143,7 @@ program
|
|
|
142
143
|
.option('-y, --yes', 'Skip guided prompts, use defaults')
|
|
143
144
|
.option('--governed', 'Create a governed project (orchestrator-owned state)')
|
|
144
145
|
.option('--dir <path>', 'Scaffold target directory. Use "." for in-place bootstrap.')
|
|
145
|
-
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, enterprise-app')
|
|
146
|
+
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app')
|
|
146
147
|
.option('--dev-command <parts...>', 'Governed local-dev command parts. Include {prompt} for argv prompt delivery.')
|
|
147
148
|
.option('--dev-prompt-transport <mode>', 'Governed local-dev prompt transport: argv, stdin, dispatch_bundle_only')
|
|
148
149
|
.option('--goal <text>', 'Project goal — persisted in config and rendered in every dispatch bundle')
|
|
@@ -445,10 +446,23 @@ missionCmd
|
|
|
445
446
|
.option('--constraint <text>', 'Add a constraint to the planner when using --plan (repeatable)', collectOption, [])
|
|
446
447
|
.option('--role-hint <role>', 'Hint available roles to the planner when using --plan (repeatable)', collectOption, [])
|
|
447
448
|
.option('--planner-output-file <path>', 'Read planner JSON output from a file instead of calling the configured planner')
|
|
449
|
+
.option('--multi', 'Create a multi-repo mission with coordinator initialization')
|
|
450
|
+
.option('--coordinator-config <path>', 'Path to agentxchain-multi.json (required with --multi)')
|
|
451
|
+
.option('--coordinator-workspace <path>', 'Coordinator workspace path (defaults to project root)')
|
|
448
452
|
.option('-j, --json', 'Output as JSON')
|
|
449
453
|
.option('-d, --dir <path>', 'Project directory')
|
|
450
454
|
.action(missionStartCommand);
|
|
451
455
|
|
|
456
|
+
missionCmd
|
|
457
|
+
.command('bind-coordinator [mission_id]')
|
|
458
|
+
.description('Bind an existing coordinator super_run to a mission')
|
|
459
|
+
.requiredOption('--super-run-id <id>', 'Coordinator super_run_id to bind')
|
|
460
|
+
.option('--coordinator-config <path>', 'Path to agentxchain-multi.json')
|
|
461
|
+
.option('--coordinator-workspace <path>', 'Coordinator workspace path')
|
|
462
|
+
.option('-j, --json', 'Output as JSON')
|
|
463
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
464
|
+
.action(missionBindCoordinatorCommand);
|
|
465
|
+
|
|
452
466
|
missionCmd
|
|
453
467
|
.command('list')
|
|
454
468
|
.description('List mission artifacts newest first')
|
|
@@ -625,6 +639,7 @@ program
|
|
|
625
639
|
.description('Resume a governed project: initialize or continue a run and assign the next turn')
|
|
626
640
|
.option('--role <role>', 'Override the target role (default: phase entry role)')
|
|
627
641
|
.option('--turn <id>', 'Target a specific retained turn when multiple exist')
|
|
642
|
+
.option('--no-intent', 'Do not bind the next queued approved/planned intake intent to the next turn')
|
|
628
643
|
.action(resumeCommand);
|
|
629
644
|
|
|
630
645
|
program
|
|
@@ -636,7 +651,7 @@ program
|
|
|
636
651
|
.command('inject <description>')
|
|
637
652
|
.description('Inject a priority work item into the intake queue (composed record + triage + approve)')
|
|
638
653
|
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)', 'p0')
|
|
639
|
-
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, enterprise-app)', 'generic')
|
|
654
|
+
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app)', 'generic')
|
|
640
655
|
.option('--charter <text>', 'Delivery charter (defaults to description)')
|
|
641
656
|
.option('--acceptance <text>', 'Comma-separated acceptance criteria')
|
|
642
657
|
.option('--approver <name>', 'Approver identity', 'human')
|
|
@@ -668,14 +683,23 @@ program
|
|
|
668
683
|
.option('--reassign', 'Immediately re-dispatch a conflicted turn with conflict context')
|
|
669
684
|
.action(rejectTurnCommand);
|
|
670
685
|
|
|
686
|
+
program
|
|
687
|
+
.command('reissue-turn')
|
|
688
|
+
.description('Invalidate an active turn and reissue it against current repo state (baseline drift recovery)')
|
|
689
|
+
.option('--turn <id>', 'Target a specific active turn when multiple turns exist')
|
|
690
|
+
.option('--reason <reason>', 'Reason for the reissue (e.g., baseline drift, runtime rebinding)')
|
|
691
|
+
.action(reissueTurnCommand);
|
|
692
|
+
|
|
671
693
|
program
|
|
672
694
|
.command('step')
|
|
673
695
|
.description('Run a single governed turn: assign, dispatch, wait, validate, accept/reject')
|
|
674
696
|
.option('--role <role>', 'Override the target role (default: phase entry role)')
|
|
675
697
|
.option('--resume', 'Resume waiting for an already-active turn')
|
|
676
698
|
.option('--turn <id>', 'Target a specific active turn (required with --resume when multiple turns exist)')
|
|
699
|
+
.option('--no-intent', 'Do not bind the next queued approved/planned intake intent to the next turn')
|
|
677
700
|
.option('--poll <seconds>', 'Polling interval for manual adapter in seconds', '2')
|
|
678
701
|
.option('--verbose', 'Stream local_cli subprocess output while the turn is running')
|
|
702
|
+
.option('--stream', 'Stream live subprocess output to terminal (alias for --verbose)')
|
|
679
703
|
.option('--auto-reject', 'Auto-reject and retry on validation failure')
|
|
680
704
|
.action(stepCommand);
|
|
681
705
|
|
|
@@ -911,7 +935,7 @@ intakeCmd
|
|
|
911
935
|
.description('Triage a detected intent — set priority, template, charter, and acceptance')
|
|
912
936
|
.option('--intent <id>', 'Intent ID to triage')
|
|
913
937
|
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
|
|
914
|
-
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, enterprise-app)')
|
|
938
|
+
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app)')
|
|
915
939
|
.option('--charter <text>', 'Delivery charter text')
|
|
916
940
|
.option('--acceptance <text>', 'Comma-separated acceptance criteria')
|
|
917
941
|
.option('--suppress', 'Suppress the intent instead of triaging')
|
package/package.json
CHANGED
|
@@ -19,6 +19,18 @@ cd "$CLI_DIR"
|
|
|
19
19
|
|
|
20
20
|
TARGET_VERSION=""
|
|
21
21
|
|
|
22
|
+
FORMULA_PATH="${CLI_DIR}/homebrew/agentxchain.rb"
|
|
23
|
+
|
|
24
|
+
formula_url() {
|
|
25
|
+
local formula_path="$1"
|
|
26
|
+
grep -E '^\s*url\s+"' "$formula_path" | sed 's/.*url *"\([^"]*\)".*/\1/' || true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
formula_sha() {
|
|
30
|
+
local formula_path="$1"
|
|
31
|
+
grep -E '^\s*sha256\s+"' "$formula_path" | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/' || true
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
while [[ $# -gt 0 ]]; do
|
|
23
35
|
case "$1" in
|
|
24
36
|
--target-version)
|
|
@@ -59,18 +71,56 @@ echo "[2/4] Syncing repo mirror to published tarball..."
|
|
|
59
71
|
bash "${SCRIPT_DIR}/sync-homebrew.sh" --target-version "$TARGET_VERSION"
|
|
60
72
|
echo " OK: repo mirror synced"
|
|
61
73
|
|
|
62
|
-
# Step 3:
|
|
63
|
-
echo "[3/
|
|
64
|
-
|
|
74
|
+
# Step 3: Explicitly prove the repo mirror now matches registry truth
|
|
75
|
+
echo "[3/5] Verifying repo mirror against registry tarball..."
|
|
76
|
+
TARBALL_URL="$(npm view "agentxchain@${TARGET_VERSION}" dist.tarball 2>/dev/null || echo "")"
|
|
77
|
+
if [[ -z "$TARBALL_URL" ]]; then
|
|
78
|
+
echo " FAIL: npm did not return dist.tarball for agentxchain@${TARGET_VERSION}"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
TARBALL_SHA="$(curl -sL "$TARBALL_URL" | shasum -a 256 | awk '{print $1}')"
|
|
83
|
+
if [[ -z "$TARBALL_SHA" ]] || [[ ${#TARBALL_SHA} -ne 64 ]]; then
|
|
84
|
+
echo " FAIL: could not compute valid SHA256 from registry tarball"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ ! -f "$FORMULA_PATH" ]]; then
|
|
89
|
+
echo " FAIL: Homebrew formula not found at ${FORMULA_PATH}"
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
FORMULA_URL="$(formula_url "$FORMULA_PATH")"
|
|
94
|
+
FORMULA_SHA="$(formula_sha "$FORMULA_PATH")"
|
|
95
|
+
|
|
96
|
+
if [[ "$FORMULA_URL" != "$TARBALL_URL" ]]; then
|
|
97
|
+
echo " FAIL: repo mirror formula URL does not match registry tarball"
|
|
98
|
+
echo " formula: ${FORMULA_URL:-<missing>}"
|
|
99
|
+
echo " registry: ${TARBALL_URL}"
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
echo " OK: repo mirror formula URL matches registry tarball"
|
|
103
|
+
|
|
104
|
+
if [[ "$FORMULA_SHA" != "$TARBALL_SHA" ]]; then
|
|
105
|
+
echo " FAIL: repo mirror formula SHA256 does not match registry tarball"
|
|
106
|
+
echo " formula: ${FORMULA_SHA:-<missing>}"
|
|
107
|
+
echo " registry: ${TARBALL_SHA}"
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
echo " OK: repo mirror formula SHA256 matches registry tarball"
|
|
111
|
+
|
|
112
|
+
# Step 4: Run the full test suite WITHOUT the preflight skip
|
|
113
|
+
echo "[4/5] Running full test suite (no preflight skip)..."
|
|
114
|
+
echo " This verifies the broader Homebrew mirror contract passes with the real SHA."
|
|
65
115
|
npm test
|
|
66
116
|
echo " OK: full test suite green"
|
|
67
117
|
|
|
68
|
-
# Step
|
|
118
|
+
# Step 5: Summary
|
|
69
119
|
echo ""
|
|
70
120
|
echo "============================================="
|
|
71
121
|
echo "Post-publish verification PASSED."
|
|
72
122
|
echo " - npm: agentxchain@${TARGET_VERSION} live"
|
|
73
|
-
echo " - repo mirror: SHA
|
|
123
|
+
echo " - repo mirror: formula URL and SHA match the published tarball"
|
|
74
124
|
echo " - test suite: green without preflight skip"
|
|
75
125
|
echo ""
|
|
76
126
|
echo "Main is now in Phase 3 (post-sync). Commit and push the mirror update."
|
|
@@ -27,7 +27,11 @@ function printText(result, exitCode) {
|
|
|
27
27
|
console.log('');
|
|
28
28
|
|
|
29
29
|
for (const connector of result.connectors) {
|
|
30
|
-
const badge = connector.level === 'pass'
|
|
30
|
+
const badge = connector.level === 'pass'
|
|
31
|
+
? chalk.green('PASS')
|
|
32
|
+
: connector.level === 'warn'
|
|
33
|
+
? chalk.yellow('WARN')
|
|
34
|
+
: chalk.red('FAIL');
|
|
31
35
|
console.log(` ${badge} ${connector.runtime_id} (${connector.type})`);
|
|
32
36
|
console.log(` ${chalk.dim('Target:')} ${connector.target}`);
|
|
33
37
|
console.log(` ${chalk.dim('Probe:')} ${connector.probe_kind}`);
|
|
@@ -44,12 +48,22 @@ function printText(result, exitCode) {
|
|
|
44
48
|
console.log(` ${chalk.dim('Time:')} ${connector.latency_ms}ms`);
|
|
45
49
|
}
|
|
46
50
|
console.log(` ${chalk.dim('Detail:')} ${connector.detail}`);
|
|
51
|
+
if (Array.isArray(connector.authority_warnings) && connector.authority_warnings.length > 0) {
|
|
52
|
+
for (const warning of connector.authority_warnings) {
|
|
53
|
+
console.log(` ${chalk.yellow('⚠')} ${warning.detail}`);
|
|
54
|
+
if (warning.fix) {
|
|
55
|
+
console.log(` ${chalk.dim('Fix:')} ${warning.fix}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
console.log('');
|
|
50
62
|
const summary = result.overall === 'pass'
|
|
51
63
|
? chalk.green(` ✓ ${result.pass_count}/${result.connectors.length} connectors passed`)
|
|
52
|
-
:
|
|
64
|
+
: result.overall === 'warn'
|
|
65
|
+
? chalk.yellow(` ⚠ ${result.warn_count} connector warning(s), ${result.pass_count} passed`)
|
|
66
|
+
: chalk.red(` ${result.fail_count} connector failure(s), ${result.pass_count} passed`);
|
|
53
67
|
console.log(summary);
|
|
54
68
|
console.log('');
|
|
55
69
|
process.exit(exitCode);
|
|
@@ -110,6 +124,7 @@ export async function connectorCheckCommand(runtimeId, options = {}) {
|
|
|
110
124
|
overall: result.overall,
|
|
111
125
|
timeout_ms: result.timeout_ms,
|
|
112
126
|
pass_count: result.pass_count,
|
|
127
|
+
warn_count: result.warn_count,
|
|
113
128
|
fail_count: result.fail_count,
|
|
114
129
|
connectors: result.connectors,
|
|
115
130
|
};
|
package/src/commands/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
2
2
|
import { execFileSync, execSync } from 'child_process';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { loadConfig, loadLock, findProjectRoot } from '../lib/config.js';
|
|
5
|
+
import { loadConfig, loadLock, findProjectRoot, loadProjectState } from '../lib/config.js';
|
|
6
6
|
import { validateProject } from '../lib/validation.js';
|
|
7
7
|
import { runAdmissionControl } from '../lib/admission-control.js';
|
|
8
8
|
import { getWatchPid } from './watch.js';
|
|
@@ -10,12 +10,16 @@ import { getDashboardPid, getDashboardSession } from './dashboard.js';
|
|
|
10
10
|
import { loadNormalizedConfig, detectConfigVersion } from '../lib/normalized-config.js';
|
|
11
11
|
import { readDaemonState, evaluateDaemonStatus } from '../lib/run-schedule.js';
|
|
12
12
|
import { getGovernedVersionSurface, formatGovernedVersionLabel } from '../lib/protocol-version.js';
|
|
13
|
+
import { getCliVersionHealth } from '../lib/cli-version.js';
|
|
13
14
|
import { PLUGIN_MANIFEST_FILE } from '../lib/plugins.js';
|
|
14
15
|
import {
|
|
15
16
|
getRoleRuntimeCapabilityContract,
|
|
16
17
|
summarizeRoleRuntimeCapability,
|
|
17
18
|
summarizeRuntimeCapabilityContract,
|
|
18
19
|
} from '../lib/runtime-capabilities.js';
|
|
20
|
+
import { detectActiveTurnBindingDrift } from '../lib/governed-state.js';
|
|
21
|
+
import { findPendingApprovedIntents } from '../lib/intake.js';
|
|
22
|
+
import { checkCleanBaseline } from '../lib/repo-observer.js';
|
|
19
23
|
|
|
20
24
|
export async function doctorCommand(opts = {}) {
|
|
21
25
|
const root = findProjectRoot(process.cwd());
|
|
@@ -55,6 +59,9 @@ export async function doctorCommand(opts = {}) {
|
|
|
55
59
|
|
|
56
60
|
function governedDoctor(root, rawConfig, opts) {
|
|
57
61
|
const checks = [];
|
|
62
|
+
const cliVersionHealth = getCliVersionHealth();
|
|
63
|
+
|
|
64
|
+
checks.push(buildCliVersionCheck(cliVersionHealth));
|
|
58
65
|
|
|
59
66
|
// 1. Config validation
|
|
60
67
|
const configResult = loadNormalizedConfig(rawConfig, root);
|
|
@@ -113,6 +120,63 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
113
120
|
checks.push({ id: 'state_health', name: 'State health', level: 'warn', detail: 'No state file yet (first run pending)' });
|
|
114
121
|
}
|
|
115
122
|
|
|
123
|
+
// 5b. Active turn binding drift (B-7: detect runtime rebinding mid-run)
|
|
124
|
+
if (normalized && existsSync(join(root, '.agentxchain', 'state.json'))) {
|
|
125
|
+
try {
|
|
126
|
+
const stateData = loadProjectState(root, normalized);
|
|
127
|
+
const drifts = detectActiveTurnBindingDrift(stateData, normalized);
|
|
128
|
+
if (drifts.length > 0) {
|
|
129
|
+
const driftSummary = drifts.map(d => {
|
|
130
|
+
const parts = [];
|
|
131
|
+
if (d.runtime_changed) parts.push(`runtime ${d.old_runtime} → ${d.new_runtime}`);
|
|
132
|
+
if (d.authority_changed) parts.push(`authority ${d.old_authority} → ${d.new_authority}`);
|
|
133
|
+
return `${d.turn_id} (${d.role_id}): ${parts.join(', ')}`;
|
|
134
|
+
}).join('; ');
|
|
135
|
+
checks.push({
|
|
136
|
+
id: 'binding_drift',
|
|
137
|
+
name: 'Active turn binding',
|
|
138
|
+
level: 'warn',
|
|
139
|
+
detail: `Stale binding: ${driftSummary}. Run: agentxchain reissue-turn`,
|
|
140
|
+
drifts,
|
|
141
|
+
});
|
|
142
|
+
} else if (Object.keys(stateData?.active_turns || {}).length > 0) {
|
|
143
|
+
checks.push({
|
|
144
|
+
id: 'binding_drift',
|
|
145
|
+
name: 'Active turn binding',
|
|
146
|
+
level: 'pass',
|
|
147
|
+
detail: 'Active turns match current config bindings',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// State couldn't be loaded — skip drift check
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 5c. Clean working tree pre-flight for authoritative/proposed roles
|
|
156
|
+
if (normalized?.roles) {
|
|
157
|
+
const writableRoles = Object.entries(normalized.roles)
|
|
158
|
+
.filter(([, role]) => role.write_authority === 'authoritative' || role.write_authority === 'proposed')
|
|
159
|
+
.map(([id]) => id);
|
|
160
|
+
if (writableRoles.length > 0) {
|
|
161
|
+
const cleanCheck = checkCleanBaseline(root, 'authoritative');
|
|
162
|
+
if (cleanCheck.clean) {
|
|
163
|
+
checks.push({
|
|
164
|
+
id: 'clean_baseline',
|
|
165
|
+
name: 'Clean working tree',
|
|
166
|
+
level: 'pass',
|
|
167
|
+
detail: 'Working tree is clean for authoritative/proposed turns',
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
checks.push({
|
|
171
|
+
id: 'clean_baseline',
|
|
172
|
+
name: 'Clean working tree',
|
|
173
|
+
level: 'warn',
|
|
174
|
+
detail: `${cleanCheck.reason} Writable roles: ${writableRoles.join(', ')}`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
116
180
|
// 6. Schedule health (only when schedules configured)
|
|
117
181
|
const schedules = normalized?.schedules;
|
|
118
182
|
const hasSchedules = schedules && typeof schedules === 'object' && Object.keys(schedules).length > 0;
|
|
@@ -258,6 +322,20 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
258
322
|
}
|
|
259
323
|
}
|
|
260
324
|
|
|
325
|
+
// 11. Pending intake intents (BUG-15 — informational)
|
|
326
|
+
{
|
|
327
|
+
const pendingIntents = findPendingApprovedIntents(root);
|
|
328
|
+
if (pendingIntents.length > 0) {
|
|
329
|
+
const summary = pendingIntents.map(pi => `[${pi.priority}] ${pi.intent_id}`).join(', ');
|
|
330
|
+
checks.push({
|
|
331
|
+
id: 'pending_intents',
|
|
332
|
+
name: 'Pending intents',
|
|
333
|
+
level: 'info',
|
|
334
|
+
detail: `${pendingIntents.length} approved intent(s) in queue, next turn will consume them: ${summary}`,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
261
339
|
// Compute summary
|
|
262
340
|
const failCount = checks.filter(c => c.level === 'fail').length;
|
|
263
341
|
const warnCount = checks.filter(c => c.level === 'warn').length;
|
|
@@ -270,6 +348,10 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
270
348
|
project: projectId,
|
|
271
349
|
...versionSurface,
|
|
272
350
|
config_version: versionSurface.config_generation,
|
|
351
|
+
cli_version: cliVersionHealth.current_version,
|
|
352
|
+
docs_min_cli_version: cliVersionHealth.docs_min_cli_version,
|
|
353
|
+
cli_version_status: cliVersionHealth.status,
|
|
354
|
+
cli_version_source: cliVersionHealth.source,
|
|
273
355
|
overall,
|
|
274
356
|
connector_probe_recommended: connectorProbe.recommended,
|
|
275
357
|
connector_probe_runtime_ids: connectorProbe.runtimeIds,
|
|
@@ -312,6 +394,11 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
312
394
|
} else {
|
|
313
395
|
console.log(chalk.red(` Not ready: ${failCount} failure${failCount > 1 ? 's' : ''}, ${warnCount} warning${warnCount > 1 ? 's' : ''}.`));
|
|
314
396
|
}
|
|
397
|
+
if (cliVersionHealth.stale) {
|
|
398
|
+
console.log(chalk.yellow(' Fix: npm install -g agentxchain@latest'));
|
|
399
|
+
console.log(chalk.yellow(' brew upgrade agentxchain'));
|
|
400
|
+
console.log(chalk.yellow(' npx --yes -p agentxchain@latest -c "agentxchain doctor"'));
|
|
401
|
+
}
|
|
315
402
|
if (failCount === 0 && connectorProbe.recommended) {
|
|
316
403
|
console.log(chalk.dim(` Next: ${connectorProbe.detail}`));
|
|
317
404
|
}
|
|
@@ -341,6 +428,33 @@ function attachRuntimeContract(baseCheck, rtId, rt, boundRoleEntries) {
|
|
|
341
428
|
};
|
|
342
429
|
}
|
|
343
430
|
|
|
431
|
+
function buildCliVersionCheck(cliVersionHealth) {
|
|
432
|
+
if (cliVersionHealth.status === 'stale') {
|
|
433
|
+
return {
|
|
434
|
+
id: 'cli_version',
|
|
435
|
+
name: 'CLI version',
|
|
436
|
+
level: 'warn',
|
|
437
|
+
detail: `${cliVersionHealth.detail} Fix with npm install -g agentxchain@latest, brew upgrade agentxchain, or npx --yes -p agentxchain@latest -c "agentxchain doctor".`,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (cliVersionHealth.status === 'ok') {
|
|
442
|
+
return {
|
|
443
|
+
id: 'cli_version',
|
|
444
|
+
name: 'CLI version',
|
|
445
|
+
level: 'pass',
|
|
446
|
+
detail: cliVersionHealth.detail,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
id: 'cli_version',
|
|
452
|
+
name: 'CLI version',
|
|
453
|
+
level: 'info',
|
|
454
|
+
detail: cliVersionHealth.detail,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
344
458
|
function checkRuntimeReachable(rtId, rt, boundRoleEntries = []) {
|
|
345
459
|
const base = { id: `runtime_${rtId}`, name: `Runtime: ${rtId}` };
|
|
346
460
|
|
|
@@ -449,8 +563,10 @@ function legacyDoctor(root, opts) {
|
|
|
449
563
|
|
|
450
564
|
const { config } = result;
|
|
451
565
|
const lock = loadLock(root);
|
|
566
|
+
const cliVersionHealth = getCliVersionHealth();
|
|
452
567
|
const checks = [];
|
|
453
568
|
|
|
569
|
+
checks.push(buildCliVersionCheck(cliVersionHealth));
|
|
454
570
|
checks.push(checkFile('agentxchain.json', existsSync(join(root, 'agentxchain.json')), 'Project config exists'));
|
|
455
571
|
checks.push(checkFile('lock.json', !!lock, 'Lock file exists'));
|
|
456
572
|
checks.push(checkBinary('cursor', 'Cursor CLI available (optional for non-macOS launch)'));
|
|
@@ -489,6 +605,11 @@ function legacyDoctor(root, opts) {
|
|
|
489
605
|
} else {
|
|
490
606
|
console.log(chalk.red(` Not ready: ${failCount} blocking issue(s).`));
|
|
491
607
|
}
|
|
608
|
+
if (cliVersionHealth.stale) {
|
|
609
|
+
console.log(chalk.yellow(' Fix: npm install -g agentxchain@latest'));
|
|
610
|
+
console.log(chalk.yellow(' brew upgrade agentxchain'));
|
|
611
|
+
console.log(chalk.yellow(' npx --yes -p agentxchain@latest -c "agentxchain doctor"'));
|
|
612
|
+
}
|
|
492
613
|
console.log('');
|
|
493
614
|
}
|
|
494
615
|
|
package/src/commands/events.js
CHANGED
|
@@ -61,12 +61,16 @@ function printEvent(evt) {
|
|
|
61
61
|
const runId = evt.run_id ? evt.run_id.slice(0, 12) : '—';
|
|
62
62
|
const phase = evt.phase || '—';
|
|
63
63
|
const turnInfo = evt.turn?.role_id ? ` [${evt.turn.role_id}]` : '';
|
|
64
|
+
const intentInfo = evt.intent_id ? ` intent=${evt.intent_id}` : '';
|
|
64
65
|
const conflictDetail = evt.event_type === 'turn_conflicted'
|
|
65
66
|
? ` — ${formatConflictDetail(evt)}`
|
|
66
67
|
: '';
|
|
67
68
|
const rejectionDetail = evt.event_type === 'turn_rejected' && evt.payload?.reason
|
|
68
69
|
? ` — ${evt.payload.reason}${evt.payload.failed_stage ? ` (${evt.payload.failed_stage})` : ''}`
|
|
69
70
|
: '';
|
|
71
|
+
const acceptanceFailedDetail = evt.event_type === 'acceptance_failed' && evt.payload?.reason
|
|
72
|
+
? ` — ${evt.payload.reason}${evt.payload.stage ? ` (${evt.payload.stage})` : ''}`
|
|
73
|
+
: '';
|
|
70
74
|
const phaseTransitionDetail = evt.event_type === 'phase_entered' && evt.payload?.from && evt.payload?.to
|
|
71
75
|
? ` ${evt.payload.from} → ${evt.payload.to}${evt.payload.trigger ? ` (${evt.payload.trigger})` : ''}`
|
|
72
76
|
: '';
|
|
@@ -78,7 +82,7 @@ function printEvent(evt) {
|
|
|
78
82
|
: evt.event_type === 'human_escalation_resolved' && evt.payload?.escalation_id
|
|
79
83
|
? ` ${evt.payload.escalation_id} via ${evt.payload.resolved_via || '?'}`
|
|
80
84
|
: '';
|
|
81
|
-
console.log(`${chalk.dim(ts)} ${type} ${chalk.cyan(runId)} ${phase}${turnInfo}${conflictDetail}${rejectionDetail}${phaseTransitionDetail}${gateFailedDetail}${humanEscalationDetail}`);
|
|
85
|
+
console.log(`${chalk.dim(ts)} ${type} ${chalk.cyan(runId)} ${phase}${turnInfo}${intentInfo}${conflictDetail}${rejectionDetail}${acceptanceFailedDetail}${phaseTransitionDetail}${gateFailedDetail}${humanEscalationDetail}`);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
function formatConflictDetail(evt) {
|
|
@@ -116,6 +120,8 @@ function colorEventType(type) {
|
|
|
116
120
|
turn_dispatched: chalk.blue,
|
|
117
121
|
turn_accepted: chalk.green,
|
|
118
122
|
turn_rejected: chalk.yellow,
|
|
123
|
+
acceptance_failed: chalk.red.bold,
|
|
124
|
+
turn_reissued: chalk.cyan,
|
|
119
125
|
turn_conflicted: chalk.redBright,
|
|
120
126
|
phase_entered: chalk.magenta,
|
|
121
127
|
escalation_raised: chalk.red.bold,
|
package/src/commands/init.js
CHANGED
|
@@ -257,9 +257,11 @@ You are QA. Your mandate: **${role.mandate}**
|
|
|
257
257
|
- \`.planning/ship-verdict.md\` — your overall ship/no-ship recommendation
|
|
258
258
|
- \`.planning/RELEASE_NOTES.md\` — user-facing release notes with impact and verification summary
|
|
259
259
|
|
|
260
|
-
##
|
|
260
|
+
## File Access
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
${role.write_authority === 'review_only'
|
|
263
|
+
? '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`.'
|
|
264
|
+
: 'You have direct write access for this role. Use it to create or update QA-owned evidence and verification artifacts directly in the repo. Do not make unrelated product changes. Your artifact type should normally be `workspace` when you modify repo files directly.'}
|
|
263
265
|
|
|
264
266
|
## Runtime Truth
|
|
265
267
|
|
|
@@ -337,9 +339,11 @@ You are invoked when the normal PM → Dev → QA loop is stuck:
|
|
|
337
339
|
- You may NOT override human decisions — escalate to \`human\` if needed
|
|
338
340
|
- Every override must be recorded as a decision with clear rationale
|
|
339
341
|
|
|
340
|
-
##
|
|
342
|
+
## File Access
|
|
341
343
|
|
|
342
|
-
|
|
344
|
+
${role.write_authority === 'review_only'
|
|
345
|
+
? 'You have `review_only` write authority. Like QA, you must raise at least one objection (protocol requirement). Your artifact type is `review`.'
|
|
346
|
+
: 'You have direct write access for this role. Use it sparingly to resolve tactical deadlocks or correct governance artifacts when necessary. Do not use Director authority as a shortcut around the normal PM -> Dev -> QA loop.'}
|
|
343
347
|
|
|
344
348
|
## Objection Requirement
|
|
345
349
|
|
|
@@ -513,6 +517,16 @@ function formatRuntimeSummary(runtimeId, runtime) {
|
|
|
513
517
|
return `${command} (${runtime.prompt_transport || runtime.type})`;
|
|
514
518
|
}
|
|
515
519
|
|
|
520
|
+
function runtimeMatchesDefaultGovernedLocalCli(runtime) {
|
|
521
|
+
if (!runtime || runtime.type !== 'local_cli') return false;
|
|
522
|
+
const command = Array.isArray(runtime.command) ? runtime.command : [];
|
|
523
|
+
return (
|
|
524
|
+
runtime.cwd === DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME.cwd
|
|
525
|
+
&& runtime.prompt_transport === DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME.prompt_transport
|
|
526
|
+
&& JSON.stringify(command) === JSON.stringify(DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME.command)
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
516
530
|
function hasTemplateDefinedRouting(template) {
|
|
517
531
|
return Boolean(template?.scaffold_blueprint?.routing && Object.keys(template.scaffold_blueprint.routing).length > 0);
|
|
518
532
|
}
|
|
@@ -620,9 +634,15 @@ function buildScaffoldConfigFromTemplate(template, localDevRuntime, workflowKitC
|
|
|
620
634
|
(Array.isArray(runtimeOptions.devCommand) && runtimeOptions.devCommand.length > 0)
|
|
621
635
|
|| (typeof runtimeOptions.devPromptTransport === 'string' && runtimeOptions.devPromptTransport.trim())
|
|
622
636
|
);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
637
|
+
const defaultLocalRuntimeIds = Object.entries(runtimes)
|
|
638
|
+
.filter(([, runtime]) => runtimeMatchesDefaultGovernedLocalCli(runtime))
|
|
639
|
+
.map(([runtimeId]) => runtimeId);
|
|
640
|
+
|
|
641
|
+
if (!blueprint || defaultLocalRuntimeIds.length > 0 || explicitLocalDevRequested) {
|
|
642
|
+
const runtimeIdsToReplace = defaultLocalRuntimeIds.length > 0 ? defaultLocalRuntimeIds : ['local-dev'];
|
|
643
|
+
for (const runtimeId of runtimeIdsToReplace) {
|
|
644
|
+
runtimes[runtimeId] = cloneJsonCompatible(localDevRuntime);
|
|
645
|
+
}
|
|
626
646
|
}
|
|
627
647
|
|
|
628
648
|
if (explicitLocalDevRequested && roles?.dev?.runtime === 'manual-dev') {
|
|
@@ -813,14 +833,23 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
813
833
|
// TALK.md
|
|
814
834
|
writeFileSync(join(dir, 'TALK.md'), `# ${projectName} — Team Talk File\n\nCanonical human-readable handoff log for all agents.\n\n---\n\n`);
|
|
815
835
|
|
|
816
|
-
// .gitignore additions
|
|
836
|
+
// .gitignore additions with inline comments so operators know what to commit vs. ignore
|
|
817
837
|
const gitignorePath = join(dir, '.gitignore');
|
|
818
|
-
const
|
|
838
|
+
const gitignoreContent = [
|
|
839
|
+
'# AgentXchain — secrets',
|
|
840
|
+
'.env',
|
|
841
|
+
'',
|
|
842
|
+
'# AgentXchain — transient execution artifacts (never commit)',
|
|
843
|
+
'.agentxchain/staging/',
|
|
844
|
+
'.agentxchain/dispatch/',
|
|
845
|
+
'.agentxchain/transactions/',
|
|
846
|
+
].join('\n') + '\n';
|
|
847
|
+
const requiredPaths = ['.env', '.agentxchain/staging/', '.agentxchain/dispatch/', '.agentxchain/transactions/'];
|
|
819
848
|
if (!existsSync(gitignorePath)) {
|
|
820
|
-
writeFileSync(gitignorePath,
|
|
849
|
+
writeFileSync(gitignorePath, gitignoreContent);
|
|
821
850
|
} else {
|
|
822
851
|
const existingIgnore = readFileSync(gitignorePath, 'utf8');
|
|
823
|
-
const missing =
|
|
852
|
+
const missing = requiredPaths.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
|
|
824
853
|
if (missing.length > 0) {
|
|
825
854
|
const prefix = existingIgnore.endsWith('\n') ? '' : '\n';
|
|
826
855
|
writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
|
|
@@ -864,6 +893,8 @@ async function initGoverned(opts) {
|
|
|
864
893
|
console.error(' cli-tool Governed scaffold for a CLI tool');
|
|
865
894
|
console.error(' library Governed scaffold for a reusable package');
|
|
866
895
|
console.error(' web-app Governed scaffold for a web application');
|
|
896
|
+
console.error(' full-local-cli Human-gated automation pattern with all roles on local_cli');
|
|
897
|
+
console.error(' enterprise-app Governed scaffold for multi-role enterprise delivery');
|
|
867
898
|
process.exit(1);
|
|
868
899
|
}
|
|
869
900
|
selectedTemplate = loadGovernedTemplate(templateId);
|
package/src/commands/inject.js
CHANGED
|
@@ -74,7 +74,7 @@ export async function injectCommand(description, opts) {
|
|
|
74
74
|
console.log('');
|
|
75
75
|
console.log(chalk.red.bold(' ⚡ Preemption marker written'));
|
|
76
76
|
console.log(chalk.dim(' The current run will yield after the active turn completes.'));
|
|
77
|
-
console.log(chalk.dim(' The
|
|
77
|
+
console.log(chalk.dim(' The next dispatch (manual resume, step --resume, or continuous loop) will consume this intent.'));
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
console.log('');
|