agentxchain 2.142.0 → 2.144.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.142.0",
3
+ "version": "2.144.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,12 +42,10 @@ function updateSidebarPosition(content, nextPosition) {
42
42
  const body = content.slice(frontmatterEnd + 5);
43
43
  const expectedLine = `sidebar_position: ${nextPosition}`;
44
44
 
45
- let updatedFrontmatter;
46
- if (/^sidebar_position:\s*\d+\s*$/m.test(frontmatter)) {
47
- updatedFrontmatter = frontmatter.replace(/^sidebar_position:\s*\d+\s*$/m, expectedLine);
48
- } else {
49
- updatedFrontmatter = frontmatter.replace(/^---\n/, `---\n${expectedLine}\n`);
50
- }
45
+ // Strip ALL existing sidebar_position lines (handles duplicates from manual edits)
46
+ let strippedFrontmatter = frontmatter.replace(/^sidebar_position:\s*-?\d+\s*\n/gm, '');
47
+ // Insert the canonical position after the opening delimiter
48
+ let updatedFrontmatter = strippedFrontmatter.replace(/^---\n/, `---\n${expectedLine}\n`);
51
49
 
52
50
  const updated = updatedFrontmatter + body;
53
51
  return {
@@ -79,6 +79,7 @@ ALLOWED_RELEASE_PATHS=(
79
79
  ".planning/LAUNCH_EVIDENCE_REPORT.md"
80
80
  ".planning/SHOW_HN_DRAFT.md"
81
81
  ".planning/MARKETING/TWITTER_THREAD.md"
82
+ ".planning/MARKETING/LINKEDIN_POST.md"
82
83
  ".planning/MARKETING/REDDIT_POSTS.md"
83
84
  ".planning/MARKETING/HN_SUBMISSION.md"
84
85
  "website-v2/static/llms.txt"
package/src/lib/export.js CHANGED
@@ -6,6 +6,10 @@ import { join, relative, resolve } from 'node:path';
6
6
  import { loadProjectContext, loadProjectState } from './config.js';
7
7
  import { loadCoordinatorConfig, COORDINATOR_CONFIG_FILE } from './coordinator-config.js';
8
8
  import { loadCoordinatorState } from './coordinator-state.js';
9
+ import {
10
+ RUN_CONTINUITY_DIRECTORY_ROOTS,
11
+ RUN_CONTINUITY_STATE_FILES,
12
+ } from './repo-observer.js';
9
13
  import { normalizeRunProvenance } from './run-provenance.js';
10
14
  import { getDashboardPid, getDashboardSession } from '../commands/dashboard.js';
11
15
  import { readRepoDecisions, summarizeRepoDecisions } from './repo-decisions.js';
@@ -23,62 +27,24 @@ const COORDINATOR_INCLUDED_ROOTS = [
23
27
  '.agentxchain/multirepo/RECOVERY_REPORT.md',
24
28
  ];
25
29
 
26
- export const RUN_EXPORT_INCLUDED_ROOTS = [
30
+ const RUN_EXPORT_ONLY_ROOTS = [
27
31
  'agentxchain.json',
28
- 'TALK.md',
29
32
  '.agentxchain-dashboard.pid',
30
33
  '.agentxchain-dashboard.json',
31
- '.agentxchain/state.json',
32
- '.agentxchain/session.json',
33
- '.agentxchain/history.jsonl',
34
- '.agentxchain/decision-ledger.jsonl',
35
- '.agentxchain/repo-decisions.jsonl',
36
- '.agentxchain/hook-audit.jsonl',
37
- '.agentxchain/hook-annotations.jsonl',
38
- '.agentxchain/notification-audit.jsonl',
39
- '.agentxchain/run-history.jsonl',
40
- '.agentxchain/events.jsonl',
41
- '.agentxchain/schedule-state.json',
42
- '.agentxchain/schedule-daemon.json',
43
- '.agentxchain/continuous-session.json',
44
- '.agentxchain/human-escalations.jsonl',
45
- '.agentxchain/sla-reminders.json',
46
- '.agentxchain/dispatch',
47
- '.agentxchain/staging',
48
- '.agentxchain/transactions/accept',
49
- '.agentxchain/intake',
50
- '.agentxchain/multirepo',
51
- '.agentxchain/reviews',
52
- '.agentxchain/proposed',
53
- '.agentxchain/reports',
54
34
  '.planning',
55
35
  ];
56
36
 
37
+ export const RUN_EXPORT_INCLUDED_ROOTS = [
38
+ 'agentxchain.json',
39
+ ...RUN_CONTINUITY_STATE_FILES,
40
+ ...RUN_CONTINUITY_DIRECTORY_ROOTS,
41
+ ...RUN_EXPORT_ONLY_ROOTS.filter((root) => root !== 'agentxchain.json'),
42
+ ];
43
+
57
44
  export const RUN_RESTORE_ROOTS = [
58
45
  'agentxchain.json',
59
- 'TALK.md',
60
- '.agentxchain/state.json',
61
- '.agentxchain/session.json',
62
- '.agentxchain/history.jsonl',
63
- '.agentxchain/decision-ledger.jsonl',
64
- '.agentxchain/hook-audit.jsonl',
65
- '.agentxchain/hook-annotations.jsonl',
66
- '.agentxchain/notification-audit.jsonl',
67
- '.agentxchain/run-history.jsonl',
68
- '.agentxchain/events.jsonl',
69
- '.agentxchain/schedule-state.json',
70
- '.agentxchain/schedule-daemon.json',
71
- '.agentxchain/continuous-session.json',
72
- '.agentxchain/human-escalations.jsonl',
73
- '.agentxchain/sla-reminders.json',
74
- '.agentxchain/dispatch',
75
- '.agentxchain/staging',
76
- '.agentxchain/transactions/accept',
77
- '.agentxchain/intake',
78
- '.agentxchain/multirepo',
79
- '.agentxchain/reviews',
80
- '.agentxchain/proposed',
81
- '.agentxchain/reports',
46
+ ...RUN_CONTINUITY_STATE_FILES,
47
+ ...RUN_CONTINUITY_DIRECTORY_ROOTS,
82
48
  '.planning',
83
49
  ];
84
50
 
@@ -3547,6 +3547,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
3547
3547
  // protocol violation for authoritative completed turns. A workspace artifact
3548
3548
  // declares "I mutated repo files" — if files_changed is empty, the declaration
3549
3549
  // is incoherent and must be rejected before replay or history persistence.
3550
+ // proposed turns cannot use workspace artifacts (validator rejects earlier).
3550
3551
  if (artifactType === 'workspace'
3551
3552
  && writeAuthority === 'authoritative'
3552
3553
  && turnResult.status === 'completed'
@@ -279,6 +279,12 @@ export const RELEASE_ALIGNMENT_SURFACES = [
279
279
  scopes: [RELEASE_ALIGNMENT_SCOPES.PREBUMP, RELEASE_ALIGNMENT_SCOPES.CURRENT],
280
280
  check: validateTextIncludesVersionAndEvidence('.planning/MARKETING/TWITTER_THREAD.md', 'twitter thread draft').check,
281
281
  },
282
+ {
283
+ id: 'linkedin_post',
284
+ label: 'linkedin release post draft',
285
+ scopes: [RELEASE_ALIGNMENT_SCOPES.PREBUMP, RELEASE_ALIGNMENT_SCOPES.CURRENT],
286
+ check: validateTextIncludesVersionAndEvidence('.planning/MARKETING/LINKEDIN_POST.md', 'linkedin release post draft').check,
287
+ },
282
288
  {
283
289
  id: 'reddit_posts',
284
290
  label: 'reddit posts draft',
@@ -22,20 +22,22 @@ import { join } from 'path';
22
22
  // They must never be attributed to agents in observation or baseline checks.
23
23
  // Frozen per Session #19 decision.
24
24
 
25
- const OPERATIONAL_PATH_PREFIXES = [
25
+ export const OPERATIONAL_PATH_PREFIXES = Object.freeze([
26
26
  '.agentxchain/dispatch/',
27
- '.agentxchain/dispatch-progress-',
27
+ '.agentxchain/dispatch-progress',
28
28
  '.agentxchain/staging/',
29
29
  '.agentxchain/intake/',
30
30
  '.agentxchain/locks/',
31
31
  '.agentxchain/transactions/',
32
32
  '.agentxchain/missions/',
33
33
  '.agentxchain/multirepo/',
34
- ];
34
+ '.agentxchain/plugins/',
35
+ '.agentxchain/prompts/',
36
+ ]);
35
37
 
36
38
  // Orchestrator-owned state files that agents must never be blamed for modifying.
37
39
  // These are written exclusively by the orchestrator (§4.1 State Ownership Rule).
38
- const ORCHESTRATOR_STATE_FILES = [
40
+ export const ORCHESTRATOR_STATE_FILES = Object.freeze([
39
41
  '.agentxchain/state.json',
40
42
  '.agentxchain/session.json',
41
43
  '.agentxchain/history.jsonl',
@@ -52,31 +54,87 @@ const ORCHESTRATOR_STATE_FILES = [
52
54
  '.agentxchain/continuous-session.json',
53
55
  '.agentxchain/human-escalations.jsonl',
54
56
  '.agentxchain/sla-reminders.json',
57
+ '.agentxchain/SESSION_RECOVERY.md',
58
+ '.agentxchain/migration-report.md',
55
59
  'TALK.md',
56
60
  'HUMAN_TASKS.md',
57
- ];
61
+ ]);
58
62
 
59
63
  // Evidence paths may legitimately remain dirty across turns without blocking the
60
64
  // next code-writing assignment. They still remain actor-observable so review
61
65
  // accountability is preserved during acceptance.
62
- const BASELINE_EXEMPT_PATH_PREFIXES = [
66
+ export const BASELINE_EXEMPT_PATH_PREFIXES = Object.freeze([
63
67
  '.agentxchain/reviews/',
64
68
  '.agentxchain/reports/',
65
69
  '.agentxchain/proposed/',
66
- ];
70
+ ]);
71
+
72
+ // Continuity export/restore must stay aligned with orchestrator ownership,
73
+ // but only for the subset that represents governed run state.
74
+ export const RUN_CONTINUITY_STATE_FILES = Object.freeze([
75
+ ...ORCHESTRATOR_STATE_FILES.filter((filePath) => filePath !== 'HUMAN_TASKS.md'),
76
+ ]);
77
+
78
+ export const RUN_CONTINUITY_DIRECTORY_ROOTS = Object.freeze([
79
+ '.agentxchain/dispatch',
80
+ '.agentxchain/staging',
81
+ '.agentxchain/transactions/accept',
82
+ '.agentxchain/intake',
83
+ '.agentxchain/missions',
84
+ '.agentxchain/multirepo',
85
+ ...BASELINE_EXEMPT_PATH_PREFIXES.map((prefix) => prefix.replace(/\/$/, '')),
86
+ ]);
87
+
88
+ function pathMatchesAnyPrefix(filePath, prefixes) {
89
+ return prefixes.some((prefix) => filePath.startsWith(prefix));
90
+ }
91
+
92
+ function pathMatchesAnyRoot(filePath, roots) {
93
+ return roots.some((root) => filePath === root || filePath.startsWith(`${root}/`));
94
+ }
95
+
96
+ /**
97
+ * Return the repo-owned vs framework-owned classification flags for a path.
98
+ *
99
+ * The categories intentionally overlap:
100
+ * - continuity state is always baseline-exempt
101
+ * - operational paths are always baseline-exempt
102
+ * - some baseline-exempt evidence paths also participate in continuity export
103
+ *
104
+ * This overlap is the contract. The framework does NOT use mutually exclusive
105
+ * buckets for observation, clean-baseline checks, and continuity export.
106
+ */
107
+ export function classifyRepoPath(filePath) {
108
+ const operational = pathMatchesAnyPrefix(filePath, OPERATIONAL_PATH_PREFIXES)
109
+ || ORCHESTRATOR_STATE_FILES.includes(filePath);
110
+ const continuityState = RUN_CONTINUITY_STATE_FILES.includes(filePath)
111
+ || pathMatchesAnyRoot(filePath, RUN_CONTINUITY_DIRECTORY_ROOTS);
112
+ const baselineExempt = operational
113
+ || continuityState
114
+ || pathMatchesAnyPrefix(filePath, BASELINE_EXEMPT_PATH_PREFIXES);
115
+
116
+ return {
117
+ operational,
118
+ baselineExempt,
119
+ continuityState,
120
+ projectOwned: !baselineExempt,
121
+ };
122
+ }
67
123
 
68
124
  /**
69
125
  * Check whether a file path belongs to orchestrator-owned operational state.
70
126
  * These paths are excluded from actor-attributed observation.
71
127
  */
72
128
  export function isOperationalPath(filePath) {
73
- return OPERATIONAL_PATH_PREFIXES.some(prefix => filePath.startsWith(prefix))
74
- || ORCHESTRATOR_STATE_FILES.includes(filePath);
129
+ return classifyRepoPath(filePath).operational;
75
130
  }
76
131
 
77
132
  export function isBaselineExemptPath(filePath) {
78
- return isOperationalPath(filePath)
79
- || BASELINE_EXEMPT_PATH_PREFIXES.some(prefix => filePath.startsWith(prefix));
133
+ return classifyRepoPath(filePath).baselineExempt;
134
+ }
135
+
136
+ export function isRunContinuityPath(filePath) {
137
+ return classifyRepoPath(filePath).continuityState;
80
138
  }
81
139
 
82
140
  export function normalizeCheckpointableFiles(filesChanged) {
@@ -1,10 +1,10 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
- import { resolveAcceptedTurnHistoryReference } from './accepted-turn-history.js';
4
+ import { queryAcceptedTurnHistory, resolveAcceptedTurnHistoryReference } from './accepted-turn-history.js';
5
5
  import { emitRunEvent } from './run-events.js';
6
6
  import { safeWriteJson } from './safe-write.js';
7
- import { normalizeCheckpointableFiles } from './repo-observer.js';
7
+ import { checkCleanBaseline, normalizeCheckpointableFiles } from './repo-observer.js';
8
8
 
9
9
  const STATE_PATH = '.agentxchain/state.json';
10
10
  const HISTORY_PATH = '.agentxchain/history.jsonl';
@@ -57,6 +57,68 @@ function normalizeFilesChanged(filesChanged) {
57
57
  return normalizeCheckpointableFiles(filesChanged);
58
58
  }
59
59
 
60
+ function supportsLegacyFilesChangedRecovery(entry) {
61
+ const artifactType = entry?.artifact?.type;
62
+ return artifactType === 'workspace' || artifactType === 'patch';
63
+ }
64
+
65
+ function getActorDirtyFiles(root, dirtyFiles = null) {
66
+ if (Array.isArray(dirtyFiles)) {
67
+ return normalizeFilesChanged(dirtyFiles);
68
+ }
69
+ const cleanCheck = checkCleanBaseline(root, 'authoritative');
70
+ return cleanCheck.clean ? [] : normalizeFilesChanged(cleanCheck.dirty_files);
71
+ }
72
+
73
+ function recoverLegacyCheckpointFiles(root, entry, opts = {}) {
74
+ if (!supportsLegacyFilesChangedRecovery(entry) || entry?.checkpoint_sha) {
75
+ return [];
76
+ }
77
+
78
+ const state = readState(root);
79
+ if (state && Object.keys(state.active_turns || {}).length > 0) {
80
+ return [];
81
+ }
82
+
83
+ const acceptedHistory = queryAcceptedTurnHistory(root);
84
+ if (acceptedHistory[0]?.turn_id !== entry?.turn_id) {
85
+ return [];
86
+ }
87
+
88
+ return getActorDirtyFiles(root, opts.dirtyFiles);
89
+ }
90
+
91
+ function persistRecoveredFilesChanged(root, turnId, recoveredFiles) {
92
+ const normalizedRecoveredFiles = normalizeFilesChanged(recoveredFiles);
93
+ if (normalizedRecoveredFiles.length === 0) return;
94
+
95
+ const recoveredAt = new Date().toISOString();
96
+ const nextEntries = readHistoryEntries(root).map((historyEntry) => {
97
+ if (historyEntry.turn_id !== turnId) {
98
+ return historyEntry;
99
+ }
100
+
101
+ const observedArtifact = historyEntry?.observed_artifact && typeof historyEntry.observed_artifact === 'object'
102
+ ? historyEntry.observed_artifact
103
+ : null;
104
+
105
+ return {
106
+ ...historyEntry,
107
+ files_changed: normalizedRecoveredFiles,
108
+ files_changed_recovered_at: recoveredAt,
109
+ files_changed_recovery_source: 'legacy_dirty_worktree',
110
+ observed_artifact: observedArtifact
111
+ ? {
112
+ ...observedArtifact,
113
+ files_changed: normalizedRecoveredFiles,
114
+ }
115
+ : observedArtifact,
116
+ };
117
+ });
118
+
119
+ writeHistoryEntries(root, nextEntries);
120
+ }
121
+
60
122
  function extractGitError(err) {
61
123
  const stderr = typeof err?.stderr === 'string' ? err.stderr.trim() : '';
62
124
  const stdout = typeof err?.stdout === 'string' ? err.stdout.trim() : '';
@@ -88,15 +150,22 @@ export function detectPendingCheckpoint(root, dirtyFiles = []) {
88
150
  if (entry.checkpoint_sha) return { required: false };
89
151
 
90
152
  const turnFiles = normalizeFilesChanged(entry.files_changed);
91
- if (turnFiles.length === 0) return { required: false };
153
+ const recoveredFiles = turnFiles.length === 0
154
+ ? recoverLegacyCheckpointFiles(root, entry, { dirtyFiles: actorDirtyFiles })
155
+ : [];
156
+ const effectiveTurnFiles = turnFiles.length > 0 ? turnFiles : recoveredFiles;
157
+ if (effectiveTurnFiles.length === 0) return { required: false };
92
158
 
93
- const dirtyOutsideTurn = actorDirtyFiles.filter((file) => !turnFiles.includes(file));
159
+ const dirtyOutsideTurn = actorDirtyFiles.filter((file) => !effectiveTurnFiles.includes(file));
94
160
  if (dirtyOutsideTurn.length > 0) return { required: false };
95
161
 
96
162
  return {
97
163
  required: true,
98
164
  turn_id: entry.turn_id,
99
- message: `Accepted turn ${entry.turn_id} is not checkpointed yet. Run agentxchain checkpoint-turn --turn ${entry.turn_id} before assigning the next code-writing turn.`,
165
+ recovered_files_changed: recoveredFiles.length > 0 ? recoveredFiles : undefined,
166
+ message: recoveredFiles.length > 0
167
+ ? `Accepted turn ${entry.turn_id} has legacy-empty files_changed history but still owns ${recoveredFiles.length} dirty actor file(s). Run agentxchain checkpoint-turn --turn ${entry.turn_id} to recover and checkpoint them.`
168
+ : `Accepted turn ${entry.turn_id} is not checkpointed yet. Run agentxchain checkpoint-turn --turn ${entry.turn_id} before assigning the next code-writing turn.`,
100
169
  };
101
170
  }
102
171
 
@@ -120,7 +189,17 @@ export function checkpointAcceptedTurn(root, opts = {}) {
120
189
  };
121
190
  }
122
191
 
123
- const filesChanged = normalizeFilesChanged(entry.files_changed);
192
+ const declaredFilesChanged = normalizeFilesChanged(entry.files_changed);
193
+ const recoveredFilesChanged = declaredFilesChanged.length === 0
194
+ ? recoverLegacyCheckpointFiles(root, entry)
195
+ : [];
196
+ const filesChanged = declaredFilesChanged.length > 0
197
+ ? declaredFilesChanged
198
+ : recoveredFilesChanged;
199
+ if (recoveredFilesChanged.length > 0) {
200
+ persistRecoveredFilesChanged(root, entry.turn_id, recoveredFilesChanged);
201
+ }
202
+
124
203
  if (filesChanged.length === 0) {
125
204
  return {
126
205
  ok: true,