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.
Files changed (39) hide show
  1. package/README.md +9 -3
  2. package/bin/agentxchain.js +20 -2
  3. package/package.json +3 -1
  4. package/scripts/release-downstream-truth.sh +15 -14
  5. package/scripts/release-postflight.sh +21 -5
  6. package/scripts/sync-homebrew.sh +225 -0
  7. package/src/commands/init.js +16 -6
  8. package/src/commands/intake-approve.js +2 -10
  9. package/src/commands/intake-handoff.js +58 -0
  10. package/src/commands/intake-plan.js +2 -11
  11. package/src/commands/intake-record.js +2 -10
  12. package/src/commands/intake-resolve.js +2 -10
  13. package/src/commands/intake-scan.js +2 -10
  14. package/src/commands/intake-start.js +2 -10
  15. package/src/commands/intake-status.js +6 -10
  16. package/src/commands/intake-triage.js +2 -10
  17. package/src/commands/intake-workspace.js +58 -0
  18. package/src/commands/migrate.js +7 -3
  19. package/src/commands/multi.js +58 -2
  20. package/src/commands/run.js +29 -1
  21. package/src/commands/template-set.js +51 -2
  22. package/src/lib/adapter-interface.js +31 -0
  23. package/src/lib/coordinator-acceptance.js +24 -98
  24. package/src/lib/coordinator-barriers.js +116 -0
  25. package/src/lib/coordinator-config.js +124 -0
  26. package/src/lib/coordinator-dispatch.js +10 -1
  27. package/src/lib/coordinator-gates.js +28 -1
  28. package/src/lib/coordinator-recovery.js +133 -68
  29. package/src/lib/coordinator-state.js +74 -0
  30. package/src/lib/cross-repo-context.js +68 -1
  31. package/src/lib/governed-templates.js +60 -0
  32. package/src/lib/intake-handoff.js +58 -0
  33. package/src/lib/intake.js +300 -11
  34. package/src/lib/report.js +759 -27
  35. package/src/lib/workflow-gate-semantics.js +209 -0
  36. package/src/templates/governed/api-service.json +8 -1
  37. package/src/templates/governed/cli-tool.json +8 -1
  38. package/src/templates/governed/library.json +8 -1
  39. package/src/templates/governed/web-app.json +8 -1
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 --repo backend --role pm
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
- | `plugin install|list|remove` | Install, inspect, or remove governed hook plugins backed by `agentxchain-plugin.json` manifests |
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
 
@@ -191,12 +194,15 @@ agentxchain.json
191
194
  .agentxchain/staging/<turn_id>/turn-result.json
192
195
  TALK.md
193
196
  .planning/
