cleargate 0.11.5 → 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 (50) hide show
  1. package/dist/MANIFEST.json +15 -15
  2. package/dist/admin-api/index.cjs +3 -1
  3. package/dist/admin-api/index.cjs.map +1 -1
  4. package/dist/admin-api/index.d.cts +1 -0
  5. package/dist/admin-api/index.d.ts +1 -0
  6. package/dist/admin-api/index.js +3 -1
  7. package/dist/admin-api/index.js.map +1 -1
  8. package/dist/{chunk-HZPJ5QX4.js → chunk-EG6YGT2O.js} +315 -33
  9. package/dist/chunk-EG6YGT2O.js.map +1 -0
  10. package/dist/cli.cjs +855 -359
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.js +316 -108
  13. package/dist/cli.js.map +1 -1
  14. package/dist/lib/lifecycle-reconcile.cjs +318 -34
  15. package/dist/lib/lifecycle-reconcile.cjs.map +1 -1
  16. package/dist/lib/lifecycle-reconcile.d.cts +55 -4
  17. package/dist/lib/lifecycle-reconcile.d.ts +55 -4
  18. package/dist/lib/lifecycle-reconcile.js +7 -3
  19. package/dist/templates/cleargate-planning/.claude/agents/architect.md +6 -4
  20. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
  21. package/dist/templates/cleargate-planning/.claude/agents/developer.md +8 -4
  22. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
  23. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +138 -0
  24. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +162 -0
  25. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
  26. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
  27. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
  28. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
  29. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  30. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
  31. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
  32. package/dist/templates/cleargate-planning/CLAUDE.md +4 -0
  33. package/dist/templates/cleargate-planning/MANIFEST.json +15 -15
  34. package/package.json +8 -9
  35. package/templates/cleargate-planning/.claude/agents/architect.md +6 -4
  36. package/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
  37. package/templates/cleargate-planning/.claude/agents/developer.md +8 -4
  38. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
  39. package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +138 -0
  40. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +162 -0
  41. package/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
  42. package/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
  43. package/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
  44. package/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
  45. package/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  46. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
  47. package/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
  48. package/templates/cleargate-planning/CLAUDE.md +4 -0
  49. package/templates/cleargate-planning/MANIFEST.json +15 -15
  50. 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.11.5",
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
 
@@ -2304,6 +2303,24 @@ var PREFIX_MAP = [
2304
2303
  { prefix: "BUG-", type: "bug", bucket: "bugs" },
2305
2304
  { prefix: "INITIATIVE-", type: "initiative", bucket: "initiatives" }
2306
2305
  ];
2306
+ var SPRINT_REPORT_FILENAMES = ["REPORT.md"];
2307
+ var SPRINT_REPORT_CANONICAL_RE = /^SPRINT-\d{2,}_REPORT\.md$/;
2308
+ function isSprintReportPath(relPath) {
2309
+ const normalised = relPath.replace(/\\/g, "/");
2310
+ const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\/([^/]+)$/.exec(normalised);
2311
+ if (!match) return false;
2312
+ const filename = match[2];
2313
+ return SPRINT_REPORT_FILENAMES.includes(filename) || SPRINT_REPORT_CANONICAL_RE.test(filename);
2314
+ }
2315
+ function deriveBucketFromReportPath(relPath) {
2316
+ const normalised = relPath.replace(/\\/g, "/");
2317
+ const match = /\.cleargate\/sprint-runs\/(SPRINT-\d{2,})\//.exec(normalised);
2318
+ if (!match) {
2319
+ throw new Error(`deriveBucketFromReportPath: cannot extract SPRINT-NN from: ${relPath}`);
2320
+ }
2321
+ const id = match[1];
2322
+ return { type: "sprint", id, bucket: "sprints" };
2323
+ }
2307
2324
  function deriveBucket(filename) {
2308
2325
  const base = filename.includes("/") ? filename.split("/").pop() : filename;
2309
2326
  const stem = base.endsWith(".md") ? base.slice(0, -3) : base;
@@ -2427,6 +2444,12 @@ function serializePage(page, body) {
2427
2444
  if (page.sprint_cleargate_id !== void 0) {
2428
2445
  lines.push(`sprint_cleargate_id: "${page.sprint_cleargate_id}"`);
2429
2446
  }
2447
+ if (page.report_raw_path !== void 0) {
2448
+ lines.push(`report_raw_path: "${page.report_raw_path}"`);
2449
+ }
2450
+ if (page.last_report_ingest_commit !== void 0) {
2451
+ lines.push(`last_report_ingest_commit: "${page.last_report_ingest_commit}"`);
2452
+ }
2430
2453
  lines.push("---");
2431
2454
  const fm = lines.join("\n");
2432
2455
  return `${fm}
@@ -2449,7 +2472,9 @@ function parsePage(raw) {
2449
2472
  const last_contradict_sha = fm["last_contradict_sha"] !== void 0 ? String(fm["last_contradict_sha"]) : void 0;
2450
2473
  const parent_cleargate_id = fm["parent_cleargate_id"] !== void 0 ? String(fm["parent_cleargate_id"]) : void 0;
2451
2474
  const sprint_cleargate_id = fm["sprint_cleargate_id"] !== void 0 ? String(fm["sprint_cleargate_id"]) : void 0;
2452
- return { type, id, parent, children, status, remote_id, raw_path, last_ingest, last_ingest_commit, repo, last_contradict_sha, parent_cleargate_id, sprint_cleargate_id };
2475
+ const report_raw_path = fm["report_raw_path"] !== void 0 ? String(fm["report_raw_path"]) : void 0;
2476
+ const last_report_ingest_commit = fm["last_report_ingest_commit"] !== void 0 ? String(fm["last_report_ingest_commit"]) : void 0;
2477
+ return { type, id, parent, children, status, remote_id, raw_path, last_ingest, last_ingest_commit, repo, last_contradict_sha, parent_cleargate_id, sprint_cleargate_id, report_raw_path, last_report_ingest_commit };
2453
2478
  }
2454
2479
  function parseFmRaw(raw) {
2455
2480
  const lines = raw.split("\n");
@@ -3981,28 +4006,33 @@ async function wikiIngestHandler(opts) {
3981
4006
  const rawPath = opts.rawPath;
3982
4007
  const absRawPath = path20.isAbsolute(rawPath) ? rawPath : path20.resolve(cwd, rawPath);
3983
4008
  const relRawPath = path20.relative(cwd, absRawPath).replace(/\\/g, "/");
3984
- const deliveryRoot = path20.join(cwd, ".cleargate", "delivery");
3985
- const deliveryRootNorm = deliveryRoot.replace(/\\/g, "/");
3986
- const absDeliveryRoot = deliveryRoot;
3987
- const relToDelivery = path20.relative(absDeliveryRoot, absRawPath);
3988
- if (relToDelivery.startsWith("..") || path20.isAbsolute(relToDelivery)) {
3989
- stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
4009
+ const isSprintReport = isSprintReportPath(relRawPath);
4010
+ if (!isSprintReport) {
4011
+ const deliveryRoot = path20.join(cwd, ".cleargate", "delivery");
4012
+ const absDeliveryRoot = deliveryRoot;
4013
+ const relToDelivery = path20.relative(absDeliveryRoot, absRawPath);
4014
+ if (relToDelivery.startsWith("..") || path20.isAbsolute(relToDelivery)) {
4015
+ stderr(`wiki ingest: ${rawPath} not under .cleargate/delivery/
3990
4016
  `);
3991
- exit(2);
3992
- return;
3993
- }
3994
- void deliveryRootNorm;
3995
- const isExcluded = EXCLUDED_SUFFIXES2.some((excl) => relRawPath.startsWith(excl));
3996
- if (isExcluded) {
3997
- stdout(`wiki ingest: ${rawPath} excluded (skip)
4017
+ exit(2);
4018
+ return;
4019
+ }
4020
+ const isExcluded = EXCLUDED_SUFFIXES2.some((excl) => relRawPath.startsWith(excl));
4021
+ if (isExcluded) {
4022
+ stdout(`wiki ingest: ${rawPath} excluded (skip)
3998
4023
  `);
3999
- exit(0);
4000
- return;
4024
+ exit(0);
4025
+ return;
4026
+ }
4001
4027
  }
4002
4028
  const filename = path20.basename(absRawPath);
4003
4029
  let bucketInfo;
