cclaw-cli 6.14.2 → 6.14.3

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.
@@ -285,13 +285,25 @@ export async function lintTddStage(ctx) {
285
285
  "refactor-deferred",
286
286
  "resolve-conflict"
287
287
  ]);
288
- // v6.14.2 — under `legacyContinuation: true` AND a stamped
289
- // boundary, exempt closed slices that NEVER recorded ANY of the
290
- // three worktree-first metadata fields. This is the "all-or-
291
- // nothing legacy" rule from v6.14.2 Fix 3: partial-metadata
292
- // slices stay flagged (a real bug), but slices that pre-date
293
- // the worktree-first flip get amnesty.
294
- const sliceWorktreeMetaState = computeSliceWorktreeMetaState(runEvents);
288
+ // v6.14.3 — under `legacyContinuation: true` AND a stamped
289
+ // boundary, exempt every slice closed at or before
290
+ // `tddWorktreeCutoverSliceId`. The cutover boundary itself is the
291
+ // contract: slices boundary were closed before the
292
+ // worktree-first metadata mandate took effect, so we trust the
293
+ // boundary as authoritative and do not require the slice to have
294
+ // recorded zero metadata across all rows.
295
+ //
296
+ // The earlier v6.14.2 "all-or-nothing" rule rejected the common
297
+ // hox-shape pattern where the GREEN row carries claim/lane/lease
298
+ // (added on the v6.14.x worktree-first flip) but a later
299
+ // `refactor-deferred` terminal row does not. That partial-
300
+ // metadata layout is the operator-visible signature of the
301
+ // failure mode this exemption was introduced to fix; flagging it
302
+ // again under a different code defeated the entire migration.
303
+ //
304
+ // Operators who want a strict gate can opt out by clearing
305
+ // `legacyContinuation` (or omitting `tddWorktreeCutoverSliceId`)
306
+ // — both fields are explicit, persisted, and operator-editable.
295
307
  const isExemptLegacySlice = (sliceId) => {
296
308
  if (!legacyContinuation)
297
309
  return false;
@@ -300,14 +312,7 @@ export async function lintTddStage(ctx) {
300
312
  const n = parseSliceNumber(sliceId);
301
313
  if (n === null)
302
314
  return false;
303
- if (n > worktreeCutoverBoundary)
304
- return false;
305
- const meta = sliceWorktreeMetaState.get(sliceId);
306
- if (!meta)
307
- return true; // no slice-implementer rows at all → fully legacy
308
- // Exempt only when the slice carries ZERO worktree fields across
309
- // all rows. Partial metadata stays flagged.
310
- return !meta.anyMeta;
315
+ return n <= worktreeCutoverBoundary;
311
316
  };
312
317
  const missingGreenMeta = new Set();
313
318
  const exemptedGreenMeta = new Set();
@@ -1333,30 +1338,6 @@ function pickEventTs(rows) {
1333
1338
  }
1334
1339
  return undefined;
1335
1340
  }
