cleargate 0.12.0 → 0.13.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.
Files changed (40) hide show
  1. package/dist/MANIFEST.json +13 -13
  2. package/dist/{chunk-HZPJ5QX4.js → chunk-EG6YGT2O.js} +315 -33
  3. package/dist/chunk-EG6YGT2O.js.map +1 -0
  4. package/dist/cli.cjs +612 -289
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.js +73 -37
  7. package/dist/cli.js.map +1 -1
  8. package/dist/lib/lifecycle-reconcile.cjs +318 -34
  9. package/dist/lib/lifecycle-reconcile.cjs.map +1 -1
  10. package/dist/lib/lifecycle-reconcile.d.cts +55 -4
  11. package/dist/lib/lifecycle-reconcile.d.ts +55 -4
  12. package/dist/lib/lifecycle-reconcile.js +7 -3
  13. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
  14. package/dist/templates/cleargate-planning/.claude/agents/developer.md +8 -4
  15. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
  16. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +73 -0
  17. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
  18. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
  19. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
  20. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
  21. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  22. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
  23. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
  24. package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
  25. package/dist/templates/cleargate-planning/MANIFEST.json +13 -13
  26. package/package.json +8 -9
  27. package/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
  28. package/templates/cleargate-planning/.claude/agents/developer.md +8 -4
  29. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
  30. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +73 -0
  31. package/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
  32. package/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
  33. package/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
  34. package/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
  35. package/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  36. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
  37. package/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
  38. package/templates/cleargate-planning/CLAUDE.md +2 -0
  39. package/templates/cleargate-planning/MANIFEST.json +13 -13
  40. package/dist/chunk-HZPJ5QX4.js.map +0 -1
package/dist/cli.cjs CHANGED
@@ -423,11 +423,11 @@ function defaultDiskCachePath(env = process.env) {
423
423
  if (typeof override === "string" && override.length > 0) return override;
424
424
  const home = os9.homedir();
425
425
  if (!home) return null;
426
- return path45.join(home, ".cleargate", "access-token.json");
426
+ return path46.join(home, ".cleargate", "access-token.json");
427
427
  }
