agentxchain 2.155.62 → 2.155.63

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.155.62",
3
+ "version": "2.155.63",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -96,7 +96,69 @@ function recoverLegacyCheckpointFiles(root, entry, opts = {}) {
96
96
  return getActorDirtyFiles(root, opts.dirtyFiles);
97
97
  }
98
98
 
99
- function persistRecoveredFilesChanged(root, turnId, recoveredFiles) {
99
+ function extractObservedDiffSummaryPaths(entry) {
100
+ const summary = entry?.observed_artifact?.diff_summary;
101
+ if (typeof summary !== 'string' || summary.trim().length === 0) {
102
+ return [];
103
+ }
104
+
105
+ const paths = [];
106
+ for (const line of summary.split('\n')) {
107
+ const tableMatch = line.match(/^\s*(.+?)\s+\|\s+/);
108
+ if (tableMatch?.[1]) {
109
+ paths.push(tableMatch[1].trim());
110
+ continue;
111
+ }
112
+
113
+ const untrackedMatch = line.match(/^\s*-\s+(.+)$/);
114
+ if (untrackedMatch?.[1]) {
115
+ paths.push(untrackedMatch[1].trim());
116
+ }
117
+ }
118
+
119
+ return normalizeFilesChanged(paths);
120
+ }
121
+
122
+ function recoverSupplementalCheckpointFiles(root, entry, opts = {}) {
123
+ if (!supportsLegacyFilesChangedRecovery(entry) || !entry?.checkpoint_sha) {
124
+ return [];
125
+ }
126
+
127
+ const state = readState(root);
128
+ if (state && Object.keys(state.active_turns || {}).length > 0) {
129
+ return [];
130
+ }
131
+
132
+ const acceptedHistory = queryAcceptedTurnHistory(root);
133
+ if (acceptedHistory[0]?.turn_id !== entry?.turn_id) {
134
+ return [];
135
+ }
136
+
137
+ const actorDirtyFiles = getActorDirtyFiles(root, opts.dirtyFiles);
138
+ if (actorDirtyFiles.length === 0) {
139
+ return [];
140
+ }
141
+
142
+ const observedSummaryFiles = new Set(extractObservedDiffSummaryPaths(entry));
143
+ if (observedSummaryFiles.size === 0) {
144
+ return [];
145
+ }
146
+
147
+ const declared = new Set(normalizeFilesChanged(entry.files_changed));
148
+ const recoverable = actorDirtyFiles.filter((filePath) => (
149
+ !declared.has(filePath) && observedSummaryFiles.has(filePath)
150
+ ));
151
+ if (recoverable.length === 0) {
152
+ return [];
153
+ }
154
+
155
+ const unrecoverable = actorDirtyFiles.filter((filePath) => (
156
+ !declared.has(filePath) && !observedSummaryFiles.has(filePath)
157
+ ));
158
+ return unrecoverable.length === 0 ? recoverable : [];
159
+ }
160
+
161
+ function persistRecoveredFilesChanged(root, turnId, recoveredFiles, source = 'legacy_dirty_worktree') {
100
162
  const normalizedRecoveredFiles = normalizeFilesChanged(recoveredFiles);
101
163
  if (normalizedRecoveredFiles.length === 0) return;
102
164
 
@@ -110,15 +172,21 @@ function persistRecoveredFilesChanged(root, turnId, recoveredFiles) {
110
172
  ? historyEntry.observed_artifact
111
173
  : null;
112
174
 
175
+ const existingFiles = normalizeFilesChanged(historyEntry.files_changed);
176
+ const mergedFiles = normalizeFilesChanged([...existingFiles, ...normalizedRecoveredFiles]);
177
+
113
178
  return {
114
179
  ...historyEntry,
115
- files_changed: normalizedRecoveredFiles,
180
+ files_changed: mergedFiles,
116
181
  files_changed_recovered_at: recoveredAt,
117
- files_changed_recovery_source: 'legacy_dirty_worktree',
182
+ files_changed_recovery_source: source,
118
183
  observed_artifact: observedArtifact
119
184
  ? {
120
185
  ...observedArtifact,
121
- files_changed: normalizedRecoveredFiles,
186
+ files_changed: normalizeFilesChanged([
187
+ ...(Array.isArray(observedArtifact.files_changed) ? observedArtifact.files_changed : []),
188
+ ...normalizedRecoveredFiles,
189
+ ]),
122
190
  }
123
191
  : observedArtifact,
124
192
  };
@@ -234,7 +302,17 @@ export function detectPendingCheckpoint(root, dirtyFiles = []) {
234
302
  if (!resolved.ok || !resolved.entry) return { required: false };
235
303
 
236
304
  const entry = resolved.entry;
237
- if (entry.checkpoint_sha) return { required: false };
305
+ if (entry.checkpoint_sha) {
306
+ const supplementalFiles = recoverSupplementalCheckpointFiles(root, entry, { dirtyFiles: actorDirtyFiles });
307
+ if (supplementalFiles.length === 0) return { required: false };
308
+ return {
309
+ required: true,
310
+ turn_id: entry.turn_id,
311
+ recovered_files_changed: supplementalFiles,
312
+ supplemental: true,
313
+ message: `Accepted turn ${entry.turn_id} is checkpointed but still owns ${supplementalFiles.length} dirty actor file(s) from its observed diff summary. Run agentxchain checkpoint-turn --turn ${entry.turn_id} to create a supplemental checkpoint before assigning the next code-writing turn.`,
314
+ };
315
+ }
238
316
 
239
317
  const turnFiles = normalizeFilesChanged(entry.files_changed);
240
318
  const recoveredFiles = turnFiles.length === 0
@@ -267,7 +345,10 @@ export function checkpointAcceptedTurn(root, opts = {}) {
267
345
  }
268
346
 
269
347
  const entry = resolved.entry;
270
- if (entry.checkpoint_sha) {
348
+ const supplementalFilesChanged = entry.checkpoint_sha
349
+ ? recoverSupplementalCheckpointFiles(root, entry)
350
+ : [];
351
+ if (entry.checkpoint_sha && supplementalFilesChanged.length === 0) {
271
352
  return {
272
353
  ok: true,
273
354
  already_checkpointed: true,
@@ -281,11 +362,14 @@ export function checkpointAcceptedTurn(root, opts = {}) {
281
362
  ? recoverLegacyCheckpointFiles(root, entry)
282
363
  : [];
283
364
  const filesChanged = declaredFilesChanged.length > 0
284
- ? declaredFilesChanged
365
+ ? normalizeFilesChanged([...declaredFilesChanged, ...supplementalFilesChanged])
285
366
  : recoveredFilesChanged;
286
367
  if (recoveredFilesChanged.length > 0) {
287
368
  persistRecoveredFilesChanged(root, entry.turn_id, recoveredFilesChanged);
288
369
  }
370
+ if (supplementalFilesChanged.length > 0) {
371
+ persistRecoveredFilesChanged(root, entry.turn_id, supplementalFilesChanged, 'supplemental_dirty_worktree');
372
+ }
289
373
 
290
374
  if (filesChanged.length === 0) {
291
375
  return {