4004
4030
  try {
4005
- bucketInfo = deriveBucket(filename);
4031
+ if (isSprintReport) {
4032
+ bucketInfo = deriveBucketFromReportPath(relRawPath);
4033
+ } else {
4034
+ bucketInfo = deriveBucket(filename);
4035
+ }
4006
4036
  } catch (e) {
4007
4037
  stderr(`wiki ingest: cannot determine bucket for ${rawPath}: ${e.message}
4008
4038
  `);
@@ -4045,16 +4075,26 @@ async function wikiIngestHandler(opts) {
4045
4075
  }
4046
4076
  const currentSha = getGitSha(absRawPath, gitRunner) ?? "";
4047
4077
  const pageExists = fs19.existsSync(pagePath);
4048
- if (pageExists && currentSha !== "") {
4049
- let isNoOp = false;
4078
+ let existingPage;
4079
+ if (pageExists) {
4050
4080
  try {
4051
4081
  const existingPageContent = fs19.readFileSync(pagePath, "utf8");
4052
- const existingPage = parsePage(existingPageContent);
4053
- if (existingPage.last_ingest_commit === currentSha) {
4054
- const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
4055
- if (contentUnchanged) {
4082
+ existingPage = parsePage(existingPageContent);
4083
+ } catch {
4084
+ }
4085
+ }
4086
+ if (existingPage !== void 0 && currentSha !== "") {
4087
+ let isNoOp = false;
4088
+ try {
4089
+ if (isSprintReport) {
4090
+ if (existingPage.last_report_ingest_commit === currentSha) {
4056
4091
  isNoOp = true;
4057
4092
  }
4093
+ } else {
4094
+ if (existingPage.last_ingest_commit === currentSha) {
4095
+ const contentUnchanged = checkContentUnchanged(absRawPath, currentSha, relRawPath, gitRunner);
4096
+ if (contentUnchanged) isNoOp = true;
4097
+ }
4058
4098
  }
4059
4099
  } catch {
4060
4100
  }
@@ -4066,36 +4106,53 @@ async function wikiIngestHandler(opts) {
4066
4106
  }
4067
4107
  }
4068
4108
  const action = pageExists ? "update" : "create";
4069
- let existingLastContradictSha;
4070
- if (pageExists) {
4071
- try {
4072
- const existingPageContent = fs19.readFileSync(pagePath, "utf8");
4073
- const existingPage = parsePage(existingPageContent);
4074
- existingLastContradictSha = existingPage.last_contradict_sha;
4075
- } catch {
4076
- }
4077
- }
4109
+ const timestamp = now();
4110
+ const existingLastContradictSha = existingPage?.last_contradict_sha;
4111
+ const existingReportRawPath = existingPage?.report_raw_path;
4112
+ const existingLastReportIngestCommit = existingPage?.last_report_ingest_commit;
4078
4113
  const parent = buildParentRef2(fm);
4079
4114
  const children = buildChildrenRefs2(fm);
4080
- const timestamp = now();
4081
4115
  const wikiPage = {
4082
4116
  type,
4083
4117
  id,
4084
- parent,
4085
- children,
4086
- status: String(fm["status"] ?? ""),
4087
- remote_id: String(fm["remote_id"] ?? ""),
4088
- raw_path: relRawPath,
4118
+ parent: isSprintReport ? existingPage?.parent ?? "" : parent,
4119
+ children: isSprintReport ? existingPage?.children ?? [] : children,
4120
+ status: isSprintReport ? existingPage?.status ?? String(fm["status"] ?? "") : String(fm["status"] ?? ""),
4121
+ remote_id: isSprintReport ? existingPage?.remote_id ?? String(fm["remote_id"] ?? "") : String(fm["remote_id"] ?? ""),
4122
+ // raw_path tracks the plan file path; for report-only ingest preserve existing or use relRawPath as fallback
4123
+ raw_path: isSprintReport ? existingPage?.raw_path ?? relRawPath : relRawPath,
4089
4124
  last_ingest: timestamp,
4090
- last_ingest_commit: currentSha,
4125
+ // last_ingest_commit tracks the plan source; preserve when re-ingesting report
4126
+ last_ingest_commit: isSprintReport ? existingPage?.last_ingest_commit ?? "" : currentSha,
4091
4127
  repo,
4092
4128
  // Carry forward last_contradict_sha so Phase 4 idempotency survives re-ingest
4093
4129
  ...existingLastContradictSha !== void 0 ? { last_contradict_sha: existingLastContradictSha } : {},
4094
- // Hierarchy keys (§11.7): read from raw fm stamped at raw-side, not wiki-side
4095
- ...typeof fm["parent_cleargate_id"] === "string" ? { parent_cleargate_id: fm["parent_cleargate_id"] } : {},
4096
- ...typeof fm["sprint_cleargate_id"] === "string" ? { sprint_cleargate_id: fm["sprint_cleargate_id"] } : {}
4130
+ // Hierarchy keys (§11.7): read from raw fm for plan ingest; preserve for report ingest
4131
+ ...isSprintReport ? existingPage?.parent_cleargate_id !== void 0 ? { parent_cleargate_id: existingPage.parent_cleargate_id } : {} : typeof fm["parent_cleargate_id"] === "string" ? { parent_cleargate_id: fm["parent_cleargate_id"] } : {},
4132
+ ...isSprintReport ? existingPage?.sprint_cleargate_id !== void 0 ? { sprint_cleargate_id: existingPage.sprint_cleargate_id } : {} : typeof fm["sprint_cleargate_id"] === "string" ? { sprint_cleargate_id: fm["sprint_cleargate_id"] } : {},
4133
+ // CR-063 sprint-report fields
4134
+ report_raw_path: isSprintReport ? relRawPath : existingReportRawPath ?? void 0,
4135
+ last_report_ingest_commit: isSprintReport ? currentSha : existingLastReportIngestCommit ?? void 0
4097
4136
  };
4098
- const pageBody = buildPageBody2({ id, fm, body });
4137
+ let existingPageBody = "";
4138
+ if (existingPage !== void 0 && pageExists) {
4139
+ try {
4140
+ const existingPageContent = fs19.readFileSync(pagePath, "utf8");
4141
+ const lines = existingPageContent.split("\n");
4142
+ let closingDash = -1;
4143
+ for (let i = 1; i < lines.length; i++) {
4144
+ if (lines[i] === "---") {
4145
+ closingDash = i;
4146
+ break;
4147
+ }
4148
+ }
4149
+ if (closingDash !== -1) {
4150
+ existingPageBody = lines.slice(closingDash + 1).join("\n").replace(/^\n/, "");
4151
+ }
4152
+ } catch {
4153
+ }
4154
+ }
4155
+ const pageBody = buildPageBody2({ id, fm, body, isSprintReport, existingPageBody });
4099
4156
  const pageContent = serializePage(wikiPage, pageBody);
4100
4157
  fs19.mkdirSync(pageDir, { recursive: true });
4101
4158
  fs19.writeFileSync(pagePath, pageContent, "utf8");
@@ -4173,7 +4230,37 @@ function buildChildrenRefs2(fm) {
4173
4230
  return `[[${s}]]`;
4174
4231
  });
4175
4232
  }
4233
+ function extractReportBlock(pageBody) {
4234
+ const beginMarker = "<!-- BEGIN sprint-report -->";
4235
+ const endMarker = "<!-- END sprint-report -->";
4236
+ const beginIdx = pageBody.indexOf(beginMarker);
4237
+ const endIdx = pageBody.indexOf(endMarker);
4238
+ if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx) return void 0;
4239
+ return pageBody.slice(beginIdx, endIdx + endMarker.length);
4240
+ }
4241
+ function extractPlanStub(pageBody) {
4242
+ const beginMarker = "<!-- BEGIN sprint-report -->";
4243
+ const beginIdx = pageBody.indexOf(beginMarker);
4244
+ if (beginIdx === -1) return pageBody;
4245
+ return pageBody.slice(0, beginIdx);
4246
+ }
4176
4247
  function buildPageBody2(item) {
4248
+ const { isSprintReport, existingPageBody } = item;
4249
+ if (isSprintReport) {
4250
+ const planStub = existingPageBody ? extractPlanStub(existingPageBody) : buildPlanStub(item);
4251
+ const reportBlock = buildReportBlock(item.body);
4252
+ return planStub + reportBlock;
4253
+ } else {
4254
+ const planStub = buildPlanStub(item);
4255
+ const existingReportBlock = existingPageBody ? extractReportBlock(existingPageBody) : void 0;
4256
+ if (existingReportBlock !== void 0) {
4257
+ const stub = planStub.trimEnd() + "\n\n";
4258
+ return stub + existingReportBlock + "\n";
4259
+ }
4260
+ return planStub;
4261
+ }
4262
+ }
4263
+ function buildPlanStub(item) {
4177
4264
  const title = String(item.fm["title"] ?? item.id);
4178
4265
  const summary = String(
4179
4266
  item.fm["description"] ?? item.body.split("\n")[0] ?? "No summary available."
@@ -4197,6 +4284,16 @@ function buildPageBody2(item) {
4197
4284
  ""
4198
4285
  ].join("\n");
4199
4286
  }
4287
+ function buildReportBlock(reportBody) {
4288
+ return [
4289
+ "<!-- BEGIN sprint-report -->",
4290
+ "## Sprint Report",
4291
+ "",
4292
+ reportBody.trim(),
4293
+ "<!-- END sprint-report -->",
4294
+ ""
4295
+ ].join("\n");
4296
+ }
4200
4297
  function appendLogEntry(wikiRoot, entry) {
4201
4298
  const logPath = path20.join(wikiRoot, "log.md");
4202
4299
  const logEntry = [
@@ -4428,9 +4525,9 @@ function detectWorkItemTypeFromFm(fm) {
4428
4525
  }
4429
4526
  function detectWorkItemType(idOrPath) {
4430
4527
  const upper = idOrPath.toUpperCase();
4431
- const basename13 = upper.split("/").pop() ?? upper;
4528
+ const basename14 = upper.split("/").pop() ?? upper;
4432
4529
  for (const { prefix, type } of PREFIX_MAP2) {
4433
- if (basename13.includes(prefix)) {
4530
+ if (basename14.includes(prefix)) {
4434
4531
  return type;
4435
4532
  }
4436
4533
  }
@@ -6677,7 +6774,7 @@ async function writeCachedGate(absPath, result, opts) {
6677
6774
  throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
6678
6775
  }
6679
6776
  const existing = coerceCachedGate(fm["cached_gate_result"]);
6680
- if (existing && JSON.stringify(existing) === JSON.stringify(newResult)) {
6777
+ if (existing && existing.pass === newResult.pass && JSON.stringify(existing.failing_criteria) === JSON.stringify(newResult.failing_criteria)) {
6681
6778
  return;
6682
6779
  }
6683
6780
  const newFm = {};
@@ -7017,24 +7114,312 @@ function gateRunHandler(name, opts, cli) {
7017
7114
 
7018
7115
  // src/commands/sprint.ts
7019
7116
  init_cjs_shims();
7020
- var fs34 = __toESM(require("fs"), 1);
7021
- var path36 = __toESM(require("path"), 1);
7117
+ var fs35 = __toESM(require("fs"), 1);
7118
+ var path37 = __toESM(require("path"), 1);
7022
7119
  var import_node_child_process11 = require("child_process");
7023
7120
  var import_js_yaml7 = __toESM(require("js-yaml"), 1);
7024
7121
 
7025
7122
  // src/lib/lifecycle-reconcile.ts
7026
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();
7027
7130
  var fs33 = __toESM(require("fs"), 1);
7028
7131
  var path35 = __toESM(require("path"), 1);
7029
- 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"];
7030
7415
  var VERB_STATUS_MAP = {
7031
7416
  feat: {
7032
7417
  types: ["STORY", "EPIC", "CR"],
7033
- expected: ["Done", "Completed"]
7418
+ expected: [...ARTIFACT_GATE_EXPECTED]
7034
7419
  },
7035
7420
  fix: {
7036
7421
  types: ["BUG", "HOTFIX"],
7037
- expected: ["Verified", "Done", "Completed"]
7422
+ expected: [...ARTIFACT_GATE_EXPECTED]
7038
7423
  }
7039
7424
  };
7040
7425
  var ID_PATTERN = /\b(STORY-\d{3}-\d{2}|(CR|BUG|EPIC|HOTFIX)-\d{3}|(PROPOSAL|PROP)-\d{3})\b/g;
@@ -7085,10 +7470,10 @@ function findArtifactFile(deliveryRoot, id) {
7085
7470
  { rel: "archive", inArchive: true }
7086
7471
  ];
7087
7472
  for (const { rel, inArchive } of dirs) {
7088
- const dir = path35.join(deliveryRoot, rel);
7473
+ const dir = path36.join(deliveryRoot, rel);
7089
7474
  let entries;
7090
7475
  try {
7091
- entries = fs33.readdirSync(dir);
7476
+ entries = fs34.readdirSync(dir);
7092
7477
  } catch {
7093
7478
  continue;
7094
7479
  }
@@ -7096,7 +7481,7 @@ function findArtifactFile(deliveryRoot, id) {
7096
7481
  (e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith(".md")
7097
7482
  );
7098
7483
  if (match) {
7099
- const absPath = path35.join(dir, match);
7484
+ const absPath = path36.join(dir, match);
7100
7485
  return { absPath, inArchive, relPath: `${rel}/${match}` };
7101
7486
  }
7102
7487
  }
@@ -7105,7 +7490,7 @@ function findArtifactFile(deliveryRoot, id) {
7105
7490
  function readArtifactStatus(absPath) {
7106
7491
  let raw;
7107
7492
  try {
7108
- raw = fs33.readFileSync(absPath, "utf8");
7493
+ raw = fs34.readFileSync(absPath, "utf8");
7109
7494
  } catch {
7110
7495
  return { status: null, carryOver: false };
7111
7496
  }
@@ -7160,7 +7545,7 @@ function reconcileLifecycle(opts) {
7160
7545
  if (carryOver) continue;
7161
7546
  let expectedStatuses;
7162
7547
  if (verb === "feat" && type === "BUG") {
7163
- expectedStatuses = ["Verified", "Done", "Completed"];
7548
+ expectedStatuses = [...ARTIFACT_GATE_EXPECTED];
7164
7549
  } else if (!verbConfig.types.includes(type)) {
7165
7550
  continue;
7166
7551
  } else {
@@ -7172,7 +7557,7 @@ function reconcileLifecycle(opts) {
7172
7557
  cleanIds.add(id);
7173
7558
  idToItem.delete(id);
7174
7559
  } else if (!idToItem.has(id)) {
7175
- const expectedStr = expectedStatuses[0] ?? "Done";
7560
+ const expectedStr = expectedStatuses[0] ?? "Completed";
7176
7561
  idToItem.set(id, {
7177
7562
  id,
7178
7563
  type,
@@ -7202,7 +7587,7 @@ function reconcileDecomposition(opts) {
7202
7587
  const { sprintPlanPath, deliveryRoot } = opts;
7203
7588
  let raw;
7204
7589
  try {
7205
- raw = fs33.readFileSync(sprintPlanPath, "utf8");
7590
+ raw = fs34.readFileSync(sprintPlanPath, "utf8");
7206
7591
  } catch {
7207
7592
  return { missing: [], clean: 0 };
7208
7593
  }
@@ -7214,11 +7599,11 @@ function reconcileDecomposition(opts) {
7214
7599
  }
7215
7600
  const epics = Array.isArray(fm["epics"]) ? fm["epics"].map(String) : [];
7216
7601
  const proposals = Array.isArray(fm["proposals"]) ? fm["proposals"].map(String) : [];
7217
- const pendingDir = path35.join(deliveryRoot, "pending-sync");
7218
- const archiveDir = path35.join(deliveryRoot, "archive");
7602
+ const pendingDir = path36.join(deliveryRoot, "pending-sync");
7603
+ const archiveDir = path36.join(deliveryRoot, "archive");
7219
7604
  function listMdFiles(dir) {
7220
7605
  try {
7221
- return fs33.readdirSync(dir).filter((f) => f.endsWith(".md"));
7606
+ return fs34.readdirSync(dir).filter((f) => f.endsWith(".md"));
7222
7607
  } catch {
7223
7608
  return [];
7224
7609
  }
@@ -7290,9 +7675,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
7290
7675
  for (const f of files) {
7291
7676
  if (!f.startsWith(storyPrefix) && !f.startsWith("STORY-")) continue;
7292
7677
  if (!f.includes(storyPrefix)) continue;
7293
- const absPath = path35.join(dir, f);
7678
+ const absPath = path36.join(dir, f);
7294
7679
  try {
7295
- const raw = fs33.readFileSync(absPath, "utf8");
7680
+ const raw = fs34.readFileSync(absPath, "utf8");
7296
7681
  const { fm } = parseFrontmatter(raw);
7297
7682
  const parentRef = fm["parent_epic_ref"];
7298
7683
  if (parentRef === epicId) {
@@ -7307,9 +7692,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
7307
7692
  function findDecomposedEpic(proposalId, pendingDir, pendingFiles) {
7308
7693
  for (const f of pendingFiles) {
7309
7694
  if (!f.startsWith("EPIC-")) continue;
7310
- const absPath = path35.join(pendingDir, f);
7695
+ const absPath = path36.join(pendingDir, f);
7311
7696
  try {
7312
- const raw = fs33.readFileSync(absPath, "utf8");
7697
+ const raw = fs34.readFileSync(absPath, "utf8");
7313
7698
  const { fm } = parseFrontmatter(raw);
7314
7699
  const contextSource = fm["context_source"];
7315
7700
  if (typeof contextSource === "string" && contextSource.includes(proposalId)) {
@@ -7335,7 +7720,7 @@ var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandone
7335
7720
  function resolveRunScript(opts) {
7336
7721
  if (opts.runScriptPath) return opts.runScriptPath;
7337
7722
  const cwd = opts.cwd ?? process.cwd();
7338
- return path36.join(cwd, ".cleargate", "scripts", "run_script.sh");
7723
+ return path37.join(cwd, ".cleargate", "scripts", "run_script.sh");
7339
7724
  }
7340
7725
  function defaultExit(code) {
7341
7726
  return process.exit(code);
@@ -7354,18 +7739,18 @@ function sprintInitHandler(opts, cli) {
7354
7739
  if (mode === "v1") {
7355
7740
  return printInertAndExit(stdoutFn, exitFn);
7356
7741
  }
7357
- const deliveryRoot = path36.join(cwd, ".cleargate", "delivery");
7742
+ const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
7358
7743
  let lifecycleInitMode = "warn";
7359
7744
  let sprintPlanPath = null;
7360
- const pendingDir = path36.join(deliveryRoot, "pending-sync");
7745
+ const pendingDir = path37.join(deliveryRoot, "pending-sync");
7361
7746
  try {
7362
- const entries = fs34.readdirSync(pendingDir);
7747
+ const entries = fs35.readdirSync(pendingDir);
7363
7748
  const sprintFile = entries.find(
7364
7749
  (e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
7365
7750
  );
7366
7751
  if (sprintFile) {
7367
- sprintPlanPath = path36.join(pendingDir, sprintFile);
7368
- const raw = fs34.readFileSync(sprintPlanPath, "utf8");
7752
+ sprintPlanPath = path37.join(pendingDir, sprintFile);
7753
+ const raw = fs35.readFileSync(sprintPlanPath, "utf8");
7369
7754
  const { fm } = parseFileFrontmatter(raw);
7370
7755
  if (fm["lifecycle_init_mode"] === "block") {
7371
7756
  lifecycleInitMode = "block";
@@ -7399,7 +7784,7 @@ function sprintInitHandler(opts, cli) {
7399
7784
  stderrFn("[cleargate sprint init] lifecycle drift waived via --allow-drift flag");
7400
7785
  if (sprintPlanPath) {
7401
7786
  try {
7402
- const rawSprint = fs34.readFileSync(sprintPlanPath, "utf8");
7787
+ const rawSprint = fs35.readFileSync(sprintPlanPath, "utf8");
7403
7788
  const { fm, body } = parseFileFrontmatter(rawSprint);
7404
7789
  const waiverLine = `lifecycle waiver: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} for ${lifecycleResult.drift.map((d) => d.id).join(", ")}`;
7405
7790
  const currentContextSource = typeof fm["context_source"] === "string" ? fm["context_source"] : "";
@@ -7496,20 +7881,20 @@ function reconcileLifecycleCliHandler(opts, cli) {
7496
7881
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
7497
7882
  const exitFn = cli?.exit ?? defaultExit;
7498
7883
  const cwd = cli?.cwd ?? process.cwd();
7499
- const deliveryRoot = path36.join(cwd, ".cleargate", "delivery");
7884
+ const deliveryRoot = path37.join(cwd, ".cleargate", "delivery");
7500
7885
  let since;
7501
7886
  let until;
7502
7887
  if (opts.since) {
7503
7888
  since = new Date(opts.since);
7504
7889
  } else {
7505
7890
  try {
7506
- const pendingDir = path36.join(deliveryRoot, "pending-sync");
7507
- const entries = fs34.readdirSync(pendingDir);
7891
+ const pendingDir = path37.join(deliveryRoot, "pending-sync");
7892
+ const entries = fs35.readdirSync(pendingDir);
7508
7893
  const sprintFile = entries.find(
7509
7894
  (e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
7510
7895
  );
7511
7896
  if (sprintFile) {
7512
- const raw = fs34.readFileSync(path36.join(pendingDir, sprintFile), "utf8");
7897
+ const raw = fs35.readFileSync(path37.join(pendingDir, sprintFile), "utf8");
7513
7898
  const { fm } = parseFileFrontmatter(raw);
7514
7899
  const startDate = fm["start_date"];
7515
7900
  since = typeof startDate === "string" ? new Date(startDate) : new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
@@ -7531,22 +7916,50 @@ function reconcileLifecycleCliHandler(opts, cli) {
7531
7916
  });
7532
7917
  if (result.drift.length === 0) {
7533
7918
  stdoutFn(`lifecycle: clean (${result.clean} artifacts reconciled)`);
7534
- return exitFn(0);
7535
- }
7536
- stderrFn(`lifecycle: DRIFT detected (${result.drift.length} unreconciled artifacts):`);
7537
- for (const item of result.drift) {
7538
- stderrFn(
7539
- ` 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"})`
7540
- );
7541
- stderrFn(
7542
- ` Remediation: git mv .cleargate/delivery/pending-sync/${item.file_path?.replace("pending-sync/", "") ?? item.id + "_*.md"} .cleargate/delivery/archive/ && update status: ${item.expected_status}`
7543
- );
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
+ }
7544
7932
  }
7545
- return exitFn(1);
7546
7933
  } catch (err) {
7547
7934
  stderrFn(`lifecycle reconciliation error: ${err instanceof Error ? err.message : String(err)}`);
7548
- return exitFn(1);
7935
+ if (!opts.parents) {
7936
+ return exitFn(1);
7937
+ }
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;
7549
7961
  }
7962
+ return exitFn(0);
7550
7963
  }
7551
7964
  function parseFileFrontmatter(raw) {
7552
7965
  const lines = raw.split("\n");
@@ -7590,8 +8003,8 @@ ${body}`;
7590
8003
  }
7591
8004
  function atomicWriteStr(filePath, content) {
7592
8005
  const tmp = `${filePath}.tmp.${process.pid}`;
7593
- fs34.writeFileSync(tmp, content, "utf8");
7594
- fs34.renameSync(tmp, filePath);
8006
+ fs35.writeFileSync(tmp, content, "utf8");
8007
+ fs35.renameSync(tmp, filePath);
7595
8008
  }
7596
8009
  function deriveSprintBranchForArchive(sprintId) {
7597
8010
  const match = /^SPRINT-(\d+)/.exec(sprintId);
@@ -7605,7 +8018,7 @@ function stampFile(raw, status, completedAt) {
7605
8018
  return serializeFileContent(fm, body);
7606
8019
  }
7607
8020
  function stampSprintClose(sprintPath, now) {
7608
- const previousContent = fs34.readFileSync(sprintPath, "utf8");
8021
+ const previousContent = fs35.readFileSync(sprintPath, "utf8");
7609
8022
  const { fm, body } = parseFileFrontmatter(previousContent);
7610
8023
  const currentStatus = typeof fm["status"] === "string" ? fm["status"] : "";
7611
8024
  const alreadyTerminal = TERMINAL_STATUSES2.has(currentStatus);
@@ -7665,14 +8078,14 @@ async function sprintArchiveHandler(opts, cli) {
7665
8078
  if (mode === "v1") {
7666
8079
  return printInertAndExit(stdoutFn, exitFn);
7667
8080
  }
7668
- const stateFile = path36.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
7669
- if (!fs34.existsSync(stateFile)) {
8081
+ const stateFile = path37.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
8082
+ if (!fs35.existsSync(stateFile)) {
7670
8083
  stderrFn(`[cleargate sprint archive] state.json not found at ${stateFile}`);
7671
8084
  return exitFn(1);
7672
8085
  }
7673
8086
  let state2;
7674
8087
  try {
7675
- state2 = JSON.parse(fs34.readFileSync(stateFile, "utf8"));
8088
+ state2 = JSON.parse(fs35.readFileSync(stateFile, "utf8"));
7676
8089
  } catch (err) {
7677
8090
  stderrFn(`[cleargate sprint archive] failed to parse state.json: ${err.message}`);
7678
8091
  return exitFn(1);
@@ -7684,18 +8097,18 @@ async function sprintArchiveHandler(opts, cli) {
7684
8097
  return exitFn(1);
7685
8098
  }
7686
8099
  const stateStories = state2.stories ?? {};
7687
- const pendingDir = path36.join(cwd, ".cleargate", "delivery", "pending-sync");
7688
- 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");
7689
8102
  let sprintFile = null;
7690
- for (const entry of fs34.readdirSync(pendingDir)) {
8103
+ for (const entry of fs35.readdirSync(pendingDir)) {
7691
8104
  if ((entry.startsWith(`${opts.sprintId}_`) || entry === `${opts.sprintId}.md`) && entry.endsWith(".md")) {
7692
- sprintFile = path36.join(pendingDir, entry);
8105
+ sprintFile = path37.join(pendingDir, entry);
7693
8106
  break;
7694
8107
  }
7695
8108
  }
7696
8109
  let epicIds = [];
7697
- if (sprintFile && fs34.existsSync(sprintFile)) {
7698
- const { fm } = parseFileFrontmatter(fs34.readFileSync(sprintFile, "utf8"));
8110
+ if (sprintFile && fs35.existsSync(sprintFile)) {
8111
+ const { fm } = parseFileFrontmatter(fs35.readFileSync(sprintFile, "utf8"));
7699
8112
  const epics = fm["epics"];
7700
8113
  if (Array.isArray(epics)) {
7701
8114
  epicIds = epics.map(String);
@@ -7705,15 +8118,15 @@ async function sprintArchiveHandler(opts, cli) {
7705
8118
  if (sprintFile) {
7706
8119
  plan.push({
7707
8120
  src: sprintFile,
7708
- destName: path36.basename(sprintFile),
8121
+ destName: path37.basename(sprintFile),
7709
8122
  status: "Completed"
7710
8123
  });
7711
8124
  }
7712
8125
  for (const epicId of epicIds) {
7713
- for (const entry of fs34.readdirSync(pendingDir)) {
8126
+ for (const entry of fs35.readdirSync(pendingDir)) {
7714
8127
  if ((entry.startsWith(`${epicId}_`) || entry === `${epicId}.md`) && entry.endsWith(".md")) {
7715
8128
  plan.push({
7716
- src: path36.join(pendingDir, entry),
8129
+ src: path37.join(pendingDir, entry),
7717
8130
  destName: entry,
7718
8131
  status: "Approved"
7719
8132
  });
@@ -7722,10 +8135,10 @@ async function sprintArchiveHandler(opts, cli) {
7722
8135
  }
7723
8136
  const storyKeys = storyKeysForEpic(stateStories, epicId);
7724
8137
  for (const storyId of storyKeys) {
7725
- for (const entry of fs34.readdirSync(pendingDir)) {
8138
+ for (const entry of fs35.readdirSync(pendingDir)) {
7726
8139
  if ((entry.startsWith(`${storyId}_`) || entry === `${storyId}.md`) && entry.endsWith(".md")) {
7727
8140
  plan.push({
7728
- src: path36.join(pendingDir, entry),
8141
+ src: path37.join(pendingDir, entry),
7729
8142
  destName: entry,
7730
8143
  status: "Done"
7731
8144
  });
@@ -7737,13 +8150,13 @@ async function sprintArchiveHandler(opts, cli) {
7737
8150
  const storyIdsInState = new Set(Object.keys(stateStories));
7738
8151
  const planSrcs = new Set(plan.map((p) => p.src));
7739
8152
  const orphans = [];
7740
- for (const entry of fs34.readdirSync(pendingDir)) {
8153
+ for (const entry of fs35.readdirSync(pendingDir)) {
7741
8154
  if (!entry.startsWith("STORY-") || !entry.endsWith(".md")) continue;
7742
- const candidate = path36.join(pendingDir, entry);
8155
+ const candidate = path37.join(pendingDir, entry);
7743
8156
  if (planSrcs.has(candidate)) continue;
7744
8157
  let raw;
7745
8158
  try {
7746
- raw = fs34.readFileSync(candidate, "utf8");
8159
+ raw = fs35.readFileSync(candidate, "utf8");
7747
8160
  } catch {
7748
8161
  continue;
7749
8162
  }
@@ -7760,18 +8173,18 @@ async function sprintArchiveHandler(opts, cli) {
7760
8173
  }
7761
8174
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
7762
8175
  const sprintBranch = deriveSprintBranchForArchive(opts.sprintId);
7763
- const activePath = path36.join(cwd, ".cleargate", "sprint-runs", ".active");
8176
+ const activePath = path37.join(cwd, ".cleargate", "sprint-runs", ".active");
7764
8177
  if (opts.dryRun) {
7765
8178
  stdoutFn(`[dry-run] Sprint archive plan for ${opts.sprintId}:`);
7766
8179
  stdoutFn(` Sprint branch: ${sprintBranch}`);
7767
8180
  stdoutFn(` Files to archive (${plan.length}):`);
7768
8181
  for (const entry of plan) {
7769
8182
  stdoutFn(
7770
- ` ${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>]`
7771
8184
  );
7772
8185
  }
7773
8186
  if (orphans.length > 0) {
7774
- 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(", ")}`);
7775
8188
  }
7776
8189
  stdoutFn(` .active \u2192 "" (truncate)`);
7777
8190
  stdoutFn(` git checkout main`);
@@ -7780,9 +8193,9 @@ async function sprintArchiveHandler(opts, cli) {
7780
8193
  return exitFn(0);
7781
8194
  }
7782
8195
  let sprintFileSnapshot = null;
7783
- const wikiRoot = path36.join(cwd, ".cleargate", "wiki");
7784
- const wikiInitialised = fs34.existsSync(wikiRoot);
7785
- 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)) {
7786
8199
  const { previousContent } = stampSprintClose(sprintFile, () => completedAt);
7787
8200
  sprintFileSnapshot = previousContent;
7788
8201
  if (wikiInitialised) {
@@ -7809,15 +8222,15 @@ async function sprintArchiveHandler(opts, cli) {
7809
8222
  }
7810
8223
  }
7811
8224
  for (const entry of plan) {
7812
- if (!fs34.existsSync(entry.src)) {
8225
+ if (!fs35.existsSync(entry.src)) {
7813
8226
  stderrFn(`[cleargate sprint archive] source not found: ${entry.src} \u2014 skipping`);
7814
8227
  continue;
7815
8228
  }
7816
- const raw = fs34.readFileSync(entry.src, "utf8");
8229
+ const raw = fs35.readFileSync(entry.src, "utf8");
7817
8230
  const stamped = stampFile(raw, entry.status, completedAt);
7818
- const dest = path36.join(archiveDir, entry.destName);
8231
+ const dest = path37.join(archiveDir, entry.destName);
7819
8232
  atomicWriteStr(entry.src, stamped);
7820
- fs34.renameSync(entry.src, dest);
8233
+ fs35.renameSync(entry.src, dest);
7821
8234
  stdoutFn(`archived: ${entry.destName}`);
7822
8235
  }
7823
8236
  try {
@@ -7876,14 +8289,14 @@ function checkPrevSprintCompleted(sprintId, cwd) {
7876
8289
  const prevNum = sprintNum - 1;
7877
8290
  const prevId = `SPRINT-${String(prevNum).padStart(2, "0")}`;
7878
8291
  const prevIdAlt = `SPRINT-${prevNum}`;
7879
- const sprintRunsBase = path36.join(cwd, ".cleargate", "sprint-runs");
8292
+ const sprintRunsBase = path37.join(cwd, ".cleargate", "sprint-runs");
7880
8293
  let stateJson = null;
7881
8294
  let resolvedPrevId = prevId;
7882
8295
  for (const pid of [prevId, prevIdAlt]) {
7883
- const stateFile = path36.join(sprintRunsBase, pid, "state.json");
7884
- if (fs34.existsSync(stateFile)) {
8296
+ const stateFile = path37.join(sprintRunsBase, pid, "state.json");
8297
+ if (fs35.existsSync(stateFile)) {
7885
8298
  try {
7886
- const raw = fs34.readFileSync(stateFile, "utf8");
8299
+ const raw = fs35.readFileSync(stateFile, "utf8");
7887
8300
  stateJson = JSON.parse(raw);
7888
8301
  resolvedPrevId = pid;
7889
8302
  break;
@@ -7986,21 +8399,21 @@ function checkMainClean(cwd, execFn) {
7986
8399
  }
7987
8400
  function findSprintFile(sprintId, cwd) {
7988
8401
  const searchDirs = [
7989
- path36.join(cwd, ".cleargate", "delivery", "pending-sync"),
7990
- path36.join(cwd, ".cleargate", "delivery", "archive")
8402
+ path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
8403
+ path37.join(cwd, ".cleargate", "delivery", "archive")
7991
8404
  ];
7992
8405
  for (const dir of searchDirs) {
7993
- if (!fs34.existsSync(dir)) continue;
8406
+ if (!fs35.existsSync(dir)) continue;
7994
8407
  let entries;
7995
8408
  try {
7996
- entries = fs34.readdirSync(dir);
8409
+ entries = fs35.readdirSync(dir);
7997
8410
  } catch {
7998
8411
  continue;
7999
8412
  }
8000
8413
  const prefix = `${sprintId}_`;
8001
8414
  for (const entry of entries) {
8002
8415
  if ((entry.startsWith(prefix) || entry === `${sprintId}.md`) && entry.endsWith(".md")) {
8003
- return path36.join(dir, entry);
8416
+ return path37.join(dir, entry);
8004
8417
  }
8005
8418
  }
8006
8419
  }
@@ -8008,26 +8421,26 @@ function findSprintFile(sprintId, cwd) {
8008
8421
  }
8009
8422
  function findWorkItemFileLocal(cwd, workItemId) {
8010
8423
  const searchDirs = [
8011
- path36.join(cwd, ".cleargate", "delivery", "pending-sync"),
8012
- path36.join(cwd, ".cleargate", "delivery", "archive")
8424
+ path37.join(cwd, ".cleargate", "delivery", "pending-sync"),
8425
+ path37.join(cwd, ".cleargate", "delivery", "archive")
8013
8426
  ];
8014
8427
  const prefix = `${workItemId}_`;
8015
8428
  for (const dir of searchDirs) {
8016
8429
  let entries;
8017
8430
  try {
8018
- entries = fs34.readdirSync(dir);
8431
+ entries = fs35.readdirSync(dir);
8019
8432
  } catch {
8020
8433
  continue;
8021
8434
  }
8022
8435
  const match = entries.find((e) => (e.startsWith(prefix) || e === `${workItemId}.md`) && e.endsWith(".md"));
8023
- if (match) return path36.join(dir, match);
8436
+ if (match) return path37.join(dir, match);
8024
8437
  }
8025
8438
  return null;
8026
8439
  }
8027
8440
  function readCachedGateSync(absPath) {
8028
8441
  let raw;
8029
8442
  try {
8030
- raw = fs34.readFileSync(absPath, "utf8");
8443
+ raw = fs35.readFileSync(absPath, "utf8");
8031
8444
  } catch {
8032
8445
  return null;
8033
8446
  }
@@ -8051,7 +8464,7 @@ function readCachedGateSync(absPath) {
8051
8464
  return null;
8052
8465
  }
8053
8466
  function extractInScopeWorkItemIds(sprintFilePath, cwd, execFn) {
8054
- const scriptPath = path36.join(cwd, ".cleargate", "scripts", "assert_story_files.mjs");
8467
+ const scriptPath = path37.join(cwd, ".cleargate", "scripts", "assert_story_files.mjs");
8055
8468
  const cmd = `node "${scriptPath}" "${sprintFilePath}" --emit-json`;
8056
8469
  let stdout;
8057
8470
  try {
@@ -8103,7 +8516,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
8103
8516
  let fm;
8104
8517
  let raw;
8105
8518
  try {
8106
- raw = fs34.readFileSync(absPath, "utf8");
8519
+ raw = fs35.readFileSync(absPath, "utf8");
8107
8520
  ({ fm } = parseFrontmatter(raw));
8108
8521
  } catch {
8109
8522
  totalChecked++;
@@ -8147,7 +8560,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
8147
8560
  let sprintRaw;
8148
8561
  let sprintFm = {};
8149
8562
  try {
8150
- sprintRaw = fs34.readFileSync(sprintFilePath, "utf8");
8563
+ sprintRaw = fs35.readFileSync(sprintFilePath, "utf8");
8151
8564
  ({ fm: sprintFm } = parseFrontmatter(sprintRaw));
8152
8565
  } catch {
8153
8566
  }
@@ -8203,9 +8616,13 @@ function refreshScopedGateCaches(sprintId, cwd, execFn) {
8203
8616
  if (!absPath) {
8204
8617
  continue;
8205
8618
  }
8619
+ if (absPath.includes(`${path37.sep}archive${path37.sep}`)) {
8620
+ result.skipped.push(id);
8621
+ continue;
8622
+ }
8206
8623
  let status = "";
8207
8624
  try {
8208
- const raw = fs34.readFileSync(absPath, "utf8");
8625
+ const raw = fs35.readFileSync(absPath, "utf8");
8209
8626
  const { fm } = parseFrontmatter(raw);
8210
8627
  status = String(fm["status"] ?? "");
8211
8628
  } catch {
@@ -8278,8 +8695,8 @@ function sprintPreflightHandler(opts, cli) {
8278
8695
 
8279
8696
  // src/commands/story.ts
8280
8697
  init_cjs_shims();
8281
- var fs35 = __toESM(require("fs"), 1);
8282
- var path37 = __toESM(require("path"), 1);
8698
+ var fs36 = __toESM(require("fs"), 1);
8699
+ var path38 = __toESM(require("path"), 1);
8283
8700
  var import_node_child_process12 = require("child_process");
8284
8701
  function defaultExit2(code) {
8285
8702
  return process.exit(code);
@@ -8287,7 +8704,7 @@ function defaultExit2(code) {
8287
8704
  function resolveRunScript2(opts) {
8288
8705
  if (opts.runScriptPath) return opts.runScriptPath;
8289
8706
  const cwd = opts.cwd ?? process.cwd();
8290
- return path37.join(cwd, ".cleargate", "scripts", "run_script.sh");
8707
+ return path38.join(cwd, ".cleargate", "scripts", "run_script.sh");
8291
8708
  }
8292
8709
  function deriveSprintBranch(sprintId) {
8293
8710
  const match = /^SPRINT-(\d+)/.exec(sprintId);
@@ -8296,11 +8713,11 @@ function deriveSprintBranch(sprintId) {
8296
8713
  }
8297
8714
  function atomicWriteString(filePath, text) {
8298
8715
  const tmpFile = `${filePath}.tmp.${process.pid}`;
8299
- fs35.writeFileSync(tmpFile, text, "utf8");
8300
- fs35.renameSync(tmpFile, filePath);
8716
+ fs36.writeFileSync(tmpFile, text, "utf8");
8717
+ fs36.renameSync(tmpFile, filePath);
8301
8718
  }
8302
8719
  function stateJsonPath(cwd, sprintId) {
8303
- return path37.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
8720
+ return path38.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
8304
8721
  }
8305
8722
  function storyStartHandler(opts, cli) {
8306
8723
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -8317,7 +8734,7 @@ function storyStartHandler(opts, cli) {
8317
8734
  return printInertAndExit(stdoutFn, exitFn);
8318
8735
  }
8319
8736
  const sprintBranch = deriveSprintBranch(sprintId);
8320
- const worktreePath = path37.join(cwd, ".worktrees", opts.storyId);
8737
+ const worktreePath = path38.join(cwd, ".worktrees", opts.storyId);
8321
8738
  const storyBranch = `story/${opts.storyId}`;
8322
8739
  const step1 = spawnFn(
8323
8740
  "git",
@@ -8350,13 +8767,13 @@ function storyStartHandler(opts, cli) {
8350
8767
  return exitFn(step2.status ?? 1);
8351
8768
  }
8352
8769
  const stateFile = stateJsonPath(cwd, sprintId);
8353
- if (!fs35.existsSync(stateFile)) {
8770
+ if (!fs36.existsSync(stateFile)) {
8354
8771
  stderrFn(`[cleargate story start] step 3: state.json not found at ${stateFile}`);
8355
8772
  return exitFn(1);
8356
8773
  }
8357
8774
  let state2;
8358
8775
  try {
8359
- state2 = JSON.parse(fs35.readFileSync(stateFile, "utf8"));
8776
+ state2 = JSON.parse(fs36.readFileSync(stateFile, "utf8"));
8360
8777
  } catch (err) {
8361
8778
  stderrFn(`[cleargate story start] step 3: failed to parse state.json: ${err.message}`);
8362
8779
  return exitFn(1);
@@ -8397,7 +8814,7 @@ function storyCompleteHandler(opts, cli) {
8397
8814
  }
8398
8815
  const sprintBranch = deriveSprintBranch(sprintId);
8399
8816
  const storyBranch = `story/${opts.storyId}`;
8400
- const worktreeRel = path37.join(".worktrees", opts.storyId);
8817
+ const worktreeRel = path38.join(".worktrees", opts.storyId);
8401
8818
  const step1 = spawnFn(
8402
8819
  "git",
8403
8820
  ["rev-list", "--count", `${sprintBranch}..${storyBranch}`],
@@ -8496,7 +8913,7 @@ function storyCompleteHandler(opts, cli) {
8496
8913
 
8497
8914
  // src/commands/state.ts
8498
8915
  init_cjs_shims();
8499
- var path38 = __toESM(require("path"), 1);
8916
+ var path39 = __toESM(require("path"), 1);
8500
8917
  var import_node_child_process13 = require("child_process");
8501
8918
  function defaultExit3(code) {
8502
8919
  return process.exit(code);
@@ -8504,7 +8921,7 @@ function defaultExit3(code) {
8504
8921
  function resolveRunScript3(opts) {
8505
8922
  if (opts.runScriptPath) return opts.runScriptPath;
8506
8923
  const cwd = opts.cwd ?? process.cwd();
8507
- return path38.join(cwd, ".cleargate", "scripts", "run_script.sh");
8924
+ return path39.join(cwd, ".cleargate", "scripts", "run_script.sh");
8508
8925
  }
8509
8926
  function stateUpdateHandler(opts, cli) {
8510
8927
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -8565,21 +8982,21 @@ function stateValidateHandler(opts, cli) {
8565
8982
 
8566
8983
  // src/commands/stamp-tokens.ts
8567
8984
  init_cjs_shims();
8568
- var fs37 = __toESM(require("fs"), 1);
8569
- var path40 = __toESM(require("path"), 1);
8985
+ var fs38 = __toESM(require("fs"), 1);
8986
+ var path41 = __toESM(require("path"), 1);
8570
8987
 
8571
8988
  // src/lib/ledger-reader.ts
8572
8989
  init_cjs_shims();
8573
- var fs36 = __toESM(require("fs"), 1);
8574
- var path39 = __toESM(require("path"), 1);
8990
+ var fs37 = __toESM(require("fs"), 1);
8991
+ var path40 = __toESM(require("path"), 1);
8575
8992
  function findSprintRunsRoot(startDir) {
8576
8993
  let dir = startDir;
8577
8994
  while (true) {
8578
- const candidate = path39.join(dir, ".cleargate", "sprint-runs");
8579
- if (fs36.existsSync(candidate)) {
8995
+ const candidate = path40.join(dir, ".cleargate", "sprint-runs");
8996
+ if (fs37.existsSync(candidate)) {
8580
8997
  return candidate;
8581
8998
  }
8582
- const parent = path39.dirname(dir);
8999
+ const parent = path40.dirname(dir);
8583
9000
  if (parent === dir) {
8584
9001
  return null;
8585
9002
  }
@@ -8632,13 +9049,13 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
8632
9049
  }
8633
9050
  sprintRunsRoot = found;
8634
9051
  }
8635
- if (!fs36.existsSync(sprintRunsRoot)) {
9052
+ if (!fs37.existsSync(sprintRunsRoot)) {
8636
9053
  return [];
8637
9054
  }
8638
9055
  let ledgerFiles;
8639
9056
  try {
8640
- const entries = fs36.readdirSync(sprintRunsRoot, { withFileTypes: true });
8641
- 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));
8642
9059
  } catch {
8643
9060
  return [];
8644
9061
  }
@@ -8646,7 +9063,7 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
8646
9063
  for (const ledgerFile of ledgerFiles) {
8647
9064
  let content;
8648
9065
  try {
8649
- content = fs36.readFileSync(ledgerFile, "utf-8");
9066
+ content = fs37.readFileSync(ledgerFile, "utf-8");
8650
9067
  } catch {
8651
9068
  continue;
8652
9069
  }
@@ -8697,7 +9114,7 @@ async function stampTokensHandler(file, opts, cli) {
8697
9114
  });
8698
9115
  const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
8699
9116
  const cwd = cli?.cwd ?? process.cwd();
8700
- const absPath = path40.isAbsolute(file) ? file : path40.resolve(cwd, file);
9117
+ const absPath = path41.isAbsolute(file) ? file : path41.resolve(cwd, file);
8701
9118
  if (/\/\.cleargate\/delivery\/archive\//.test(absPath)) {
8702
9119
  stdoutFn(`[frozen] ${absPath}`);
8703
9120
  exitFn(0);
@@ -8705,7 +9122,7 @@ async function stampTokensHandler(file, opts, cli) {
8705
9122
  }
8706
9123
  let rawContent;
8707
9124
  try {
8708
- rawContent = fs37.readFileSync(absPath, "utf-8");
9125
+ rawContent = fs38.readFileSync(absPath, "utf-8");
8709
9126
  } catch {
8710
9127
  stdoutFn(`[stamp-tokens] error: cannot read file: ${absPath}`);
8711
9128
  exitFn(1);
@@ -8777,7 +9194,7 @@ async function stampTokensHandler(file, opts, cli) {
8777
9194
  return;
8778
9195
  }
8779
9196
  try {
8780
- fs37.writeFileSync(absPath, serialized, "utf-8");
9197
+ fs38.writeFileSync(absPath, serialized, "utf-8");
8781
9198
  } catch {
8782
9199
  stdoutFn(`[stamp-tokens] error: cannot write file: ${absPath}`);
8783
9200
  exitFn(1);
@@ -8794,14 +9211,14 @@ function extractWorkItemId(fm, absPath) {
8794
9211
  return val.trim();
8795
9212
  }
8796
9213
  }
8797
- const basename13 = path40.basename(absPath);
8798
- 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);
8799
9216
  if (match) {
8800
9217
  return match[0].toUpperCase();
8801
9218
  }
8802
9219
  const typeFromPath = detectWorkItemType(absPath);
8803
9220
  if (typeFromPath) {
8804
- 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);
8805
9222
  if (idMatch) {
8806
9223
  return idMatch[1].toUpperCase();
8807
9224
  }
@@ -8899,9 +9316,9 @@ ${body}`;
8899
9316
 
8900
9317
  // src/commands/upgrade.ts
8901
9318
  init_cjs_shims();
8902
- var fs38 = __toESM(require("fs"), 1);
9319
+ var fs39 = __toESM(require("fs"), 1);
8903
9320
  var fsp = __toESM(require("fs/promises"), 1);
8904
- var path41 = __toESM(require("path"), 1);
9321
+ var path42 = __toESM(require("path"), 1);
8905
9322
 
8906
9323
  // src/lib/changelog.ts
8907
9324
  init_cjs_shims();
@@ -9105,7 +9522,7 @@ async function writeAtomic2(filePath, content) {
9105
9522
  }
9106
9523
  }
9107
9524
  async function updateSnapshotEntry(projectRoot, filePath, newSha) {
9108
- const snapshotPath = path41.join(projectRoot, ".cleargate", ".install-manifest.json");
9525
+ const snapshotPath = path42.join(projectRoot, ".cleargate", ".install-manifest.json");
9109
9526
  let snapshot;
9110
9527
  try {
9111
9528
  const raw = await fsp.readFile(snapshotPath, "utf-8");
@@ -9122,17 +9539,17 @@ async function updateSnapshotEntry(projectRoot, filePath, newSha) {
9122
9539
  await writeAtomic2(snapshotPath, JSON.stringify(updated, null, 2) + "\n");
9123
9540
  }
9124
9541
  function isClaudeMd(filePath) {
9125
- return path41.basename(filePath) === "CLAUDE.md";
9542
+ return path42.basename(filePath) === "CLAUDE.md";
9126
9543
  }
9127
9544
  function isSettingsJson(filePath) {
9128
- return path41.basename(filePath) === "settings.json" && filePath.includes(".claude");
9545
+ return path42.basename(filePath) === "settings.json" && filePath.includes(".claude");
9129
9546
  }
9130
9547
  async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
9131
- const targetPath = path41.join(projectRoot, entry.path);
9132
- const sourcePath = path41.join(packageRoot, entry.path);
9548
+ const targetPath = path42.join(projectRoot, entry.path);
9549
+ const sourcePath = path42.join(packageRoot, entry.path);
9133
9550
  try {
9134
9551
  const pkgContent = await fsp.readFile(sourcePath, "utf-8");
9135
- await fsp.mkdir(path41.dirname(targetPath), { recursive: true });
9552
+ await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
9136
9553
  await writeAtomic2(targetPath, pkgContent);
9137
9554
  await updateSnapshotEntry(projectRoot, entry.path, entry.sha256);
9138
9555
  stdout(`[always] overwritten: ${entry.path}`);
@@ -9142,8 +9559,8 @@ async function applyAlwaysOverwrite(entry, projectRoot, packageRoot, stdout) {
9142
9559
  }
9143
9560
  async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, currentSha, flags, opts) {
9144
9561
  const { stdout, stderr, promptMergeChoiceFn, openInEditorFn, stdin } = opts;
9145
- const targetPath = path41.join(projectRoot, entry.path);
9146
- const sourcePath = path41.join(packageRoot, entry.path);
9562
+ const targetPath = path42.join(projectRoot, entry.path);
9563
+ const sourcePath = path42.join(packageRoot, entry.path);
9147
9564
  let ours = "";
9148
9565
  let theirs = "";
9149
9566
  try {
@@ -9206,7 +9623,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
9206
9623
  mergedContent = theirs;
9207
9624
  }
9208
9625
  }
9209
- await fsp.mkdir(path41.dirname(targetPath), { recursive: true });
9626
+ await fsp.mkdir(path42.dirname(targetPath), { recursive: true });
9210
9627
  await writeAtomic2(targetPath, mergedContent);
9211
9628
  const newSha2 = hashNormalized(mergedContent);
9212
9629
  await updateSnapshotEntry(projectRoot, entry.path, newSha2);
@@ -9218,7 +9635,7 @@ async function applyMerge3Way(entry, projectRoot, packageRoot, installSha, curre
9218
9635
  ${ours}=======
9219
9636
  ${theirs}>>>>>>> theirs (upstream)
9220
9637
  `;
9221
- await fsp.mkdir(path41.dirname(mergeFilePath), { recursive: true });
9638
+ await fsp.mkdir(path42.dirname(mergeFilePath), { recursive: true });
9222
9639
  await writeAtomic2(mergeFilePath, conflictContent);
9223
9640
  try {
9224
9641
  const result = await openInEditorFn(mergeFilePath);
@@ -9278,10 +9695,10 @@ async function upgradeHandler(flags, cli) {
9278
9695
  const installedVersion = installSnapshot?.cleargate_version ?? pkgManifest.cleargate_version;
9279
9696
  const targetVersion = pkgManifest.cleargate_version;
9280
9697
  if (installedVersion !== targetVersion) {
9281
- const pkgRoot = cli?.packageRoot ?? path41.join(path41.dirname(new URL(importMetaUrl).pathname), "..", "..");
9282
- 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");
9283
9700
  try {
9284
- const changelogContent = fs38.readFileSync(changelogPath, "utf-8");
9701
+ const changelogContent = fs39.readFileSync(changelogPath, "utf-8");
9285
9702
  const sections = sliceChangelog(changelogContent, installedVersion, targetVersion);
9286
9703
  if (sections.length > 0) {
9287
9704
  const deltaText = sections.map((s) => s.body).join("\n\n");
@@ -9347,7 +9764,7 @@ async function upgradeHandler(flags, cli) {
9347
9764
  const { entry, currentSha, installSha, action } = item;
9348
9765
  let preMutationContent = null;
9349
9766
  if (SESSION_LOAD_PATHS.has(entry.path)) {
9350
- const targetPath = path41.join(cwd, entry.path);
9767
+ const targetPath = path42.join(cwd, entry.path);
9351
9768
  try {
9352
9769
  preMutationContent = await fsp.readFile(targetPath, "utf-8");
9353
9770
  } catch {
@@ -9385,7 +9802,7 @@ async function upgradeHandler(flags, cli) {
9385
9802
  package_sha: entry.sha256
9386
9803
  };
9387
9804
  if (SESSION_LOAD_PATHS.has(entry.path) && preMutationContent !== null) {
9388
- const targetPath = path41.join(cwd, entry.path);
9805
+ const targetPath = path42.join(cwd, entry.path);
9389
9806
  let postMutationContent;
9390
9807
  try {
9391
9808
  postMutationContent = await fsp.readFile(targetPath, "utf-8");
@@ -9410,9 +9827,9 @@ async function upgradeHandler(flags, cli) {
9410
9827
 
9411
9828
  // src/commands/uninstall.ts
9412
9829
  init_cjs_shims();
9413
- var fs39 = __toESM(require("fs"), 1);
9830
+ var fs40 = __toESM(require("fs"), 1);
9414
9831
  var fsp2 = __toESM(require("fs/promises"), 1);
9415
- var path42 = __toESM(require("path"), 1);
9832
+ var path43 = __toESM(require("path"), 1);
9416
9833
  var import_node_child_process15 = require("child_process");
9417
9834
  var USER_ARTIFACT_TIERS = ["user-artifact"];
9418
9835
  var FRAMEWORK_TIERS = ["protocol", "template", "agent", "hook", "skill", "cli-config", "derived"];
@@ -9438,10 +9855,10 @@ function shouldPreserve(entry, preserveSet, removeSet) {
9438
9855
  return false;
9439
9856
  }
9440
9857
  function resolveProjectName(target) {
9441
- const pkgPath = path42.join(target, "package.json");
9442
- if (fs39.existsSync(pkgPath)) {
9858
+ const pkgPath = path43.join(target, "package.json");
9859
+ if (fs40.existsSync(pkgPath)) {
9443
9860
  try {
9444
- const raw = fs39.readFileSync(pkgPath, "utf-8");
9861
+ const raw = fs40.readFileSync(pkgPath, "utf-8");
9445
9862
  const parsed = JSON.parse(raw);
9446
9863
  if (parsed.name && typeof parsed.name === "string") {
9447
9864
  return parsed.name;
@@ -9449,7 +9866,7 @@ function resolveProjectName(target) {
9449
9866
  } catch {
9450
9867
  }
9451
9868
  }
9452
- return path42.basename(target);
9869
+ return path43.basename(target);
9453
9870
  }
9454
9871
  function detectUncommittedChanges(target, manifestPaths, gitRunner) {
9455
9872
  const run = gitRunner ?? ((args) => {
@@ -9478,8 +9895,8 @@ function detectUncommittedChanges(target, manifestPaths, gitRunner) {
9478
9895
  return changedFiles.filter((f) => manifestSet.has(f));
9479
9896
  }
9480
9897
  async function removeFromPackageJson(target, dryRun) {
9481
- const pkgPath = path42.join(target, "package.json");
9482
- if (!fs39.existsSync(pkgPath)) return false;
9898
+ const pkgPath = path43.join(target, "package.json");
9899
+ if (!fs40.existsSync(pkgPath)) return false;
9483
9900
  let raw;
9484
9901
  try {
9485
9902
  raw = await fsp2.readFile(pkgPath, "utf-8");
@@ -9520,7 +9937,7 @@ async function removeFile(filePath) {
9520
9937
  }
9521
9938
  async function removeDir(dirPath) {
9522
9939
  try {
9523
- fs39.rmSync(dirPath, { recursive: true, force: true });
9940
+ fs40.rmSync(dirPath, { recursive: true, force: true });
9524
9941
  } catch {
9525
9942
  }
9526
9943
  }
@@ -9540,12 +9957,12 @@ async function uninstallHandler(opts) {
9540
9957
  for (const t of FRAMEWORK_TIERS) removeSet.add(t);
9541
9958
  for (const u of USER_ARTIFACT_TIERS) removeSet.add(u);
9542
9959
  }
9543
- const target = opts.path ? path42.resolve(opts.path) : cwd;
9544
- const cleargateDir = path42.join(target, ".cleargate");
9545
- const manifestPath = path42.join(cleargateDir, ".install-manifest.json");
9546
- const uninstalledPath = path42.join(cleargateDir, ".uninstalled");
9547
- if (!fs39.existsSync(manifestPath)) {
9548
- 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)) {
9549
9966
  stdout("already uninstalled");
9550
9967
  exit(0);
9551
9968
  return;
@@ -9554,7 +9971,7 @@ async function uninstallHandler(opts) {
9554
9971
  exit(0);
9555
9972
  return;
9556
9973
  }
9557
- if (fs39.existsSync(uninstalledPath) && !fs39.existsSync(manifestPath)) {
9974
+ if (fs40.existsSync(uninstalledPath) && !fs40.existsSync(manifestPath)) {
9558
9975
  stdout("already uninstalled");
9559
9976
  exit(0);
9560
9977
  return;
@@ -9576,10 +9993,10 @@ async function uninstallHandler(opts) {
9576
9993
  return;
9577
9994
  }
9578
9995
  }
9579
- const claudeMdPath = path42.join(target, "CLAUDE.md");
9996
+ const claudeMdPath = path43.join(target, "CLAUDE.md");
9580
9997
  let claudeMdContent = null;
9581
- if (fs39.existsSync(claudeMdPath)) {
9582
- claudeMdContent = fs39.readFileSync(claudeMdPath, "utf-8");
9998
+ if (fs40.existsSync(claudeMdPath)) {
9999
+ claudeMdContent = fs40.readFileSync(claudeMdPath, "utf-8");
9583
10000
  if (!claudeMdContent.includes(CLEARGATE_START)) {
9584
10001
  stderr("CLAUDE.md is missing <!-- CLEARGATE:START --> marker");
9585
10002
  exit(1);
@@ -9595,8 +10012,8 @@ async function uninstallHandler(opts) {
9595
10012
  const toPreserve = [];
9596
10013
  const toSkip = [];
9597
10014
  for (const entry of snapshot.files) {
9598
- const filePath = path42.join(target, entry.path);
9599
- if (!fs39.existsSync(filePath)) {
10015
+ const filePath = path43.join(target, entry.path);
10016
+ if (!fs40.existsSync(filePath)) {
9600
10017
  toSkip.push(entry);
9601
10018
  continue;
9602
10019
  }
@@ -9653,7 +10070,7 @@ async function uninstallHandler(opts) {
9653
10070
  const removedPaths = [];
9654
10071
  const preservedPaths = [];
9655
10072
  for (const entry of toRemove) {
9656
- const filePath = path42.join(target, entry.path);
10073
+ const filePath = path43.join(target, entry.path);
9657
10074
  await removeFile(filePath);
9658
10075
  removedPaths.push(entry.path);
9659
10076
  }
@@ -9669,10 +10086,10 @@ async function uninstallHandler(opts) {
9669
10086
  stderr(`Warning: could not strip CLAUDE.md block: ${err.message}`);
9670
10087
  }
9671
10088
  }
9672
- const settingsPath = path42.join(target, ".claude", "settings.json");
9673
- if (fs39.existsSync(settingsPath)) {
10089
+ const settingsPath = path43.join(target, ".claude", "settings.json");
10090
+ if (fs40.existsSync(settingsPath)) {
9674
10091
  try {
9675
- const raw = fs39.readFileSync(settingsPath, "utf-8");
10092
+ const raw = fs40.readFileSync(settingsPath, "utf-8");
9676
10093
  const settings = JSON.parse(raw);
9677
10094
  const cleaned = removeClearGateHooks(settings);
9678
10095
  await writeAtomic3(settingsPath, JSON.stringify(cleaned, null, 2) + "\n");
@@ -9687,7 +10104,7 @@ async function uninstallHandler(opts) {
9687
10104
  stdout("Removed @cleargate/cli from package.json. Run `npm install` to update package-lock.json.");
9688
10105
  }
9689
10106
  await removeFile(manifestPath);
9690
- await removeFile(path42.join(cleargateDir, ".drift-state.json"));
10107
+ await removeFile(path43.join(cleargateDir, ".drift-state.json"));
9691
10108
  const marker = {
9692
10109
  uninstalled_at: now().toISOString(),
9693
10110
  prior_version: snapshot.cleargate_version,
@@ -9712,30 +10129,30 @@ async function uninstallHandler(opts) {
9712
10129
  // src/commands/sync.ts
9713
10130
  init_cjs_shims();
9714
10131
  var fsPromises8 = __toESM(require("fs/promises"), 1);
9715
- var path51 = __toESM(require("path"), 1);
10132
+ var path52 = __toESM(require("path"), 1);
9716
10133
 
9717
10134
  // src/lib/sync-log.ts
9718
10135
  init_cjs_shims();
9719
- var fs40 = __toESM(require("fs"), 1);
10136
+ var fs41 = __toESM(require("fs"), 1);
9720
10137
  var fsPromises2 = __toESM(require("fs/promises"), 1);
9721
- var path43 = __toESM(require("path"), 1);
10138
+ var path44 = __toESM(require("path"), 1);
9722
10139
  function resolveActiveSprintDir(projectRoot, _opts) {
9723
- const sprintRunsRoot = path43.join(projectRoot, ".cleargate", "sprint-runs");
9724
- const offSprint = path43.join(sprintRunsRoot, "_off-sprint");
9725
- if (!fs40.existsSync(sprintRunsRoot)) {
9726
- fs40.mkdirSync(sprintRunsRoot, { recursive: true });
9727
- 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 });
9728
10145
  return offSprint;
9729
10146
  }
9730
- const entries = fs40.readdirSync(sprintRunsRoot, { withFileTypes: true });
10147
+ const entries = fs41.readdirSync(sprintRunsRoot, { withFileTypes: true });
9731
10148
  const sprintDirs = entries.filter((e) => e.isDirectory() && e.name !== "_off-sprint").map((e) => {
9732
- const fullPath = path43.join(sprintRunsRoot, e.name);
9733
- const stat = fs40.statSync(fullPath);
10149
+ const fullPath = path44.join(sprintRunsRoot, e.name);
10150
+ const stat = fs41.statSync(fullPath);
9734
10151
  return { name: e.name, fullPath, mtimeMs: stat.mtimeMs };
9735
10152
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
9736
10153
  if (sprintDirs.length === 0) {
9737
- if (!fs40.existsSync(offSprint)) {
9738
- fs40.mkdirSync(offSprint, { recursive: true });
10154
+ if (!fs41.existsSync(offSprint)) {
10155
+ fs41.mkdirSync(offSprint, { recursive: true });
9739
10156
  }
9740
10157
  return offSprint;
9741
10158
  }
@@ -9746,7 +10163,7 @@ function redactDetail(detail) {
9746
10163
  return detail.replace(/eyJ[A-Za-z0-9._-]+/g, "[REDACTED]");
9747
10164
  }
9748
10165
  async function appendSyncLog(sprintRoot, entry) {
9749
- const logPath = path43.join(sprintRoot, "sync-log.jsonl");
10166
+ const logPath = path44.join(sprintRoot, "sync-log.jsonl");
9750
10167
  await fsPromises2.mkdir(sprintRoot, { recursive: true });
9751
10168
  const safeEntry = {
9752
10169
  ...entry,
@@ -9756,7 +10173,7 @@ async function appendSyncLog(sprintRoot, entry) {
9756
10173
  await fsPromises2.appendFile(logPath, line, { encoding: "utf8" });
9757
10174
  }
9758
10175
  async function readSyncLog(sprintRoot, filters) {
9759
- const logPath = path43.join(sprintRoot, "sync-log.jsonl");
10176
+ const logPath = path44.join(sprintRoot, "sync-log.jsonl");
9760
10177
  let raw;
9761
10178
  try {
9762
10179
  raw = await fsPromises2.readFile(logPath, "utf8");
@@ -9862,7 +10279,7 @@ function classify2(local, remote, since) {
9862
10279
  init_cjs_shims();
9863
10280
  var import_node_fs2 = require("fs");
9864
10281
  var os8 = __toESM(require("os"), 1);
9865
- var path44 = __toESM(require("path"), 1);
10282
+ var path45 = __toESM(require("path"), 1);
9866
10283
  function promptFourChoice(opts) {
9867
10284
  const { stdin, stdout } = opts;
9868
10285
  stdout("[k]eep mine / [t]ake theirs / [e]dit in $EDITOR / [a]bort: ");
@@ -9932,7 +10349,7 @@ async function promptThreeWayMerge(opts) {
9932
10349
  case "a":
9933
10350
  return { resolution: "aborted", body: local };
9934
10351
  case "e": {
9935
- const tmpFile = path44.join(os8.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
10352
+ const tmpFile = path45.join(os8.tmpdir(), `cleargate-merge-${itemId}-${now()}.md`);
9936
10353
  const markerContent = `<<<<<<< local
9937
10354
  ${local}
9938
10355
  =======
@@ -10032,12 +10449,12 @@ init_config();
10032
10449
  // src/lib/intake.ts
10033
10450
  init_cjs_shims();
10034
10451
  var fsPromises4 = __toESM(require("fs/promises"), 1);
10035
- var path47 = __toESM(require("path"), 1);
10452
+ var path48 = __toESM(require("path"), 1);
10036
10453
 
10037
10454
  // src/lib/slug.ts
10038
10455
  init_cjs_shims();
10039
10456
  var fsPromises3 = __toESM(require("fs/promises"), 1);
10040
- var path46 = __toESM(require("path"), 1);
10457
+ var path47 = __toESM(require("path"), 1);
10041
10458
  function slugify(title, max = 40) {
10042
10459
  const normalized = title.normalize("NFKD").replace(new RegExp("\\p{M}", "gu"), "");
10043
10460
  const lowered = normalized.toLowerCase();
@@ -10052,8 +10469,8 @@ function slugify(title, max = 40) {
10052
10469
  var PROPOSAL_ID_RE = /^proposal_id:\s*"?PROP-(\d+)"?/m;
10053
10470
  async function nextProposalId(projectRoot) {
10054
10471
  const dirs = [
10055
- path46.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10056
- path46.join(projectRoot, ".cleargate", "delivery", "archive")
10472
+ path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10473
+ path47.join(projectRoot, ".cleargate", "delivery", "archive")
10057
10474
  ];
10058
10475
  let maxN = 0;
10059
10476
  for (const dir of dirs) {
@@ -10065,7 +10482,7 @@ async function nextProposalId(projectRoot) {
10065
10482
  }
10066
10483
  for (const entry of entries) {
10067
10484
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10068
- const fullPath = path46.join(dir, entry.name);
10485
+ const fullPath = path47.join(dir, entry.name);
10069
10486
  try {
10070
10487
  const raw = await fsPromises3.readFile(fullPath, "utf8");
10071
10488
  const fmEnd = extractFrontmatterBlock(raw);
@@ -10083,8 +10500,8 @@ async function nextProposalId(projectRoot) {
10083
10500
  }
10084
10501
  async function findByRemoteId(projectRoot, remoteId) {
10085
10502
  const dirs = [
10086
- path46.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10087
- path46.join(projectRoot, ".cleargate", "delivery", "archive")
10503
+ path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10504
+ path47.join(projectRoot, ".cleargate", "delivery", "archive")
10088
10505
  ];
10089
10506
  const escaped = remoteId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10090
10507
  const re = new RegExp(`^remote_id:\\s*"?${escaped}"?\\s*$`, "m");
@@ -10097,7 +10514,7 @@ async function findByRemoteId(projectRoot, remoteId) {
10097
10514
  }
10098
10515
  for (const entry of entries) {
10099
10516
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10100
- const fullPath = path46.join(dir, entry.name);
10517
+ const fullPath = path47.join(dir, entry.name);
10101
10518
  try {
10102
10519
  const raw = await fsPromises3.readFile(fullPath, "utf8");
10103
10520
  const fm = extractFrontmatterBlock(raw);
@@ -10134,7 +10551,7 @@ async function runIntakeBranch(opts) {
10134
10551
  labelFilter = "cleargate:proposal",
10135
10552
  now = () => (/* @__PURE__ */ new Date()).toISOString()
10136
10553
  } = opts;
10137
- const pendingSyncDir = path47.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10554
+ const pendingSyncDir = path48.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10138
10555
  let remoteItems = [];
10139
10556
  try {
10140
10557
  remoteItems = await mcp2.call(
@@ -10165,7 +10582,7 @@ async function runIntakeBranch(opts) {
10165
10582
  const slug2 = slugify(item.title ?? "untitled");
10166
10583
  const num2 = proposalId2.replace("PROP-", "");
10167
10584
  const filename2 = `PROPOSAL-${num2}-remote-${slug2}.md`;
10168
- const targetPath2 = path47.join(pendingSyncDir, filename2);
10585
+ const targetPath2 = path48.join(pendingSyncDir, filename2);
10169
10586
  createdItems.push({
10170
10587
  proposalId: proposalId2,
10171
10588
  remoteId: item.remote_id,
@@ -10178,7 +10595,7 @@ async function runIntakeBranch(opts) {
10178
10595
  const num = proposalId.replace("PROP-", "");
10179
10596
  const slug = slugify(item.title ?? "untitled");
10180
10597
  const filename = `PROPOSAL-${num}-remote-${slug}.md`;
10181
- const targetPath = path47.join(pendingSyncDir, filename);
10598
+ const targetPath = path48.join(pendingSyncDir, filename);
10182
10599
  const nowTs = now();
10183
10600
  const fm = {
10184
10601
  proposal_id: proposalId,
@@ -10270,8 +10687,8 @@ path/to/new/file.ext - {Explanation of purpose}
10270
10687
  }
10271
10688
  async function hasAnyRemoteAuthored(projectRoot) {
10272
10689
  const dirs = [
10273
- path47.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10274
- path47.join(projectRoot, ".cleargate", "delivery", "archive")
10690
+ path48.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
10691
+ path48.join(projectRoot, ".cleargate", "delivery", "archive")
10275
10692
  ];
10276
10693
  for (const dir of dirs) {
10277
10694
  let entries;
@@ -10282,7 +10699,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
10282
10699
  }
10283
10700
  for (const entry of entries) {
10284
10701
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10285
- const fullPath = path47.join(dir, entry.name);
10702
+ const fullPath = path48.join(dir, entry.name);
10286
10703
  try {
10287
10704
  const raw = await fsPromises4.readFile(fullPath, "utf8");
10288
10705
  const fmEnd = raw.indexOf("\n---", 4);
@@ -10300,9 +10717,9 @@ async function hasAnyRemoteAuthored(projectRoot) {
10300
10717
 
10301
10718
  // src/lib/active-criteria.ts
10302
10719
  init_cjs_shims();
10303
- var fs43 = __toESM(require("fs"), 1);
10720
+ var fs44 = __toESM(require("fs"), 1);
10304
10721
  var fsPromises5 = __toESM(require("fs/promises"), 1);
10305
- var path48 = __toESM(require("path"), 1);
10722
+ var path49 = __toESM(require("path"), 1);
10306
10723
  async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
10307
10724
  const active = /* @__PURE__ */ new Set();
10308
10725
  const now = Date.parse(nowFn());
@@ -10327,7 +10744,7 @@ async function resolveInSprintIds(projectRoot) {
10327
10744
  const ids = /* @__PURE__ */ new Set();
10328
10745
  try {
10329
10746
  const sprintDir = resolveActiveSprintDir(projectRoot);
10330
- const sprintId = path48.basename(sprintDir);
10747
+ const sprintId = path49.basename(sprintDir);
10331
10748
  if (sprintId === "_off-sprint") return ids;
10332
10749
  const sprintFile = await findSprintFile2(projectRoot, sprintId);
10333
10750
  if (!sprintFile) return ids;
@@ -10342,14 +10759,14 @@ async function resolveInSprintIds(projectRoot) {
10342
10759
  return ids;
10343
10760
  }
10344
10761
  async function findSprintFile2(projectRoot, sprintId) {
10345
- const pendingSync = path48.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10346
- 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");
10347
10764
  for (const dir of [pendingSync, archive]) {
10348
10765
  try {
10349
- const entries = fs43.readdirSync(dir, { withFileTypes: true });
10766
+ const entries = fs44.readdirSync(dir, { withFileTypes: true });
10350
10767
  for (const entry of entries) {
10351
10768
  if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
10352
- return path48.join(dir, entry.name);
10769
+ return path49.join(dir, entry.name);
10353
10770
  }
10354
10771
  }
10355
10772
  } catch {
@@ -10361,12 +10778,12 @@ async function findSprintFile2(projectRoot, sprintId) {
10361
10778
  // src/lib/comments-cache.ts
10362
10779
  init_cjs_shims();
10363
10780
  var fsPromises6 = __toESM(require("fs/promises"), 1);
10364
- var path49 = __toESM(require("path"), 1);
10781
+ var path50 = __toESM(require("path"), 1);
10365
10782
  function cacheDir(projectRoot) {
10366
- return path49.join(projectRoot, ".cleargate", ".comments-cache");
10783
+ return path50.join(projectRoot, ".cleargate", ".comments-cache");
10367
10784
  }
10368
10785
  function cachePath(projectRoot, remoteId) {
10369
- return path49.join(cacheDir(projectRoot), `${remoteId}.json`);
10786
+ return path50.join(cacheDir(projectRoot), `${remoteId}.json`);
10370
10787
  }
10371
10788
  async function writeCommentCache(projectRoot, remoteId, comments) {
10372
10789
  const dir = cacheDir(projectRoot);
@@ -10381,7 +10798,7 @@ async function writeCommentCache(projectRoot, remoteId, comments) {
10381
10798
  // src/lib/wiki-comments-render.ts
10382
10799
  init_cjs_shims();
10383
10800
  var fsPromises7 = __toESM(require("fs/promises"), 1);
10384
- var path50 = __toESM(require("path"), 1);
10801
+ var path51 = __toESM(require("path"), 1);
10385
10802
  var START = "<!-- cleargate:comments:start -->";
10386
10803
  var END = "<!-- cleargate:comments:end -->";
10387
10804
  function resolveBucket(fm) {
@@ -10426,7 +10843,7 @@ async function renderCommentsSection(opts) {
10426
10843
  const bucket = resolveBucket(localItem.fm);
10427
10844
  const primaryId = getPrimaryId(localItem.fm);
10428
10845
  if (!bucket || !primaryId) return;
10429
- const wikiPath = path50.join(
10846
+ const wikiPath = path51.join(
10430
10847
  projectRoot,
10431
10848
  ".cleargate",
10432
10849
  "wiki",
@@ -10462,7 +10879,7 @@ async function renderCommentsSection(opts) {
10462
10879
  await writeAtomic4(wikiPath, updated);
10463
10880
  }
10464
10881
  async function writeAtomic4(filePath, content) {
10465
- await fsPromises7.mkdir(path50.dirname(filePath), { recursive: true });
10882
+ await fsPromises7.mkdir(path51.dirname(filePath), { recursive: true });
10466
10883
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
10467
10884
  await fsPromises7.writeFile(tmpPath, content, "utf8");
10468
10885
  await fsPromises7.rename(tmpPath, filePath);
@@ -10474,11 +10891,11 @@ async function syncCheckHandler(opts = {}) {
10474
10891
  const env = opts.env ?? process.env;
10475
10892
  const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
10476
10893
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10477
- const markerPath = path51.join(projectRoot, ".cleargate", ".sync-marker.json");
10894
+ const markerPath = path52.join(projectRoot, ".cleargate", ".sync-marker.json");
10478
10895
  const updateMarker = async (nowIso2) => {
10479
10896
  try {
10480
10897
  const content = JSON.stringify({ last_check: nowIso2 });
10481
- await fsPromises8.mkdir(path51.dirname(markerPath), { recursive: true });
10898
+ await fsPromises8.mkdir(path52.dirname(markerPath), { recursive: true });
10482
10899
  const tmpPath = `${markerPath}.tmp.${Date.now()}`;
10483
10900
  await fsPromises8.writeFile(tmpPath, content, "utf8");
10484
10901
  await fsPromises8.rename(tmpPath, markerPath);
@@ -10561,7 +10978,7 @@ async function syncHandler(opts = {}) {
10561
10978
  const nowFn = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
10562
10979
  const identity = resolveIdentity(projectRoot);
10563
10980
  const sprintRoot = resolveActiveSprintDir(projectRoot);
10564
- const sprintId = path51.basename(sprintRoot);
10981
+ const sprintId = path52.basename(sprintRoot);
10565
10982
  let mcp2;
10566
10983
  if (opts.mcp) {
10567
10984
  mcp2 = opts.mcp;
@@ -10622,7 +11039,7 @@ async function syncHandler(opts = {}) {
10622
11039
  exit(2);
10623
11040
  return;
10624
11041
  }
10625
- const wikiMetaPath = path51.join(projectRoot, ".cleargate", "wiki", "meta.json");
11042
+ const wikiMetaPath = path52.join(projectRoot, ".cleargate", "wiki", "meta.json");
10626
11043
  let lastRemoteSync = "1970-01-01T00:00:00.000Z";
10627
11044
  try {
10628
11045
  const metaRaw = await fsPromises8.readFile(wikiMetaPath, "utf8");
@@ -10863,7 +11280,7 @@ async function syncHandler(opts = {}) {
10863
11280
  };
10864
11281
  await appendSyncLog(sprintRoot, entry);
10865
11282
  }
10866
- const conflictsFile = path51.join(projectRoot, ".cleargate", ".conflicts.json");
11283
+ const conflictsFile = path52.join(projectRoot, ".cleargate", ".conflicts.json");
10867
11284
  const conflictsContent = {
10868
11285
  generated_at: nowFn(),
10869
11286
  sprint_id: sprintId,
@@ -10871,7 +11288,7 @@ async function syncHandler(opts = {}) {
10871
11288
  };
10872
11289
  await writeAtomic5(conflictsFile, JSON.stringify(conflictsContent, null, 2) + "\n");
10873
11290
  try {
10874
- await fsPromises8.mkdir(path51.dirname(wikiMetaPath), { recursive: true });
11291
+ await fsPromises8.mkdir(path52.dirname(wikiMetaPath), { recursive: true });
10875
11292
  let meta = {};
10876
11293
  try {
10877
11294
  const raw = await fsPromises8.readFile(wikiMetaPath, "utf8");
@@ -10912,13 +11329,13 @@ async function applyPull(item, localPath, fm, actorEmail, nowFn) {
10912
11329
  await writeAtomic5(localPath, newContent);
10913
11330
  }
10914
11331
  async function writeAtomic5(filePath, content) {
10915
- await fsPromises8.mkdir(path51.dirname(filePath), { recursive: true });
11332
+ await fsPromises8.mkdir(path52.dirname(filePath), { recursive: true });
10916
11333
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
10917
11334
  await fsPromises8.writeFile(tmpPath, content, "utf8");
10918
11335
  await fsPromises8.rename(tmpPath, filePath);
10919
11336
  }
10920
11337
  async function scanLocalItems(projectRoot) {
10921
- const pendingSync = path51.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11338
+ const pendingSync = path52.join(projectRoot, ".cleargate", "delivery", "pending-sync");
10922
11339
  const results = [];
10923
11340
  let entries;
10924
11341
  try {
@@ -10928,7 +11345,7 @@ async function scanLocalItems(projectRoot) {
10928
11345
  }
10929
11346
  for (const entry of entries) {
10930
11347
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
10931
- const fullPath = path51.join(pendingSync, entry.name);
11348
+ const fullPath = path52.join(pendingSync, entry.name);
10932
11349
  try {
10933
11350
  const raw = await fsPromises8.readFile(fullPath, "utf8");
10934
11351
  const { fm, body } = parseFrontmatter(raw);
@@ -10956,7 +11373,7 @@ init_config();
10956
11373
  // src/lib/sync/work-items.ts
10957
11374
  init_cjs_shims();
10958
11375
  var fsPromises9 = __toESM(require("fs/promises"), 1);
10959
- var path52 = __toESM(require("path"), 1);
11376
+ var path53 = __toESM(require("path"), 1);
10960
11377
  var import_node_crypto2 = require("crypto");
10961
11378
  var BATCH_SIZE = 100;
10962
11379
  var ATTRIBUTION_FIELDS = /* @__PURE__ */ new Set([
@@ -11004,8 +11421,8 @@ function getItemId2(fm) {
11004
11421
  }
11005
11422
  async function walkDeliveryDirs(projectRoot) {
11006
11423
  const dirs = [
11007
- path52.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
11008
- path52.join(projectRoot, ".cleargate", "delivery", "archive")
11424
+ path53.join(projectRoot, ".cleargate", "delivery", "pending-sync"),
11425
+ path53.join(projectRoot, ".cleargate", "delivery", "archive")
11009
11426
  ];
11010
11427
  const results = [];
11011
11428
  for (const dir of dirs) {
@@ -11017,7 +11434,7 @@ async function walkDeliveryDirs(projectRoot) {
11017
11434
  }
11018
11435
  for (const entry of entries) {
11019
11436
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11020
- const fullPath = path52.join(dir, entry.name);
11437
+ const fullPath = path53.join(dir, entry.name);
11021
11438
  try {
11022
11439
  const raw = await fsPromises9.readFile(fullPath, "utf8");
11023
11440
  const { fm, body } = parseFrontmatter(raw);
@@ -11042,7 +11459,7 @@ async function walkDeliveryDirs(projectRoot) {
11042
11459
  return results;
11043
11460
  }
11044
11461
  async function writeAtomic6(filePath, content) {
11045
- await fsPromises9.mkdir(path52.dirname(filePath), { recursive: true });
11462
+ await fsPromises9.mkdir(path53.dirname(filePath), { recursive: true });
11046
11463
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11047
11464
  await fsPromises9.writeFile(tmpPath, content, "utf8");
11048
11465
  await fsPromises9.rename(tmpPath, filePath);
@@ -11109,9 +11526,9 @@ async function syncWorkItems(opts) {
11109
11526
 
11110
11527
  // src/lib/admin-url.ts
11111
11528
  init_cjs_shims();
11112
- var fs44 = __toESM(require("fs"), 1);
11529
+ var fs45 = __toESM(require("fs"), 1);
11113
11530
  var os10 = __toESM(require("os"), 1);
11114
- var path53 = __toESM(require("path"), 1);
11531
+ var path54 = __toESM(require("path"), 1);
11115
11532
  var DEFAULT_BASE = "https://admin.cleargate.soula.ge/";
11116
11533
  function adminUrl(urlPath, opts) {
11117
11534
  const env = opts?.env ?? process.env;
@@ -11134,8 +11551,8 @@ function adminUrl(urlPath, opts) {
11134
11551
  function readLocalConfig() {
11135
11552
  const home = os10.homedir();
11136
11553
  if (!home) return null;
11137
- const configPath = path53.join(home, ".cleargate", "config.json");
11138
- const raw = fs44.readFileSync(configPath, "utf8");
11554
+ const configPath = path54.join(home, ".cleargate", "config.json");
11555
+ const raw = fs45.readFileSync(configPath, "utf8");
11139
11556
  return JSON.parse(raw);
11140
11557
  }
11141
11558
 
@@ -11250,7 +11667,7 @@ async function syncWorkItemsHandler(opts = {}) {
11250
11667
  // src/commands/pull.ts
11251
11668
  init_cjs_shims();
11252
11669
  var fsPromises10 = __toESM(require("fs/promises"), 1);
11253
- var path54 = __toESM(require("path"), 1);
11670
+ var path55 = __toESM(require("path"), 1);
11254
11671
  init_acquire();
11255
11672
  init_config();
11256
11673
  async function pullHandler(idOrRemoteId, opts = {}) {
@@ -11365,7 +11782,7 @@ async function pullHandler(idOrRemoteId, opts = {}) {
11365
11782
  result: "ok"
11366
11783
  };
11367
11784
  await appendSyncLog(sprintRoot, entry);
11368
- stdout(`pull: ${remoteId} applied to ${path54.relative(projectRoot, localPath)}
11785
+ stdout(`pull: ${remoteId} applied to ${path55.relative(projectRoot, localPath)}
11369
11786
  `);
11370
11787
  if (opts.comments) {
11371
11788
  const comments = await mcp2.call(
@@ -11388,7 +11805,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11388
11805
  if (/^[A-Z]+-\d+/.test(idOrRemoteId)) {
11389
11806
  return idOrRemoteId;
11390
11807
  }
11391
- const pendingSync = path54.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11808
+ const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11392
11809
  let entries;
11393
11810
  try {
11394
11811
  entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
@@ -11398,7 +11815,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11398
11815
  for (const entry of entries) {
11399
11816
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11400
11817
  try {
11401
- const raw = await fsPromises10.readFile(path54.join(pendingSync, entry.name), "utf8");
11818
+ const raw = await fsPromises10.readFile(path55.join(pendingSync, entry.name), "utf8");
11402
11819
  const { fm } = parseFrontmatter(raw);
11403
11820
  for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
11404
11821
  if (fm[key] === idOrRemoteId && typeof fm["remote_id"] === "string") {
@@ -11411,7 +11828,7 @@ async function resolveRemoteId(idOrRemoteId, projectRoot) {
11411
11828
  return null;
11412
11829
  }
11413
11830
  async function findLocalFile(remoteId, projectRoot) {
11414
- const pendingSync = path54.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11831
+ const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11415
11832
  let entries;
11416
11833
  try {
11417
11834
  entries = await fsPromises10.readdir(pendingSync, { withFileTypes: true });
@@ -11420,7 +11837,7 @@ async function findLocalFile(remoteId, projectRoot) {
11420
11837
  }
11421
11838
  for (const entry of entries) {
11422
11839
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11423
- const fullPath = path54.join(pendingSync, entry.name);
11840
+ const fullPath = path55.join(pendingSync, entry.name);
11424
11841
  try {
11425
11842
  const raw = await fsPromises10.readFile(fullPath, "utf8");
11426
11843
  const { fm } = parseFrontmatter(raw);
@@ -11431,7 +11848,7 @@ async function findLocalFile(remoteId, projectRoot) {
11431
11848
  return null;
11432
11849
  }
11433
11850
  async function writeAtomic7(filePath, content) {
11434
- await fsPromises10.mkdir(path54.dirname(filePath), { recursive: true });
11851
+ await fsPromises10.mkdir(path55.dirname(filePath), { recursive: true });
11435
11852
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11436
11853
  await fsPromises10.writeFile(tmpPath, content, "utf8");
11437
11854
  await fsPromises10.rename(tmpPath, filePath);
@@ -11446,8 +11863,9 @@ function getItemId3(fm) {
11446
11863
 
11447
11864
  // src/commands/push.ts
11448
11865
  init_cjs_shims();
11866
+ var fs46 = __toESM(require("fs"), 1);
11449
11867
  var fsPromises11 = __toESM(require("fs/promises"), 1);
11450
- var path55 = __toESM(require("path"), 1);
11868
+ var path56 = __toESM(require("path"), 1);
11451
11869
  init_acquire();
11452
11870
  init_config();
11453
11871
  async function pushHandler(fileOrId, opts = {}) {
@@ -11457,6 +11875,13 @@ async function pushHandler(fileOrId, opts = {}) {
11457
11875
  const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
11458
11876
  const exit = opts.exit ?? ((c) => process.exit(c));
11459
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
+ }
11460
11885
  const identity = resolveIdentity(projectRoot);
11461
11886
  const sprintRoot = resolveActiveSprintDir(projectRoot);
11462
11887
  async function resolveMcp() {
@@ -11523,7 +11948,20 @@ async function pushHandler(fileOrId, opts = {}) {
11523
11948
  }
11524
11949
  async function handlePush(filePath, ctx) {
11525
11950
  const { projectRoot, identity, sprintRoot, nowFn, resolveMcp, stdout, stderr, exit } = ctx;
11526
- const resolvedPath = path55.isAbsolute(filePath) ? filePath : path55.resolve(projectRoot, filePath);
11951
+ const resolvedPath = path56.isAbsolute(filePath) ? filePath : path56.resolve(projectRoot, filePath);
11952
+ if (SPRINT_RUNS_PATH_REGEX.test(resolvedPath)) {
11953
+ if (!SPRINT_REPORT_PATH_REGEX.test(resolvedPath)) {
11954
+ stderr(
11955
+ `Error: path not in allowlist. Only sprint report files are accepted from sprint-runs/.
11956
+ Allowed: .cleargate/sprint-runs/SPRINT-NN/REPORT.md
11957
+ .cleargate/sprint-runs/SPRINT-NN/SPRINT-NN_REPORT.md
11958
+ Got: "${resolvedPath}"
11959
+ `
11960
+ );
11961
+ exit(2);
11962
+ return;
11963
+ }
11964
+ }
11527
11965
  let rawContent;
11528
11966
  try {
11529
11967
  rawContent = await fsPromises11.readFile(resolvedPath, "utf8");
@@ -11553,7 +11991,7 @@ async function handlePush(filePath, ctx) {
11553
11991
  return;
11554
11992
  }
11555
11993
  const itemId = getItemId4(fm);
11556
- const type = getItemType2(fm);
11994
+ const type = getItemTypeWithPathOverride(resolvedPath, fm);
11557
11995
  if (!type) {
11558
11996
  stderr(`Error: cannot determine item type from frontmatter in "${resolvedPath}".
11559
11997
  `);
@@ -11566,6 +12004,9 @@ async function handlePush(filePath, ctx) {
11566
12004
  if (h1) payloadForPush["title"] = h1;
11567
12005
  }
11568
12006
  payloadForPush["body"] = body;
12007
+ if (payloadForPush["origin"] === void 0) {
12008
+ payloadForPush["origin"] = "cleargate-cli";
12009
+ }
11569
12010
  const mcp2 = await resolveMcp();
11570
12011
  let result;
11571
12012
  try {
@@ -11649,8 +12090,8 @@ async function handleRevert(idOrRemoteId, ctx) {
11649
12090
  void localPath;
11650
12091
  }
11651
12092
  async function resolveLocalItem(idOrRemoteId, projectRoot) {
11652
- const pendingSync = path55.join(projectRoot, ".cleargate", "delivery", "pending-sync");
11653
- 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");
11654
12095
  for (const dir of [pendingSync, archive]) {
11655
12096
  let entries;
11656
12097
  try {
@@ -11660,7 +12101,7 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
11660
12101
  }
11661
12102
  for (const entry of entries) {
11662
12103
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
11663
- const fullPath = path55.join(dir, entry.name);
12104
+ const fullPath = path56.join(dir, entry.name);
11664
12105
  try {
11665
12106
  const raw = await fsPromises11.readFile(fullPath, "utf8");
11666
12107
  const { fm } = parseFrontmatter(raw);
@@ -11679,23 +12120,26 @@ async function resolveLocalItem(idOrRemoteId, projectRoot) {
11679
12120
  return null;
11680
12121
  }
11681
12122
  async function writeAtomic8(filePath, content) {
11682
- await fsPromises11.mkdir(path55.dirname(filePath), { recursive: true });
12123
+ await fsPromises11.mkdir(path56.dirname(filePath), { recursive: true });
11683
12124
  const tmpPath = `${filePath}.tmp.${Date.now()}`;
11684
12125
  await fsPromises11.writeFile(tmpPath, content, "utf8");
11685
12126
  await fsPromises11.rename(tmpPath, filePath);
11686
12127
  }
11687
12128
  function getItemId4(fm) {
11688
- for (const key of ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"]) {
12129
+ for (const key of ["story_id", "epic_id", "proposal_id", "sprint_id", "cr_id", "bug_id"]) {
11689
12130
  const val = fm[key];
11690
12131
  if (typeof val === "string" && val) return val;
11691
12132
  }
11692
12133
  return "unknown";
11693
12134
  }
12135
+ var SPRINT_RUNS_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]/;
12136
+ var SPRINT_REPORT_PATH_REGEX = /\.cleargate[\\/]sprint-runs[\\/]SPRINT-\d{2,}[\\/](REPORT|SPRINT-\d{2,}_REPORT)\.md$/;
11694
12137
  function getItemType2(fm) {
11695
12138
  const typeMap = {
11696
12139
  story_id: "story",
11697
12140
  epic_id: "epic",
11698
12141
  proposal_id: "proposal",
12142
+ sprint_id: "sprint",
11699
12143
  cr_id: "cr",
11700
12144
  bug_id: "bug"
11701
12145
  };
@@ -11704,11 +12148,15 @@ function getItemType2(fm) {
11704
12148
  }
11705
12149
  return null;
11706
12150
  }
12151
+ function getItemTypeWithPathOverride(localPath, fm) {
12152
+ if (SPRINT_REPORT_PATH_REGEX.test(localPath)) return "sprint_report";
12153
+ return getItemType2(fm);
12154
+ }
11707
12155
 
11708
12156
  // src/commands/conflicts.ts
11709
12157
  init_cjs_shims();
11710
12158
  var fsPromises12 = __toESM(require("fs/promises"), 1);
11711
- var path56 = __toESM(require("path"), 1);
12159
+ var path57 = __toESM(require("path"), 1);
11712
12160
  init_acquire();
11713
12161
  init_config();
11714
12162
  var RESOLUTION_HINTS = {
@@ -11746,7 +12194,7 @@ async function conflictsHandler(opts = {}) {
11746
12194
  }
11747
12195
  }
11748
12196
  }
11749
- const conflictsFile = path56.join(projectRoot, ".cleargate", ".conflicts.json");
12197
+ const conflictsFile = path57.join(projectRoot, ".cleargate", ".conflicts.json");
11750
12198
  let data;
11751
12199
  try {
11752
12200
  const raw = await fsPromises12.readFile(conflictsFile, "utf8");
@@ -11834,8 +12282,8 @@ function formatEntry(entry) {
11834
12282
 
11835
12283
  // src/commands/admin-login.ts
11836
12284
  init_cjs_shims();
11837
- var fs45 = __toESM(require("fs"), 1);
11838
- var path57 = __toESM(require("path"), 1);
12285
+ var fs47 = __toESM(require("fs"), 1);
12286
+ var path58 = __toESM(require("path"), 1);
11839
12287
  var os11 = __toESM(require("os"), 1);
11840
12288
  var DEFAULT_MCP_URL = "http://localhost:3000";
11841
12289
  function resolveMcpUrl(mcpUrlFlag, env) {
@@ -11844,14 +12292,14 @@ function resolveMcpUrl(mcpUrlFlag, env) {
11844
12292
  function resolveAuthFilePath(opts) {
11845
12293
  if (opts.authFilePath) return opts.authFilePath;
11846
12294
  const homedirFn = opts.homedir ?? os11.homedir;
11847
- return path57.join(homedirFn(), ".cleargate", "admin-auth.json");
12295
+ return path58.join(homedirFn(), ".cleargate", "admin-auth.json");
11848
12296
  }
11849
12297
  function writeAdminAuth(filePath, token) {
11850
- const dir = path57.dirname(filePath);
11851
- fs45.mkdirSync(dir, { recursive: true });
12298
+ const dir = path58.dirname(filePath);
12299
+ fs47.mkdirSync(dir, { recursive: true });
11852
12300
  const payload = JSON.stringify({ version: 1, token }, null, 2);
11853
- fs45.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
11854
- fs45.chmodSync(filePath, 384);
12301
+ fs47.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
12302
+ fs47.chmodSync(filePath, 384);
11855
12303
  }
11856
12304
  async function adminLoginHandler(opts = {}) {
11857
12305
  const fetchFn = opts.fetch ?? globalThis.fetch;
@@ -11961,8 +12409,8 @@ async function adminLoginHandler(opts = {}) {
11961
12409
 
11962
12410
  // src/commands/hotfix.ts
11963
12411
  init_cjs_shims();
11964
- var fs46 = __toESM(require("fs"), 1);
11965
- var path58 = __toESM(require("path"), 1);
12412
+ var fs48 = __toESM(require("fs"), 1);
12413
+ var path59 = __toESM(require("path"), 1);
11966
12414
  function defaultExit4(code) {
11967
12415
  return process.exit(code);
11968
12416
  }
@@ -11972,7 +12420,7 @@ function maxHotfixId(pendingDir) {
11972
12420
  let max = 0;
11973
12421
  let entries;
11974
12422
  try {
11975
- entries = fs46.readdirSync(pendingDir);
12423
+ entries = fs48.readdirSync(pendingDir);
11976
12424
  } catch {
11977
12425
  return 0;
11978
12426
  }
@@ -11986,13 +12434,13 @@ function maxHotfixId(pendingDir) {
11986
12434
  return max;
11987
12435
  }
11988
12436
  function countActiveHotfixes(repoRoot) {
11989
- const pendingDir = path58.join(repoRoot, ".cleargate", "delivery", "pending-sync");
11990
- 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");
11991
12439
  const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
11992
12440
  let count = 0;
11993
12441
  let pendingEntries = [];
11994
12442
  try {
11995
- pendingEntries = fs46.readdirSync(pendingDir);
12443
+ pendingEntries = fs48.readdirSync(pendingDir);
11996
12444
  } catch {
11997
12445
  }
11998
12446
  for (const entry of pendingEntries) {
@@ -12000,13 +12448,13 @@ function countActiveHotfixes(repoRoot) {
12000
12448
  }
12001
12449
  let archiveEntries = [];
12002
12450
  try {
12003
- archiveEntries = fs46.readdirSync(archiveDir);
12451
+ archiveEntries = fs48.readdirSync(archiveDir);
12004
12452
  } catch {
12005
12453
  }
12006
12454
  for (const entry of archiveEntries) {
12007
12455
  if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
12008
12456
  try {
12009
- const stat = fs46.statSync(path58.join(archiveDir, entry));
12457
+ const stat = fs48.statSync(path59.join(archiveDir, entry));
12010
12458
  if (stat.mtimeMs >= sevenDaysAgo) count++;
12011
12459
  } catch {
12012
12460
  }
@@ -12015,7 +12463,7 @@ function countActiveHotfixes(repoRoot) {
12015
12463
  return count;
12016
12464
  }
12017
12465
  function resolveTemplatePath(repoRoot) {
12018
- return path58.join(repoRoot, ".cleargate", "templates", "hotfix.md");
12466
+ return path59.join(repoRoot, ".cleargate", "templates", "hotfix.md");
12019
12467
  }
12020
12468
  function hotfixNewHandler(opts, cli) {
12021
12469
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -12034,14 +12482,14 @@ function hotfixNewHandler(opts, cli) {
12034
12482
  );
12035
12483
  return exitFn(1);
12036
12484
  }
12037
- const pendingDir = path58.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12485
+ const pendingDir = path59.join(repoRoot, ".cleargate", "delivery", "pending-sync");
12038
12486
  const maxId = maxHotfixId(pendingDir);
12039
12487
  const nextId = maxId + 1;
12040
12488
  const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
12041
12489
  const templatePath = resolveTemplatePath(repoRoot);
12042
12490
  let templateContent;
12043
12491
  try {
12044
- templateContent = fs46.readFileSync(templatePath, "utf8");
12492
+ templateContent = fs48.readFileSync(templatePath, "utf8");
12045
12493
  } catch {
12046
12494
  stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
12047
12495
  return exitFn(2);
@@ -12049,10 +12497,10 @@ function hotfixNewHandler(opts, cli) {
12049
12497
  const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
12050
12498
  const fileSlug = opts.slug.replace(/-/g, "_");
12051
12499
  const fileName = `${idStr}_${fileSlug}.md`;
12052
- const outPath = path58.join(pendingDir, fileName);
12500
+ const outPath = path59.join(pendingDir, fileName);
12053
12501
  try {
12054
- fs46.mkdirSync(pendingDir, { recursive: true });
12055
- fs46.writeFileSync(outPath, content, "utf8");
12502
+ fs48.mkdirSync(pendingDir, { recursive: true });
12503
+ fs48.writeFileSync(outPath, content, "utf8");
12056
12504
  } catch (err) {
12057
12505
  const msg = err instanceof Error ? err.message : String(err);
12058
12506
  stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
@@ -12140,8 +12588,26 @@ var AuthFetcher = class {
12140
12588
  }
12141
12589
  };
12142
12590
 
12591
+ // src/auth/service-token-fetcher.ts
12592
+ init_cjs_shims();
12593
+ var ServiceTokenFetcher = class {
12594
+ constructor(token) {
12595
+ this.token = token;
12596
+ }
12597
+ token;
12598
+ async getAccessToken() {
12599
+ return this.token;
12600
+ }
12601
+ };
12602
+
12143
12603
  // src/commands/mcp-serve.ts
12144
12604
  var DEFAULT_BASE_URL = "https://cleargate-mcp.soula.ge";
12605
+ var ServiceToken401Error = class extends Error {
12606
+ constructor() {
12607
+ super("service-token-401");
12608
+ this.name = "ServiceToken401Error";
12609
+ }
12610
+ };
12145
12611
  async function mcpServeHandler(opts) {
12146
12612
  const fetchFn = opts.fetch ?? globalThis.fetch;
12147
12613
  const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
@@ -12151,32 +12617,44 @@ async function mcpServeHandler(opts) {
12151
12617
  flags: { profile: opts.profile, mcpUrl: opts.mcpUrlFlag }
12152
12618
  });
12153
12619
  const baseUrl = cfg.mcpUrl ?? DEFAULT_BASE_URL;
12154
- const store = await (opts.createStore ?? createTokenStore)({
12155
- ...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
12156
- ...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
12157
- });
12158
- const fetcher = new AuthFetcher({
12159
- baseUrl,
12160
- loadRefresh: () => store.load(opts.profile),
12161
- saveRefresh: (t) => store.save(opts.profile, t),
12162
- ...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
12163
- ...opts.now !== void 0 ? { now: opts.now } : {}
12164
- });
12165
- try {
12166
- await fetcher.getAccessToken();
12167
- } catch (err) {
12168
- if (err instanceof RefreshError) {
12169
- stderr(
12170
- `cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
12620
+ const serviceToken = process.env["CLEARGATE_SERVICE_TOKEN"] ?? "";
12621
+ let fetcher;
12622
+ let isServiceTokenMode;
12623
+ if (serviceToken.length > 0) {
12624
+ isServiceTokenMode = true;
12625
+ fetcher = new ServiceTokenFetcher(serviceToken);
12626
+ stderr("cleargate mcp serve: auth mode = service-token\n");
12627
+ } else {
12628
+ isServiceTokenMode = false;
12629
+ const store = await (opts.createStore ?? createTokenStore)({
12630
+ ...opts.keychainService !== void 0 ? { keychainService: opts.keychainService } : {},
12631
+ ...opts.forceBackend !== void 0 ? { forceBackend: opts.forceBackend } : {}
12632
+ });
12633
+ const authFetcher = new AuthFetcher({
12634
+ baseUrl,
12635
+ loadRefresh: () => store.load(opts.profile),
12636
+ saveRefresh: (t) => store.save(opts.profile, t),
12637
+ ...opts.fetch !== void 0 ? { fetch: opts.fetch } : {},
12638
+ ...opts.now !== void 0 ? { now: opts.now } : {}
12639
+ });
12640
+ fetcher = authFetcher;
12641
+ stderr("cleargate mcp serve: auth mode = keychain-refresh\n");
12642
+ try {
12643
+ await authFetcher.getAccessToken();
12644
+ } catch (err) {
12645
+ if (err instanceof RefreshError) {
12646
+ stderr(
12647
+ `cleargate mcp serve: refresh failed (${err.status} ${err.code}). Run \`cleargate join <invite-url>\` to re-authenticate.
12171
12648
  `
12172
- );
12173
- } else {
12174
- stderr(
12175
- `cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
12649
+ );
12650
+ } else {
12651
+ stderr(
12652
+ `cleargate mcp serve: ${err instanceof Error ? err.message : String(err)}
12176
12653
  `
12177
- );
12654
+ );
12655
+ }
12656
+ return exit(1);
12178
12657
  }
12179
- return exit(1);
12180
12658
  }
12181
12659
  const inputStream = opts.stdin ?? process.stdin;
12182
12660
  const rl = readline5.createInterface({
@@ -12187,12 +12665,27 @@ async function mcpServeHandler(opts) {
12187
12665
  for await (const line of rl) {
12188
12666
  if (!line.trim()) continue;
12189
12667
  try {
12190
- await proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr);
12668
+ await proxyOne(
12669
+ line,
12670
+ baseUrl,
12671
+ fetcher,
12672
+ isServiceTokenMode,
12673
+ fetchFn,
12674
+ stdout,
12675
+ stderr
12676
+ );
12191
12677
  } catch (err) {
12678
+ if (err instanceof ServiceToken401Error) {
12679
+ stderr(
12680
+ `cleargate mcp serve: CLEARGATE_SERVICE_TOKEN rejected by /mcp (401). Issue a new token in the admin console: Tokens \u2192 Issue \u2192 copy snippet.
12681
+ `
12682
+ );
12683
+ return exit(1);
12684
+ }
12192
12685
  const errMsg = err instanceof Error ? err.message : String(err);
12193
12686
  stderr(`cleargate mcp serve: proxy error: ${errMsg}
12194
12687
  `);
12195
- const id = extractId(line);
12688
+ const id = extractId2(line);
12196
12689
  if (id !== void 0) {
12197
12690
  stdout(
12198
12691
  JSON.stringify({
@@ -12205,7 +12698,7 @@ async function mcpServeHandler(opts) {
12205
12698
  }
12206
12699
  }
12207
12700
  }
12208
- async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
12701
+ async function proxyOne(line, baseUrl, fetcher, isServiceTokenMode, fetchFn, stdout, stderr) {
12209
12702
  let parsed;
12210
12703
  try {
12211
12704
  parsed = JSON.parse(line);
@@ -12218,6 +12711,9 @@ async function proxyOne(line, baseUrl, fetcher, fetchFn, stdout, stderr) {
12218
12711
  let access = await fetcher.getAccessToken();
12219
12712
  let res = await postFrame(baseUrl, line, access, fetchFn);
12220
12713
  if (res.status === 401) {
12714
+ if (isServiceTokenMode) {
12715
+ throw new ServiceToken401Error();
12716
+ }
12221
12717
  fetcher.invalidate();
12222
12718
  access = await fetcher.getAccessToken();
12223
12719
  res = await postFrame(baseUrl, line, access, fetchFn);
@@ -12265,7 +12761,7 @@ async function streamSse(res, stdout) {
12265
12761
  }
12266
12762
  }
12267
12763
  }
12268
- function extractId(line) {
12764
+ function extractId2(line) {
12269
12765
  try {
12270
12766
  const obj = JSON.parse(line);
12271
12767
  return "id" in obj ? obj.id : void 0;
@@ -12437,8 +12933,8 @@ sprint.command("close <sprint-id>").description("close a sprint \u2014 validates
12437
12933
  }
12438
12934
  sprintCloseHandler(handlerOpts);
12439
12935
  });
12440
- 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) => {
12441
- 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 });
12442
12938
  });
12443
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) => {
12444
12940
  const handlerOpts = { sprintId };