428
428
  function readDiskCache(filePath) {
429
429
  try {
430
- const raw = fs42.readFileSync(filePath, "utf8");
430
+ const raw = fs43.readFileSync(filePath, "utf8");
431
431
  const parsed = JSON.parse(raw);
432
432
  if (parsed !== null && typeof parsed === "object" && parsed.version === 1 && typeof parsed.entries === "object" && parsed.entries !== null) {
433
433
  return parsed;
@@ -437,18 +437,18 @@ function readDiskCache(filePath) {
437
437
  return { version: 1, entries: {} };
438
438
  }
439
439
  function writeDiskCache(filePath, data) {
440
- const dir = path45.dirname(filePath);
440
+ const dir = path46.dirname(filePath);
441
441
  try {
442
- fs42.mkdirSync(dir, { recursive: true, mode: 448 });
442
+ fs43.mkdirSync(dir, { recursive: true, mode: 448 });
443
443
  try {
444
- fs42.chmodSync(dir, 448);
444
+ fs43.chmodSync(dir, 448);
445
445
  } catch {
446
446
  }
447
- const tmpPath = path45.join(dir, ".access-token.json.tmp");
448
- fs42.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
449
- fs42.chmodSync(tmpPath, 384);
450
- fs42.renameSync(tmpPath, filePath);
451
- fs42.chmodSync(filePath, 384);
447
+ const tmpPath = path46.join(dir, ".access-token.json.tmp");
448
+ fs43.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
449
+ fs43.chmodSync(tmpPath, 384);
450
+ fs43.renameSync(tmpPath, filePath);
451
+ fs43.chmodSync(filePath, 384);
452
452
  } catch {
453
453
  }
454
454
  }
@@ -544,14 +544,14 @@ async function acquireAccessToken(opts) {
544
544
  }
545
545
  return accessToken;
546
546
  }
547
- var fs42, os9, path45, CACHE, AcquireError;
547
+ var fs43, os9, path46, CACHE, AcquireError;
548
548
  var init_acquire = __esm({
549
549
  "src/auth/acquire.ts"() {
550
550
  "use strict";
551
551
  init_cjs_shims();
552
- fs42 = __toESM(require("fs"), 1);
552
+ fs43 = __toESM(require("fs"), 1);
553
553
  os9 = __toESM(require("os"), 1);
554
- path45 = __toESM(require("path"), 1);
554
+ path46 = __toESM(require("path"), 1);
555
555
  init_factory();
556
556
  CACHE = /* @__PURE__ */ new Map();
557
557
  AcquireError = class extends Error {
@@ -783,7 +783,7 @@ var import_commander = require("commander");
783
783
  // package.json
784
784
  var package_default = {
785
785
  name: "cleargate",
786
- version: "0.12.0",
786
+ version: "0.13.0",
787
787
  private: false,
788
788
  type: "module",
789
789
  description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, five-role agent team (architect/developer/qa/devops/reporter), Karpathy-style awareness wiki.",
@@ -830,12 +830,11 @@ var package_default = {
830
830
  build: "tsup",
831
831
  dev: "tsup --watch",
832
832
  typecheck: "tsc --noEmit",
833
- test: "tsx --test --test-reporter=spec 'test/**/*.node.test.ts'",
834
- "test:file": "tsx --test --test-reporter=spec",
835
- "test:vitest": "npm run build && vitest run",
836
- "test:vitest:watch": "vitest",
837
- "test:node": "tsx --test --test-reporter=spec 'test/**/*.node.test.ts'",
838
- "test:node:file": "tsx --test --test-reporter=spec"
833
+ test: "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec 'test/**/*.node.test.ts' '!test/fixtures/**'",
834
+ "test:file": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec",
835
+ "test:node": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec 'test/**/*.node.test.ts' '!test/fixtures/**'",
836
+ "test:node:file": "tsx --test --test-concurrency=1 --experimental-test-module-mocks --test-reporter=spec",
837
+ "check:no-vitest": `node -e "const r=require('child_process').execSync('grep -rE \\"\\\\b(vitest|vi\\\\.fn|vi\\\\.mock|vi\\\\.spyOn|vi\\\\.stubGlobal|vi\\\\.useFakeTimers|vi\\\\.useRealTimers|vi\\\\.advanceTimersByTime|vi\\\\.hoisted)\\\\b\\" --include=\\"*.ts\\" --include=\\"*.js\\" --include=\\"*.mjs\\" --exclude-dir=test/fixtures . 2>/dev/null || true', {encoding:'utf8'}); if(r.trim()){console.error('vitest residue detected:\\\\n'+r); process.exit(1)} console.log('no vitest residue')"`
839
838
  },
840
839
  dependencies: {
841
840
  "@napi-rs/keyring": "^1.2.0",
@@ -850,10 +849,10 @@ var package_default = {
850
849
  "@types/js-yaml": "^4.0.9",
851
850
  "@types/node": "^24.0.0",
852
851
  "@types/pg": "^8.11.10",
852
+ "ts-morph": "28.0.0",
853
853
  tsup: "^8",
854
854
  tsx: "^4.21.0",
855
- typescript: "^5.8.0",
856
- vitest: "^2.1.0"
855
+ typescript: "^5.8.0"
857
856
  }
858
857
  };
859
858
 
@@ -4526,9 +4525,9 @@ function detectWorkItemTypeFromFm(fm) {
4526
4525
  }
4527
4526
  function detectWorkItemType(idOrPath) {
4528
4527
  const upper = idOrPath.toUpperCase();
4529
- const basename13 = upper.split("/").pop() ?? upper;
4528
+ const basename14 = upper.split("/").pop() ?? upper;
4530
4529
  for (const { prefix, type } of PREFIX_MAP2) {
4531
- if (basename13.includes(prefix)) {
4530
+ if (basename14.includes(prefix)) {
4532
4531
  return type;
4533
4532
  }
4534
4533
  }
@@ -7115,24 +7114,312 @@ function gateRunHandler(name, opts, cli) {
7115
7114
 
7116
7115
  // src/commands/sprint.ts
7117
7116
  init_cjs_shims();
7118
- var fs34 = __toESM(require("fs"), 1);
7119
- var path36 = __toESM(require("path"), 1);
7117
+ var fs35 = __toESM(require("fs"), 1);
7118
+ var path37 = __toESM(require("path"), 1);
7120
7119
  var import_node_child_process11 = require("child_process");
7121
7120
  var import_js_yaml7 = __toESM(require("js-yaml"), 1);
7122
7121
 
7123
7122
  // src/lib/lifecycle-reconcile.ts
7124
7123
  init_cjs_shims();
7124
+ var fs34 = __toESM(require("fs"), 1);
7125
+ var path36 = __toESM(require("path"), 1);
7126
+ var import_node_child_process10 = require("child_process");
7127
+
7128
+ // src/lib/parent-rollup.ts
7129
+ init_cjs_shims();
7125
7130
  var fs33 = __toESM(require("fs"), 1);
7126
7131
  var path35 = __toESM(require("path"), 1);
7127
- var import_node_child_process10 = require("child_process");
7132
+ function readFm(filePath) {
7133
+ try {
7134
+ const raw = fs33.readFileSync(filePath, "utf8");
7135
+ const { fm } = parseFrontmatter(raw);
7136
+ return fm;
7137
+ } catch {
7138
+ return null;
7139
+ }
7140
+ }
7141
+ function extractId(fm, filePath) {
7142
+ for (const key of [
7143
+ "story_id",
7144
+ "epic_id",
7145
+ "sprint_id",
7146
+ "bug_id",
7147
+ "cr_id",
7148
+ "initiative_id",
7149
+ "hotfix_id"
7150
+ ]) {
7151
+ const val = fm[key];
7152
+ if (typeof val === "string" && val.trim() !== "") return val.trim();
7153
+ }
7154
+ const stem = path35.basename(filePath, ".md");
7155
+ return stem.split("_")[0] ?? stem;
7156
+ }
7157
+ function enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache) {
7158
+ const pendingSyncDir = path35.join(deliveryRoot, "pending-sync");
7159
+ const results = [];
7160
+ const pools = [];
7161
+ if (fs33.existsSync(archiveRoot)) pools.push(archiveRoot);
7162
+ if (fs33.existsSync(pendingSyncDir)) pools.push(pendingSyncDir);
7163
+ for (const dir of pools) {
7164
+ let entries;
7165
+ try {
7166
+ entries = fs33.readdirSync(dir);
7167
+ } catch {
7168
+ entries = [];
7169
+ }
7170
+ for (const entry of entries) {
7171
+ if (!entry.endsWith(".md")) continue;
7172
+ const absPath = path35.join(dir, entry);
7173
+ let fm = fmCache.get(absPath);
7174
+ if (fm === void 0) {
7175
+ const parsed = readFm(absPath);
7176
+ if (parsed === null) continue;
7177
+ fm = parsed;
7178
+ fmCache.set(absPath, fm);
7179
+ }
7180
+ const parentCleargateId = fm["parent_cleargate_id"];
7181
+ const parentEpicRef = fm["parent_epic_ref"];
7182
+ const isChild = typeof parentCleargateId === "string" && parentCleargateId.trim() === parentId || typeof parentEpicRef === "string" && parentEpicRef.trim() === parentId;
7183
+ if (!isChild) continue;
7184
+ const childId = extractId(fm, absPath);
7185
+ const status = typeof fm["status"] === "string" ? fm["status"].trim() : "";
7186
+ results.push({ id: childId, status });
7187
+ }
7188
+ }
7189
+ return results;
7190
+ }
7191
+ async function rollUpParentStatusInternal(parentFilePath, opts, visited, fmCache) {
7192
+ const { deliveryRoot, archiveRoot } = opts;
7193
+ let fm = fmCache.get(parentFilePath);
7194
+ if (fm === void 0) {
7195
+ const raw = readFm(parentFilePath);
7196
+ if (raw === null) {
7197
+ throw new Error(`parent-rollup: cannot read frontmatter from ${parentFilePath}`);
7198
+ }
7199
+ fm = raw;
7200
+ fmCache.set(parentFilePath, fm);
7201
+ }
7202
+ const parentId = extractId(fm, parentFilePath);
7203
+ const currentStatus = typeof fm["status"] === "string" ? fm["status"].trim() : "";
7204
+ if (ARTIFACT_TERMINAL_STATUSES.has(currentStatus)) {
7205
+ return {
7206
+ parent_id: parentId,
7207
+ parent_path: parentFilePath,
7208
+ current_status: currentStatus,
7209
+ proposed_status: null,
7210
+ coverage: "full",
7211
+ terminal_children: [],
7212
+ pending_children: [],
7213
+ verdict: "no-op"
7214
+ };
7215
+ }
7216
+ if (visited.has(parentId)) {
7217
+ throw new Error(`parent-rollup: sub_epics cycle detected at ${parentId}`);
7218
+ }
7219
+ visited.add(parentId);
7220
+ const subEpicsField = fm["sub_epics"];
7221
+ const subEpics = Array.isArray(subEpicsField) && subEpicsField.length > 0 ? subEpicsField.filter((s) => typeof s === "string") : [];
7222
+ if (subEpics.length > 0) {
7223
+ const pendingSyncDir = path35.join(deliveryRoot, "pending-sync");
7224
+ const terminalSubEpics = [];
7225
+ const pendingSubEpics = [];
7226
+ for (const subEpicId of subEpics) {
7227
+ let subEpicPath = null;
7228
+ const candidateDirs = [pendingSyncDir, archiveRoot];
7229
+ for (const dir of candidateDirs) {
7230
+ if (!fs33.existsSync(dir)) continue;
7231
+ let entries;
7232
+ try {
7233
+ entries = fs33.readdirSync(dir);
7234
+ } catch {
7235
+ entries = [];
7236
+ }
7237
+ for (const entry of entries) {
7238
+ if (!entry.endsWith(".md")) continue;
7239
+ const absPath = path35.join(dir, entry);
7240
+ let subFm2 = fmCache.get(absPath);
7241
+ if (subFm2 === void 0) {
7242
+ const parsed = readFm(absPath);
7243
+ if (parsed === null) continue;
7244
+ subFm2 = parsed;
7245
+ fmCache.set(absPath, subFm2);
7246
+ }
7247
+ const entryId = extractId(subFm2, absPath);
7248
+ if (entryId === subEpicId) {
7249
+ subEpicPath = absPath;
7250
+ break;
7251
+ }
7252
+ }
7253
+ if (subEpicPath !== null) break;
7254
+ }
7255
+ if (subEpicPath === null) {
7256
+ pendingSubEpics.push(subEpicId);
7257
+ continue;
7258
+ }
7259
+ let subFm = fmCache.get(subEpicPath);
7260
+ if (subFm === void 0) {
7261
+ const parsed = readFm(subEpicPath);
7262
+ if (parsed === null) {
7263
+ pendingSubEpics.push(subEpicId);
7264
+ continue;
7265
+ }
7266
+ subFm = parsed;
7267
+ fmCache.set(subEpicPath, subFm);
7268
+ }
7269
+ const subStatus = typeof subFm["status"] === "string" ? subFm["status"].trim() : "";
7270
+ if (subStatus === "DEFERRED") {
7271
+ continue;
7272
+ }
7273
+ if (ARTIFACT_TERMINAL_STATUSES.has(subStatus)) {
7274
+ terminalSubEpics.push(subEpicId);
7275
+ continue;
7276
+ }
7277
+ const visitedSnapshot = new Set(visited);
7278
+ const subResult = await rollUpParentStatusInternal(
7279
+ subEpicPath,
7280
+ opts,
7281
+ visitedSnapshot,
7282
+ fmCache
7283
+ );
7284
+ if (subResult.verdict === "auto-flip" || subResult.verdict === "no-op") {
7285
+ terminalSubEpics.push(subEpicId);
7286
+ } else {
7287
+ pendingSubEpics.push(subEpicId);
7288
+ }
7289
+ }
7290
+ visited.delete(parentId);
7291
+ const total2 = terminalSubEpics.length + pendingSubEpics.length;
7292
+ if (total2 === 0) {
7293
+ return {
7294
+ parent_id: parentId,
7295
+ parent_path: parentFilePath,
7296
+ current_status: currentStatus,
7297
+ proposed_status: null,
7298
+ coverage: "zero",
7299
+ terminal_children: [],
7300
+ pending_children: [],
7301
+ verdict: "halt-zero-children",
7302
+ halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
7303
+ };
7304
+ }
7305
+ if (pendingSubEpics.length === 0) {
7306
+ return {
7307
+ parent_id: parentId,
7308
+ parent_path: parentFilePath,
7309
+ current_status: currentStatus,
7310
+ proposed_status: "Completed",
7311
+ coverage: "full",
7312
+ terminal_children: terminalSubEpics,
7313
+ pending_children: [],
7314
+ verdict: "auto-flip"
7315
+ };
7316
+ }
7317
+ return {
7318
+ parent_id: parentId,
7319
+ parent_path: parentFilePath,
7320
+ current_status: currentStatus,
7321
+ proposed_status: null,
7322
+ coverage: "sub-epic-partial",
7323
+ terminal_children: terminalSubEpics,
7324
+ pending_children: pendingSubEpics,
7325
+ verdict: "halt-partial",
7326
+ halt_reason: `${parentId}: ${terminalSubEpics.length}/${total2} sub-epics terminal \u2014 pending: ${pendingSubEpics.join(", ")}`
7327
+ };
7328
+ }
7329
+ const children = enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache);
7330
+ visited.delete(parentId);
7331
+ if (children.length === 0) {
7332
+ return {
7333
+ parent_id: parentId,
7334
+ parent_path: parentFilePath,
7335
+ current_status: currentStatus,
7336
+ proposed_status: null,
7337
+ coverage: "zero",
7338
+ terminal_children: [],
7339
+ pending_children: [],
7340
+ verdict: "halt-zero-children",
7341
+ halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
7342
+ };
7343
+ }
7344
+ const terminalChildren = [];
7345
+ const pendingChildren = [];
7346
+ for (const child of children) {
7347
+ if (ARTIFACT_TERMINAL_STATUSES.has(child.status)) {
7348
+ terminalChildren.push(child.id);
7349
+ } else {
7350
+ pendingChildren.push(child.id);
7351
+ }
7352
+ }
7353
+ const total = terminalChildren.length + pendingChildren.length;
7354
+ if (pendingChildren.length === 0) {
7355
+ return {
7356
+ parent_id: parentId,
7357
+ parent_path: parentFilePath,
7358
+ current_status: currentStatus,
7359
+ proposed_status: "Completed",
7360
+ coverage: "full",
7361
+ terminal_children: terminalChildren,
7362
+ pending_children: [],
7363
+ verdict: "auto-flip"
7364
+ };
7365
+ }
7366
+ return {
7367
+ parent_id: parentId,
7368
+ parent_path: parentFilePath,
7369
+ current_status: currentStatus,
7370
+ proposed_status: null,
7371
+ coverage: "partial",
7372
+ terminal_children: terminalChildren,
7373
+ pending_children: pendingChildren,
7374
+ verdict: "halt-partial",
7375
+ halt_reason: `${parentId}: ${terminalChildren.length}/${total} children terminal \u2014 pending: ${pendingChildren.join(", ")}`
7376
+ };
7377
+ }
7378
+ async function walkActiveParents(opts) {
7379
+ const { deliveryRoot } = opts;
7380
+ const pendingSyncDir = path35.join(deliveryRoot, "pending-sync");
7381
+ let entries;
7382
+ try {
7383
+ entries = fs33.readdirSync(pendingSyncDir);
7384
+ } catch {
7385
+ return [];
7386
+ }
7387
+ const parentFiles = entries.filter(
7388
+ (e) => e.endsWith(".md") && (e.startsWith("EPIC-") || e.startsWith("SPRINT-"))
7389
+ );
7390
+ const results = [];
7391
+ const fmCache = /* @__PURE__ */ new Map();
7392
+ for (const entry of parentFiles) {
7393
+ const absPath = path35.join(pendingSyncDir, entry);
7394
+ try {
7395
+ const visited = /* @__PURE__ */ new Set();
7396
+ const result = await rollUpParentStatusInternal(absPath, opts, visited, fmCache);
7397
+ results.push(result);
7398
+ } catch (err) {
7399
+ if (err instanceof Error && err.message.includes("sub_epics cycle detected")) {
7400
+ throw err;
7401
+ }
7402
+ }
7403
+ }
7404
+ return results;
7405
+ }
7406
+
7407
+ // src/lib/lifecycle-reconcile.ts
7408
+ var ARTIFACT_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
7409
+ "Completed",
7410
+ "Abandoned",
7411
+ "Closed",
7412
+ "Resolved"
7413
+ ]);
7414
+ var ARTIFACT_GATE_EXPECTED = ["Completed"];
7128
7415
  var VERB_STATUS_MAP = {
7129
7416
  feat: {
7130
7417
  types: ["STORY", "EPIC", "CR"],
7131
- expected: ["Done", "Completed"]
7418
+ expected: [...ARTIFACT_GATE_EXPECTED]
7132
7419
  },
7133
7420
  fix: {
7134
7421
  types: ["BUG", "HOTFIX"],
7135
- expected: ["Verified", "Done", "Completed"]
7422
+ expected: [...ARTIFACT_GATE_EXPECTED]
7136
7423
  }
7137
7424
  };
7138
7425
  var ID_PATTERN = /\b(STORY-\d{3}-\d{2}|(CR|BUG|EPIC|HOTFIX)-\d{3}|(PROPOSAL|PROP)-\d{3})\b/g;
@@ -7183,10 +7470,10 @@ function findArtifactFile(deliveryRoot, id) {
7183
7470
  { rel: "archive", inArchive: true }
7184
7471
  ];
7185
7472
  for (const { rel, inArchive } of dirs) {
7186
- const dir = path35.join(deliveryRoot, rel);
7473
+ const dir = path36.join(deliveryRoot, rel);
7187
7474
  let entries;
7188
7475
  try {
7189
- entries = fs33.readdirSync(dir);
7476
+ entries = fs34.readdirSync(dir);
7190
7477
  } catch {
7191
7478
  continue;
7192
7479
  }
@@ -7194,7 +7481,7 @@ function findArtifactFile(deliveryRoot, id) {
7194
7481
  (e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith(".md")
7195
7482
  );
7196
7483
  if (match) {
7197
- const absPath = path35.join(dir, match);
7484
+ const absPath = path36.join(dir, match);
7198
7485
  return { absPath, inArchive, relPath: `${rel}/${match}` };
7199
7486
  }
7200
7487
  }
@@ -7203,7 +7490,7 @@ function findArtifactFile(deliveryRoot, id) {
7203
7490
  function readArtifactStatus(absPath) {
7204
7491
  let raw;
7205
7492
  try {
7206
- raw = fs33.readFileSync(absPath, "utf8");
7493
+ raw = fs34.readFileSync(absPath, "utf8");
7207
7494
  } catch {
7208
7495
  return { status: null, carryOver: false };
7209
7496
  }
@@ -7258,7 +7545,7 @@ function reconcileLifecycle(opts) {
7258
7545
  if (carryOver) continue;
7259
7546
  let expectedStatuses;
7260
7547
  if (verb === "feat" && type === "BUG") {
7261
- expectedStatuses = ["Verified", "Done", "Completed"];
7548
+ expectedStatuses = [...ARTIFACT_GATE_EXPECTED];
7262
7549
  } else if (!verbConfig.types.includes(type)) {
7263
7550
  continue;
7264
7551
  } else {
@@ -7270,7 +7557,7 @@ function reconcileLifecycle(opts) {
7270
7557
  cleanIds.add(id);
7271
7558
  idToItem.delete(id);
7272
7559
  } else if (!idToItem.has(id)) {
7273
- const expectedStr = expectedStatuses[0] ?? "Done";
7560
+ const expectedStr = expectedStatuses[0] ?? "Completed";
7274
7561
  idToItem.set(id, {
7275
7562
  id,
7276
7563
  type,
@@ -7300,7 +7587,7 @@ function reconcileDecomposition(opts) {
7300
7587
  const { sprintPlanPath, deliveryRoot } = opts;
7301
7588
  let raw;
7302
7589
  try {
7303
- raw = fs33.readFileSync(sprintPlanPath, "utf8");
7590
+ raw = fs34.readFileSync(sprintPlanPath, "utf8");
7304
7591
  } catch {
7305
7592
  return { missing: [], clean: 0 };
7306
7593
  }
@@ -7312,11 +7599,11 @@ function reconcileDecomposition(opts) {
7312
7599
  }
7313
7600
  const epics = Array.isArray(fm["epics"]) ? fm["epics"].map(String) : [];
7314
7601
  const proposals = Array.isArray(fm["proposals"]) ? fm["proposals"].map(String) : [];
7315
- const pendingDir = path35.join(deliveryRoot, "pending-sync");
7316
- const archiveDir = path35.join(deliveryRoot, "archive");
7602
+ const pendingDir = path36.join(deliveryRoot, "pending-sync");
7603
+ const archiveDir = path36.join(deliveryRoot, "archive");
7317
7604
  function listMdFiles(dir) {
7318
7605
  try {
7319
- return fs33.readdirSync(dir).filter((f) => f.endsWith(".md"));
7606
+ return fs34.readdirSync(dir).filter((f) => f.endsWith(".md"));
7320
7607
  } catch {
7321
7608
  return [];
7322
7609
  }
@@ -7388,9 +7675,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
7388
7675
  for (const f of files) {
7389
7676
  if (!f.startsWith(storyPrefix) && !f.startsWith("STORY-")) continue;
7390
7677
  if (!f.includes(storyPrefix)) continue;
7391
- const absPath = path35.join(dir, f);
7678
+ const absPath = path36.join(dir, f);
7392
7679
  try {
7393
- const raw = fs33.readFileSync(absPath, "utf8");
7680
+ const raw = fs34.readFileSync(absPath, "utf8");
7394
7681
  const { fm } = parseFrontmatter(raw);
7395
7682
  const parentRef = fm["parent_epic_ref"];
7396
7683
  if (parentRef === epicId) {
@@ -7405,9 +7692,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
7405
7692
  function findDecomposedEpic(proposalId, pendingDir, pendingFiles) {
7406
7693
  for (const f of pendingFiles) {
7407
7694
  if (!f.startsWith("EPIC-")) continue;
7408
- const absPath = path35.join(pendingDir, f);
7695
+ const absPath = path36.join(pendingDir, f);
7409
7696
  try {
7410
- const raw = fs33.readFileSync(absPath, "utf8");
7697
+ const raw = fs34.readFileSync(absPath, "utf8");
7411
7698
  const { fm } = parseFrontmatter(raw);
7412
7699
  const contextSource = fm["context_source"];
7413
7700
  if (typeof contextSource === "string" && contextSource.includes(proposalId)) {
@@ -7433,7 +7720,7 @@ var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandone
7433
7720
  function resolveRunScript(opts) {
7434
7721
  if (opts.runScriptPath) return opts.runScriptPath;
7435
7722
  const cwd = opts.cwd ?? process.cwd();
7436
- return path36.join(cwd, ".cleargate", "scripts", "run_script.sh");
7723
+ return path37.join(cwd, ".cleargate", "scripts", "run_script.sh");
7437
7724
  }
7438
7725
  function defaultExit(code) {
7439
7726
  return process.exit(code);
@@ -7452,18 +7739,18 @@ function sprintInitHandler(opts, cli) {
7452
7739
  if (mode === "v1") {
7453
7740
  return printInertAndExit(stdoutFn, exitFn);
7454
7741
  }
7455
- const deliveryRoot = path36.join(cwd, ".cleargate", "delivery");
7742
+ const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
7456
7743
  let lifecycleInitMode = "warn";
7457
7744
  let sprintPlanPath = null;
7458
- const pendingDir = path36.join(deliveryRoot, "pending-sync");
7745
+ const pendingDir = path37.join(deliveryRoot, "pending-sync");
7459
7746
  try {
7460
- const entries = fs34.readdirSync(pendingDir);
7747
+ const entries = fs35.readdirSync(pendingDir);
7461
7748
  const sprintFile = entries.find(
7462
7749
  (e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
7463
7750
  );
7464
7751
  if (sprintFile) {
7465
- sprintPlanPath = path36.join(pendingDir, sprintFile);
7466
- const raw = fs34.readFileSync(sprintPlanPath, "utf8");
7752
+ sprintPlanPath = path37.join(pendingDir, sprintFile);
7753
+ const raw = fs35.readFileSync(sprintPlanPath, "utf8");
7467
7754
  const { fm } = parseFileFrontmatter(raw);
7468
7755
  if (fm["lifecycle_init_mode"] === "block") {
7469
7756
  lifecycleInitMode = "block";
@@ -7497,7 +7784,7 @@ function sprintInitHandler(opts, cli) {
7497
7784
  stderrFn("[cleargate sprint init] lifecycle drift waived via --allow-drift flag");
7498
7785
  if (sprintPlanPath) {
7499
7786
  try {
7500
- const rawSprint = fs34.readFileSync(sprintPlanPath, "utf8");
7787
+ const rawSprint = fs35.readFileSync(sprintPlanPath, "utf8");
7501
7788
  const { fm, body } = parseFileFrontmatter(rawSprint);
7502
7789
  const waiverLine = `lifecycle waiver: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} for ${lifecycleResult.drift.map((d) => d.id).join(", ")}`;
7503
7790
  const currentContextSource = typeof fm["context_source"] === "string" ? fm["context_source"] : "";
@@ -7594,20 +7881,20 @@ function reconcileLifecycleCliHandler(opts, cli) {
7594
7881
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
7595
7882
  const exitFn = cli?.exit ?? defaultExit;
7596
7883
  const cwd = cli?.cwd ?? process.cwd();
7597
- const deliveryRoot = path36.join(cwd, ".cleargate", "delivery");
7884
+ const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
7598
7885
  let since;
7599
7886
  let until;
7600
7887
  if (opts.since) {
7601
7888
  since = new Date(opts.since);
7602
7889
  } else {
7603
7890
  try {
7604
- const pendingDir = path36.join(deliveryRoot, "pending-sync");
7605
- const entries = fs34.readdirSync(pendingDir);
7891
+ const pendingDir = path37.join(deliveryRoot, "pending-sync");
7892
+ const entries = fs35.readdirSync(pendingDir);
7606
7893
  const sprintFile = entries.find(
7607
7894
  (e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
7608
7895
  );
7609
7896
  if (sprintFile) {
7610
- const raw = fs34.readFileSync(path36.join(pendingDir, sprintFile), "utf8");
7897
+ const raw = fs35.readFileSync(path37.join(pendingDir, sprintFile), "utf8");
7611
7898
  const { fm } = parseFileFrontmatter(raw);
7612
7899
  const startDate = fm["start_date"];
7613
7900
  since = typeof startDate === "string" ? new Date(startDate) : new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
@@ -7629,22 +7916,50 @@ function reconcileLifecycleCliHandler(opts, cli) {
7629
7916
  });
7630
7917
  if (result.drift.length === 0) {
7631
7918
  stdoutFn(`lifecycle: clean (${result.clean} artifacts reconciled)`);
7632
- return exitFn(0);
7633
- }
7634
- stderrFn(`lifecycle: DRIFT detected (${result.drift.length} unreconciled artifacts):`);
7635
- for (const item of result.drift) {
7636
- stderrFn(
7637
- ` DRIFT: ${item.id} status=${item.actual_status ?? "missing"} in ${item.in_archive ? "archive" : "pending-sync"}, expected ${item.expected_status} (commit ${item.commit_shas[0] ?? "unknown"})`
7638
- );
7639
- stderrFn(
7640
- ` Remediation: git mv .cleargate/delivery/pending-sync/${item.file_path?.replace("pending-sync/", "") ?? item.id + "_*.md"} .cleargate/delivery/archive/ && update status: ${item.expected_status}`
7641
- );
7919
+ } else {
7920
+ stderrFn(`lifecycle: DRIFT detected (${result.drift.length} unreconciled artifacts):`);
7921
+ for (const item of result.drift) {
7922
+ stderrFn(
7923
+ ` DRIFT: ${item.id} status=${item.actual_status ?? "missing"} in ${item.in_archive ? "archive" : "pending-sync"}, expected ${item.expected_status} (commit ${item.commit_shas[0] ?? "unknown"})`
7924
+ );
7925
+ stderrFn(
7926
+ ` Remediation: git mv .cleargate/delivery/pending-sync/${item.file_path?.replace("pending-sync/", "") ?? item.id + "_*.md"} .cleargate/delivery/archive/ && update status: ${item.expected_status}`
7927
+ );
7928
+ }
7929
+ if (!opts.parents) {
7930
+ return exitFn(1);
7931
+ }
7642
7932
  }
7643
- return exitFn(1);
7644
7933
  } catch (err) {
7645
7934
  stderrFn(`lifecycle reconciliation error: ${err instanceof Error ? err.message : String(err)}`);
7646
- return exitFn(1);
7935
+ if (!opts.parents) {
7936
+ return exitFn(1);
7937
+ }
7647
7938
  }
7939
+ if (opts.parents) {
7940
+ const archiveRoot = path37.join(deliveryRoot, "archive");
7941
+ walkActiveParents({ deliveryRoot, archiveRoot }).then((results) => {
7942
+ stdoutFn("Parent rollup audit (--parents):");
7943
+ for (const r of results) {
7944
+ if (r.verdict === "auto-flip") {
7945
+ stdoutFn(
7946
+ ` ${r.parent_id} \u2713 proposed: Completed (${r.terminal_children.length}/${r.terminal_children.length} children Completed)`
7947
+ );
7948
+ } else if (r.verdict === "halt-partial" || r.verdict === "halt-zero-children") {
7949
+ stdoutFn(` ${r.parent_id} \u2717 ${r.verdict}: ${r.halt_reason ?? "no details"}`);
7950
+ } else if (r.verdict === "no-op") {
7951
+ } else {
7952
+ stdoutFn(` ${r.parent_id} ~ ${r.verdict}`);
7953
+ }
7954
+ }
7955
+ exitFn(0);
7956
+ }).catch((err) => {
7957
+ stderrFn(`--parents audit error: ${err instanceof Error ? err.message : String(err)}`);
7958
+ exitFn(0);
7959
+ });
7960
+ return;
7961
+ }
7962
+ return exitFn(0);
7648
7963
  }
7649
7964
  function parseFileFrontmatter(raw) {
7650
7965
  const lines = raw.split("\n");
@@ -7688,8 +8003,8 @@ ${body}`;
7688
8003
  }
7689
8004
  function atomicWriteStr(filePath, content) {
7690
8005
  const tmp = `${filePath}.tmp.${process.pid}`;
7691
- fs34.writeFileSync(tmp, content, "utf8");
7692
- fs34.renameSync(tmp, filePath);
8006
+ fs35.writeFileSync(tmp, content, "utf8");
8007
+ fs35.renameSync(tmp, filePath);
7693
8008
  }
7694
8009
  function deriveSprintBranchForArchive(sprintId) {
7695
8010
  const match = /^SPRINT-(\d+)/.exec(sprintId);
@@ -7703,7 +8018,7 @@ function stampFile(raw, status, completedAt) {
7703
8018
  return serializeFileContent(fm, body);
7704
8019
  }
7705
8020
  function stampSprintClose(sprintPath, now) {
7706
- const previousContent = fs34.readFileSync(sprintPath, "utf8");
8021
+ const previousContent = fs35.readFileSync(sprintPath, "utf8");
7707
8022
  const { fm, body } = parseFileFrontmatter(previousContent);
7708
8023
  const currentStatus = typeof fm["status"] === "string" ? fm["status"] : "";
7709
8024
  const alreadyTerminal = TERMINAL_STATUSES2.has(currentStatus);
@@ -7763,14 +8078,14 @@ async function sprintArchiveHandler(opts, cli) {
7763
8078
  if (mode === "v1") {
7764
8079
  return printInertAndExit(stdoutFn, exitFn);
7765
8080
  }
7766
- const stateFile = path36.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
7767
- if (!fs34.existsSync(stateFile)) {
8081
+ const stateFile = path37.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
8082
+ if (!fs35.existsSync(stateFile)) {
7768
8083
  stderrFn(`[cleargate sprint archive] state.json not found at ${stateFile}`);
7769
8084
  return exitFn(1);
7770
8085
  }
7771
8086
  let state2;
7772
8087
  try {
7773
- state2 = JSON.parse(fs34.readFileSync(stateFile, "utf8"));
8088
+ state2 = JSON.parse(fs35.readFileSync(stateFile, "utf8"));
7774
8089
  } catch (err) {
7775
8090
  stderrFn(`[cleargate sprint archive] failed to parse state.json: ${err.message}`);
7776
8091
  return exitFn(1);
@@ -7782,18 +8097,18 @@ async function sprintArchiveHandler(opts, cli) {
7782
8097
  return exitFn(1);
7783
8098
  }
7784
8099
  const stateStories = state2.stories ?? {};
7785
- const pendingDir = path36.join(cwd, ".cleargate", "delivery", "pending-sync");
7786
- const archiveDir = path36.join(cwd, ".cleargate", "delivery", "archive");
8100
+ const pendingDir = path37.join(cwd, ".cleargate", "delivery", "pending-sync");
8101
+ const archiveDir = path37.join(cwd, ".cleargate", "delivery", "archive");
7787
8102
  let sprintFile = null;
7788
- for (const entry of fs34.readdirSync(pendingDir)) {
8103
+ for (const entry of fs35.readdirSync(pendingDir)) {
7789
8104
  if ((entry.startsWith(`${opts.sprintId}_`) || entry === `${opts.sprintId}.md`) && entry.endsWith(".md")) {
7790
- sprintFile = path36.join(pendingDir, entry);
8105
+ sprintFile = path37.join(pendingDir, entry);
7791
8106
  break;
7792
8107
  }
7793
8108
  }
7794
8109
  let epicIds = [];
7795
- if (sprintFile && fs34.existsSync(sprintFile)) {
7796
- const { fm } = parseFileFrontmatter(fs34.readFileSync(sprintFile, "utf8"));
8110
+ if (sprintFile && fs35.existsSync(sprintFile)) {
8111
+ const { fm } = parseFileFrontmatter(fs35.readFileSync(sprintFile, "utf8"));
7797
8112
  const epics = fm["epics"];
7798
8113
  if (Array.isArray(epics)) {
7799
8114
  epicIds = epics.map(String);
@@ -7803,15 +8118,15 @@ async function sprintArchiveHandler(opts, cli) {
7803
8118
  if (sprintFile) {
7804
8119
  plan.push({
7805
8120
  src: sprintFile,
7806
- destName: path36.basename(sprintFile),
8121
+ destName: path37.basename(sprintFile),
7807
8122
  status: "Completed"
7808
8123
  });
7809
8124
  }
7810
8125
  for (const epicId of epicIds) {
7811
- for (const entry of fs34.readdirSync(pendingDir)) {
8126
+ for (const entry of fs35.readdirSync(pendingDir)) {
7812
8127
  if ((entry.startsWith(`${epicId}_`) || entry === `${epicId}.md`) && entry.endsWith(".md")) {
7813
8128
  plan.push({
7814
- src: path36.join(pendingDir, entry),
8129
+ src: path37.join(pendingDir, entry),
7815
8130
  destName: entry,
7816
8131
  status: "Approved"
7817
8132
  });
@@ -7820,10 +8135,10 @@ async function sprintArchiveHandler(opts, cli) {
7820
8135
  }
7821
8136
  const storyKeys = storyKeysForEpic(stateStories, epicId);
7822
8137
  for (const storyId of storyKeys) {
7823
- for (const entry of fs34.readdirSync(pendingDir)) {
8138
+ for (const entry of fs35.readdirSync(pendingDir)) {
7824
8139
  if ((entry.startsWith(`${storyId}_`) || entry === `${storyId}.md`) && entry.endsWith(".md")) {
7825
8140
  plan.push({
7826
- src: path36.join(pendingDir, entry),
8141
+ src: path37.join(pendingDir, entry),
7827
8142
  destName: entry,
7828
8143
  status: "Done"
7829
8144
  });
@@ -7835,13 +8150,13 @@ async function sprintArchiveHandler(opts, cli) {
7835
8150
  const storyIdsInState = new Set(Object.keys(stateStories));
7836
8151
  const planSrcs = new Set(plan.map((p) => p.src));
7837
8152
  const orphans = [];
7838
- for (const entry of fs34.readdirSync(pendingDir)) {
8153
+ for (const entry of fs35.readdirSync(pendingDir)) {
7839
8154
  if (!entry.startsWith("STORY-") || !entry.endsWith(".md")) continue;
7840
- const candidate = path36.join(pendingDir, entry);
8155
+ const candidate = path37.join(pendingDir, entry);
7841
8156
  if (planSrcs.has(candidate)) continue;
7842
8157
  let raw;
7843
8158
  try {
7844
- raw = fs34.readFileSync(candidate, "utf8");
8159
+ raw = fs35.readFileSync(candidate, "utf8");
7845
8160
  } catch {
7846
8161
  continue;
7847
8162
  }
@@ -7858,18 +8173,18 @@ async function sprintArchiveHandler(opts, cli) {
7858
8173
  }
7859
8174
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
7860
8175
  const sprintBranch = deriveSprintBranchForArchive(opts.sprintId);
7861
- const activePath = path36.join(cwd, ".cleargate", "sprint-runs", ".active");
8176
+ const activePath = path37.join(cwd, ".cleargate", "sprint-runs", ".active");
7862
8177
  if (opts.dryRun) {
7863
8178
  stdoutFn(`[dry-run] Sprint archive plan for ${opts.sprintId}:`);
7864
8179
  stdoutFn(` Sprint branch: ${sprintBranch}`);
7865
8180
  stdoutFn(` Files to archive (${plan.length}):`);
7866
8181
  for (const entry of plan) {
7867
8182
  stdoutFn(
7868
- ` ${path36.basename(entry.src)} \u2192 archive/${entry.destName} [stamp: status=${entry.status}, completed_at=<now>]`
8183
+ ` ${path37.basename(entry.src)} \u2192 archive/${entry.destName} [stamp: status=${entry.status}, completed_at=<now>]`
7869
8184
  );
7870
8185
  }
7871
8186
  if (orphans.length > 0) {
7872
- stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) => path36.basename(o)).join(", ")}`);
8187
+ stdoutFn(` Orphan files (${orphans.length}): ${orphans.map((o) => path37.basename(o)).join(", ")}`);
7873
8188
  }
7874
8189
  stdoutFn(` .active \u2192 "" (truncate)`);
7875
8190
  stdoutFn(` git checkout main`);
@@ -7878,9 +8193,9 @@ async function sprintArchiveHandler(opts, cli) {
7878
8193
  return exitFn(0);
7879
8194
  }
7880
8195
  let sprintFileSnapshot = null;
7881
- const wikiRoot = path36.join(cwd, ".cleargate", "wiki");
7882
- const wikiInitialised = fs34.existsSync(wikiRoot);
7883
- if (sprintFile && fs34.existsSync(sprintFile)) {
8196
+ const wikiRoot = path37.join(cwd, ".cleargate", "wiki");
8197
+ const wikiInitialised = fs35.existsSync(wikiRoot);
8198
+ if (sprintFile && fs35.existsSync(sprintFile)) {
7884
8199
  const { previousContent } = stampSprintClose(sprintFile, () => completedAt);
7885
8200
  sprintFileSnapshot = previousContent;
7886
8201
  if (wikiInitialised) {
@@ -7907,15 +8222,15 @@ async function sprintArchiveHandler(opts, cli) {
7907
8222
  }
7908
8223
  }
7909
8224
  for (const entry of plan) {
7910
- if (!fs34.existsSync(entry.src)) {
8225
+ if (!fs35.existsSync(entry.src)) {
7911
8226
  stderrFn(`[cleargate sprint archive] source not found: ${entry.src} \u2014 skipping`);
7912
8227
  continue;
7913
8228
  }
7914
- const raw = fs34.readFileSync(entry.src, "utf8");
8229
+ const raw = fs35.readFileSync(entry.src, "utf8");
7915
8230
  const stamped = stampFile(raw, entry.status, completedAt);
7916
- const dest = path36.join(archiveDir, entry.destName);
8231
+ const dest = path37.join(archiveDir, entry.destName);
7917
8232
  atomicWriteStr(entry.src, stamped);
7918
- fs34.renameSync(entry.src, dest);
8233
+ fs35.renameSync(entry.src, dest);
7919
8234
  stdoutFn(`archived: ${entry.destName}`);
7920
8235
  }
7921
8236
  try {
@@ -7974,14 +8289,14 @@ function checkPrevSprintCompleted(sprintId, cwd) {
7974
8289
  const prevNum = sprintNum - 1;
7975
8290
  const prevId = `SPRINT-${String(prevNum).padStart(2, "0")}`;
7976
8291
  const prevIdAlt = `SPRINT-${prevNum}`;
7977
- const sprintRunsBase = path36.join(cwd, ".cleargate", "sprint-runs");
8292
+ const sprintRunsBase = path37.join(cwd, ".cleargate", "sprint-runs");
7978
8293
  let stateJson = null;
7979
8294
  let resolvedPrevId = prevId;
7980
8295
  for (const pid of [prevId, prevIdAlt]) {
7981
- const stateFile = path36.join(sprintRunsBase, pid, "state.json");
7982
- if (fs34.existsSync(stateFile)) {
8296
+ const stateFile = path37.join(sprintRunsBase, pid, "state.json");
8297
+ if (fs35.existsSync(stateFile)) {
7983
8298
  try {
7984
- const raw = fs34.readFileSync(stateFile, "utf8");
8299
+ const raw = fs35.readFileSync(stateFile, "utf8");
7985
8300
  stateJson = JSON.parse(raw);
7986
8301
  resolvedPrevId = pid;
7987
8302
  break;
@@ -8084,21 +8399,21 @@ function checkMainClean(cwd, execFn) {
8084
8399
  }
8085
8400
  function findSprintFile(sprintId, cwd) {
8086
8401
  const searchDirs = [
8087
- path36.join(cwd, ".cleargate", "delivery", "pending-sync"),
8088
- path36.join(cwd, ".cleargate", "delivery", "archive")
8402
+ path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
8403
+ path37.join(cwd, ".cleargate", "delivery", "archive")
8089
8404
  ];
8090
8405
  for (const dir of searchDirs) {
8091
- if (!fs34.existsSync(dir)) continue;
8406
+ if (!fs35.existsSync(dir)) continue;
8092
8407
  let entries;
8093
8408
  try {
8094
- entries = fs34.readdirSync(dir);
8409
+ entries = fs35.readdirSync(dir);
8095
8410
  } catch {
8096
8411
  continue;
8097
8412
  }
8098
8413
  const prefix = `${sprintId}_`;
8099
8414
  for (const entry of entries) {
8100
8415
  if ((entry.startsWith(prefix) || entry === `${sprintId}.md`) && entry.endsWith(".md")) {
8101
- return path36.join(dir, entry);
8416
+ return path37.join(dir, entry);
8102
8417
  }
8103
8418
  }
8104
8419
  }
@@ -8106,26 +8421,26 @@ function findSprintFile(sprintId, cwd) {
8106
8421
  }
8107
8422
  function findWorkItemFileLocal(cwd, workItemId) {
8108
8423
  const searchDirs = [
8109
- path36.join(cwd, ".cleargate", "delivery", "pending-sync"),
8110
- path36.join(cwd, ".cleargate", "delivery", "archive")
8424
+ path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
8425
+ path37.join(cwd, ".cleargate", "delivery", "archive")
8111
8426
  ];
8112
8427
  const prefix = `${workItemId}_`;
8113
8428
  for (const dir of searchDirs) {
8114
8429
  let entries;
8115
8430
  try {
8116
- entries = fs34.readdirSync(dir);
8431
+ entries = fs35.readdirSync(dir);
8117
8432
  } catch {
8118
8433
  continue;
8119
8434
  }
8120
8435
  const match = entries.find((e) => (e.startsWith(prefix) || e === `${workItemId}.md`) && e.endsWith(".md"));
8121
- if (match) return path36.join(dir, match);
8436
+ if (match) return path37.join(dir, match);
8122
8437
  }
8123
8438
  return null;
8124
8439
  }
8125
8440
  function readCachedGateSync(absPath) {
8126
8441
  let raw;
8127
8442
  try {
8128
- raw = fs34.readFileSync(absPath, "utf8");
8443
+ raw = fs35.readFileSync(absPath, "utf8");
8129
8444
  } catch {
8130
8445
  return null;
8131
8446
  }
@@ -8149,7 +8464,7 @@ function readCachedGateSync(absPath) {
8149
8464
  return null;
8150
8465
  }
8151
8466
  function extractInScopeWorkItemIds(sprintFilePath, cwd, execFn) {
8152
- const scriptPath = path36.join(cwd, ".cleargate", "scripts", "assert_story_files.mjs");
8467
+ const scriptPath = path37.join(cwd, ".cleargate", "scripts", "assert_story_files.mjs");
8153
8468
  const cmd = `node "${scriptPath}" "${sprintFilePath}" --emit-json`;
8154
8469
  let stdout;
8155
8470
  try {
@@ -8201,7 +8516,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
8201
8516
  let fm;
8202
8517
  let raw;
8203
8518
  try {
8204
- raw = fs34.readFileSync(absPath, "utf8");
8519
+ raw = fs35.readFileSync(absPath, "utf8");
8205
8520
  ({ fm } = parseFrontmatter(raw));
8206
8521
  } catch {
8207
8522
  totalChecked++;
@@ -8245,7 +8560,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
8245
8560
  let sprintRaw;
8246
8561
  let sprintFm = {};
8247
8562
  try {
8248
- sprintRaw = fs34.readFileSync(sprintFilePath, "utf8");
8563
+ sprintRaw = fs35.readFileSync(sprintFilePath, "utf8");
8249
8564
  ({ fm: sprintFm } = parseFrontmatter(sprintRaw));
8250
8565
  } catch {
8251
8566
  }
@@ -8301,13 +8616,13 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
8301
8616
  if (!absPath) {
8302
8617
  continue;
8303
8618
  }
8304
- if (absPath.includes(`${path36.sep}archive${path36.sep}`)) {
8619
+ if (absPath.includes(`${path37.sep}archive${path37.sep}`)) {
8305
8620
  result.skipped.push(id);
8306
8621
  continue;
8307
8622
  }
8308
8623
  let status = "";
8309
8624
  try {
8310
- const raw = fs34.readFileSync(absPath, "utf8");
8625
+ const raw = fs35.readFileSync(absPath, "utf8");
8311
8626
  const { fm } = parseFrontmatter(raw);
8312
8627
  status = String(fm["status"] ?? "");
8313
8628
  } catch {
@@ -8380,8 +8695,8 @@ function sprintPreflightHandler(opts, cli) {
8380
8695
 
8381
8696
  // src/commands/story.ts
8382
8697
  init_cjs_shims();
8383
- var fs35 = __toESM(require("fs"), 1);
8384
- var path37 = __toESM(require("path"), 1);
8698
+ var fs36 = __toESM(require("fs"), 1);
8699
+ var path38 = __toESM(require("path"), 1);
8385
8700
  var import_node_child_process12 = require("child_process");
8386
8701
  function defaultExit2(code) {
8387
8702
  return process.exit(code);
@@ -8389,7 +8704,7 @@ function defaultExit2(code) {
8389
8704
  function resolveRunScript2(opts) {
8390
8705
  if (opts.runScriptPath) return opts.runScriptPath;
8391
8706
  const cwd = opts.cwd ?? process.cwd();
8392
- return path37.join(cwd, ".cleargate", "scripts", "run_script.sh");
8707
+ return path38.join(cwd, ".cleargate", "scripts", "run_script.sh");
8393
8708
  }
8394
8709
  function deriveSprintBranch(sprintId) {
8395
8710
  const match = /^SPRINT-(\d+)/.exec(sprintId);
@@ -8398,11 +8713,11 @@ function deriveSprintBranch(sprintId) {
8398
8713
  }
8399
8714
  function atomicWriteString(filePath, text) {
8400
8715
  const tmpFile = `${filePath}.tmp.${process.pid}`;
8401
- fs35.writeFileSync(tmpFile, text, "utf8");
8402
- fs35.renameSync(tmpFile, filePath);
8716
+ fs36.writeFileSync(tmpFile, text, "utf8");
8717
+ fs36.renameSync(tmpFile, filePath);
8403
8718
  }
8404
8719
  function stateJsonPath(cwd, sprintId) {
8405
- return path37.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
8720
+ return path38.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
8406
8721
  }
8407
8722
  function storyStartHandler(opts, cli) {
8408
8723
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -8419,7 +8734,7 @@ function storyStartHandler(opts, cli) {
8419
8734
  return printInertAndExit(stdoutFn, exitFn);
8420
8735
  }
8421
8736
  const sprintBranch = deriveSprintBranch(sprintId);
8422
- const worktreePath = path37.join(cwd, ".worktrees", opts.storyId);
8737
+ const worktreePath = path38.join(cwd, ".worktrees", opts.storyId);
8423
8738
  const storyBranch = `story/${opts.storyId}`;
8424
8739
  const step1 = spawnFn(
8425
8740
  "git",
@@ -8452,13 +8767,13 @@ function storyStartHandler(opts, cli) {
8452
8767
  return exitFn(step2.status ?? 1);
8453
8768
  }
8454
8769
  const stateFile = stateJsonPath(cwd, sprintId);
8455
- if (!fs35.existsSync(stateFile)) {
8770
+ if (!fs36.existsSync(stateFile)) {
8456
8771
  stderrFn(`[cleargate story start] step 3: state.json not found at ${stateFile}`);
8457
8772
  return exitFn(1);
8458
8773
  }
8459
8774
  let state2;
8460
8775
  try {
8461
- state2 = JSON.parse(fs35.readFileSync(stateFile, "utf8"));
8776
+ state2 = JSON.parse(fs36.readFileSync(stateFile, "utf8"));
8462
8777
  } catch (err) {
8463
8778
  stderrFn(`[cleargate story start] step 3: failed to parse state.json: ${err.message}`);
8464
8779
  return exitFn(1);
@@ -8499,7 +8814,7 @@ function storyCompleteHandler(opts, cli) {
8499
8814
  }
8500
8815
  const sprintBranch = deriveSprintBranch(sprintId);
8501
8816
  const storyBranch = `story/${opts.storyId}`;
8502
- const worktreeRel = path37.join(".worktrees", opts.storyId);
8817
+ const worktreeRel = path38.join(".worktrees", opts.storyId);
8503
8818
  const step1 = spawnFn(
8504
8819
  "git",
8505
8820
  ["rev-list", "--count", `${sprintBranch}..${storyBranch}`],
@@ -8598,7 +8913,7 @@ function storyCompleteHandler(opts, cli) {
8598
8913
 
8599
8914
  // src/commands/state.ts
8600
8915
  init_cjs_shims();
8601
- var path38 = __toESM(require("path"), 1);
8916
+ var path39 = __toESM(require("path"), 1);
8602
8917
  var import_node_child_process13 = require("child_process");
8603
8918
  function defaultExit3(code) {
8604
8919
  return process.exit(code);
@@ -8606,7 +8921,7 @@ function defaultExit3(code) {
8606
8921
  function resolveRunScript3(opts) {
8607
8922
  if (opts.runScriptPath) return opts.runScriptPath;
8608
8923
  const cwd = opts.cwd ?? process.cwd();
8609
- return path38.join(cwd, ".cleargate", "scripts", "run_script.sh");
8924
+ return path39.join(cwd, ".cleargate", "scripts", "run_script.sh");
8610
8925
  }
8611
8926
  function stateUpdateHandler(opts, cli) {
8612
8927
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -8667,21 +8982,21 @@ function stateValidateHandler(opts, cli) {
8667
8982
 
8668
8983
  // src/commands/stamp-tokens.ts
8669
8984
  init_cjs_shims();
8670
- var fs37 = __toESM(require("fs"), 1);
8671
- var path40 = __toESM(require("path"), 1);
8985
+ var fs38 = __toESM(require("fs"), 1);
8986
+ var path41 = __toESM(require("path"), 1);
8672
8987
 
8673
8988
  // src/lib/ledger-reader.ts
8674
8989
  init_cjs_shims();
8675
- var fs36 = __toESM(require("fs"), 1);
8676
- var path39 = __toESM(require("path"), 1);
8990
+ var fs37 = __toESM(require("fs"), 1);
8991
+ var path40 = __toESM(require("path"), 1);
8677
8992
  function findSprintRunsRoot(startDir) {
8678
8993
  let dir = startDir;
8679
8994
  while (true) {
8680
- const candidate = path39.join(dir, ".cleargate", "sprint-runs");
8681
- if (fs36.existsSync(candidate)) {
8995
+ const candidate = path40.join(dir, ".cleargate", "sprint-runs");
8996
+ if (fs37.existsSync(candidate)) {
8682
8997
  return candidate;
8683
8998
  }
8684
- const parent = path39.dirname(dir);
8999
+ const parent = path40.dirname(dir);
8685
9000
  if (parent === dir) {
8686
9001
  return null;
8687
9002
  }
@@ -8734,13 +9049,13 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
8734
9049
  }
8735
9050
  sprintRunsRoot = found;
8736
9051
  }
8737
- if (!fs36.existsSync(sprintRunsRoot)) {
9052
+ if (!fs37.existsSync(sprintRunsRoot)) {
8738
9053
  return [];
8739
9054
  }
8740
9055
  let ledgerFiles;
8741
9056
  try {
8742
- const entries = fs36.readdirSync(sprintRunsRoot, { withFileTypes: true });
8743
- ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) => path39.join(sprintRunsRoot, e.name, "token-ledger.jsonl")).filter((f) => fs36.existsSync(f));
9057
+ const entries = fs37.readdirSync(sprintRunsRoot, { withFileTypes: true });
9058
+ ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) => path40.join(sprintRunsRoot, e.name, "token-ledger.jsonl")).filter((f) => fs37.existsSync(f));
8744
9059
  } catch {
8745
9060
  return [];
8746
9061
  }
@@ -8748,7 +9063,7 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
8748
9063
  for (const ledgerFile of ledgerFiles) {
8749
9064
  let content;
8750
9065
  try {
8751
- content = fs36.readFileSync(ledgerFile, "utf-8");
9066
+ content = fs37.readFileSync(ledgerFile, "utf-8");
8752
9067
  } catch {
8753
9068
  continue;
8754
9069
  }
@@ -8799,7 +9114,7 @@ async function stampTokensHandler(file, opts, cli) {
8799
9114
  });
8800
9115
  const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
8801
9116
  const cwd = cli?.cwd ?? process.cwd();
8802
- const absPath = path40.isAbsolute(file) ? file : path40.resolve(cwd, file);
9117
+ const absPath = path41.isAbsolute(file) ? file : path41.resolve(cwd, file);
8803
9118
  if (/\/\.cleargate\/delivery\/archive\//.test(absPath)) {
8804
9119
  stdoutFn(`[frozen] ${absPath}`);
8805
9120
  exitFn(0);
@@ -8807,7 +9122,7 @@ async function stampTokensHandler(file, opts, cli) {
8807
9122
  }
8808
9123
  let rawContent;
8809
9124
  try {
8810
- rawContent = fs37.readFileSync(absPath, "utf-8");
9125
+ rawContent = fs38.readFileSync(absPath, "utf-8");
8811
9126
  } catch {
8812
9127
  stdoutFn(`[stamp-tokens] error: cannot read file: ${absPath}`);
8813
9128
  exitFn(1);
@@ -8879,7 +9194,7 @@ async function stampTokensHandler(file, opts, cli) {
8879
9194
  return;
8880
9195
  }
8881
9196
  try {
8882
- fs37.writeFileSync(absPath, serialized, "utf-8");
9197
+ fs38.writeFileSync(absPath, serialized, "utf-8");
8883
9198
  } catch {
8884
9199
  stdoutFn(`[stamp-tokens] error: cannot write file: ${absPath}`);
8885
9200
  exitFn(1);
@@ -8896,14 +9211,14 @@ function extractWorkItemId(fm, absPath) {
8896
9211
  return val.trim();
8897
9212
  }
8898
9213
  }
8899
- const basename13 = path40.basename(absPath);
8900
- const match = basename13.match(/^(STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(-\d+)?/i);
9214
+ const basename14 = path41.basename(absPath);
9215
+ const match = basename14.match(/^(STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(-\d+)?/i);
8901
9216
  if (match) {
8902
9217
  return match[0].toUpperCase();
8903
9218
  }
8904
9219
  const typeFromPath = detectWorkItemType(absPath);
8905
9220
  if (typeFromPath) {
8906
- const idMatch = basename13.match(/((?:STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(?:-\d+)?)/i);
9221
+ const idMatch = basename14.match(/((?:STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(?:-\d+)?)/i);
8907
9222
  if (idMatch) {
8908
9223
  return idMatch[1].toUpperCase();
8909
9224
  }
@@ -9001,9 +9316,9 @@ ${body}`;
9001
9316
 
9002
9317
  // src/commands/upgrade.ts
9003
9318
  init_cjs_shims();
9004
- var fs38 = __toESM(require("fs"), 1);
9319
+ var fs39 = __toESM(require("fs"), 1);
9005
9320
  var fsp = __toESM(require("fs/promises"), 1);
9006
- var path41 = __toESM(require("path"), 1);
9321
+ var path42 = __toESM(require("path"), 1);
9007
9322
 
9008
9323
  // src/lib/changelog.ts
9009
9324
  init_cjs_shims();
@@ -9207,7 +9522,7 @@ async function writeAtomic2(filePath, content) {
9207
9522
  }
9208
9523
  }
9209
9524
  async function updateSnapshotEntry(projectRoot, filePath, newSha) {
9210
- const snapshotPath = path41.join(projectRoot, ".cleargate", ".install-manifest.json");
9525
+ const snapshotPath = path42.join(projectRoot, ".cleargate", ".install-manifest.json");
9211
9526
  let snapshot;
9212
9527
  try {
9213
9528
  const raw = await fsp.readFile(snapshotPath, "utf-8");
@@ -9224,17 +9539,17 @@ async function updateSnapshotEntry(projectRoot, filePath, newSha) {
9224
9539
  await writeAtomic2(snapshotPath, JSON.stringify(updated, null, 2) + "\n");
9225
9540
  }
9226
9541
  function isClaudeMd(filePath) {
9227
- return path41.basename(filePath) === "CLAUDE.md";
9542
+ return path42.basename(filePath) === "CLAUDE.md";
9228
9543
  }
9229
9544
  function isSettingsJson(filePath) {
9230
- return path41.basename(filePath) === "settings.json" && filePath.includes(".claude");
9545
+ return path42.basename(filePath) === "settings.json" && filePath.includes(".claude");
9231
9546
  }
9232
9547
  async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
9233
- const targetPath = path41.join(projectRoot, entry.path);
9234
- const sourcePath = path41.join(packageRoot, entry.path);
9548
+ const targetPath = path42.join(projectRoot, entry.path);
9549
+ const sourcePath = path42.join(packageRoot, entry.path);
9235
9550
  try {
9236
9551
  const pkgContent = await fsp.readFile(sourcePath, "utf-8");
9237
- await fsp.mkdir(path41.dirname(targetPath), { recursive: true });
9552
+ await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
9238
9553
  await writeAtomic2(targetPath, pkgContent);
9239
9554
  await updateSnapshotEntry(projectRoot, entry.path, entry.sha256);
9240
9555
  stdout(`[always] overwritten: ${entry.path}`);
@@ -9244,8 +9559,8 @@ async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
9244
9559
  }
9245
9560
  async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, currentSha, flags, opts) {
9246
9561
  const { stdout, stderr, promptMergeChoiceFn, openInEditorFn, stdin } = opts;
9247
- const targetPath = path41.join(projectRoot, entry.path);
9248
- const sourcePath = path41.join(packageRoot, entry.path);
9562
+ const targetPath = path42.join(projectRoot, entry.path);
9563
+ const sourcePath = path42.join(packageRoot, entry.path);
9249
9564
  let ours = "";
9250
9565
  let theirs = "";
9251
9566
  try {
@@ -9308,7 +9623,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
9308
9623
  mergedContent = theirs;
9309
9624
  }
9310
9625
  }
9311
- await fsp.mkdir(path41.dirname(targetPath), { recursive: true });
9626
+ await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
9312
9627
  await writeAtomic2(targetPath, mergedContent);
9313
9628
  const newSha2 = hashNormalized(mergedContent);
9314
9629
  await updateSnapshotEntry(projectRoot, entry.path, newSha2);
@@ -9320,7 +9635,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
9320
9635
  ${ours}=======
9321
9636
  ${theirs}>>>>>>> theirs (upstream)
9322
9637
  `;
9323
- await fsp.mkdir(path41.dirname(mergeFilePath), { recursive: true });
9638
+ await fsp.mkdir(path42.dirname(mergeFilePath), { recursive: true });
9324
9639
  await writeAtomic2(mergeFilePath, conflictContent);
9325
9640
  try {
9326
9641
  const result = await openInEditorFn(mergeFilePath);
@@ -9380,10 +9695,10 @@ async function upgradeHandler(flags, cli) {
9380
9695
  const installedVersion = installSnapshot?.cleargate_version ?? pkgManifest.cleargate_version;
9381
9696
  const targetVersion = pkgManifest.cleargate_version;
9382
9697
  if (installedVersion !== targetVersion) {
9383
- const pkgRoot = cli?.packageRoot ?? path41.join(path41.dirname(new URL(importMetaUrl).pathname), "..", "..");
9384
- const changelogPath = path41.join(pkgRoot, "CHANGELOG.md");
9698
+ const pkgRoot = cli?.packageRoot ?? path42.join(path42.dirname(new URL(importMetaUrl).pathname), "..", "..");
9699
+ const changelogPath = path42.join(pkgRoot, "CHANGELOG.md");
9385
9700
  try {
9386
- const changelogContent = fs38.readFileSync(changelogPath, "utf-8");
9701
+ const changelogContent = fs39.readFileSync(changelogPath, "utf-8");
9387
9702
  const sections = sliceChangelog(changelogContent, installedVersion, targetVersion);
9388
9703
  if (sections.length > 0) {
9389
9704
  const deltaText = sections.map((s) => s.body).join("\n\n");
@@ -9449,7 +9764,7 @@ async function upgradeHandler(flags, cli) {
9449
9764
  const { entry, currentSha, installSha, action } = item;
9450
9765
  let preMutationContent = null;
9451
9766
  if (SESSION_LOAD_PATHS.has(entry.path)) {
9452
- const targetPath = path41.join(cwd, entry.path);
9767
+ const targetPath = path42.join(cwd, entry.path);
9453
9768
  try {
9454
9769
  preMutationContent = await fsp.readFile(targetPath, "utf-8");
9455
9770
  } catch {
@@ -9487,7 +9802,7 @@ async function upgradeHandler(flags, cli) {
9487
9802
  package_sha: entry.sha256
9488
9803
  };
9489
9804
  if (SESSION_LOAD_PATHS.has(entry.path) && preMutationContent !== null) {
9490
- const targetPath = path41.join(cwd, entry.path);
9805
+ const targetPath = path42.join(cwd, entry.path);
9491
9806
  let postMutationContent;
9492
9807
  try {
9493
9808
  postMutationContent = await fsp.readFile(targetPath, "utf-8");
@@ -9512,9 +9827,9 @@ async function upgradeHandler(flags, cli) {
9512
9827
 
9513
9828
  // src/commands/uninstall.ts
9514
9829
  init_cjs_shims();
9515
- var fs39 = __toESM(require("fs"), 1);
9830
+ var fs40 = __toESM(require("fs"), 1);
9516
9831
  var fsp2 = __toESM(require("fs/promises"), 1);
9517
- var path42 = __toESM(require("path"), 1);
9832
+ var path43 = __toESM(require("path"), 1);
9518
9833
  var import_node_child_process15 = require("child_process");
9519
9834
  var USER_ARTIFACT_TIERS = ["user-artifact"];
9520
9835
  var FRAMEWORK_TIERS = ["protocol", "template", "agent", "hook", "skill", "cli-config", "derived"];
@@ -9540,10 +9855,10 @@ function shouldPreserve(entry, preserveSet, removeSet) {
9540
9855
  return false;
9541
9856
  }
9542
9857
  function resolveProjectName(target) {
9543
- const pkgPath = path42.join(target, "package.json");
9544
- if (fs39.existsSync(pkgPath)) {
9858
+ const pkgPath = path43.join(target, "package.json");
9859
+ if (fs40.existsSync(pkgPath)) {
9545
9860
  try {
9546
- const raw = fs39.readFileSync(pkgPath, "utf-8");
9861
+ const raw = fs40.readFileSync(pkgPath, "utf-8");
9547
9862
  const parsed = JSON.parse(raw);
9548
9863
  if (parsed.name && typeof parsed.name === "string") {
9549
9864
  return parsed.name;
@@ -9551,7 +9866,7 @@ function resolveProjectName(target) {
9551
9866
  } catch {
9552
9867
  }
9553
9868
  }
9554
- return path42.basename(target);
9869
+ return path43.basename(target);
9555
9870
  }
9556
9871
  function detectUncommittedChanges(target, manifestPaths, gitRunner) {
9557
9872
  const run = gitRunner ?? ((args) => {
@@ -9580,8 +9895,8 @@ function detectUncommittedChanges(target, manifestPaths, gitRunner) {
9580
9895
  return changedFiles.filter((f) => manifestSet.has(f));
9581
9896
  }
9582
9897
  async function removeFromPackageJson(target, dryRun) {
9583
- const pkgPath = path42.join(target, "package.json");
9584
- if (!fs39.existsSync(pkgPath)) return false;
9898
+ const pkgPath = path43.join(target, "package.json");
9899
+ if (!fs40.existsSync(pkgPath)) return false;
9585
9900
  let raw;
9586
9901
  try {
9587
9902
  raw = await fsp2.readFile(pkgPath, "utf-8");
@@ -9622,7 +9937,7 @@ async function removeFile(filePath) {
9622
9937
  }
9623
9938
  async function removeDir(dirPath) {
9624
9939
  try {
9625
- fs39.rmSync(dirPath, { recursive: true, force: true });
9940
+ fs40.rmSync(dirPath, { recursive: true, force: true });
9626
9941
  } catch {
9627
9942
  }
9628
9943
  }
@@ -9642,12 +9957,12 @@ async function uninstallHandler(opts) {
9642
9957
  for (const t of FRAMEWORK_TIERS) removeSet.add(t);
9643
9958
  for (const u of USER_ARTIFACT_TIERS) removeSet.add(u);
9644
9959
  }
9645
- const target = opts.path ? path42.resolve(opts.path) : cwd;
9646
- const cleargateDir = path42.join(target, ".cleargate");
9647
- const manifestPath = path42.join(cleargateDir, ".install-manifest.json");
9648
- const uninstalledPath = path42.join(cleargateDir, ".uninstalled");
9649
- if (!fs39.existsSync(manifestPath)) {
9650
- if (fs39.existsSync(uninstalledPath)) {
9960
+ const target = opts.path ? path43.resolve(opts.path) : cwd;
9961
+ const cleargateDir = path43.join(target, ".cleargate");
9962
+ const manifestPath = path43.join(cleargateDir, ".install-manifest.json");
9963
+ const uninstalledPath = path43.join(cleargateDir, ".uninstalled");
9964
+ if (!fs40.existsSync(manifestPath)) {
9965
+ if (fs40.existsSync(uninstalledPath)) {
9651
9966
  stdout("already uninstalled");
9652
9967
  exit(0);
9653
9968
  return;
@@ -9656,7 +9971,7 @@ async function uninstallHandler(opts) {
9656
9971
  exit(0);
9657
9972
  return;
9658
9973
  }
9659
- if (fs39.existsSync(uninstalledPath) && !fs39.existsSync(manifestPath)) {
9974
+ if (fs40.existsSync(uninstalledPath) && !fs40.existsSync(manifestPath)) {
9660
9975
  stdout("already uninstalled");
9661
9976
  exit(0);
9662
9977
  return;
@@ -9678,10 +9993,10 @@ async function uninstallHandler(opts) {
9678
9993
  return;
9679
9994
  }
9680
9995
  }
9681
- const claudeMdPath = path42.join(target, "CLAUDE.md");
9996
+ const claudeMdPath = path43.join(target, "CLAUDE.md");
9682
9997
  let claudeMdContent = null;
9683
- if (fs39.existsSync(claudeMdPath)) {
9684
- claudeMdContent = fs39.readFileSync(claudeMdPath, "utf-8");
9998
+ if (fs40.existsSync(claudeMdPath)) {
9999
+ claudeMdContent = fs40.readFileSync(claudeMdPath, "utf-8");
9685
10000
  if (!claudeMdContent.includes(CLEARGATE_START)) {
9686
10001
  stderr("CLAUDE.md is missing <!-- CLEARGATE:START --> marker");
9687
10002
  exit(1);
@@ -9697,8 +10012,8 @@ async function uninstallHandler(opts) {
9697
10012
  const toPreserve = [];
9698
10013
  const toSkip = [];
9699
10014
  for (const entry of snapshot.files) {
9700
- const filePath = path42.join(target, entry.path);
9701
- if (!fs39.existsSync(filePath)) {
10015
+ const filePath = path43.join(target, entry.path);
10016
+ if (!fs40.existsSync(filePath)) {
9702
10017
  toSkip.push(entry);
9703
10018
  continue;
9704
10019
  }
@@ -9755,7 +10070,7 @@ async function uninstallHandler(opts) {
9755
10070
  const removedPaths = [];
9756
10071
  const preservedPaths = [];
9757
10072
  for (const entry of toRemove) {
9758
- const filePath = path42.join(target, entry.path);
10073
+ const filePath = path43.join(target, entry.path);
9759
10074
  await removeFile(filePath);
9760
10075
  removedPaths.push(entry.path);
9761
10076
  }
@@ -9771,10 +10086,10 @@ async function uninstallHandler(opts) {
9771
10086
  stderr(`Warning: could not strip CLAUDE.md block: ${err.message}`);
9772
10087
  }
9773
10088
  }
9774
- const settingsPath = path42.join(target, ".claude", "settings.json");
9775
- if (fs39.existsSync(settingsPath)) {
10089
+ const settingsPath = path43.join(target, ".claude", "settings.json");
10090
+ if (fs40.existsSync(settingsPath)) {
9776
10091
  try {
9777
- const raw = fs39.readFileSync(settingsPath, "utf-8");
10092
+ const raw = fs40.readFileSync(settingsPath, "utf-8");
9778
10093
  const settings = JSON.parse(raw);
9779
10094
  const cleaned = removeClearGateHooks(settings);
9780
10095
  await writeAtomic3(settingsPath, JSON.stringify(cleaned, null, 2) + "\n");
@@ -9789,7 +10104,7 @@ async function uninstallHandler(opts) {
9789
10104
  stdout("Removed @cleargate/cli from package.json. Run `npm install` to update package-lock.json.");
9790
10105
  }
9791
10106
  await removeFile(manifestPath);
9792
- await removeFile(path42.join(cleargateDir, ".drift-state.json"));
10107
+ await removeFile(path43.join(cleargateDir, ".drift-state.json"));
9793
10108
  const marker = {
9794
10109
  uninstalled_at: now().toISOString(),
9795
10110
  prior_version: snapshot.cleargate_version,
@@ -9814,30 +10129,30 @@ async function uninstallHandler(opts) {
9814
10129
  // src/commands/sync.ts
9815
10130
  init_cjs_shims();
9816
10131
  var fsPromises8 = __toESM(require("fs/promises"), 1);
9817
- var path51 = __toESM(require("path"), 1);
10132
+ var path52 = __toESM(require("path"), 1);
9818
10133
 
9819
10134
  // src/lib/sync-log.ts
9820
10135
  init_cjs_shims();
9821
- var fs40 = __toESM(require("fs"), 1);
10136
+ var fs41 = __toESM(require("fs"), 1);
9822
10137
  var fsPromises2 = __toESM(require("fs/promises"), 1);
9823
- var path43 = __toESM(require("path"), 1);
10138
+ var path44 = __toESM(require("path"), 1);
9824
10139
  function resolveActiveSprintDir(projectRoot, _opts) {
9825
- const sprintRunsRoot = path43.join(projectRoot, ".cleargate", "sprint-runs");
9826
- const offSprint = path43.join(sprintRunsRoot, "_off-sprint");
9827
- if (!fs40.existsSync(sprintRunsRoot)) {
9828
- fs40.mkdirSync(sprintRunsRoot, { recursive: true });
9829
- fs40.mkdirSync(offSprint, { recursive: true });
10140
+ const sprintRunsRoot = path44.join(projectRoot, ".cleargate", "sprint-runs");
10141
+ const offSprint = path44.join(sprintRunsRoot, "_off-sprint");
10142
+ if (!fs41.existsSync(sprintRunsRoot)) {
10143
+ fs41.mkdirSync(sprintRunsRoot, { recursive: true });
10144
+ fs41.mkdirSync(offSprint, { recursive: true });
9830
10145
  return offSprint;
9831
10146
  }
9832
- const entries = fs40.readdirSync(sprintRunsRoot, { withFileTypes: true });
10147
+ const entries = fs41.readdirSync(sprintRunsRoot, { withFileTypes: true });
9833
10148
  const sprintDirs = entries.filter((e) => e.isDirectory() && e.name !== "_off-sprint").map((e) => {
9834
- const fullPath = path43.join(sprintRunsRoot, e.name);
9835
- const stat = fs40.statSync(fullPath);
10149
+ const fullPath = path44.join(sprintRunsRoot, e.name);
10150
+ const stat = fs41.statSync(fullPath);
9836
10151
  return { name: e.name, fullPath, mtimeMs: stat.mtimeMs };
9837
10152
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
9838
10153
  if (sprintDirs.length === 0) {
9839
- if (!fs40.existsSync(offSprint)) {
9840
- fs40.mkdirSync(offSprint, { recursive: true });
10154
+ if (!fs41.existsSync(offSprint)) {
10155
+ fs41.mkdirSync(offSprint, { recursive: true });
9841
10156
  }
9842
10157
  return offSprint;
9843
10158
  }
@@ -9848,7 +10163,7 @@ function redactDetail(detail) {
9848
10163
  return detail.replace(/eyJ[A-Za-z0-9._-]+/g, "[REDACTED]");
9849
10164
  }
9850
10165
  async function appendSyncLog(sprintRoot, entry) {
9851
- const logPath = path43.join(sprintRoot, "sync-log.jsonl");
10166
+ const logPath = path44.join(sprintRoot, "sync-log.jsonl");
9852
10167
  await fsPromises2.mkdir(sprintRoot, { recursive: true });
9853
10168
  const safeEntry = {
9854
10169
  ...entry,
@@ -9858,7 +10173,7 @@ async function appendSyncLog(sprintRoot, entry) {
9858
10173
  await fsPromises2.appendFile(logPath, line, { encoding: "utf8" });
9859
10174
  }
9860
10175
  async function readSyncLog(sprintRoot, filters) {
9861
- const logPath = path43.join(sprintRoot, "sync-log.jsonl");
10176
+ const logPath = path44.join(sprintRoot, "sync-log.jsonl");
9862
10177
  let raw;
9863
10178
  try {
9864
10179
  raw = await fsPromises2.readFile(logPath, "utf8");
@@ -9964,7 +10279,7 @@ function classify2(local, remote, since) {
9964
10279
  init_cjs_shims();
9965
10280
  var import_node_fs2 = require("fs");
9966
10281
  var os8 = __toESM(require("os"), 1);
9967
- var path44 = __toESM(require("path"), 1);
10282
+ var path45 = __toESM(require("path"), 1);
9968
10283
  function promptFourChoice(opts) {
9969
10284
  const { stdin, stdout } = opts;
9970
10285
  stdout("[k]eep mine / [t]ake theirs / [e]dit in $EDITOR / [a]bort: ");
@@ -10034,7 +10349,7 @@ async function promptThreeWayMerge(opts) {
10034
10349
  case "a":
10035
10350
  return { resolution: "aborted", body: local };
10036
10351
  case "e": {
10037
- const tmpFile = path44.join(os8.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
10352
+ const tmpFile = path45.join(os8.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
10038
10353
  const markerContent = `<<<<<<< local
10039
10354
  ${local}
10040
10355
  =======
@@ -10134,12 +10449,12 @@ init_config();
10134
10449
  // src/lib/intake.ts
10135
10450
  init_cjs_shims();
10136
10451
  var fsPromises4 = __toESM(require("fs/promises"), 1);
10137
- var path47 = __toESM(require("path"), 1);
10452
+ var path48 = __toESM(require("path"), 1);
10138
10453
 
10139
10454
  // src/lib/slug.ts
10140
10455
  init_cjs_shims();
10141
10456
  var fsPromises3 = __toESM(require("fs/promises"), 1);
10142
- var path46 = __toESM(require("path"), 1);
10457
+ var path47 = __toESM(require("path"), 1);
10143
10458
  function slugify(title, max = 40) {
10144
10459
  const normalized = title.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
10145
10460
  const lowered = normalized.toLowerCase();
@@ -10154,8 +10469,8 @@ function slugify(title, max = 40) {
10154
10469
  var PROPOSAL_ID_RE = /^proposal_id:\s*"?PROP-(\d+)"?/m;
10155
10470
  async function nextProposalId(projectRoot) {
10156
10471
  const dirs = [
10157
- path46.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10158
- path46.join(projectRoot, ".cleargate", "delivery", "archive")
10472
+ path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10473
+ path47.join(projectRoot, ".cleargate", "delivery", "archive")
10159
10474
  ];
10160
10475
  let maxN = 0;
10161
10476
  for (const dir of dirs) {
@@ -10167,7 +10482,7 @@ async function nextProposalId(projectRoot) {
10167
10482
  }
10168
10483
  for (const entry of entries) {
10169
10484
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10170
- const fullPath = path46.join(dir, entry.name);
10485
+ const fullPath = path47.join(dir, entry.name);
10171
10486
  try {
10172
10487
  const raw = await fsPromises3.readFile(fullPath, "utf8");
10173
10488
  const fmEnd = extractFrontmatterBlock(raw);
@@ -10185,8 +10500,8 @@ async function nextProposalId(projectRoot) {
10185
10500
  }
10186
10501
  async function findByRemoteId(projectRoot, remoteId) {
10187
10502
  const dirs = [
10188
- path46.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10189
- path46.join(projectRoot, ".cleargate", "delivery", "archive")
10503
+ path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10504
+ path47.join(projectRoot, ".cleargate", "delivery", "archive")
10190
10505
  ];
10191
10506
  const escaped = remoteId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10192
10507
  const re = new RegExp(`^remote_id:\\s*"?${escaped}"?\\s*$`, "m");
@@ -10199,7 +10514,7 @@ async function findByRemoteId(projectRoot, remoteId) {
10199
10514
  }
10200
10515
  for (const entry of entries) {
10201
10516
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10202
- const fullPath = path46.join(dir, entry.name);
10517
+ const fullPath = path47.join(dir, entry.name);
10203
10518
  try {
10204
10519
  const raw = await fsPromises3.readFile(fullPath, "utf8");
10205
10520
  const fm = extractFrontmatterBlock(raw);
@@ -10236,7 +10551,7 @@ async function runIntakeBranch(opts) {
10236
10551
  labelFilter = "cleargate:proposal",
10237
10552
  now = () => (/* @__PURE__ */ new Date()).toISOString()
10238
10553
  } = opts;
10239
- const pendingSyncDir = path47.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10554
+ const pendingSyncDir = path48.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10240
10555
  let remoteItems = [];
10241
10556
  try {
10242
10557
  remoteItems = await mcp2.call(
@@ -10267,7 +10582,7 @@ async function runIntakeBranch(opts) {
10267
10582
  const slug2 = slugify(item.title ?? "untitled");
10268
10583
  const num2 = proposalId2.replace("PROP-", "");
10269
10584
  const filename2 = `PROPOSAL-${num2}-remote-${slug2}.md`;
10270
- const targetPath2 = path47.join(pendingSyncDir, filename2);
10585
+ const targetPath2 = path48.join(pendingSyncDir, filename2);
10271
10586
  createdItems.push({
10272
10587
  proposalId: proposalId2,
10273
10588
  remoteId: item.remote_id,
@@ -10280,7 +10595,7 @@ async function runIntakeBranch(opts) {
10280
10595
  const num = proposalId.replace("PROP-", "");
10281
10596
  const slug = slugify(item.title ?? "untitled");
10282
10597
  const filename = `PROPOSAL-${num}-remote-${slug}.md`;
10283
- const targetPath = path47.join(pendingSyncDir, filename);
10598
+ const targetPath = path48.join(pendingSyncDir, filename);
10284
10599
  const nowTs = now();
10285
10600
  const fm = {
10286
10601
  proposal_id: proposalId,
@@ -10372,8 +10687,8 @@ path/to/new/file.ext - {Explanation of purpose}
10372
10687
  }
10373
10688
  async function hasAnyRemoteAuthored(projectRoot) {
10374
10689
  const dirs = [
10375
- path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10376
- path47.join(projectRoot, ".cleargate", "delivery", "archive")
10690
+ path48.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10691
+ path48.join(projectRoot, ".cleargate", "delivery", "archive")
10377
10692
  ];
10378
10693
  for (const dir of dirs) {
10379
10694
  let entries;
@@ -10384,7 +10699,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
10384
10699
  }
10385
10700
  for (const entry of entries) {
10386
10701
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10387
- const fullPath = path47.join(dir, entry.name);
10702
+ const fullPath = path48.join(dir, entry.name);
10388
10703
  try {
10389
10704
  const raw = await fsPromises4.readFile(fullPath, "utf8");
10390
10705
  const fmEnd = raw.indexOf("\n---", 4);
@@ -10402,9 +10717,9 @@ async function hasAnyRemoteAuthored(projectRoot) {
10402
10717
 
10403
10718
  // src/lib/active-criteria.ts
10404
10719
  init_cjs_shims();
10405
- var fs43 = __toESM(require("fs"), 1);
10720
+ var fs44 = __toESM(require("fs"), 1);
10406
10721
  var fsPromises5 = __toESM(require("fs/promises"), 1);
10407
- var path48 = __toESM(require("path"), 1);
10722
+ var path49 = __toESM(require("path"), 1);
10408
10723
  async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
10409
10724
  const active = /* @__PURE__ */ new Set();
10410
10725
  const now = Date.parse(nowFn());
@@ -10429,7 +10744,7 @@ async function resolveInSprintIds(projectRoot) {
10429
10744
  const ids = /* @__PURE__ */ new Set();
10430
10745
  try {
10431
10746
  const sprintDir = resolveActiveSprintDir(projectRoot);
10432
- const sprintId = path48.basename(sprintDir);
10747
+ const sprintId = path49.basename(sprintDir);
10433
10748
  if (sprintId === "_off-sprint") return ids;
10434
10749
  const sprintFile = await findSprintFile2(projectRoot, sprintId);
10435
10750
  if (!sprintFile) return ids;
@@ -10444,14 +10759,14 @@ async function resolveInSprintIds(projectRoot) {
10444
10759
  return ids;
10445
10760
  }
10446
10761
  async function findSprintFile2(projectRoot, sprintId) {
10447
- const pendingSync = path48.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10448
- const archive = path48.join(projectRoot, ".cleargate", "delivery", "archive");
10762
+ const pendingSync = path49.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10763
+ const archive = path49.join(projectRoot, ".cleargate", "delivery", "archive");
10449
10764
  for (const dir of [pendingSync, archive]) {
10450
10765
  try {
10451
- const entries = fs43.readdirSync(dir, { withFileTypes: true });
10766
+ const entries = fs44.readdirSync(dir, { withFileTypes: true });
10452
10767
  for (const entry of entries) {
10453
10768
  if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
10454
- return path48.join(dir, entry.name);
10769
+ return path49.join(dir, entry.name);
10455
10770
  }
10456
10771
  }
10457
10772
  } catch {
@@ -10463,12 +10778,12 @@ async function findSprintFile2(projectRoot, sprintId) {
10463
10778
  // src/lib/comments-cache.ts
10464
10779
  init_cjs_shims();
10465
10780
  var fsPromises6 = __toESM(require("fs/promises"), 1);
10466
- var path49 = __toESM(require("path"), 1);
10781
+ var path50 = __toESM(require("path"), 1);
10467
10782
  function cacheDir(projectRoot) {
10468
- return path49.join(projectRoot, ".cleargate", ".comments-cache");
10783
+ return path50.join(projectRoot, ".cleargate", ".comments-cache");
10469
10784
  }
10470
10785
  function cachePath(projectRoot, remoteId) {
10471
- return path49.join(cacheDir(projectRoot), `${remoteId}.json`);
10786
+ return path50.join(cacheDir(projectRoot), `${remoteId}.json`);
10472
10787
  }
10473
10788
  async function writeCommentCache(projectRoot, remoteId, comments) {
10474
10789
  const dir = cacheDir(projectRoot);
@@ -10483,7 +10798,7 @@ async function writeCommentCache(projectRoot, remoteId, comments) {
10483
10798
  // src/lib/wiki-comments-render.ts
10484
10799
  init_cjs_shims();
10485
10800
  var fsPromises7 = __toESM(require("fs/promises"), 1);
10486
- var path50 = __toESM(require("path"), 1);
10801
+ var path51 = __toESM(require("path"), 1);
10487
10802
  var START = "<!-- cleargate:comments:start -->";
10488
10803
  var END = "<!-- cleargate:comments:end -->";
10489
10804
  function resolveBucket(fm) {
@@ -10528,7 +10843,7 @@ async function renderCommentsSection(opts) {
10528
10843
  const bucket = resolveBucket(localItem.fm);
10529
10844
  const primaryId = getPrimaryId(localItem.fm);
10530
10845
  if (!bucket || !primaryId) return;
10531
- const wikiPath = path50.join(
10846
+ const wikiPath = path51.join(
10532
10847
  projectRoot,
10533
10848
  ".cleargate",
10534
10849
  "wiki",
@@ -10564,7 +10879,7 @@ async function renderCommentsSection(opts) {
10564
10879
  await writeAtomic4(wikiPath, updated);
10565
10880
  }
10566
10881
  async function writeAtomic4(filePath, content) {
10567
- await fsPromises7.mkdir(path50.dirname(filePath), { recursive: true });
10882
+ await fsPromises7.mkdir(path51.dirname(filePath), { recursive: true });
10568
10883
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
10569
10884
  await fsPromises7.writeFile(tmpPath, content, "utf8");
10570
10885
  await fsPromises7.rename(tmpPath, filePath);
@@ -10576,11 +10891,11 @@ async function syncCheckHandler(opts = {}) {
10576
10891
  const env = opts.env ?? process.env;
10577
10892
  const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
10578
10893
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10579
- const markerPath = path51.join(projectRoot, ".cleargate", ".sync-marker.json");
10894
+ const markerPath = path52.join(projectRoot, ".cleargate", ".sync-marker.json");
10580
10895
  const updateMarker = async (nowIso2) => {
10581
10896
  try {
10582
10897
  const content = JSON.stringify({ last_check: nowIso2 });
10583
- await fsPromises8.mkdir(path51.dirname(markerPath), { recursive: true });
10898
+ await fsPromises8.mkdir(path52.dirname(markerPath), { recursive: true });
10584
10899
  const tmpPath = `${markerPath}.tmp.${Date.now()}`;
10585
10900
  await fsPromises8.writeFile(tmpPath, content, "utf8");
10586
10901
  await fsPromises8.rename(tmpPath, markerPath);
@@ -10663,7 +10978,7 @@ async function syncHandler(opts = {}) {
10663
10978
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10664
10979
  const identity = resolveIdentity(projectRoot);
10665
10980
  const sprintRoot = resolveActiveSprintDir(projectRoot);
10666
- const sprintId = path51.basename(sprintRoot);
10981
+ const sprintId = path52.basename(sprintRoot);
10667
10982
  let mcp2;
10668
10983
  if (opts.mcp) {
10669
10984
  mcp2 = opts.mcp;
@@ -10724,7 +11039,7 @@ async function syncHandler(opts = {}) {
10724
11039
  exit(2);
10725
11040
  return;
10726
11041
  }
10727
- const wikiMetaPath = path51.join(projectRoot, ".cleargate", "wiki", "meta.json");
11042
+ const wikiMetaPath = path52.join(projectRoot, ".cleargate", "wiki", "meta.json");
10728
11043
  let lastRemoteSync = "1970-01-01T00:00:00.000Z";
10729
11044
  try {
10730
11045
  const metaRaw = await fsPromises8.readFile(wikiMetaPath, "utf8");
@@ -10965,7 +11280,7 @@ async function syncHandler(opts = {}) {
10965
11280
  };
10966
11281
  await appendSyncLog(sprintRoot, entry);
10967
11282
  }
10968
- const conflictsFile = path51.join(projectRoot, ".cleargate", ".conflicts.json");
11283
+ const conflictsFile = path52.join(projectRoot, ".cleargate", ".conflicts.json");
10969
11284
  const conflictsContent = {
10970
11285
  generated_at: nowFn(),
10971
11286
  sprint_id: sprintId,
@@ -10973,7 +11288,7 @@ async function syncHandler(opts = {}) {
10973
11288
  };
10974
11289
  await writeAtomic5(conflictsFile, JSON.stringify(conflictsContent, null, 2) + "\n");
10975
11290
  try {
10976
- await fsPromises8.mkdir(path51.dirname(wikiMetaPath), { recursive: true });
11291
+ await fsPromises8.mkdir(path52.dirname(wikiMetaPath), { recursive: true });
10977
11292
  let meta = {};
10978
11293
  try {
10979
11294
  const raw = await fsPromises8.readFile(wikiMetaPath, "utf8");
@@ -11014,13 +11329,13 @@ async function applyPull(item, localPath, fm, actorEmail, nowFn) {
11014
11329
  await writeAtomic5(localPath, newContent);
11015
11330
  }
11016
11331
  async function writeAtomic5(filePath, content) {
11017
- await fsPromises8.mkdir(path51.dirname(filePath), { recursive: true });
11332
+ await fsPromises8.mkdir(path52.dirname(filePath), { recursive: true });
11018
11333
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11019
11334
  await fsPromises8.writeFile(tmpPath, content, "utf8");
11020
11335
  await fsPromises8.rename(tmpPath, filePath);
11021
11336
  }
11022
11337
  async function scanLocalItems(projectRoot) {
11023
- const pendingSync = path51.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11338
+ const pendingSync = path52.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11024
11339
  const results = [];
11025
11340
  let entries;
11026
11341
  try {
@@ -11030,7 +11345,7 @@ async function scanLocalItems(projectRoot) {
11030
11345
  }
11031
11346
  for (const entry of entries) {
11032
11347
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11033
- const fullPath = path51.join(pendingSync, entry.name);
11348
+ const fullPath = path52.join(pendingSync, entry.name);
11034
11349
  try {
11035
11350
  const raw = await fsPromises8.readFile(fullPath, "utf8");
11036
11351
  const { fm, body } = parseFrontmatter(raw);
@@ -11058,7 +11373,7 @@ init_config();
11058
11373
  // src/lib/sync/work-items.ts
11059
11374
  init_cjs_shims();
11060
11375
  var fsPromises9 = __toESM(require("fs/promises"), 1);
11061
- var path52 = __toESM(require("path"), 1);
11376
+ var path53 = __toESM(require("path"), 1);
11062
11377
  var import_node_crypto2 = require("crypto");
11063
11378
  var BATCH_SIZE = 100;
11064
11379
  var ATTRIBUTION_FIELDS = /* @__PURE__ */ new Set([
@@ -11106,8 +11421,8 @@ function getItemId2(fm) {
11106
11421
  }
11107
11422
  async function walkDeliveryDirs(projectRoot) {
11108
11423
  const dirs = [
11109
- path52.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
11110
- path52.join(projectRoot, ".cleargate", "delivery", "archive")
11424
+ path53.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
11425
+ path53.join(projectRoot, ".cleargate", "delivery", "archive")
11111
11426
  ];
11112
11427
  const results = [];
11113
11428
  for (const dir of dirs) {
@@ -11119,7 +11434,7 @@ async function walkDeliveryDirs(projectRoot) {
11119
11434
  }
11120
11435
  for (const entry of entries) {
11121
11436
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11122
- const fullPath = path52.join(dir, entry.name);
11437
+ const fullPath = path53.join(dir, entry.name);
11123
11438
  try {
11124
11439
  const raw = await fsPromises9.readFile(fullPath, "utf8");
11125
11440
  const { fm, body } = parseFrontmatter(raw);
@@ -11144,7 +11459,7 @@ async function walkDeliveryDirs(projectRoot) {
11144
11459
  return results;
11145
11460
  }
11146
11461
  async function writeAtomic6(filePath, content) {
11147
- await fsPromises9.mkdir(path52.dirname(filePath), { recursive: true });
11462
+ await fsPromises9.mkdir(path53.dirname(filePath), { recursive: true });
11148
11463
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11149
11464
  await fsPromises9.writeFile(tmpPath, content, "utf8");
11150
11465
  await fsPromises9.rename(tmpPath, filePath);
@@ -11211,9 +11526,9 @@ async function syncWorkItems(opts) {
11211
11526
 
11212
11527
  // src/lib/admin-url.ts
11213
11528
  init_cjs_shims();
11214
- var fs44 = __toESM(require("fs"), 1);
11529
+ var fs45 = __toESM(require("fs"), 1);
11215
11530
  var os10 = __toESM(require("os"), 1);
11216
- var path53 = __toESM(require("path"), 1);
11531
+ var path54 = __toESM(require("path"), 1);
11217
11532
  var DEFAULT_BASE = "https://admin.cleargate.soula.ge/";
11218
11533
  function adminUrl(urlPath, opts) {
11219
11534
  const env = opts?.env ?? process.env;
@@ -11236,8 +11551,8 @@ function adminUrl(urlPath, opts) {
11236
11551
  function readLocalConfig() {
11237
11552
  const home = os10.homedir();
11238
11553
  if (!home) return null;
11239
- const configPath = path53.join(home, ".cleargate", "config.json");
11240
- const raw = fs44.readFileSync(configPath, "utf8");
11554
+ const configPath = path54.join(home, ".cleargate", "config.json");
11555
+ const raw = fs45.readFileSync(configPath, "utf8");
11241
11556
  return JSON.parse(raw);
11242
11557
  }
11243
11558
 
@@ -11352,7 +11667,7 @@ async function syncWorkItemsHandler(opts = {}) {
11352
11667
  // src/commands/pull.ts
11353
11668
  init_cjs_shims();
11354
11669
  var fsPromises10 = __toESM(require("fs/promises"), 1);
11355
- var path54 = __toESM(require("path"), 1);
11670
+ var path55 = __toESM(require("path"), 1);
11356
11671
  init_acquire();
11357
11672
  init_config();
11358
11673
  async function pullHandler(idOrRemoteId, opts = {}) {
@@ -11467,7 +11782,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
11467
11782
  result: "ok"
11468
11783
  };
11469
11784
  await appendSyncLog(sprintRoot, entry);
11470
- stdout(`pull: ${remoteId} applied to ${path54.relative(projectRoot, localPath)}
11785
+ stdout(`pull: ${remoteId} applied to ${path55.relative(projectRoot, localPath)}
11471
11786
  `);
11472
11787
  if (opts.comments) {
11473
11788
  const comments = await mcp2.call(
@@ -11490,7 +11805,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11490
11805
  if (/^[A-Z]+-\d+/.test(idOrRemoteId)) {
11491
11806
  return idOrRemoteId;
11492
11807
  }
11493
- const pendingSync = path54.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11808
+ const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11494
11809
  let entries;
11495
11810
  try {
11496
11811
  entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
@@ -11500,7 +11815,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11500
11815
  for (const entry of entries) {
11501
11816
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11502
11817
  try {
11503
- const raw = await fsPromises10.readFile(path54.join(pendingSync, entry.name), "utf8");
11818
+ const raw = await fsPromises10.readFile(path55.join(pendingSync, entry.name), "utf8");
11504
11819
  const { fm } = parseFrontmatter(raw);
11505
11820
  for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
11506
11821
  if (fm[key] === idOrRemoteId && typeof fm["remote_id"] === "string") {
@@ -11513,7 +11828,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11513
11828
  return null;
11514
11829
  }
11515
11830
  async function findLocalFile(remoteId, projectRoot) {
11516
- const pendingSync = path54.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11831
+ const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11517
11832
  let entries;
11518
11833
  try {
11519
11834
  entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
@@ -11522,7 +11837,7 @@ async function findLocalFile(remoteId, projectRoot) {
11522
11837
  }
11523
11838
  for (const entry of entries) {
11524
11839
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11525
- const fullPath = path54.join(pendingSync, entry.name);
11840
+ const fullPath = path55.join(pendingSync, entry.name);
11526
11841
  try {
11527
11842
  const raw = await fsPromises10.readFile(fullPath, "utf8");
11528
11843
  const { fm } = parseFrontmatter(raw);
@@ -11533,7 +11848,7 @@ async function findLocalFile(remoteId, projectRoot) {
11533
11848
  return null;
11534
11849
  }
11535
11850
  async function writeAtomic7(filePath, content) {
11536
- await fsPromises10.mkdir(path54.dirname(filePath), { recursive: true });
11851
+ await fsPromises10.mkdir(path55.dirname(filePath), { recursive: true });
11537
11852
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11538
11853
  await fsPromises10.writeFile(tmpPath, content, "utf8");
11539
11854
  await fsPromises10.rename(tmpPath, filePath);
@@ -11548,8 +11863,9 @@ function getItemId3(fm) {
11548
11863
 
11549
11864
  // src/commands/push.ts
11550
11865
  init_cjs_shims();
11866
+ var fs46 = __toESM(require("fs"), 1);
11551
11867
  var fsPromises11 = __toESM(require("fs/promises"), 1);
11552
- var path55 = __toESM(require("path"), 1);
11868
+ var path56 = __toESM(require("path"), 1);
11553
11869
  init_acquire();
11554
11870
  init_config();
11555
11871
  async function pushHandler(fileOrId, opts = {}) {
@@ -11559,6 +11875,13 @@ async function pushHandler(fileOrId, opts = {}) {
11559
11875
  const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
11560
11876
  const exit = opts.exit ?? ((c) => process.exit(c));
11561
11877
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
11878
+ const migrationLockPath = path56.join(projectRoot, ".cleargate", ".migration-lock");
11879
+ if (fs46.existsSync(migrationLockPath)) {
11880
+ stderr(`Error: CR-067 migration in progress (.migration-lock held); retry in 30s
11881
+ `);
11882
+ exit(75);
11883
+ return;
11884
+ }
11562
11885
  const identity = resolveIdentity(projectRoot);
11563
11886
  const sprintRoot = resolveActiveSprintDir(projectRoot);
11564
11887
  async function resolveMcp() {
@@ -11625,7 +11948,7 @@ async function pushHandler(fileOrId, opts = {}) {
11625
11948
  }
11626
11949
  async function handlePush(filePath, ctx) {
11627
11950
  const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
11628
- const resolvedPath = path55.isAbsolute(filePath) ? filePath : path55.resolve(projectRoot, filePath);
11951
+ const resolvedPath = path56.isAbsolute(filePath) ? filePath : path56.resolve(projectRoot, filePath);
11629
11952
  if (SPRINT_RUNS_PATH_REGEX.test(resolvedPath)) {
11630
11953
  if (!SPRINT_REPORT_PATH_REGEX.test(resolvedPath)) {
11631
11954
  stderr(
@@ -11767,8 +12090,8 @@ async function handleRevert(idOrRemoteId, ctx) {
11767
12090
  void localPath;
11768
12091
  }
11769
12092
  async function resolveLocalItem(idOrRemoteId, projectRoot) {
11770
- const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11771
- const archive = path55.join(projectRoot, ".cleargate", "delivery", "archive");
12093
+ const pendingSync = path56.join(projectRoot, ".cleargate", "delivery", "pending-sync");
12094
+ const archive = path56.join(projectRoot, ".cleargate", "delivery", "archive");
11772
12095
  for (const dir of [pendingSync, archive]) {
11773
12096
  let entries;
11774
12097
  try {
@@ -11778,7 +12101,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
11778
12101
  }
11779
12102
  for (const entry of entries) {
11780
12103
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11781
- const fullPath = path55.join(dir, entry.name);
12104
+ const fullPath = path56.join(dir, entry.name);
11782
12105
  try {
11783
12106
  const raw = await fsPromises11.readFile(fullPath, "utf8");
11784
12107
  const { fm } = parseFrontmatter(raw);
@@ -11797,7 +12120,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
11797
12120
  return null;
11798
12121
  }
11799
12122
  async function writeAtomic8(filePath, content) {
11800
- await fsPromises11.mkdir(path55.dirname(filePath), { recursive: true });
12123
+ await fsPromises11.mkdir(path56.dirname(filePath), { recursive: true });
11801
12124
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11802
12125
  await fsPromises11.writeFile(tmpPath, content, "utf8");
11803
12126
  await fsPromises11.rename(tmpPath, filePath);
@@ -11833,7 +12156,7 @@ function getItemTypeWithPathOverride(localPath, fm) {
11833
12156
  // src/commands/conflicts.ts
11834
12157
  init_cjs_shims();
11835
12158
  var fsPromises12 = __toESM(require("fs/promises"), 1);
11836
- var path56 = __toESM(require("path"), 1);
12159
+ var path57 = __toESM(require("path"), 1);
11837
12160
  init_acquire();
11838
12161
  init_config();
11839
12162
  var RESOLUTION_HINTS = {
@@ -11871,7 +12194,7 @@ async function conflictsHandler(opts = {}) {
11871
12194
  }
11872
12195
  }
11873
12196
  }
11874
- const conflictsFile = path56.join(projectRoot, ".cleargate", ".conflicts.json");
12197
+ const conflictsFile = path57.join(projectRoot, ".cleargate", ".conflicts.json");
11875
12198
  let data;
11876
12199
  try {
11877
12200
  const raw = await fsPromises12.readFile(conflictsFile, "utf8");
@@ -11959,8 +12282,8 @@ function formatEntry(entry) {
11959
12282
 
11960
12283
  // src/commands/admin-login.ts
11961
12284
  init_cjs_shims();
11962
- var fs45 = __toESM(require("fs"), 1);
11963
- var path57 = __toESM(require("path"), 1);
12285
+ var fs47 = __toESM(require("fs"), 1);
12286
+ var path58 = __toESM(require("path"), 1);
11964
12287
  var os11 = __toESM(require("os"), 1);
11965
12288
  var DEFAULT_MCP_URL = "http://localhost:3000";
11966
12289
  function resolveMcpUrl(mcpUrlFlag, env) {
@@ -11969,14 +12292,14 @@ function resolveMcpUrl(mcpUrlFlag, env) {
11969
12292
  function resolveAuthFilePath(opts) {
11970
12293
  if (opts.authFilePath) return opts.authFilePath;
11971
12294
  const homedirFn = opts.homedir ?? os11.homedir;
11972
- return path57.join(homedirFn(), ".cleargate", "admin-auth.json");
12295
+ return path58.join(homedirFn(), ".cleargate", "admin-auth.json");
11973
12296
  }
11974
12297
  function writeAdminAuth(filePath, token) {
11975
- const dir = path57.dirname(filePath);
11976
- fs45.mkdirSync(dir, { recursive: true });
12298
+ const dir = path58.dirname(filePath);
12299
+ fs47.mkdirSync(dir, { recursive: true });
11977
12300
  const payload = JSON.stringify({ version: 1, token }, null, 2);
11978
- fs45.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
11979
- fs45.chmodSync(filePath, 384);
12301
+ fs47.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
12302
+ fs47.chmodSync(filePath, 384);
11980
12303
  }
11981
12304
  async function adminLoginHandler(opts = {}) {
11982
12305
  const fetchFn = opts.fetch ?? globalThis.fetch;
@@ -12086,8 +12409,8 @@ async function adminLoginHandler(opts = {}) {
12086
12409
 
12087
12410
  // src/commands/hotfix.ts
12088
12411
  init_cjs_shims();
12089
- var fs46 = __toESM(require("fs"), 1);
12090
- var path58 = __toESM(require("path"), 1);
12412
+ var fs48 = __toESM(require("fs"), 1);
12413
+ var path59 = __toESM(require("path"), 1);
12091
12414
  function defaultExit4(code) {
12092
12415
  return process.exit(code);
12093
12416
  }
@@ -12097,7 +12420,7 @@ function maxHotfixId(pendingDir) {
12097
12420
  let max = 0;
12098
12421
  let entries;
12099
12422
  try {
12100
- entries = fs46.readdirSync(pendingDir);
12423
+ entries = fs48.readdirSync(pendingDir);
12101
12424
  } catch {
12102
12425
  return 0;
12103
12426
  }
@@ -12111,13 +12434,13 @@ function maxHotfixId(pendingDir) {
12111
12434
  return max;
12112
12435
  }
12113
12436
  function countActiveHotfixes(repoRoot) {
12114
- const pendingDir = path58.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12115
- const archiveDir = path58.join(repoRoot, ".cleargate", "delivery", "archive");
12437
+ const pendingDir = path59.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12438
+ const archiveDir = path59.join(repoRoot, ".cleargate", "delivery", "archive");
12116
12439
  const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
12117
12440
  let count = 0;
12118
12441
  let pendingEntries = [];
12119
12442
  try {
12120
- pendingEntries = fs46.readdirSync(pendingDir);
12443
+ pendingEntries = fs48.readdirSync(pendingDir);
12121
12444
  } catch {
12122
12445
  }
12123
12446
  for (const entry of pendingEntries) {
@@ -12125,13 +12448,13 @@ function countActiveHotfixes(repoRoot) {
12125
12448
  }
12126
12449
  let archiveEntries = [];
12127
12450
  try {
12128
- archiveEntries = fs46.readdirSync(archiveDir);
12451
+ archiveEntries = fs48.readdirSync(archiveDir);
12129
12452
  } catch {
12130
12453
  }
12131
12454
  for (const entry of archiveEntries) {
12132
12455
  if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
12133
12456
  try {
12134
- const stat = fs46.statSync(path58.join(archiveDir, entry));
12457
+ const stat = fs48.statSync(path59.join(archiveDir, entry));
12135
12458
  if (stat.mtimeMs >= sevenDaysAgo) count++;
12136
12459
  } catch {
12137
12460
  }
@@ -12140,7 +12463,7 @@ function countActiveHotfixes(repoRoot) {
12140
12463
  return count;
12141
12464
  }
12142
12465
  function resolveTemplatePath(repoRoot) {
12143
- return path58.join(repoRoot, ".cleargate", "templates", "hotfix.md");
12466
+ return path59.join(repoRoot, ".cleargate", "templates", "hotfix.md");
12144
12467
  }
12145
12468
  function hotfixNewHandler(opts, cli) {
12146
12469
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -12159,14 +12482,14 @@ function hotfixNewHandler(opts, cli) {
12159
12482
  );
12160
12483
  return exitFn(1);
12161
12484
  }
12162
- const pendingDir = path58.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12485
+ const pendingDir = path59.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12163
12486
  const maxId = maxHotfixId(pendingDir);
12164
12487
  const nextId = maxId + 1;
12165
12488
  const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
12166
12489
  const templatePath = resolveTemplatePath(repoRoot);
12167
12490
  let templateContent;
12168
12491
  try {
12169
- templateContent = fs46.readFileSync(templatePath, "utf8");
12492
+ templateContent = fs48.readFileSync(templatePath, "utf8");
12170
12493
  } catch {
12171
12494
  stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
12172
12495
  return exitFn(2);
@@ -12174,10 +12497,10 @@ function hotfixNewHandler(opts, cli) {
12174
12497
  const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
12175
12498
  const fileSlug = opts.slug.replace(/-/g, "_");
12176
12499
  const fileName = `${idStr}_${fileSlug}.md`;
12177
- const outPath = path58.join(pendingDir, fileName);
12500
+ const outPath = path59.join(pendingDir, fileName);
12178
12501
  try {
12179
- fs46.mkdirSync(pendingDir, { recursive: true });
12180
- fs46.writeFileSync(outPath, content, "utf8");
12502
+ fs48.mkdirSync(pendingDir, { recursive: true });
12503
+ fs48.writeFileSync(outPath, content, "utf8");
12181
12504
  } catch (err) {
12182
12505
  const msg = err instanceof Error ? err.message : String(err);
12183
12506
  stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
@@ -12362,7 +12685,7 @@ async function mcpServeHandler(opts) {
12362
12685
  const errMsg = err instanceof Error ? err.message : String(err);
12363
12686
  stderr(`cleargate mcp serve: proxy error: ${errMsg}
12364
12687
  `);
12365
- const id = extractId(line);
12688
+ const id = extractId2(line);
12366
12689
  if (id !== void 0) {
12367
12690
  stdout(
12368
12691
  JSON.stringify({
@@ -12438,7 +12761,7 @@ async function streamSse(res, stdout) {
12438
12761
  }
12439
12762
  }
12440
12763
  }
12441
- function extractId(line) {
12764
+ function extractId2(line) {
12442
12765
  try {
12443
12766
  const obj = JSON.parse(line);
12444
12767
  return "id" in obj ? obj.id : void 0;
@@ -12610,8 +12933,8 @@ sprint.command("close <sprint-id>").description("close a sprint \u2014 validates
12610
12933
  }
12611
12934
  sprintCloseHandler(handlerOpts);
12612
12935
  });
12613
- sprint.command("reconcile-lifecycle <sprint-id>").description("CR-017: check lifecycle status of artifacts referenced in this sprint's commits (exits 1 on drift)").option("--since <iso-date>", "start of git log range (default: sprint start_date or 90 days ago)").option("--until <iso-date>", "end of git log range (default: now)").action((sprintId, opts) => {
12614
- reconcileLifecycleCliHandler({ sprintId, since: opts.since, until: opts.until });
12936
+ sprint.command("reconcile-lifecycle <sprint-id>").description("CR-017: check lifecycle status of artifacts referenced in this sprint's commits (exits 1 on drift)").option("--since <iso-date>", "start of git log range (default: sprint start_date or 90 days ago)").option("--until <iso-date>", "end of git log range (default: now)").option("--parents", "audit parent (Epic/Sprint) rollup statuses; read-only (CR-066)").action((sprintId, opts) => {
12937
+ reconcileLifecycleCliHandler({ sprintId, since: opts.since, until: opts.until, parents: opts.parents });
12615
12938
  });
12616
12939
  sprint.command("archive <sprint-id>").description("archive a completed sprint \u2014 move pending-sync files, clear .active, merge + delete sprint branch").option("--dry-run", "print the archive plan without making any changes").option("--allow-wiki-lint-debt", "CR-022 M5: waive wiki-lint findings during archive (mirrors --allow-drift pattern)").action(async (sprintId, opts) => {
12617
12940
  const handlerOpts = { sprintId };