agentxchain 2.15.0 → 2.17.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 CHANGED
@@ -194,6 +194,8 @@ agentxchain.json
194
194
  .agentxchain/staging/<turn_id>/turn-result.json
195
195
  TALK.md
196
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.
197
199
  ```
198
200
 
199
201
  ### Runtime support today
@@ -321,6 +321,7 @@ program
321
321
  .option('--auto-approve', 'Auto-approve all gates (non-interactive mode)')
322
322
  .option('--verbose', 'Stream adapter subprocess output')
323
323
  .option('--dry-run', 'Print what would be dispatched without executing')
324
+ .option('--no-report', 'Suppress automatic governance report after run completes')
324
325
  .action(runCommand);
325
326
 
326
327
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.15.0",
3
+ "version": "2.17.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"
@@ -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
 
@@ -107,7 +107,7 @@ for example_dir in "${CLI_DIR}/../examples/mcp-echo-agent" "${CLI_DIR}/../exampl
107
107
  (cd "$example_dir" && env -u NODE_AUTH_TOKEN -u NPM_CONFIG_USERCONFIG npm install --ignore-scripts --userconfig /dev/null 2>&1) || true
108
108
  fi
109
109
  done
110
- if run_and_capture TEST_OUTPUT npm test; then
110
+ if run_and_capture TEST_OUTPUT env AGENTXCHAIN_RELEASE_TARGET_VERSION="${TARGET_VERSION}" AGENTXCHAIN_RELEASE_PREFLIGHT=1 npm test; then
111
111
  TEST_STATUS=0
112
112
  else
113
113
  TEST_STATUS=$?
@@ -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`);
@@ -165,14 +165,15 @@ export async function migrateCommand(opts) {
165
165
  routing,
166
166
  gates: {
167
167
  planning_signoff: {
168
- requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md'],
168
+ requires_files: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md', '.planning/SYSTEM_SPEC.md'],
169
169
  requires_human_approval: true
170
170
  },
171
171
  implementation_complete: {
172
+ requires_files: ['.planning/IMPLEMENTATION_NOTES.md'],
172
173
  requires_verification_pass: true
173
174
  },
174
175
  qa_ship_verdict: {
175
- requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md'],
176
+ requires_files: ['.planning/acceptance-matrix.md', '.planning/ship-verdict.md', '.planning/RELEASE_NOTES.md'],
176
177
  requires_human_approval: true
177
178
  }
178
179
  },
@@ -311,8 +312,11 @@ ${report.requires_human_review.map((r, i) => `${i + 1}. ${r}`).join('\n')}
311
312
  const planningFiles = {
312
313
  'PM_SIGNOFF.md': `# PM Signoff — ${projectName}\n\nApproved: NO\n`,
313
314
  'ROADMAP.md': `# Roadmap — ${projectName}\n\n(Migrated from v3. Review and update.)\n`,
315
+ 'SYSTEM_SPEC.md': `# System Spec — ${projectName}\n\n## Purpose\n\n(Describe the migrated subsystem purpose.)\n\n## Interface\n\n(List the commands, files, APIs, or contracts this repo owns.)\n\n## Behavior\n\n(Describe the expected runtime behavior.)\n\n## Error Cases\n\n(List the important failure modes.)\n\n## Acceptance Tests\n\n- [ ] Name the executable checks required before implementation resumes.\n\n## Open Questions\n\n- (Capture migration-specific gaps here.)\n`,
316
+ 'IMPLEMENTATION_NOTES.md': `# Implementation Notes — ${projectName}\n\n## Changes\n\n(Dev fills this during implementation)\n\n## Verification\n\n(Dev fills this during implementation)\n\n## Unresolved Follow-ups\n\n(Dev lists any known gaps, tech debt, or follow-up items here.)\n`,
314
317
  'acceptance-matrix.md': `# Acceptance Matrix — ${projectName}\n\n(QA fills this.)\n`,
315
- 'ship-verdict.md': `# Ship Verdict — ${projectName}\n\n## Verdict: PENDING\n`
318
+ 'ship-verdict.md': `# Ship Verdict — ${projectName}\n\n## Verdict: PENDING\n`,
319
+ 'RELEASE_NOTES.md': `# Release Notes — ${projectName}\n\n## User Impact\n\n(QA fills this during the QA phase)\n\n## Verification Summary\n\n(QA fills this during the QA phase)\n\n## Upgrade Notes\n\n(QA fills this during the QA phase)\n\n## Known Issues\n\n(QA fills this during the QA phase)\n`
316
320
  };
317
321
  for (const [file, content] of Object.entries(planningFiles)) {
318
322
  const path = join(root, '.planning', file);
@@ -40,6 +40,7 @@ import {
40
40
  buildEscalationPayload,
41
41
  } from '../lib/coordinator-hooks.js';
42
42
  import { computeContextInvalidations } from '../lib/cross-repo-context.js';
43
+ import { scaffoldRecoveryReport } from '../lib/workflow-gate-semantics.js';
43
44
 
44
45
  // ── multi init ─────────────────────────────────────────────────────────────
45
46
 
@@ -601,5 +602,6 @@ function blockCoordinator(workspacePath, state, blockedReason) {
601
602
  blocked_reason: blockedReason,
602
603
  };
603
604
  saveCoordinatorState(workspacePath, blockedState);
605
+ scaffoldRecoveryReport(workspacePath, blockedReason);
604
606
  return blockedState;
605
607
  }
@@ -14,10 +14,12 @@
14
14
 
15
15
  import chalk from 'chalk';
16
16
  import { createInterface } from 'readline';
17
- import { readFileSync, existsSync } from 'fs';
17
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
18
18
  import { join } from 'path';
19
19
  import { loadProjectContext, loadProjectState } from '../lib/config.js';
20
20
  import { runLoop } from '../lib/run-loop.js';
21
+ import { buildRunExport } from '../lib/export.js';
22
+ import { buildGovernanceReport, formatGovernanceReportMarkdown } from '../lib/report.js';
21
23
  import { dispatchApiProxy } from '../lib/adapters/api-proxy-adapter.js';
22
24
  import {
23
25
  dispatchLocalCli,
@@ -328,6 +330,32 @@ export async function runCommand(opts) {
328
330
  }
329
331
  }
330
332
 
333
+ // ── Auto governance report ──────────────────────────────────────────────
334
+ if (opts.report !== false && result.state) {
335
+ try {
336
+ const reportsDir = join(root, '.agentxchain', 'reports');
337
+ mkdirSync(reportsDir, { recursive: true });
338
+
339
+ const exportResult = buildRunExport(root);
340
+ if (exportResult.ok) {
341
+ const runId = result.state.run_id || 'unknown';
342
+ const exportPath = join(reportsDir, `export-${runId}.json`);
343
+ writeFileSync(exportPath, JSON.stringify(exportResult.export, null, 2));
344
+
345
+ const reportResult = buildGovernanceReport(exportResult.export, { input: exportPath });
346
+ const reportPath = join(reportsDir, `report-${runId}.md`);
347
+ writeFileSync(reportPath, formatGovernanceReportMarkdown(reportResult.report));
348
+
349
+ console.log('');
350
+ console.log(chalk.dim(` Governance report: .agentxchain/reports/report-${runId}.md`));
351
+ } else {
352
+ console.log(chalk.dim(` Governance report skipped: ${exportResult.error}`));
353
+ }
354
+ } catch (err) {
355
+ console.log(chalk.dim(` Governance report failed: ${err.message}`));
356
+ }
357
+ }
358
+
331
359
  // ── Exit code ───────────────────────────────────────────────────────────
332
360
  const successReasons = new Set(['completed', 'gate_held', 'caller_stopped', 'max_turns_reached']);
333
361
  if (result.ok || successReasons.has(result.stop_reason)) {
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import chalk from 'chalk';
4
4
  import { CONFIG_FILE } from '../lib/config.js';
5
- import { loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS } from '../lib/governed-templates.js';
5
+ import { loadGovernedTemplate, VALID_GOVERNED_TEMPLATE_IDS, SYSTEM_SPEC_OVERLAY_SEPARATOR } from '../lib/governed-templates.js';
6
6
 
7
7
  const LEDGER_PATH = '.agentxchain/decision-ledger.jsonl';
8
8
  const PROMPT_OVERRIDE_SEPARATOR = '## Project-Type-Specific Guidance';
@@ -76,6 +76,7 @@ export async function templateSetCommand(templateId, opts) {
76
76
  prompts_missing_paths: [],
77
77
  prompts_missing_files: [],
78
78
  acceptance_hints_status: 'none',
79
+ system_spec_overlay_status: 'none',
79
80
  };
80
81
 
81
82
  // Planning artifacts
@@ -127,6 +128,22 @@ export async function templateSetCommand(templateId, opts) {
127
128
  }
128
129
  }
129
130
 
131
+ // System spec overlay
132
+ const systemSpecOverlay = manifest.system_spec_overlay;
133
+ const systemSpecPath = join(root, '.planning', 'SYSTEM_SPEC.md');
134
+ if (systemSpecOverlay && Object.keys(systemSpecOverlay).length > 0) {
135
+ if (!existsSync(systemSpecPath)) {
136
+ plan.system_spec_overlay_status = 'missing_file';
137
+ } else {
138
+ const specContent = readFileSync(systemSpecPath, 'utf8');
139
+ if (specContent.includes(SYSTEM_SPEC_OVERLAY_SEPARATOR)) {
140
+ plan.system_spec_overlay_status = 'existing_guidance';
141
+ } else {
142
+ plan.system_spec_overlay_status = 'append';
143
+ }
144
+ }
145
+ }
146
+
130
147
  // ── Dry run: print plan and exit ──────────────────────────────────────
131
148
  if (opts.dryRun) {
132
149
  console.log(chalk.bold(`\n Template: ${previousTemplate} → ${templateId}\n`));
@@ -175,6 +192,16 @@ export async function templateSetCommand(templateId, opts) {
175
192
  } else {
176
193
  console.log(` ${chalk.dim('(none)')}`);
177
194
  }
195
+ console.log('\n System spec overlay:');
196
+ if (plan.system_spec_overlay_status === 'append') {
197
+ console.log(` .planning/SYSTEM_SPEC.md: ${chalk.green('WILL APPEND template guidance')}`);
198
+ } else if (plan.system_spec_overlay_status === 'existing_guidance') {
199
+ console.log(` .planning/SYSTEM_SPEC.md: ${chalk.dim('ALREADY HAS guidance (skip)')}`);
200
+ } else if (plan.system_spec_overlay_status === 'missing_file') {
201
+ console.log(` .planning/SYSTEM_SPEC.md: ${chalk.yellow('MISSING FILE (skip)')}`);
202
+ } else {
203
+ console.log(` ${chalk.dim('(none)')}`);
204
+ }
178
205
  console.log(chalk.dim('\n No changes written. Use without --dry-run to apply.\n'));
179
206
  process.exit(0);
180
207
  }
@@ -242,7 +269,24 @@ export async function templateSetCommand(templateId, opts) {
242
269
  console.log(chalk.yellow(' Warning: .planning/acceptance-matrix.md not found. Skipping template guidance hints.'));
243
270
  }
244
271
 
245
- // 5. Decision ledger
272
+ // 5. Append system spec overlay
273
+ if (plan.system_spec_overlay_status === 'append' && existsSync(systemSpecPath)) {
274
+ const specContent = readFileSync(systemSpecPath, 'utf8');
275
+ const guidanceLines = [];
276
+ if (systemSpecOverlay.purpose_guidance) guidanceLines.push(`**Purpose:** ${systemSpecOverlay.purpose_guidance}`);
277
+ if (systemSpecOverlay.interface_guidance) guidanceLines.push(`**Interface:** ${systemSpecOverlay.interface_guidance}`);
278
+ if (systemSpecOverlay.behavior_guidance) guidanceLines.push(`**Behavior:** ${systemSpecOverlay.behavior_guidance}`);
279
+ if (systemSpecOverlay.error_cases_guidance) guidanceLines.push(`**Error Cases:** ${systemSpecOverlay.error_cases_guidance}`);
280
+ if (systemSpecOverlay.acceptance_tests_guidance) guidanceLines.push(`**Acceptance Tests:**\n${systemSpecOverlay.acceptance_tests_guidance}`);
281
+ if (systemSpecOverlay.extra_sections) guidanceLines.push(systemSpecOverlay.extra_sections);
282
+ const guidanceBlock = guidanceLines.join('\n\n');
283
+ const appended = `${specContent}\n\n${SYSTEM_SPEC_OVERLAY_SEPARATOR}\n\n${guidanceBlock}\n`;
284
+ writeFileSync(systemSpecPath, appended);
285
+ } else if (plan.system_spec_overlay_status === 'missing_file') {
286
+ console.log(chalk.yellow(' Warning: .planning/SYSTEM_SPEC.md not found. Skipping template spec overlay.'));
287
+ }
288
+
289
+ // 6. Decision ledger
246
290
  const ledgerEntry = {
247
291
  type: 'template_set',
248
292
  timestamp: new Date().toISOString(),
@@ -260,6 +304,8 @@ export async function templateSetCommand(templateId, opts) {
260
304
  prompt_missing_files: plan.prompts_missing_files,
261
305
  acceptance_hints_appended: plan.acceptance_hints_status === 'append',
262
306
  acceptance_hints_skipped_reason: plan.acceptance_hints_status === 'append' ? null : plan.acceptance_hints_status,
307
+ system_spec_overlay_appended: plan.system_spec_overlay_status === 'append',
308
+ system_spec_overlay_skipped_reason: plan.system_spec_overlay_status === 'append' ? null : plan.system_spec_overlay_status,
263
309
  operator: 'human',
264
310
  };
265
311
  appendJsonl(root, LEDGER_PATH, ledgerEntry);
@@ -275,5 +321,8 @@ export async function templateSetCommand(templateId, opts) {
275
321
  if (plan.acceptance_hints_status === 'append') {
276
322
  console.log(` Appended template guidance to acceptance-matrix.md`);
277
323
  }
324
+ if (plan.system_spec_overlay_status === 'append') {
325
+ console.log(` Appended template-specific guidance to SYSTEM_SPEC.md`);
326
+ }
278
327
  console.log('');
279
328
  }