197
+
198
+ The first-party governed workflow kit includes `.planning/SYSTEM_SPEC.md` alongside `PM_SIGNOFF.md`, `ROADMAP.md`, `acceptance-matrix.md`, and `ship-verdict.md`. `template validate --json` exposes this under the `workflow_kit` block.
194
199
  ```
195
200
 
196
201
  ### Runtime support today
197
202
 
198
203
  - `manual`: implemented
199
204
  - `local_cli`: implemented
205
+ - `mcp`: implemented for stdio and streamable HTTP tool-contract dispatch
200
206
  - `api_proxy`: implemented for synchronous review-only turns and stages a provider-backed result during `step`
201
207
 
202
208
  ## Legacy IDE Mode
@@ -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';
@@ -319,6 +321,7 @@ program
319
321
  .option('--auto-approve', 'Auto-approve all gates (non-interactive mode)')
320
322
  .option('--verbose', 'Stream adapter subprocess output')
321
323
  .option('--dry-run', 'Print what would be dispatched without executing')
324
+ .option('--no-report', 'Suppress automatic governance report after run completes')
322
325
  .action(runCommand);
323
326
 
324
327
  program
@@ -415,6 +418,12 @@ multiCmd
415
418
  .option('-j, --json', 'Output as JSON')
416
419
  .action(multiStepCommand);
417
420
 
421
+ multiCmd
422
+ .command('resume')
423
+ .description('Clear a blocked coordinator state after operator recovery')
424
+ .option('-j, --json', 'Output as JSON')
425
+ .action(multiResumeCommand);
426
+
418
427
  multiCmd
419
428
  .command('approve-gate')
420
429
  .description('Approve a pending phase transition or completion gate')
@@ -449,7 +458,7 @@ intakeCmd
449
458
  intakeCmd
450
459
  .command('triage')
451
460
  .description('Triage a detected intent — set priority, template, charter, and acceptance')
452
- .requiredOption('--intent <id>', 'Intent ID to triage')
461
+ .option('--intent <id>', 'Intent ID to triage')
453
462
  .option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
454
463
  .option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app)')
455
464
  .option('--charter <text>', 'Delivery charter text')
@@ -486,10 +495,19 @@ intakeCmd
486
495
  .option('-j, --json', 'Output as JSON')
487
496
  .action(intakeStartCommand);
488
497
 
498
+ intakeCmd
499
+ .command('handoff')
500
+ .description('Hand off a planned intent to a coordinator workstream')
501
+ .option('--intent <id>', 'Intent ID to hand off')
502
+ .option('--coordinator-root <path>', 'Path to the coordinator workspace root')
503
+ .option('--workstream <id>', 'Coordinator workstream ID')
504
+ .option('-j, --json', 'Output as JSON')
505
+ .action(intakeHandoffCommand);
506
+
489
507
  intakeCmd
490
508
  .command('scan')
491
509
  .description('Scan a structured source snapshot into intake events')
492
- .requiredOption('--source <id>', 'Source type: ci_failure, git_ref_change, schedule')
510
+ .option('--source <id>', 'Source type: ci_failure, git_ref_change, schedule')
493
511
  .option('--file <path>', 'Path to snapshot JSON file')
494
512
  .option('--stdin', 'Read snapshot from stdin')
495
513
  .option('-j, --json', 'Output as JSON')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "exports": {
10
10
  ".": "./bin/agentxchain.js",
11
+ "./adapter-interface": "./src/lib/adapter-interface.js",
11
12
  "./runner-interface": "./src/lib/runner-interface.js",
12
13
  "./run-loop": "./src/lib/run-loop.js"
13
14
  },
@@ -27,6 +28,7 @@
27
28
  "preflight:release:strict": "bash scripts/release-preflight.sh --strict",
28
29
  "postflight:release": "bash scripts/release-postflight.sh",
29
30
  "postflight:downstream": "bash scripts/release-downstream-truth.sh",
31
+ "sync:homebrew": "bash scripts/sync-homebrew.sh",
30
32
  "build:macos": "bun build bin/agentxchain.js --compile --target=bun-darwin-arm64 --outfile=dist/agentxchain-macos-arm64",
31
33
  "build:linux": "bun build bin/agentxchain.js --compile --target=bun-linux-x64 --outfile=dist/agentxchain-linux-x64",
32
34
  "publish:npm": "bash scripts/publish-npm.sh"
@@ -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
- HOMEBREW_FORMULA="${REPO_ROOT}/cli/homebrew/agentxchain.rb"
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+"' "$HOMEBREW_FORMULA" | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/')"
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 [[ ! -f "$HOMEBREW_FORMULA" ]]; then
113
- fail "Homebrew formula not found at ${HOMEBREW_FORMULA}"
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+"' "$HOMEBREW_FORMULA" | sed 's/.*url *"\([^"]*\)".*/\1/')"
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
 
@@ -78,6 +78,7 @@ REGISTRY_CHECKSUM=""
78
78
  PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
79
79
  PACKAGE_BIN_NAME="$(node -e "const pkg = JSON.parse(require('fs').readFileSync('package.json', 'utf8')); if (typeof pkg.bin === 'string') { console.log(pkg.name); process.exit(0); } const names = Object.keys(pkg.bin || {}); if (names.length !== 1) { console.error('package.json bin must declare exactly one entry'); process.exit(1); } console.log(names[0]);")"
80
80
  RUNNER_INTERFACE_VERSION_EXPECTED="$(node --input-type=module -e "import('./src/lib/runner-interface.js').then((mod) => { console.log(mod.RUNNER_INTERFACE_VERSION); }).catch((error) => { console.error(error.message); process.exit(1); });")"
81
+ ADAPTER_INTERFACE_VERSION_EXPECTED="$(node --input-type=module -e "import('./src/lib/adapter-interface.js').then((mod) => { console.log(mod.ADAPTER_INTERFACE_VERSION); }).catch((error) => { console.error(error.message); process.exit(1); });")"
81
82
 
82
83
  pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
83
84
  fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
@@ -178,17 +179,25 @@ EOF
178
179
 
179
180
  cat > "${consumer_root}/runner-export-smoke.mjs" <<'EOF'
180
181
  import { RUNNER_INTERFACE_VERSION, loadContext } from 'agentxchain/runner-interface';
182
+ import { ADAPTER_INTERFACE_VERSION, dispatchLocalCli } from 'agentxchain/adapter-interface';
181
183
  import { runLoop } from 'agentxchain/run-loop';
182
184
 
183
185
  if (typeof loadContext !== 'function') {
184
186
  throw new Error('loadContext export missing');
185
187
  }
186
188
 
189
+ if (typeof dispatchLocalCli !== 'function') {
190
+ throw new Error('dispatchLocalCli export missing');
191
+ }
192
+
187
193
  if (typeof runLoop !== 'function') {
188
194
  throw new Error('runLoop export missing');
189
195
  }
190
196
 
191
- console.log(RUNNER_INTERFACE_VERSION);
197
+ console.log(JSON.stringify({
198
+ runner_interface_version: RUNNER_INTERFACE_VERSION,
199
+ adapter_interface_version: ADAPTER_INTERFACE_VERSION
200
+ }));
192
201
  EOF
193
202
 
194
203
  local runner_output
@@ -252,7 +261,7 @@ run_with_retry() {
252
261
 
253
262
  echo "AgentXchain v${TARGET_VERSION} Release Postflight"
254
263
  echo "====================================="
255
- echo "Checks release truth after publish: tag, registry visibility, metadata, CLI install smoke, and runner export smoke."
264
+ echo "Checks release truth after publish: tag, registry visibility, metadata, CLI install smoke, and package export smoke."
256
265
  echo ""
257
266
 
258
267
  echo "[1/6] Git tag"
@@ -316,16 +325,23 @@ else
316
325
  printf '%s\n' "$EXEC_OUTPUT" | tail -20
317
326
  fi
318
327
 
319
- echo "[6/6] Runner export smoke"
328
+ echo "[6/6] Package export smoke"
320
329
  if run_with_retry RUNNER_EXPORT_OUTPUT "runner export smoke" nonempty "" run_runner_export_smoke; then
321
- RUNNER_EXPORT_VERSION="$(trim_last_line "$RUNNER_EXPORT_OUTPUT")"
330
+ RUNNER_EXPORT_JSON="$(trim_last_line "$RUNNER_EXPORT_OUTPUT")"
331
+ RUNNER_EXPORT_VERSION="$(printf '%s' "$RUNNER_EXPORT_JSON" | node --input-type=module -e "process.stdin.setEncoding('utf8'); let raw=''; process.stdin.on('data', (chunk) => raw += chunk); process.stdin.on('end', () => { const parsed = JSON.parse(raw); console.log(parsed.runner_interface_version || ''); });")"
332
+ ADAPTER_EXPORT_VERSION="$(printf '%s' "$RUNNER_EXPORT_JSON" | node --input-type=module -e "process.stdin.setEncoding('utf8'); let raw=''; process.stdin.on('data', (chunk) => raw += chunk); process.stdin.on('end', () => { const parsed = JSON.parse(raw); console.log(parsed.adapter_interface_version || ''); });")"
322
333
  if [[ "$RUNNER_EXPORT_VERSION" == "$RUNNER_INTERFACE_VERSION_EXPECTED" ]]; then
323
334
  pass "published runner exports import with interface ${RUNNER_INTERFACE_VERSION_EXPECTED}"
324
335
  else
325
336
  fail "published runner exports reported interface '${RUNNER_EXPORT_VERSION}', expected '${RUNNER_INTERFACE_VERSION_EXPECTED}'"
326
337
  fi
338
+ if [[ "$ADAPTER_EXPORT_VERSION" == "$ADAPTER_INTERFACE_VERSION_EXPECTED" ]]; then
339
+ pass "published adapter exports import with interface ${ADAPTER_INTERFACE_VERSION_EXPECTED}"
340
+ else
341
+ fail "published adapter exports reported interface '${ADAPTER_EXPORT_VERSION}', expected '${ADAPTER_INTERFACE_VERSION_EXPECTED}'"
342
+ fi
327
343
  else
328
- fail "published runner exports install smoke failed"
344
+ fail "published runner/adapter exports install smoke failed"
329
345
  printf '%s\n' "$RUNNER_EXPORT_OUTPUT" | tail -20
330
346
  fi
331
347
 
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env bash
2
+ # Sync Homebrew formula from live npm registry metadata.
3
+ # Updates both the repo mirror (cli/homebrew/) and optionally the canonical tap.
4
+ # Usage: bash scripts/sync-homebrew.sh --target-version <semver> [--push-tap] [--dry-run]
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ CLI_DIR="${SCRIPT_DIR}/.."
9
+ REPO_ROOT="${CLI_DIR}/.."
10
+
11
+ TARGET_VERSION=""
12
+ PUSH_TAP=false
13
+ DRY_RUN=false
14
+
15
+ FORMULA_PATH="${CLI_DIR}/homebrew/agentxchain.rb"
16
+ README_PATH="${CLI_DIR}/homebrew/README.md"
17
+ CANONICAL_TAP_REPO="shivamtiwari93/homebrew-tap"
18
+ PACKAGE_NAME="agentxchain"
19
+
20
+ formula_url() {
21
+ local formula_path="$1"
22
+ grep -E '^\s*url\s+"' "$formula_path" | sed 's/.*url *"\([^"]*\)".*/\1/' || true
23
+ }
24
+
25
+ formula_sha() {
26
+ local formula_path="$1"
27
+ grep -E '^\s*sha256\s+"' "$formula_path" | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/' || true
28
+ }
29
+
30
+ usage() {
31
+ echo "Usage: bash scripts/sync-homebrew.sh --target-version <semver> [--push-tap] [--dry-run]" >&2
32
+ }
33
+
34
+ while [[ $# -gt 0 ]]; do
35
+ case "$1" in
36
+ --target-version)
37
+ if [[ -z "${2:-}" ]]; then
38
+ echo "Error: --target-version requires a semver argument" >&2
39
+ usage
40
+ exit 1
41
+ fi
42
+ if ! [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
43
+ echo "Invalid semver: $2" >&2
44
+ usage
45
+ exit 1
46
+ fi
47
+ TARGET_VERSION="$2"
48
+ shift 2
49
+ ;;
50
+ --push-tap)
51
+ PUSH_TAP=true
52
+ shift
53
+ ;;
54
+ --dry-run)
55
+ DRY_RUN=true
56
+ shift
57
+ ;;
58
+ *)
59
+ usage
60
+ exit 1
61
+ ;;
62
+ esac
63
+ done
64
+
65
+ if [[ -z "$TARGET_VERSION" ]]; then
66
+ echo "Error: --target-version is required" >&2
67
+ usage
68
+ exit 1
69
+ fi
70
+
71
+ echo "Homebrew Sync — ${PACKAGE_NAME}@${TARGET_VERSION}"
72
+ echo "====================================="
73
+
74
+ # --- Step 1: Fetch tarball URL from npm ---
75
+ echo "[1/5] Fetching tarball URL from npm registry..."
76
+ TARBALL_URL="$(npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball 2>/dev/null || true)"
77
+ if [[ -z "$TARBALL_URL" ]]; then
78
+ echo "FAIL: npm registry does not serve ${PACKAGE_NAME}@${TARGET_VERSION}" >&2
79
+ exit 1
80
+ fi
81
+ echo " tarball: ${TARBALL_URL}"
82
+
83
+ # --- Step 2: Download tarball and compute SHA256 ---
84
+ echo "[2/5] Computing SHA256 from registry tarball..."
85
+ TARBALL_SHA="$(curl -sL "$TARBALL_URL" | shasum -a 256 | awk '{print $1}')"
86
+ if [[ -z "$TARBALL_SHA" ]] || [[ ${#TARBALL_SHA} -ne 64 ]]; then
87
+ echo "FAIL: could not compute valid SHA256 from tarball" >&2
88
+ exit 1
89
+ fi
90
+ echo " sha256: ${TARBALL_SHA}"
91
+
92
+ # --- Step 3: Check if already in sync ---
93
+ echo "[3/5] Checking repo mirror..."
94
+ MIRROR_IN_SYNC=false
95
+ if [[ -f "$FORMULA_PATH" ]]; then
96
+ CURRENT_URL="$(formula_url "$FORMULA_PATH")"
97
+ CURRENT_SHA="$(formula_sha "$FORMULA_PATH")"
98
+ if [[ "$CURRENT_URL" == "$TARBALL_URL" && "$CURRENT_SHA" == "$TARBALL_SHA" ]]; then
99
+ MIRROR_IN_SYNC=true
100
+ echo " Repo mirror already matches npm registry."
101
+ if ! $PUSH_TAP; then
102
+ echo "====================================="
103
+ echo "SYNC COMPLETE — repo mirror already up to date."
104
+ exit 0
105
+ fi
106
+ echo " Repo mirror is current, but canonical tap verification is still required."
107
+ fi
108
+ if ! $MIRROR_IN_SYNC; then
109
+ echo " Current URL: ${CURRENT_URL}"
110
+ echo " Current SHA: ${CURRENT_SHA}"
111
+ echo " Updating to match registry..."
112
+ fi
113
+ else
114
+ echo " Formula not found at ${FORMULA_PATH} — will create."
115
+ fi
116
+
117
+ if $DRY_RUN; then
118
+ echo ""
119
+ echo "[DRY RUN] Would update:"
120
+ if $MIRROR_IN_SYNC; then
121
+ echo " Repo mirror already matches:"
122
+ echo " url -> ${TARBALL_URL}"
123
+ echo " sha256 -> ${TARBALL_SHA}"
124
+ else
125
+ echo " ${FORMULA_PATH}:"
126
+ echo " url -> ${TARBALL_URL}"
127
+ echo " sha256 -> ${TARBALL_SHA}"
128
+ echo " ${README_PATH}:"
129
+ echo " version -> ${TARGET_VERSION}"
130
+ echo " tarball -> ${TARBALL_URL}"
131
+ fi
132
+ if $PUSH_TAP; then
133
+ echo " Canonical tap ${CANONICAL_TAP_REPO}:"
134
+ echo " Formula/agentxchain.rb -> ${TARBALL_URL}"
135
+ echo " Formula/agentxchain.rb sha256 -> ${TARBALL_SHA}"
136
+ fi
137
+ echo "====================================="
138
+ echo "DRY RUN COMPLETE — no files modified."
139
+ exit 0
140
+ fi
141
+
142
+ # --- Step 4: Update repo mirror ---
143
+ echo "[4/5] Updating repo mirror..."
144
+
145
+ if $MIRROR_IN_SYNC; then
146
+ echo " Repo mirror already in sync — no local file changes needed."
147
+ else
148
+ # Update formula
149
+ ESCAPED_URL="$(printf '%s' "$TARBALL_URL" | sed 's/[&/\]/\\&/g')"
150
+ ESCAPED_SHA="$(printf '%s' "$TARBALL_SHA" | sed 's/[&/\]/\\&/g')"
151
+ sed -i.bak -E "s|^([[:space:]]*url \").*(\")|\1${ESCAPED_URL}\2|" "$FORMULA_PATH"
152
+ sed -i.bak -E "s|^([[:space:]]*sha256 \").*(\")|\1${ESCAPED_SHA}\2|" "$FORMULA_PATH"
153
+ rm -f "${FORMULA_PATH}.bak"
154
+
155
+ # Update README version and tarball lines
156
+ if [[ -f "$README_PATH" ]]; then
157
+ # Update version line: "- version: `X.Y.Z`"
158
+ sed -i.bak -E "s|^(- version: \`).*(\`)|\1${TARGET_VERSION}\2|" "$README_PATH"
159
+ # Update tarball line: "- source tarball: `URL`"
160
+ sed -i.bak -E "s|^(- source tarball: \`).*(\`)|\1${TARBALL_URL}\2|" "$README_PATH"
161
+ rm -f "${README_PATH}.bak"
162
+ fi
163
+
164
+ echo " Updated ${FORMULA_PATH}"
165
+ echo " Updated ${README_PATH}"
166
+ fi
167
+
168
+ # --- Step 5: Push to canonical tap (optional) ---
169
+ if $PUSH_TAP; then
170
+ echo "[5/5] Pushing to canonical tap ${CANONICAL_TAP_REPO}..."
171
+ TAP_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/homebrew-tap-sync.XXXXXX")"
172
+ TAP_REMOTE_URL="https://github.com/${CANONICAL_TAP_REPO}.git"
173
+ if [[ -n "${HOMEBREW_TAP_TOKEN:-}" ]]; then
174
+ TAP_REMOTE_URL="https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/${CANONICAL_TAP_REPO}.git"
175
+ fi
176
+
177
+ if ! git clone "$TAP_REMOTE_URL" "$TAP_TMPDIR" 2>/dev/null; then
178
+ echo "FAIL: could not clone ${CANONICAL_TAP_REPO}" >&2
179
+ rm -rf "$TAP_TMPDIR"
180
+ exit 1
181
+ fi
182
+
183
+ TAP_FORMULA="${TAP_TMPDIR}/Formula/agentxchain.rb"
184
+ if [[ ! -f "$TAP_FORMULA" ]]; then
185
+ mkdir -p "${TAP_TMPDIR}/Formula"
186
+ fi
187
+
188
+ TAP_CURRENT_URL=""
189
+ TAP_CURRENT_SHA=""
190
+ if [[ -f "$TAP_FORMULA" ]]; then
191
+ TAP_CURRENT_URL="$(formula_url "$TAP_FORMULA")"
192
+ TAP_CURRENT_SHA="$(formula_sha "$TAP_FORMULA")"
193
+ fi
194
+
195
+ (
196
+ cd "$TAP_TMPDIR" || exit 1
197
+ if [[ "$TAP_CURRENT_URL" == "$TARBALL_URL" && "$TAP_CURRENT_SHA" == "$TARBALL_SHA" ]]; then
198
+ echo " Canonical tap already in sync — no push needed."
199
+ else
200
+ cp "$FORMULA_PATH" "$TAP_FORMULA"
201
+ if ! git config user.name >/dev/null; then
202
+ git config user.name "${HOMEBREW_TAP_GIT_NAME:-github-actions[bot]}"
203
+ fi
204
+ if ! git config user.email >/dev/null; then
205
+ git config user.email "${HOMEBREW_TAP_GIT_EMAIL:-github-actions[bot]@users.noreply.github.com}"
206
+ fi
207
+ git add Formula/agentxchain.rb
208
+ git commit -m "agentxchain ${TARGET_VERSION}"
209
+ if ! git push origin HEAD:main; then
210
+ echo "FAIL: could not push to ${CANONICAL_TAP_REPO}" >&2
211
+ exit 1
212
+ fi
213
+ echo " Pushed to ${CANONICAL_TAP_REPO}"
214
+ fi
215
+ )
216
+
217
+ rm -rf "$TAP_TMPDIR"
218
+ else
219
+ echo "[5/5] Skipping tap push (--push-tap not set)."
220
+ fi
221
+
222
+ echo ""
223
+ echo "====================================="
224
+ echo "SYNC COMPLETE — Homebrew formula updated to ${PACKAGE_NAME}@${TARGET_VERSION}."
225
+ exit 0
@@ -5,7 +5,7 @@ import chalk from 'chalk';
5
5
  import inquirer from 'inquirer';
6
6
  import { CONFIG_FILE, LOCK_FILE, STATE_FILE } from '../lib/config.js';
7
7
  import { generateVSCodeFiles } from '../lib/generate-vscode.js';
8
- import { loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS } from '../lib/governed-templates.js';
8
+ import { loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS, buildSystemSpecContent } from '../lib/governed-templates.js';
9
9
  import { VALID_PROMPT_TRANSPORTS } from '../lib/normalized-config.js';
10
10
 
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -128,14 +128,15 @@ const GOVERNED_ROUTING = {
128
128
 
129
129
  const GOVERNED_GATES = {
130
130
  planning_signoff: {
131
- requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md'],
131
+ requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md', '.planning/SYSTEM_SPEC.md'],
132
132
  requires_human_approval: true
133
133
  },
134
134
  implementation_complete: {
135
+ requires_files: ['.planning/IMPLEMENTATION_NOTES.md'],
135
136
  requires_verification_pass: true
136
137
  },
137
138
  qa_ship_verdict: {
138
- requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md'],
139
+ requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md', '.planning/RELEASE_NOTES.md'],
139
140
  requires_human_approval: true
140
141
  }
141
142
  };
@@ -166,6 +167,7 @@ You are the Product Manager. Your mandate: **${role.mandate}**
166
167
  2. **Challenge it.** Even if the work looks correct, identify at least one risk, scope gap, or assumption worth questioning. Rubber-stamping violates the protocol.
167
168
  3. **Create or refine planning artifacts:**
168
169
  - \`.planning/ROADMAP.md\` — what will be built, in what order, with acceptance criteria
170
+ - \`.planning/SYSTEM_SPEC.md\` — the baseline subsystem contract implementation will follow
169
171
  - \`.planning/PM_SIGNOFF.md\` — your formal sign-off when planning is complete
170
172
  - \`.planning/acceptance-matrix.md\` — the acceptance criteria checklist for QA
171
173
  4. **Propose the next role.** Typically \`dev\` after planning is complete, or \`eng_director\` if there's a technical deadlock.
@@ -175,6 +177,7 @@ You are the Product Manager. Your mandate: **${role.mandate}**
175
177
  To exit the planning phase, you must:
176
178
  - Ensure \`.planning/PM_SIGNOFF.md\` exists with your explicit sign-off
177
179
  - Ensure \`.planning/ROADMAP.md\` exists with clear acceptance criteria
180
+ - Ensure \`.planning/SYSTEM_SPEC.md\` defines \`## Purpose\`, \`## Interface\`, and \`## Acceptance Tests\`
178
181
  - Set \`phase_transition_request: "implementation"\` in your turn result
179
182
 
180
183
  The orchestrator will evaluate the gate and may require human approval.
@@ -246,6 +249,7 @@ You are QA. Your mandate: **${role.mandate}**
246
249
  4. **Create review artifacts:**
247
250
  - \`.planning/acceptance-matrix.md\` — updated with pass/fail verdicts per criterion
248
251
  - \`.planning/ship-verdict.md\` — your overall ship/no-ship recommendation
252
+ - \`.planning/RELEASE_NOTES.md\` — user-facing release notes with impact and verification summary
249
253
 
250
254
  ## You Cannot Modify Code
251
255
 
@@ -273,12 +277,14 @@ Each objection must have:
273
277
  When you are satisfied the work meets acceptance criteria:
274
278
  1. Create \`.planning/ship-verdict.md\` with your verdict
275
279
  2. Create/update \`.planning/acceptance-matrix.md\` with all criteria checked
276
- 3. Set \`run_completion_request: true\` in your turn result
280
+ 3. Create/update \`.planning/RELEASE_NOTES.md\` with \`## User Impact\` and \`## Verification Summary\`
281
+ 4. Set \`run_completion_request: true\` in your turn result
277
282
 
278
283
  **Only set \`run_completion_request: true\` when:**
279
284
  - All blocking objections from prior turns are resolved
280
285
  - The acceptance matrix shows all critical criteria passing
281
286
  - \`.planning/ship-verdict.md\` exists with an affirmative verdict
287
+ - \`.planning/RELEASE_NOTES.md\` exists with real \`## User Impact\` and \`## Verification Summary\` content
282
288
 
283
289
  **Do NOT set \`run_completion_request: true\` if:**
284
290
  - You have unresolved blocking objections
@@ -531,12 +537,15 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
531
537
  // Planning artifacts
532
538
  writeFileSync(join(dir, '.planning', 'PM_SIGNOFF.md'), `# PM Signoff — ${projectName}\n\nApproved: NO\n\n## Discovery Checklist\n- [ ] Target user defined\n- [ ] Core pain point defined\n- [ ] Core workflow defined\n- [ ] MVP scope defined\n- [ ] Out-of-scope list defined\n- [ ] Success metric defined\n\n## Notes for team\n(PM and human add final kickoff notes here.)\n`);
