agentxchain 2.14.0 → 2.15.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 +7 -3
- package/bin/agentxchain.js +19 -2
- package/package.json +1 -1
- package/scripts/release-downstream-truth.sh +15 -14
- 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/multi.js +58 -2
- package/src/lib/coordinator-acceptance.js +24 -98
- package/src/lib/coordinator-barriers.js +116 -0
- package/src/lib/coordinator-config.js +93 -0
- package/src/lib/coordinator-recovery.js +123 -68
- package/src/lib/coordinator-state.js +1 -0
- package/src/lib/cross-repo-context.js +68 -1
- package/src/lib/intake-handoff.js +58 -0
- package/src/lib/intake.js +300 -11
package/README.md
CHANGED
|
@@ -100,10 +100,10 @@ For initiatives spanning multiple governed repos, use the coordinator to add cro
|
|
|
100
100
|
npx agentxchain init --governed --template api-service --dir repos/backend -y
|
|
101
101
|
npx agentxchain init --governed --template web-app --dir repos/frontend -y
|
|
102
102
|
agentxchain multi init
|
|
103
|
-
agentxchain multi step --
|
|
103
|
+
agentxchain multi step --json
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
See the [multi-repo quickstart](https://agentxchain.dev/docs/quickstart#multi-repo-cold-start) for the full cold-start walkthrough.
|
|
106
|
+
If the coordinator enters `blocked`, fix the cause and run `agentxchain multi resume` before continuing with `multi step` or `multi approve-gate`. See the [multi-repo quickstart](https://agentxchain.dev/docs/quickstart#multi-repo-cold-start) for the full cold-start walkthrough.
|
|
107
107
|
|
|
108
108
|
### Migrate a legacy project
|
|
109
109
|
|
|
@@ -132,7 +132,10 @@ agentxchain step
|
|
|
132
132
|
| `template validate` | Prove the template registry, workflow-kit scaffold contract, and planning artifact completeness (`--json` exposes a `workflow_kit` block) |
|
|
133
133
|
| `verify protocol` | Run the shipped protocol conformance suite against a target implementation |
|
|
134
134
|
| `dashboard` | Open the local governance dashboard in your browser for repo-local runs or multi-repo coordinator initiatives, including pending gate approvals |
|
|
135
|
-
| `
|
|
135
|
+
| `multi init\|status\|step\|resume\|approve-gate\|resync` | Run the multi-repo coordinator lifecycle, including blocked-state recovery via `multi resume` |
|
|
136
|
+
| `intake record\|triage\|approve\|plan\|start\|scan\|resolve` | Continuous-delivery intake: turn delivery signals into governed work items |
|
|
137
|
+
| `intake handoff` | Bridge a planned intake intent to a coordinator workstream for multi-repo execution |
|
|
138
|
+
| `plugin install\|list\|remove` | Install, inspect, or remove governed hook plugins backed by `agentxchain-plugin.json` manifests |
|
|
136
139
|
|
|
137
140
|
### Shared utilities
|
|
138
141
|
|
|
@@ -197,6 +200,7 @@ TALK.md
|
|
|
197
200
|
|
|
198
201
|
- `manual`: implemented
|
|
199
202
|
- `local_cli`: implemented
|
|
203
|
+
- `mcp`: implemented for stdio and streamable HTTP tool-contract dispatch
|
|
200
204
|
- `api_proxy`: implemented for synchronous review-only turns and stages a provider-backed result during `step`
|
|
201
205
|
|
|
202
206
|
## Legacy IDE Mode
|
package/bin/agentxchain.js
CHANGED
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
multiInitCommand,
|
|
89
89
|
multiStatusCommand,
|
|
90
90
|
multiStepCommand,
|
|
91
|
+
multiResumeCommand,
|
|
91
92
|
multiApproveGateCommand,
|
|
92
93
|
multiResyncCommand,
|
|
93
94
|
} from '../src/commands/multi.js';
|
|
@@ -96,6 +97,7 @@ import { intakeTriageCommand } from '../src/commands/intake-triage.js';
|
|
|
96
97
|
import { intakeApproveCommand } from '../src/commands/intake-approve.js';
|
|
97
98
|
import { intakePlanCommand } from '../src/commands/intake-plan.js';
|
|
98
99
|
import { intakeStartCommand } from '../src/commands/intake-start.js';
|
|
100
|
+
import { intakeHandoffCommand } from '../src/commands/intake-handoff.js';
|
|
99
101
|
import { intakeScanCommand } from '../src/commands/intake-scan.js';
|
|
100
102
|
import { intakeResolveCommand } from '../src/commands/intake-resolve.js';
|
|
101
103
|
import { intakeStatusCommand } from '../src/commands/intake-status.js';
|
|
@@ -415,6 +417,12 @@ multiCmd
|
|
|
415
417
|
.option('-j, --json', 'Output as JSON')
|
|
416
418
|
.action(multiStepCommand);
|
|
417
419
|
|
|
420
|
+
multiCmd
|
|
421
|
+
.command('resume')
|
|
422
|
+
.description('Clear a blocked coordinator state after operator recovery')
|
|
423
|
+
.option('-j, --json', 'Output as JSON')
|
|
424
|
+
.action(multiResumeCommand);
|
|
425
|
+
|
|
418
426
|
multiCmd
|
|
419
427
|
.command('approve-gate')
|
|
420
428
|
.description('Approve a pending phase transition or completion gate')
|
|
@@ -449,7 +457,7 @@ intakeCmd
|
|
|
449
457
|
intakeCmd
|
|
450
458
|
.command('triage')
|
|
451
459
|
.description('Triage a detected intent — set priority, template, charter, and acceptance')
|
|
452
|
-
.
|
|
460
|
+
.option('--intent <id>', 'Intent ID to triage')
|
|
453
461
|
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
|
|
454
462
|
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app)')
|
|
455
463
|
.option('--charter <text>', 'Delivery charter text')
|
|
@@ -486,10 +494,19 @@ intakeCmd
|
|
|
486
494
|
.option('-j, --json', 'Output as JSON')
|
|
487
495
|
.action(intakeStartCommand);
|
|
488
496
|
|
|
497
|
+
intakeCmd
|
|
498
|
+
.command('handoff')
|
|
499
|
+
.description('Hand off a planned intent to a coordinator workstream')
|
|
500
|
+
.option('--intent <id>', 'Intent ID to hand off')
|
|
501
|
+
.option('--coordinator-root <path>', 'Path to the coordinator workspace root')
|
|
502
|
+
.option('--workstream <id>', 'Coordinator workstream ID')
|
|
503
|
+
.option('-j, --json', 'Output as JSON')
|
|
504
|
+
.action(intakeHandoffCommand);
|
|
505
|
+
|
|
489
506
|
intakeCmd
|
|
490
507
|
.command('scan')
|
|
491
508
|
.description('Scan a structured source snapshot into intake events')
|
|
492
|
-
.
|
|
509
|
+
.option('--source <id>', 'Source type: ci_failure, git_ref_change, schedule')
|
|
493
510
|
.option('--file <path>', 'Path to snapshot JSON file')
|
|
494
511
|
.option('--stdin', 'Read snapshot from stdin')
|
|
495
512
|
.option('-j, --json', 'Output as JSON')
|
package/package.json
CHANGED
|
@@ -47,7 +47,7 @@ if [[ -z "$TARGET_VERSION" ]]; then
|
|
|
47
47
|
fi
|
|
48
48
|
|
|
49
49
|
PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
|
|
50
|
-
|
|
50
|
+
CANONICAL_HOMEBREW_FORMULA_URL="${AGENTXCHAIN_DOWNSTREAM_FORMULA_URL:-https://raw.githubusercontent.com/shivamtiwari93/homebrew-tap/main/Formula/agentxchain.rb}"
|
|
51
51
|
|
|
52
52
|
PASS=0
|
|
53
53
|
FAIL=0
|
|
@@ -57,7 +57,7 @@ fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
|
|
|
57
57
|
|
|
58
58
|
echo "AgentXchain v${TARGET_VERSION} Downstream Release Truth"
|
|
59
59
|
echo "====================================="
|
|
60
|
-
echo "Checks downstream surfaces after publish: GitHub release, Homebrew tap."
|
|
60
|
+
echo "Checks downstream surfaces after publish: GitHub release, canonical Homebrew tap."
|
|
61
61
|
echo ""
|
|
62
62
|
|
|
63
63
|
# --- Check 1: GitHub Release ---
|
|
@@ -85,38 +85,39 @@ else
|
|
|
85
85
|
fi
|
|
86
86
|
|
|
87
87
|
# --- Get registry tarball URL and compute SHA ---
|
|
88
|
-
echo "[2/3] Homebrew tap SHA matches registry tarball"
|
|
88
|
+
echo "[2/3] Canonical Homebrew tap SHA matches registry tarball"
|
|
89
89
|
REGISTRY_TARBALL_URL="$(npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball 2>/dev/null || true)"
|
|
90
|
+
FORMULA_CONTENT="$(curl -fsSL "$CANONICAL_HOMEBREW_FORMULA_URL" 2>/dev/null || true)"
|
|
90
91
|
if [[ -z "$REGISTRY_TARBALL_URL" ]]; then
|
|
91
92
|
fail "cannot fetch registry tarball URL for ${PACKAGE_NAME}@${TARGET_VERSION}"
|
|
93
|
+
elif [[ -z "$FORMULA_CONTENT" ]]; then
|
|
94
|
+
fail "cannot fetch canonical Homebrew formula from ${CANONICAL_HOMEBREW_FORMULA_URL}"
|
|
92
95
|
else
|
|
93
96
|
REGISTRY_SHA="$(curl -sL "$REGISTRY_TARBALL_URL" | shasum -a 256 | awk '{print $1}')"
|
|
94
97
|
if [[ -z "$REGISTRY_SHA" ]]; then
|
|
95
98
|
fail "cannot compute SHA256 of registry tarball"
|
|
96
|
-
elif [[ ! -f "$HOMEBREW_FORMULA" ]]; then
|
|
97
|
-
fail "Homebrew formula not found at ${HOMEBREW_FORMULA}"
|
|
98
99
|
else
|
|
99
|
-
FORMULA_SHA="$(grep -E '^\s*sha256\s+"'
|
|
100
|
+
FORMULA_SHA="$(printf '%s\n' "$FORMULA_CONTENT" | grep -E '^\s*sha256\s+"' | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/')"
|
|
100
101
|
if [[ "$REGISTRY_SHA" == "$FORMULA_SHA" ]]; then
|
|
101
|
-
pass "Homebrew formula SHA256 matches registry tarball (${REGISTRY_SHA:0:16}...)"
|
|
102
|
+
pass "canonical Homebrew formula SHA256 matches registry tarball (${REGISTRY_SHA:0:16}...)"
|
|
102
103
|
else
|
|
103
|
-
fail "Homebrew formula SHA256 mismatch: formula=${FORMULA_SHA:0:16}... registry=${REGISTRY_SHA:0:16}..."
|
|
104
|
+
fail "canonical Homebrew formula SHA256 mismatch: formula=${FORMULA_SHA:0:16}... registry=${REGISTRY_SHA:0:16}..."
|
|
104
105
|
fi
|
|
105
106
|
fi
|
|
106
107
|
fi
|
|
107
108
|
|
|
108
109
|
# --- Check 3: Homebrew tap URL matches registry tarball URL ---
|
|
109
|
-
echo "[3/3] Homebrew tap URL matches registry tarball"
|
|
110
|
+
echo "[3/3] Canonical Homebrew tap URL matches registry tarball"
|
|
110
111
|
if [[ -z "$REGISTRY_TARBALL_URL" ]]; then
|
|
111
112
|
fail "cannot verify URL — registry tarball URL unavailable"
|
|
112
|
-
elif [[
|
|
113
|
-
fail "
|
|
113
|
+
elif [[ -z "$FORMULA_CONTENT" ]]; then
|
|
114
|
+
fail "cannot verify URL — canonical Homebrew formula unavailable"
|
|
114
115
|
else
|
|
115
|
-
FORMULA_URL="$(grep -E '^\s*url\s+"'
|
|
116
|
+
FORMULA_URL="$(printf '%s\n' "$FORMULA_CONTENT" | grep -E '^\s*url\s+"' | sed 's/.*url *"\([^"]*\)".*/\1/')"
|
|
116
117
|
if [[ "$FORMULA_URL" == "$REGISTRY_TARBALL_URL" ]]; then
|
|
117
|
-
pass "Homebrew formula URL matches registry tarball"
|
|
118
|
+
pass "canonical Homebrew formula URL matches registry tarball"
|
|
118
119
|
else
|
|
119
|
-
fail "Homebrew formula URL mismatch: formula=${FORMULA_URL} registry=${REGISTRY_TARBALL_URL}"
|
|
120
|
+
fail "canonical Homebrew formula URL mismatch: formula=${FORMULA_URL} registry=${REGISTRY_TARBALL_URL}"
|
|
120
121
|
fi
|
|
121
122
|
fi
|
|
122
123
|
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { findProjectRoot } from '../lib/config.js';
|
|
3
2
|
import { approveIntent } from '../lib/intake.js';
|
|
3
|
+
import { requireIntakeWorkspaceOrExit } from './intake-workspace.js';
|
|
4
4
|
|
|
5
5
|
export async function intakeApproveCommand(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 { 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/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) {
|