agentxchain 2.34.2 → 2.36.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/dashboard/app.js CHANGED
@@ -16,7 +16,7 @@ import { render as renderBlockers } from './components/blockers.js';
16
16
  import { render as renderArtifacts } from './components/artifacts.js';
17
17
 
18
18
  const VIEWS = {
19
- timeline: { fetch: ['state', 'history', 'audit', 'annotations'], render: renderTimeline },
19
+ timeline: { fetch: ['state', 'continuity', 'history', 'audit', 'annotations'], render: renderTimeline },
20
20
  ledger: { fetch: ['ledger'], render: renderLedger },
21
21
  hooks: { fetch: ['audit', 'annotations'], render: renderHooks },
22
22
  blocked: { fetch: ['state', 'audit', 'coordinatorState', 'coordinatorAudit'], render: renderBlocked },
@@ -29,6 +29,7 @@ const VIEWS = {
29
29
 
30
30
  const API_MAP = {
31
31
  state: '/api/state',
32
+ continuity: '/api/continuity',
32
33
  history: '/api/history',
33
34
  ledger: '/api/ledger',
34
35
  audit: '/api/hooks/audit',
@@ -132,7 +132,41 @@ function renderTurnDetailPanel(turnId, annotations, audit) {
132
132
  return html;
133
133
  }
134
134
 
135
- export function render({ state, history, annotations, audit }) {
135
+ function renderContinuityPanel(continuity) {
136
+ if (!continuity) return '';
137
+
138
+ const checkpoint = continuity.checkpoint;
139
+ const checkpointSummary = checkpoint?.last_checkpoint_at
140
+ ? `${checkpoint.checkpoint_reason || 'unknown'} at ${checkpoint.last_checkpoint_at}`
141
+ : (checkpoint?.checkpoint_reason || 'No session checkpoint recorded');
142
+
143
+ let html = `<div class="section continuity-section"><h3>Continuity</h3><div class="turn-card">`;
144
+
145
+ if (checkpoint) {
146
+ html += `<div class="turn-detail"><span class="detail-label">Session:</span> <span class="mono">${esc(checkpoint.session_id || 'unknown')}</span></div>`;
147
+ html += `<div class="turn-detail"><span class="detail-label">Checkpoint:</span> ${esc(checkpointSummary)}</div>`;
148
+ html += `<div class="turn-detail"><span class="detail-label">Last turn:</span> <span class="mono">${esc(checkpoint.last_turn_id || 'none')}</span></div>`;
149
+ html += `<div class="turn-detail"><span class="detail-label">Last role:</span> ${esc(checkpoint.last_role || 'unknown')}</div>`;
150
+ if (continuity.stale_checkpoint) {
151
+ html += `<div class="turn-detail risks"><span class="detail-label">Warning:</span> checkpoint tracks <span class="mono">${esc(checkpoint.run_id || 'unknown')}</span>, but state.json remains source of truth.</div>`;
152
+ }
153
+ } else {
154
+ html += `<div class="turn-detail"><span class="detail-label">Checkpoint:</span> No session checkpoint recorded</div>`;
155
+ }
156
+
157
+ if (continuity.restart_recommended) {
158
+ html += `<div class="turn-detail"><span class="detail-label">Restart:</span> <span class="mono">agentxchain restart</span></div>`;
159
+ }
160
+
161
+ if (continuity.recovery_report_path) {
162
+ html += `<div class="turn-detail"><span class="detail-label">Report:</span> <span class="mono">${esc(continuity.recovery_report_path)}</span></div>`;
163
+ }
164
+
165
+ html += `</div></div>`;
166
+ return html;
167
+ }
168
+
169
+ export function render({ state, continuity, history, annotations, audit }) {
136
170
  if (!state) {
137
171
  return `<div class="placeholder"><h2>No Run</h2><p>No governed run found. Start one with <code class="mono">agentxchain init --governed</code></p></div>`;
138
172
  }
@@ -152,6 +186,8 @@ export function render({ state, history, annotations, audit }) {
152
186
  </div>
153
187
  </div>`;
154
188
 
189
+ html += renderContinuityPanel(continuity);
190
+
155
191
  // Active turns
156
192
  if (activeTurns.length > 0) {
157
193
  html += `<div class="section"><h3>Active Turns</h3><div class="turn-list">`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.34.2",
3
+ "version": "2.36.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -56,6 +56,8 @@ ALLOWED_RELEASE_PATHS=(
56
56
  ".agentxchain-conformance/capabilities.json"
57
57
  "website-v2/docs/protocol-implementor-guide.mdx"
58
58
  ".planning/LAUNCH_EVIDENCE_REPORT.md"
59
+ "cli/homebrew/agentxchain.rb"
60
+ "cli/homebrew/README.md"
59
61
  )
60
62
 
61
63
  is_allowed_release_path() {
@@ -81,7 +83,7 @@ stage_if_present() {
81
83
  }
82
84
 
83
85
  # 1. Assert only allowed release-surface dirt is present
84
- echo "[1/7] Checking release-prep tree state..."
86
+ echo "[1/8] Checking release-prep tree state..."
85
87
  DISALLOWED_DIRTY=()
86
88
  while IFS= read -r status_line; do
87
89
  [[ -z "$status_line" ]] && continue
@@ -99,7 +101,7 @@ fi
99
101
  echo " OK: tree contains only allowed release-prep changes"
100
102
 
101
103
  # 2. Assert not already at target version
102
- echo "[2/7] Checking current version..."
104
+ echo "[2/8] Checking current version..."
103
105
  CURRENT_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).version)")
104
106
  if [[ "$CURRENT_VERSION" == "$TARGET_VERSION" ]]; then
105
107
  echo "FAIL: package.json is already at ${TARGET_VERSION}. Cannot double-bump." >&2
@@ -108,20 +110,96 @@ fi
108
110
  echo " OK: current version is ${CURRENT_VERSION}, bumping to ${TARGET_VERSION}"
109
111
 
110
112
  # 3. Assert tag does not already exist
111
- echo "[3/7] Checking for existing tag..."
113
+ echo "[3/8] Checking for existing tag..."
112
114
  if git rev-parse "v${TARGET_VERSION}" >/dev/null 2>&1; then
113
115
  echo "FAIL: tag v${TARGET_VERSION} already exists. Delete it first or choose a different version." >&2
114
116
  exit 1
115
117
  fi
116
118
  echo " OK: tag v${TARGET_VERSION} does not exist"
117
119
 
118
- # 4. Update version files (no git operations)
119
- echo "[4/7] Updating version files..."
120
+ # 4. Pre-bump version-surface alignment guard
121
+ # Ensures all governed version surfaces already reference the target version
122
+ # BEFORE the bump commit is created. This catches stale drift that would
123
+ # otherwise only be discovered after minting local release identities.
124
+ echo "[4/8] Verifying version-surface alignment for ${TARGET_VERSION}..."
125
+ SURFACE_ERRORS=()
126
+
127
+ # 4a. CHANGELOG top heading
128
+ CHANGELOG_TOP=$(grep -m1 -E '^## [0-9]+\.[0-9]+\.[0-9]+$' "${REPO_ROOT}/cli/CHANGELOG.md" 2>/dev/null | sed 's/^## //' || true)
129
+ if [[ "$CHANGELOG_TOP" != "$TARGET_VERSION" ]]; then
130
+ SURFACE_ERRORS+=("CHANGELOG.md top heading is '${CHANGELOG_TOP:-missing}', expected '${TARGET_VERSION}'")
131
+ fi
132
+
133
+ # 4b. Release notes page exists
134
+ RELEASE_DOC_ID="v${TARGET_VERSION//./-}"
135
+ RELEASE_DOC_PATH="website-v2/docs/releases/${RELEASE_DOC_ID}.mdx"
136
+ if [[ ! -f "${REPO_ROOT}/${RELEASE_DOC_PATH}" ]]; then
137
+ SURFACE_ERRORS+=("release notes page missing: ${RELEASE_DOC_PATH}")
138
+ fi
139
+
140
+ # 4c. Docs sidebar links the release page
141
+ if ! grep -q "'releases/${RELEASE_DOC_ID}'" "${REPO_ROOT}/website-v2/sidebars.ts" 2>/dev/null; then
142
+ SURFACE_ERRORS+=("sidebars.ts does not link 'releases/${RELEASE_DOC_ID}'")
143
+ fi
144
+
145
+ # 4d. Homepage hero badge shows target version
146
+ if ! grep -q "v${TARGET_VERSION}" "${REPO_ROOT}/website-v2/src/pages/index.tsx" 2>/dev/null; then
147
+ SURFACE_ERRORS+=("homepage index.tsx does not contain 'v${TARGET_VERSION}'")
148
+ fi
149
+
150
+ # 4e. Conformance capabilities version
151
+ CAPS_VERSION=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('${REPO_ROOT}/.agentxchain-conformance/capabilities.json','utf8')).version)}catch{console.log('missing')}" 2>/dev/null || echo "missing")
152
+ if [[ "$CAPS_VERSION" != "$TARGET_VERSION" ]]; then
153
+ SURFACE_ERRORS+=("capabilities.json version is '${CAPS_VERSION}', expected '${TARGET_VERSION}'")
154
+ fi
155
+
156
+ # 4f. Protocol implementor guide example
157
+ if ! grep -q "\"version\": \"${TARGET_VERSION}\"" "${REPO_ROOT}/website-v2/docs/protocol-implementor-guide.mdx" 2>/dev/null; then
158
+ SURFACE_ERRORS+=("protocol-implementor-guide.mdx does not contain '\"version\": \"${TARGET_VERSION}\"'")
159
+ fi
160
+
161
+ # 4g. Launch evidence report title
162
+ ESCAPED_VERSION="${TARGET_VERSION//./\\.}"
163
+ if ! grep -qE "^# Launch Evidence Report — AgentXchain v${ESCAPED_VERSION}" "${REPO_ROOT}/.planning/LAUNCH_EVIDENCE_REPORT.md" 2>/dev/null; then
164
+ SURFACE_ERRORS+=("LAUNCH_EVIDENCE_REPORT.md title does not carry v${TARGET_VERSION}")
165
+ fi
166
+
167
+ # 4h. Homebrew mirror formula version
168
+ HOMEBREW_MIRROR="${REPO_ROOT}/cli/homebrew/agentxchain.rb"
169
+ if [[ -f "$HOMEBREW_MIRROR" ]]; then
170
+ if ! grep -q "agentxchain-${TARGET_VERSION}\.tgz" "$HOMEBREW_MIRROR" 2>/dev/null; then
171
+ SURFACE_ERRORS+=("homebrew mirror formula does not reference agentxchain-${TARGET_VERSION}.tgz")
172
+ fi
173
+ fi
174
+
175
+ # 4i. Homebrew mirror maintainer README version
176
+ HOMEBREW_MIRROR_README="${REPO_ROOT}/cli/homebrew/README.md"
177
+ if [[ -f "$HOMEBREW_MIRROR_README" ]]; then
178
+ if ! grep -q -- "- version: \`${TARGET_VERSION}\`" "$HOMEBREW_MIRROR_README" 2>/dev/null; then
179
+ SURFACE_ERRORS+=("homebrew mirror README does not declare version ${TARGET_VERSION}")
180
+ fi
181
+ if ! grep -q "agentxchain-${TARGET_VERSION}\.tgz" "$HOMEBREW_MIRROR_README" 2>/dev/null; then
182
+ SURFACE_ERRORS+=("homebrew mirror README does not reference agentxchain-${TARGET_VERSION}.tgz")
183
+ fi
184
+ fi
185
+
186
+ if [[ "${#SURFACE_ERRORS[@]}" -gt 0 ]]; then
187
+ echo "FAIL: ${#SURFACE_ERRORS[@]} version-surface(s) not aligned to ${TARGET_VERSION}:" >&2
188
+ printf ' - %s\n' "${SURFACE_ERRORS[@]}" >&2
189
+ echo "" >&2
190
+ echo "Fix these surfaces before running release-bump. The bump script refuses to" >&2
191
+ echo "create release identity when governed surfaces are stale." >&2
192
+ exit 1
193
+ fi
194
+ echo " OK: all 10 governed version surfaces reference ${TARGET_VERSION}"
195
+
196
+ # 5. Update version files (no git operations)
197
+ echo "[5/8] Updating version files..."
120
198
  npm version "$TARGET_VERSION" --no-git-tag-version
121
199
  echo " OK: package.json updated to ${TARGET_VERSION}"
122
200
 
123
- # 5. Stage version files
124
- echo "[5/7] Staging version files..."
201
+ # 6. Stage version files
202
+ echo "[6/8] Staging version files..."
125
203
  git add -- package.json
126
204
  if [[ -f package-lock.json ]]; then
127
205
  git add -- package-lock.json
@@ -131,8 +209,8 @@ for rel_path in "${ALLOWED_RELEASE_PATHS[@]}"; do
131
209
  done
132
210
  echo " OK: version files and allowed release surfaces staged"
133
211
 
134
- # 6. Create release commit
135
- echo "[6/7] Creating release commit..."
212
+ # 7. Create release commit
213
+ echo "[7/8] Creating release commit..."
136
214
  git commit -m "${TARGET_VERSION}"
137
215
  RELEASE_SHA=$(git rev-parse HEAD)
138
216
  COMMIT_MSG=$(git log -1 --format=%s)
@@ -142,8 +220,8 @@ if [[ "$COMMIT_MSG" != "$TARGET_VERSION" ]]; then
142
220
  fi
143
221
  echo " OK: commit ${RELEASE_SHA:0:7} with message '${TARGET_VERSION}'"
144
222
 
145
- # 7. Create annotated tag
146
- echo "[7/7] Creating annotated tag..."
223
+ # 8. Create annotated tag
224
+ echo "[8/8] Creating annotated tag..."
147
225
  git tag -a "v${TARGET_VERSION}" -m "v${TARGET_VERSION}"
148
226
  TAG_SHA=$(git rev-parse "v${TARGET_VERSION}")
149
227
  if [[ -z "$TAG_SHA" ]]; then
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import { loadConfig, loadLock, loadProjectContext, loadProjectState, loadState } from '../lib/config.js';
3
3
  import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
4
4
  import { getActiveTurn, getActiveTurnCount, getActiveTurns } from '../lib/governed-state.js';
5
+ import { getContinuityStatus } from '../lib/continuity-status.js';
5
6
 
6
7
  export async function statusCommand(opts) {
7
8
  const context = loadProjectContext();
@@ -74,6 +75,7 @@ export async function statusCommand(opts) {
74
75
  function renderGovernedStatus(context, opts) {
75
76
  const { root, config, version } = context;
76
77
  const state = loadProjectState(root, config);
78
+ const continuity = getContinuityStatus(root, state);
77
79
 
78
80
  if (opts.json) {
79
81
  console.log(JSON.stringify({
@@ -82,6 +84,7 @@ function renderGovernedStatus(context, opts) {
82
84
  template: config.template || 'generic',
83
85
  config,
84
86
  state,
87
+ continuity,
85
88
  }, null, 2));
86
89
  return;
87
90
  }
@@ -101,6 +104,8 @@ function renderGovernedStatus(context, opts) {
101
104
  }
102
105
  console.log('');
103
106
 
107
+ renderContinuityStatus(continuity, state);
108
+
104
109
  const activeTurnCount = getActiveTurnCount(state);
105
110
  const activeTurns = getActiveTurns(state);
106
111
  const singleActiveTurn = getActiveTurn(state);
@@ -238,6 +243,42 @@ function renderGovernedStatus(context, opts) {
238
243
  console.log('');
239
244
  }
240
245
 
246
+ function renderContinuityStatus(continuity, state) {
247
+ if (!continuity) return;
248
+
249
+ console.log(` ${chalk.dim('Continuity:')}`);
250
+
251
+ if (continuity.checkpoint) {
252
+ const checkpoint = continuity.checkpoint;
253
+ const checkpointSummary = checkpoint.last_checkpoint_at
254
+ ? `${checkpoint.checkpoint_reason || 'unknown'} at ${checkpoint.last_checkpoint_at}`
255
+ : (checkpoint.checkpoint_reason || 'unknown');
256
+ console.log(` ${chalk.dim('Session:')} ${checkpoint.session_id || chalk.dim('unknown')}`);
257
+ console.log(` ${chalk.dim('Checkpoint:')} ${checkpointSummary}`);
258
+ console.log(` ${chalk.dim('Last turn:')} ${checkpoint.last_turn_id || chalk.dim('none')}`);
259
+ console.log(` ${chalk.dim('Last role:')} ${checkpoint.last_role || chalk.dim('unknown')}`);
260
+ if (continuity.stale_checkpoint) {
261
+ console.log(
262
+ ` ${chalk.dim('Warning:')} ${chalk.yellow(
263
+ `session checkpoint tracks ${checkpoint.run_id}, but state.json tracks ${state?.run_id || 'unknown'}; state.json remains source of truth`
264
+ )}`
265
+ );
266
+ }
267
+ } else {
268
+ console.log(` ${chalk.dim('Checkpoint:')} ${chalk.yellow('No session checkpoint recorded')}`);
269
+ }
270
+
271
+ if (continuity.restart_recommended) {
272
+ console.log(` ${chalk.dim('Restart:')} ${chalk.cyan('agentxchain restart')} (rebuild session context from disk)`);
273
+ }
274
+
275
+ if (continuity.recovery_report_path) {
276
+ console.log(` ${chalk.dim('Report:')} ${continuity.recovery_report_path}`);
277
+ }
278
+
279
+ console.log('');
280
+ }
281
+
241
282
  function formatPhase(phase) {
242
283
  const colors = { discovery: chalk.blue, build: chalk.green, qa: chalk.yellow, deploy: chalk.magenta, blocked: chalk.red };
243
284
  return (colors[phase] || chalk.white)(phase);
@@ -0,0 +1,27 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { readSessionCheckpoint } from './session-checkpoint.js';
4
+
5
+ export const SESSION_RECOVERY_PATH = '.agentxchain/SESSION_RECOVERY.md';
6
+
7
+ export function getContinuityStatus(root, state) {
8
+ const checkpoint = readSessionCheckpoint(root);
9
+ const recoveryReportPath = existsSync(join(root, SESSION_RECOVERY_PATH))
10
+ ? SESSION_RECOVERY_PATH
11
+ : null;
12
+
13
+ if (!checkpoint && !recoveryReportPath) return null;
14
+
15
+ const staleCheckpoint = !!(
16
+ checkpoint?.run_id
17
+ && state?.run_id
18
+ && checkpoint.run_id !== state.run_id
19
+ );
20
+
21
+ return {
22
+ checkpoint,
23
+ stale_checkpoint: staleCheckpoint,
24
+ recovery_report_path: recoveryReportPath,
25
+ restart_recommended: !!state && !['blocked', 'completed', 'failed'].includes(state.status),
26
+ };
27
+ }
@@ -7,9 +7,12 @@
7
7
  */
8
8
 
9
9
  import { readFileSync, existsSync } from 'fs';
10
- import { join, normalize } from 'path';
10
+ import { join, normalize, resolve } from 'path';
11
+ import { getContinuityStatus } from '../continuity-status.js';
11
12
 
12
13
  const STATE_FILE = 'state.json';
14
+ const SESSION_FILE = 'session.json';
15
+ const SESSION_RECOVERY_FILE = 'SESSION_RECOVERY.md';
13
16
  const HISTORY_FILE = 'history.jsonl';
14
17
  const LEDGER_FILE = 'decision-ledger.jsonl';
15
18
  const HOOK_AUDIT_FILE = 'hook-audit.jsonl';
@@ -23,6 +26,7 @@ const BARRIER_LEDGER_FILE = 'barrier-ledger.jsonl';
23
26
  */
24
27
  export const RESOURCE_MAP = {
25
28
  '/api/state': STATE_FILE,
29
+ '/api/continuity': SESSION_FILE,
26
30
  '/api/history': HISTORY_FILE,
27
31
  '/api/ledger': LEDGER_FILE,
28
32
  '/api/hooks/audit': HOOK_AUDIT_FILE,
@@ -42,6 +46,7 @@ export const RESOURCE_MAP = {
42
46
  export const FILE_TO_RESOURCE = Object.fromEntries(
43
47
  Object.entries(RESOURCE_MAP).map(([resource, file]) => [normalizeRelativePath(file), resource])
44
48
  );
49
+ FILE_TO_RESOURCE[normalizeRelativePath(SESSION_RECOVERY_FILE)] = '/api/continuity';
45
50
 
46
51
  export const WATCH_DIRECTORIES = [
47
52
  '',
@@ -84,6 +89,13 @@ export function readJsonlFile(agentxchainDir, filename) {
84
89
  * Read a resource by its API path. Returns { data, format } or null.
85
90
  */
86
91
  export function readResource(agentxchainDir, resourcePath) {
92
+ if (resourcePath === '/api/continuity') {
93
+ const root = resolve(agentxchainDir, '..');
94
+ const state = readJsonFile(agentxchainDir, STATE_FILE);
95
+ const data = getContinuityStatus(root, state);
96
+ return { data, format: 'json' };
97
+ }
98
+
87
99
  const filename = RESOURCE_MAP[resourcePath];
88
100
  if (!filename) return null;
89
101
 
package/src/lib/export.js CHANGED
@@ -23,6 +23,7 @@ export const RUN_EXPORT_INCLUDED_ROOTS = [
23
23
  'agentxchain.json',
24
24
  'TALK.md',
25
25
  '.agentxchain/state.json',
26
+ '.agentxchain/session.json',
26
27
  '.agentxchain/history.jsonl',
27
28
  '.agentxchain/decision-ledger.jsonl',
28
29
  '.agentxchain/hook-audit.jsonl',
package/src/lib/report.js CHANGED
@@ -503,6 +503,30 @@ export function extractWorkflowKitArtifacts(artifact) {
503
503
  .sort((a, b) => a.path.localeCompare(b.path, 'en'));
504
504
  }
505
505
 
506
+ function extractContinuityMetadata(artifact) {
507
+ const checkpoint = extractFileData(artifact, '.agentxchain/session.json');
508
+ if (!checkpoint || typeof checkpoint !== 'object') return null;
509
+
510
+ const runId = artifact.summary?.run_id || artifact.state?.run_id || null;
511
+ const staleCheckpoint = !!(
512
+ checkpoint.run_id
513
+ && runId
514
+ && checkpoint.run_id !== runId
515
+ );
516
+
517
+ return {
518
+ session_id: checkpoint.session_id || null,
519
+ run_id: checkpoint.run_id || null,
520
+ started_at: checkpoint.started_at || null,
521
+ last_checkpoint_at: checkpoint.last_checkpoint_at || null,
522
+ last_turn_id: checkpoint.last_turn_id || null,
523
+ last_phase: checkpoint.last_phase || null,
524
+ last_role: checkpoint.last_role || null,
525
+ checkpoint_reason: checkpoint.checkpoint_reason || null,
526
+ stale_checkpoint: staleCheckpoint,
527
+ };
528
+ }
529
+
506
530
  function buildRunSubject(artifact) {
507
531
  const activeTurns = artifact.summary?.active_turn_ids || [];
508
532
  const retainedTurns = artifact.summary?.retained_turn_ids || [];
@@ -519,6 +543,7 @@ function buildRunSubject(artifact) {
519
543
  const gateSummary = extractGateSummary(artifact);
520
544
  const intakeLinks = extractIntakeLinks(artifact);
521
545
  const recoverySummary = extractRecoverySummary(artifact);
546
+ const continuity = extractContinuityMetadata(artifact);
522
547
 
523
548
  return {
524
549
  kind: 'governed_run',
@@ -550,6 +575,7 @@ function buildRunSubject(artifact) {
550
575
  gate_summary: gateSummary,
551
576
  intake_links: intakeLinks,
552
577
  recovery_summary: recoverySummary,
578
+ continuity,
553
579
  workflow_kit_artifacts: extractWorkflowKitArtifacts(artifact),
554
580
  },
555
581
  artifacts: {
@@ -620,6 +646,7 @@ function buildCoordinatorSubject(artifact) {
620
646
  base.hook_summary = extractHookSummary(childExport);
621
647
  base.gate_summary = extractGateSummary(childExport);
622
648
  base.recovery_summary = extractRecoverySummary(childExport);
649
+ base.continuity = extractContinuityMetadata(childExport);
623
650
  base.blocked_on = childExport.state?.blocked_on || null;
624
651
  return base;
625
652
  });
@@ -843,6 +870,18 @@ export function formatGovernanceReportText(report) {
843
870
  lines.push(` Turn retained: ${run.recovery_summary.turn_retained == null ? 'n/a' : yesNo(run.recovery_summary.turn_retained)}`);
844
871
  }
845
872
 
873
+ if (run.continuity) {
874
+ lines.push('', 'Continuity:');
875
+ lines.push(` Session: ${run.continuity.session_id || 'unknown'}`);
876
+ lines.push(` Checkpoint: ${run.continuity.checkpoint_reason || 'unknown'} at ${run.continuity.last_checkpoint_at || 'n/a'}`);
877
+ lines.push(` Last turn: ${run.continuity.last_turn_id || 'none'}`);
878
+ lines.push(` Last role: ${run.continuity.last_role || 'unknown'}`);
879
+ lines.push(` Last phase: ${run.continuity.last_phase || 'unknown'}`);
880
+ if (run.continuity.stale_checkpoint) {
881
+ lines.push(` WARNING: checkpoint tracks run ${run.continuity.run_id}, but export tracks ${run.run_id}`);
882
+ }
883
+ }
884
+
846
885
  if (Array.isArray(run.workflow_kit_artifacts) && run.workflow_kit_artifacts.length > 0) {
847
886
  lines.push('', `Workflow Artifacts (${run.phase || 'unknown'} phase):`);
848
887
  for (const art of run.workflow_kit_artifacts) {
@@ -985,6 +1024,17 @@ export function formatGovernanceReportText(report) {
985
1024
  if (repo.recovery_summary) {
986
1025
  repoLines.push(` Recovery: ${repo.recovery_summary.category || 'unknown'} — ${repo.recovery_summary.typed_reason || 'unknown'} (owner: ${repo.recovery_summary.owner || 'unknown'})`);
987
1026
  }
1027
+ if (repo.continuity) {
1028
+ repoLines.push(' Continuity:');
1029
+ repoLines.push(` Session: ${repo.continuity.session_id || 'unknown'}`);
1030
+ repoLines.push(` Checkpoint: ${repo.continuity.checkpoint_reason || 'unknown'} at ${repo.continuity.last_checkpoint_at || 'n/a'}`);
1031
+ repoLines.push(` Last turn: ${repo.continuity.last_turn_id || 'none'}`);
1032
+ repoLines.push(` Last role: ${repo.continuity.last_role || 'unknown'}`);
1033
+ repoLines.push(` Last phase: ${repo.continuity.last_phase || 'unknown'}`);
1034
+ if (repo.continuity.stale_checkpoint) {
1035
+ repoLines.push(` WARNING: checkpoint tracks run ${repo.continuity.run_id}, but repo export tracks ${repo.run_id}`);
1036
+ }
1037
+ }
988
1038
  return repoLines;
989
1039
  }));
990
1040
  return lines.join('\n');
@@ -1110,6 +1160,18 @@ export function formatGovernanceReportMarkdown(report) {
1110
1160
  lines.push(`- Turn retained: \`${run.recovery_summary.turn_retained == null ? 'n/a' : yesNo(run.recovery_summary.turn_retained)}\``);
1111
1161
  }
1112
1162
 
1163
+ if (run.continuity) {
1164
+ lines.push('', '## Continuity', '');
1165
+ lines.push(`- Session: \`${run.continuity.session_id || 'unknown'}\``);
1166
+ lines.push(`- Checkpoint: \`${run.continuity.checkpoint_reason || 'unknown'}\` at \`${run.continuity.last_checkpoint_at || 'n/a'}\``);
1167
+ lines.push(`- Last turn: \`${run.continuity.last_turn_id || 'none'}\``);
1168
+ lines.push(`- Last role: \`${run.continuity.last_role || 'unknown'}\``);
1169
+ lines.push(`- Last phase: \`${run.continuity.last_phase || 'unknown'}\``);
1170
+ if (run.continuity.stale_checkpoint) {
1171
+ lines.push(`- **Warning:** checkpoint tracks run \`${run.continuity.run_id}\`, but export tracks \`${run.run_id}\``);
1172
+ }
1173
+ }
1174
+
1113
1175
  if (Array.isArray(run.workflow_kit_artifacts) && run.workflow_kit_artifacts.length > 0) {
1114
1176
  lines.push('', '## Workflow Artifacts', '');
1115
1177
  lines.push(`Phase: \`${run.phase || 'unknown'}\``, '');
@@ -1258,6 +1320,17 @@ export function formatGovernanceReportMarkdown(report) {
1258
1320
  if (repo.recovery_summary) {
1259
1321
  repoLines.push('', '#### Recovery', '', `- Category: \`${repo.recovery_summary.category || 'unknown'}\``, `- Typed reason: \`${repo.recovery_summary.typed_reason || 'unknown'}\``, `- Owner: \`${repo.recovery_summary.owner || 'unknown'}\``, `- Action: \`${repo.recovery_summary.recovery_action || 'n/a'}\``);
1260
1322
  }
1323
+ if (repo.continuity) {
1324
+ repoLines.push('', '#### Continuity', '');
1325
+ repoLines.push(`- Session: \`${repo.continuity.session_id || 'unknown'}\``);
1326
+ repoLines.push(`- Checkpoint: \`${repo.continuity.checkpoint_reason || 'unknown'}\` at \`${repo.continuity.last_checkpoint_at || 'n/a'}\``);
1327
+ repoLines.push(`- Last turn: \`${repo.continuity.last_turn_id || 'none'}\``);
1328
+ repoLines.push(`- Last role: \`${repo.continuity.last_role || 'unknown'}\``);
1329
+ repoLines.push(`- Last phase: \`${repo.continuity.last_phase || 'unknown'}\``);
1330
+ if (repo.continuity.stale_checkpoint) {
1331
+ repoLines.push(`- **Warning:** checkpoint tracks run \`${repo.continuity.run_id}\`, but repo export tracks \`${repo.run_id}\``);
1332
+ }
1333
+ }
1261
1334
  repoLines.push('');
1262
1335
  return repoLines;
1263
1336
  }));