533
539
  writeFileSync(join(dir, '.planning', 'ROADMAP.md'), `# Roadmap — ${projectName}\n\n## Phases\n\n| Phase | Goal | Status |\n|-------|------|--------|\n| Planning | Align scope, requirements, acceptance criteria | In progress |\n| Implementation | Build and verify | Pending |\n| QA | Challenge correctness and ship readiness | Pending |\n`);
540
+ writeFileSync(join(dir, '.planning', 'SYSTEM_SPEC.md'), buildSystemSpecContent(projectName, template.system_spec_overlay));
541
+ writeFileSync(join(dir, '.planning', '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`);
534
542
  const baseAcceptanceMatrix = `# Acceptance Matrix — ${projectName}\n\n| Req # | Requirement | Acceptance criteria | Test status | Last tested | Status |\n|-------|-------------|-------------------|-------------|-------------|--------|\n| (QA fills this from ROADMAP.md) | | | | | |\n`;
535
543
  writeFileSync(
536
544
  join(dir, '.planning', 'acceptance-matrix.md'),
537
545
  appendAcceptanceHints(baseAcceptanceMatrix, template.acceptance_hints)
538
546
  );
539
547
  writeFileSync(join(dir, '.planning', 'ship-verdict.md'), `# Ship Verdict — ${projectName}\n\n## Verdict: PENDING\n\n## QA Summary\n\n(QA writes the final ship/no-ship assessment here.)\n\n## Open Blockers\n\n(List any blocking issues.)\n\n## Conditions\n\n(List any conditions for shipping.)\n`);
548
+ writeFileSync(join(dir, '.planning', '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`);
540
549
  for (const artifact of template.planning_artifacts) {
541
550
  writeFileSync(
542
551
  join(dir, '.planning', artifact.filename),
@@ -658,8 +667,9 @@ async function initGoverned(opts) {
658
667
  console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} reviews/`);
659
668
  console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} dispatch/`);
660
669
  console.log(` ${chalk.dim('├──')} .planning/`);
661
- console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} PM_SIGNOFF.md / ROADMAP.md`);
662
- console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} acceptance-matrix.md / ship-verdict.md`);
670
+ console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} PM_SIGNOFF.md / ROADMAP.md / SYSTEM_SPEC.md`);
671
+ console.log(` ${chalk.dim('│')} ${chalk.dim('├──')} acceptance-matrix.md / ship-verdict.md`);
672
+ console.log(` ${chalk.dim('│')} ${chalk.dim('└──')} RELEASE_NOTES.md`);
663
673
  console.log(` ${chalk.dim('└──')} TALK.md`);
664
674
  console.log('');
665
675
  console.log(` ${chalk.dim('Roles:')} pm, dev, qa, eng_director`);
@@ -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 = findProjectRoot(process.cwd());
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';