agentxchain 2.143.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.143.0",
3
+ "version": "2.144.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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"
@@ -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',
@@ -31,6 +31,7 @@ export const OPERATIONAL_PATH_PREFIXES = Object.freeze([
31
31
  '.agentxchain/transactions/',
32
32
  '.agentxchain/missions/',
33
33
  '.agentxchain/multirepo/',
34
+ '.agentxchain/plugins/',
34
35
  '.agentxchain/prompts/',
35
36
  ]);
36
37
 
@@ -84,18 +85,56 @@ export const RUN_CONTINUITY_DIRECTORY_ROOTS = Object.freeze([
84
85
  ...BASELINE_EXEMPT_PATH_PREFIXES.map((prefix) => prefix.replace(/\/$/, '')),
85
86
  ]);
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
+ }
123
+
87
124
  /**
88
125
  * Check whether a file path belongs to orchestrator-owned operational state.
89
126
  * These paths are excluded from actor-attributed observation.
90
127
  */
91
128
  export function isOperationalPath(filePath) {
92
- return OPERATIONAL_PATH_PREFIXES.some(prefix => filePath.startsWith(prefix))
93
- || ORCHESTRATOR_STATE_FILES.includes(filePath);
129
+ return classifyRepoPath(filePath).operational;
94
130
  }
95
131
 
96
132
  export function isBaselineExemptPath(filePath) {
97
- return isOperationalPath(filePath)
98
- || 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;
99
138
  }
100
139
 
101
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,