1336
- /**
1337
- * v6.14.2 — for each slice id appearing in `slice-implementer` rows of
1338
- * the active run, record whether ANY row carried at least one of the
1339
- * three worktree-first metadata fields (`claimToken`, `ownerLaneId`,
1340
- * `leasedUntil`). Used by `isExemptLegacySlice` to enforce the "all-or-
1341
- * nothing legacy" rule: only slices with NO worktree fields anywhere
1342
- * in their rows qualify for the legacyContinuation amnesty.
1343
- */
1344
- function computeSliceWorktreeMetaState(events) {
1345
- const out = new Map();
1346
- for (const ev of events) {
1347
- if (ev.stage !== "tdd" || ev.agent !== "slice-implementer")
1348
- continue;
1349
- if (typeof ev.sliceId !== "string")
1350
- continue;
1351
- const tok = ev.claimToken?.trim() ?? "";
1352
- const lane = ev.ownerLaneId?.trim() ?? "";
1353
- const lease = ev.leasedUntil?.trim() ?? "";
1354
- const anyHere = tok.length > 0 || lane.length > 0 || lease.length > 0;
1355
- const prev = out.get(ev.sliceId) ?? { anyMeta: false };
1356
- out.set(ev.sliceId, { anyMeta: prev.anyMeta || anyHere });
1357
- }
1358
- return out;
1359
- }
1360
1341
  /**
1361
1342
  * v6.14.2 — slices whose terminal `refactor` / `refactor-deferred` /
1362
1343
  * `resolve-conflict` row recorded a `completedTs` that PRECEDES the
package/dist/install.js CHANGED
@@ -939,8 +939,20 @@ async function applyTddCutoverIfNeeded(projectRoot) {
939
939
  if (typeof obj.tddCutoverSliceId === "string" && obj.tddCutoverSliceId.length > 0) {
940
940
  return;
941
941
  }
942
- obj.tddCutoverSliceId = cutoverSliceId;
943
- await writeFileSafe(flowStatePath, `${JSON.stringify(obj, null, 2)}\n`, { mode: 0o600 });
942
+ // v6.14.3 refresh the SHA256 sidecar by writing through
943
+ // `writeFlowState`. The previous direct `writeFileSafe` invocation
944
+ // left the sidecar stale, so the very next guarded hook on a synced
945
+ // legacy project rejected its own `tddCutoverSliceId` stamp.
946
+ try {
947
+ const state = await readFlowState(projectRoot);
948
+ await writeFlowState(projectRoot, { ...state, tddCutoverSliceId: cutoverSliceId }, {
949
+ allowReset: true,
950
+ writerSubsystem: "sync-v6.12-tdd-cutover-stamp"
951
+ });
952
+ }
953
+ catch {
954
+ // Best-effort: corrupt/missing state is handled elsewhere on sync.
955
+ }
944
956
  }
945
957
  const V613_LEGACY_PLAN_BANNER = "<!-- legacy-continuation: predates v6.13 parallel metadata. New units MAY add dependsOn/claimedPaths/parallelizable; existing units treated as best-effort serial. -->";
946
958
  /**
@@ -1091,9 +1103,16 @@ async function applyV614DefaultsIfNeeded(projectRoot) {
1091
1103
  if (summary.length === 0) {
1092
1104
  return null;
1093
1105
  }
1094
- const merged = { ...obj, ...updates };
1106
+ // v6.14.3 refresh the SHA256 sidecar in lockstep so guarded reads
1107
+ // (verify-current-state, advance-stage, etc.) don't trip a guard
1108
+ // mismatch immediately after `cclaw-cli sync`/`upgrade` writes the
1109
+ // v6.14.2 stream-style defaults.
1095
1110
  try {
1096
- await writeFileSafe(flowStatePath, `${JSON.stringify(merged, null, 2)}\n`, { mode: 0o600 });
1111
+ const state = await readFlowState(projectRoot);
1112
+ await writeFlowState(projectRoot, { ...state, ...updates }, {
1113
+ allowReset: true,
1114
+ writerSubsystem: "sync-v6.14.2-stream-defaults"
1115
+ });
1097
1116
  }
1098
1117
  catch {
1099
1118
  return null;
@@ -1204,10 +1223,18 @@ async function applyV6142WorktreeCutoverIfNeeded(projectRoot) {
1204
1223
  }
1205
1224
  if (!stamped)
1206
1225
  return null;
1207
- const merged = { ...obj, tddWorktreeCutoverSliceId: stamped };
1226
+ // v6.14.3 go through `writeFlowState` so the SHA256 sidecar
1227
+ // (`.cclaw/.flow-state.guard.json`) is refreshed in lockstep with
1228
+ // the on-disk flow-state.json. The previous v6.14.2 implementation
1229
+ // wrote the field via `writeFileSafe` directly, which left the
1230
+ // sidecar pointing at the pre-stamp digest; the next guarded hook
1231
+ // (e.g. `cclaw internal verify-current-state`) then failed with
1232
+ // `flow-state guard mismatch` and demanded a manual repair.
1208
1233
  try {
1209
- await writeFileSafe(flowStatePath, `${JSON.stringify(merged, null, 2)}\n`, {
1210
- mode: 0o600
1234
+ const state = await readFlowState(projectRoot);
1235
+ await writeFlowState(projectRoot, { ...state, tddWorktreeCutoverSliceId: stamped }, {
1236
+ allowReset: true,
1237
+ writerSubsystem: "sync-v6.14.2-worktree-cutover-stamp"
1211
1238
  });
1212
1239
  }
1213
1240
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "6.14.2",
3
+ "version": "6.14.3",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {