cleargate 0.10.0 → 0.11.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 (72) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +11 -1
  3. package/dist/MANIFEST.json +40 -26
  4. package/dist/chunk-HZPJ5QX4.js +459 -0
  5. package/dist/chunk-HZPJ5QX4.js.map +1 -0
  6. package/dist/cli.cjs +419 -202
  7. package/dist/cli.cjs.map +1 -1
  8. package/dist/cli.js +387 -513
  9. package/dist/cli.js.map +1 -1
  10. package/dist/lib/lifecycle-reconcile.cjs +497 -0
  11. package/dist/lib/lifecycle-reconcile.cjs.map +1 -0
  12. package/dist/lib/lifecycle-reconcile.d.cts +136 -0
  13. package/dist/lib/lifecycle-reconcile.d.ts +136 -0
  14. package/dist/lib/lifecycle-reconcile.js +20 -0
  15. package/dist/lib/lifecycle-reconcile.js.map +1 -0
  16. package/dist/templates/cleargate-planning/.claude/agents/architect.md +55 -2
  17. package/dist/templates/cleargate-planning/.claude/agents/developer.md +22 -0
  18. package/dist/templates/cleargate-planning/.claude/agents/devops.md +249 -0
  19. package/dist/templates/cleargate-planning/.claude/agents/qa.md +41 -0
  20. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +44 -8
  21. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +21 -0
  22. package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +12 -1
  23. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +21 -1
  24. package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +200 -29
  25. package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +160 -0
  26. package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +41 -9
  27. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +98 -16
  28. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +3 -3
  29. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +86 -10
  30. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +173 -87
  31. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +150 -22
  32. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +20 -20
  33. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +32 -8
  34. package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +12 -1
  35. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +3 -0
  36. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +3 -0
  37. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +3 -0
  38. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +3 -0
  39. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  40. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +8 -0
  41. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +3 -0
  42. package/dist/templates/cleargate-planning/CLAUDE.md +3 -1
  43. package/dist/templates/cleargate-planning/MANIFEST.json +40 -26
  44. package/package.json +8 -5
  45. package/templates/cleargate-planning/.claude/agents/architect.md +55 -2
  46. package/templates/cleargate-planning/.claude/agents/developer.md +22 -0
  47. package/templates/cleargate-planning/.claude/agents/devops.md +249 -0
  48. package/templates/cleargate-planning/.claude/agents/qa.md +41 -0
  49. package/templates/cleargate-planning/.claude/agents/reporter.md +44 -8
  50. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +21 -0
  51. package/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +12 -1
  52. package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +21 -1
  53. package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +200 -29
  54. package/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +160 -0
  55. package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +41 -9
  56. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +98 -16
  57. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +3 -3
  58. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +86 -10
  59. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +173 -87
  60. package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +150 -22
  61. package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +20 -20
  62. package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +32 -8
  63. package/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +12 -1
  64. package/templates/cleargate-planning/.cleargate/templates/Bug.md +3 -0
  65. package/templates/cleargate-planning/.cleargate/templates/CR.md +3 -0
  66. package/templates/cleargate-planning/.cleargate/templates/epic.md +3 -0
  67. package/templates/cleargate-planning/.cleargate/templates/hotfix.md +3 -0
  68. package/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  69. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +8 -0
  70. package/templates/cleargate-planning/.cleargate/templates/story.md +3 -0
  71. package/templates/cleargate-planning/CLAUDE.md +3 -1
  72. package/templates/cleargate-planning/MANIFEST.json +40 -26
package/dist/cli.js CHANGED
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ checkVerbMismatch,
4
+ parseFrontmatter,
5
+ reconcileDecomposition,
6
+ reconcileLifecycle
7
+ } from "./chunk-HZPJ5QX4.js";
2
8
  import {
3
9
  AcquireError,
4
10
  acquireAccessToken,
@@ -15,10 +21,10 @@ import { Command } from "commander";
15
21
  // package.json
16
22
  var package_default = {
17
23
  name: "cleargate",
18
- version: "0.10.0",
24
+ version: "0.11.0",
19
25
  private: false,
20
26
  type: "module",
21
- description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
27
+ 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.",
22
28
  license: "MIT",
23
29
  bin: {
24
30
  cleargate: "dist/cli.js"
@@ -62,9 +68,12 @@ var package_default = {
62
68
  build: "tsup",
63
69
  dev: "tsup --watch",
64
70
  typecheck: "tsc --noEmit",
65
- pretest: "npm run build",
66
- test: "vitest run",
67
- "test:watch": "vitest"
71
+ test: "tsx --test --test-reporter=spec 'test/**/*.node.test.ts'",
72
+ "test:file": "tsx --test --test-reporter=spec",
73
+ "test:vitest": "npm run build && vitest run",
74
+ "test:vitest:watch": "vitest",
75
+ "test:node": "tsx --test --test-reporter=spec 'test/**/*.node.test.ts'",
76
+ "test:node:file": "tsx --test --test-reporter=spec"
68
77
  },
69
78
  dependencies: {
70
79
  "@napi-rs/keyring": "^1.2.0",
@@ -1069,53 +1078,14 @@ function getCodebaseVersion(opts) {
1069
1078
  // src/lib/stamp-frontmatter.ts
1070
1079
  import * as fs3 from "fs/promises";
1071
1080
 
1072
- // src/wiki/parse-frontmatter.ts
1073
- import yaml from "js-yaml";
1074
- function parseFrontmatter(raw) {
1075
- const lines = raw.split("\n");
1076
- if (lines[0] !== "---") {
1077
- throw new Error("parseFrontmatter: input does not start with ---");
1078
- }
1079
- let closeIdx = -1;
1080
- for (let i = 1; i < lines.length; i++) {
1081
- if (lines[i] === "---") {
1082
- closeIdx = i;
1083
- break;
1084
- }
1085
- }
1086
- if (closeIdx === -1) {
1087
- throw new Error("parseFrontmatter: missing closing ---");
1088
- }
1089
- const yamlText = lines.slice(1, closeIdx).join("\n");
1090
- const bodyLines = lines.slice(closeIdx + 1);
1091
- if (bodyLines[0] === "") bodyLines.shift();
1092
- const body = bodyLines.join("\n");
1093
- if (yamlText.trim() === "") {
1094
- return { fm: {}, body };
1095
- }
1096
- let parsed;
1097
- try {
1098
- parsed = yaml.load(yamlText, { schema: yaml.CORE_SCHEMA });
1099
- } catch (err) {
1100
- throw new Error(`parseFrontmatter: invalid YAML: ${err.message}`);
1101
- }
1102
- if (parsed === null || parsed === void 0) {
1103
- return { fm: {}, body };
1104
- }
1105
- if (typeof parsed !== "object" || Array.isArray(parsed)) {
1106
- throw new Error("parseFrontmatter: frontmatter is not a YAML mapping");
1107
- }
1108
- return { fm: parsed, body };
1109
- }
1110
-
1111
1081
  // src/lib/frontmatter-yaml.ts
1112
- import yaml2 from "js-yaml";
1082
+ import yaml from "js-yaml";
1113
1083
  function serializeFrontmatter(fm) {
1114
1084
  if (Object.keys(fm).length === 0) {
1115
1085
  return "---\n---";
1116
1086
  }
1117
- const yamlBody = yaml2.dump(fm, {
1118
- schema: yaml2.CORE_SCHEMA,
1087
+ const yamlBody = yaml.dump(fm, {
1088
+ schema: yaml.CORE_SCHEMA,
1119
1089
  lineWidth: -1,
1120
1090
  noRefs: true,
1121
1091
  noCompatMode: true,
@@ -1297,7 +1267,20 @@ var HOOK_FILES_WITH_PIN = /* @__PURE__ */ new Set([
1297
1267
  ".claude/hooks/stamp-and-gate.sh",
1298
1268
  ".claude/hooks/session-start.sh"
1299
1269
  ]);
1300
- var SKIP_FILES = /* @__PURE__ */ new Set(["CLAUDE.md"]);
1270
+ var SKIP_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "MANIFEST.json"]);
1271
+ var FIRST_INSTALL_ONLY = [
1272
+ ".gitignore",
1273
+ ".cleargate/FLASHCARD.md",
1274
+ /^\.cleargate\/scripts\//
1275
+ ];
1276
+ function isFirstInstallOnly(relPath) {
1277
+ for (const pattern of FIRST_INSTALL_ONLY) {
1278
+ if (typeof pattern === "string" ? pattern === relPath : pattern.test(relPath)) {
1279
+ return true;
1280
+ }
1281
+ }
1282
+ return false;
1283
+ }
1301
1284
  function listFilesRecursive(dir) {
1302
1285
  const results = [];
1303
1286
  function walk(current, rel) {
@@ -1351,6 +1334,14 @@ function copyPayload(payloadDir, targetCwd, opts) {
1351
1334
  report.actions.push({ action: "skipped", relPath });
1352
1335
  continue;
1353
1336
  }
1337
+ if (isFirstInstallOnly(relPath)) {
1338
+ if (needsExec && process.platform !== "win32") {
1339
+ fs5.chmodSync(dstPath, 493);
1340
+ }
1341
+ report.skipped++;
1342
+ report.actions.push({ action: "skipped", relPath });
1343
+ continue;
1344
+ }
1354
1345
  fs5.writeFileSync(dstPath, srcBuffer);
1355
1346
  if (needsExec && process.platform !== "win32") {
1356
1347
  fs5.chmodSync(dstPath, 493);
@@ -1483,7 +1474,8 @@ var PREFIX_MAP = [
1483
1474
  { prefix: "SPRINT-", type: "sprint", bucket: "sprints" },
1484
1475
  { prefix: "PROPOSAL-", type: "proposal", bucket: "proposals" },
1485
1476
  { prefix: "CR-", type: "cr", bucket: "crs" },
1486
- { prefix: "BUG-", type: "bug", bucket: "bugs" }
1477
+ { prefix: "BUG-", type: "bug", bucket: "bugs" },
1478
+ { prefix: "INITIATIVE-", type: "initiative", bucket: "initiatives" }
1487
1479
  ];
1488
1480
  function deriveBucket(filename) {
1489
1481
  const base = filename.includes("/") ? filename.split("/").pop() : filename;
@@ -3496,7 +3488,7 @@ function loadWikiPages(wikiRoot) {
3496
3488
  import * as fs19 from "fs";
3497
3489
  import * as path19 from "path";
3498
3490
  import { spawnSync as spawnSync5 } from "child_process";
3499
- import yaml3 from "js-yaml";
3491
+ import yaml2 from "js-yaml";
3500
3492
 
3501
3493
  // src/lib/work-item-type.ts
3502
3494
  var FM_KEY_MAP = [
@@ -3504,14 +3496,18 @@ var FM_KEY_MAP = [
3504
3496
  { key: "epic_id", type: "epic" },
3505
3497
  { key: "proposal_id", type: "proposal" },
3506
3498
  { key: "cr_id", type: "cr" },
3507
- { key: "bug_id", type: "bug" }
3499
+ { key: "bug_id", type: "bug" },
3500
+ { key: "initiative_id", type: "initiative" },
3501
+ { key: "sprint_id", type: "sprint" }
3508
3502
  ];
3509
3503
  var PREFIX_MAP2 = [
3510
3504
  { prefix: "STORY-", type: "story" },
3511
3505
  { prefix: "EPIC-", type: "epic" },
3512
3506
  { prefix: "PROPOSAL-", type: "proposal" },
3513
3507
  { prefix: "CR-", type: "cr" },
3514
- { prefix: "BUG-", type: "bug" }
3508
+ { prefix: "BUG-", type: "bug" },
3509
+ { prefix: "INITIATIVE-", type: "initiative" },
3510
+ { prefix: "SPRINT-", type: "sprint" }
3515
3511
  ];
3516
3512
  function detectWorkItemTypeFromFm(fm) {
3517
3513
  for (const { key, type } of FM_KEY_MAP) {
@@ -3536,7 +3532,9 @@ var WORK_ITEM_TRANSITIONS = {
3536
3532
  epic: ["ready-for-decomposition", "ready-for-coding"],
3537
3533
  story: ["ready-for-execution"],
3538
3534
  cr: ["ready-to-apply"],
3539
- bug: ["ready-for-fix"]
3535
+ bug: ["ready-for-fix"],
3536
+ initiative: ["ready-for-decomposition"],
3537
+ sprint: ["ready-for-execution"]
3540
3538
  };
3541
3539
 
3542
3540
  // src/wiki/lint-checks.ts
@@ -3743,7 +3741,7 @@ function parseCachedGateResult(raw) {
3743
3741
  if (typeof raw === "string") {
3744
3742
  if (!raw.startsWith("{")) return null;
3745
3743
  try {
3746
- const parsed = yaml3.load(raw);
3744
+ const parsed = yaml2.load(raw);
3747
3745
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
3748
3746
  const p = parsed;
3749
3747
  return { pass: p["pass"], failing_criteria: p["failing_criteria"], last_gate_check: p["last_gate_check"] };
@@ -3831,7 +3829,7 @@ function discoverPlainTextMentions(pages, repoRoot) {
3831
3829
  if (p.page.id) byId.set(p.page.id, true);
3832
3830
  }
3833
3831
  const suggestions = [];
3834
- const ID_PATTERN2 = /\b((?:EPIC|STORY|SPRINT|PROPOSAL|CR|BUG)-[\w-]+)\b/g;
3832
+ const ID_PATTERN = /\b((?:EPIC|STORY|SPRINT|PROPOSAL|CR|BUG)-[\w-]+)\b/g;
3835
3833
  const LINK_PATTERN = /\[\[[\w-]+\]\]/g;
3836
3834
  for (const page of pages) {
3837
3835
  const relPage = path19.relative(wikiRoot, page.absPath).replace(/\\/g, "/");
@@ -3840,7 +3838,7 @@ function discoverPlainTextMentions(pages, repoRoot) {
3840
3838
  const inner = m[0].slice(2, -2);
3841
3839
  wrappedRefs.add(inner);
3842
3840
  }
3843
- for (const m of page.body.matchAll(ID_PATTERN2)) {
3841
+ for (const m of page.body.matchAll(ID_PATTERN)) {
3844
3842
  const mentionedId = m[1];
3845
3843
  if (!byId.has(mentionedId)) continue;
3846
3844
  if (wrappedRefs.has(mentionedId)) continue;
@@ -3874,7 +3872,7 @@ function checkIndexBudget(repoRoot, indexTokenCeiling) {
3874
3872
  // src/lib/wiki-config.ts
3875
3873
  import * as fs20 from "fs";
3876
3874
  import * as path20 from "path";
3877
- import yaml4 from "js-yaml";
3875
+ import yaml3 from "js-yaml";
3878
3876
  var DEFAULT_INDEX_TOKEN_CEILING = 8e3;
3879
3877
  var DEFAULT_BUCKET_PAGINATION_CEILING = 50;
3880
3878
  function loadWikiConfig(repoRoot) {
@@ -3896,7 +3894,7 @@ function loadWikiConfig(repoRoot) {
3896
3894
  }
3897
3895
  let parsed;
3898
3896
  try {
3899
- parsed = yaml4.load(raw, { schema: yaml4.CORE_SCHEMA });
3897
+ parsed = yaml3.load(raw, { schema: yaml3.CORE_SCHEMA });
3900
3898
  } catch (err) {
3901
3899
  throw new Error(`Malformed YAML in ${configPath}: ${String(err)}`);
3902
3900
  }
@@ -5138,7 +5136,7 @@ async function doctorHandler(flags, cli) {
5138
5136
 
5139
5137
  // src/commands/gate.ts
5140
5138
  import * as fs29 from "fs";
5141
- import * as path29 from "path";
5139
+ import * as path30 from "path";
5142
5140
  import { spawnSync as spawnSync8 } from "child_process";
5143
5141
 
5144
5142
  // src/commands/execution-mode.ts
@@ -5226,12 +5224,19 @@ function printInertAndExit(stdoutFn, exitFn) {
5226
5224
  return exitFn(0);
5227
5225
  }
5228
5226
 
5227
+ // src/lib/script-paths.ts
5228
+ import * as path28 from "path";
5229
+ function resolveCleargateScript(opts, scriptName) {
5230
+ const cwd = opts.cwd ?? process.cwd();
5231
+ return path28.join(cwd, ".cleargate", "scripts", scriptName);
5232
+ }
5233
+
5229
5234
  // src/commands/gate.ts
5230
- import yaml6 from "js-yaml";
5235
+ import yaml5 from "js-yaml";
5231
5236
 
5232
5237
  // src/lib/readiness-predicates.ts
5233
5238
  import * as fs27 from "fs";
5234
- import * as path28 from "path";
5239
+ import * as path29 from "path";
5235
5240
  function parsePredicate(src) {
5236
5241
  const s = src.trim();
5237
5242
  const fmMatch = s.match(
@@ -5263,7 +5268,7 @@ function parsePredicate(src) {
5263
5268
  return { kind: "body-contains", needle: bodyMatch[1], negated: false };
5264
5269
  }
5265
5270
  const sectionMatch = s.match(
5266
- /^section\((\d+)\) has (≥|>=|==|>)(\d+) (checked-checkbox|unchecked-checkbox|listed-item)$/
5271
+ /^section\((\d+)\) has (≥|>=|==|>)(\d+) (checked-checkbox|unchecked-checkbox|listed-item|declared-item)$/
5267
5272
  );
5268
5273
  if (sectionMatch) {
5269
5274
  const index = parseInt(sectionMatch[1], 10);
@@ -5291,6 +5296,9 @@ function parsePredicate(src) {
5291
5296
  const value = statusMatch[2].trim().replace(/^['"]|['"]$/g, "");
5292
5297
  return { kind: "status-of", id, value };
5293
5298
  }
5299
+ if (s === "existing-surfaces-verified") {
5300
+ return { kind: "existing-surfaces-verified" };
5301
+ }
5294
5302
  throw new Error(`unsupported predicate shape: ${src}`);
5295
5303
  }
5296
5304
  function parseValue(raw) {
@@ -5319,6 +5327,8 @@ function evaluate(predicate, doc, opts) {
5319
5327
  return evalLinkTargetExists(parsed, opts);
5320
5328
  case "status-of":
5321
5329
  return evalStatusOf(parsed, opts, projectRoot);
5330
+ case "existing-surfaces-verified":
5331
+ return evalExistingSurfacesVerified(doc, projectRoot);
5322
5332
  }
5323
5333
  }
5324
5334
  function evalFrontmatter(parsed, doc, projectRoot) {
@@ -5395,8 +5405,14 @@ function compareValues(actual, op, expected) {
5395
5405
  }
5396
5406
  function resolveLinkedPath(ref, docAbsPath, projectRoot) {
5397
5407
  const candidates = [
5398
- path28.resolve(path28.dirname(docAbsPath), ref),
5399
- path28.resolve(projectRoot, ref)
5408
+ path29.resolve(path29.dirname(docAbsPath), ref),
5409
+ // 1. relative to citer
5410
+ path29.resolve(projectRoot, ref),
5411
+ // 2. project root
5412
+ path29.resolve(projectRoot, ".cleargate", "delivery", "pending-sync", ref),
5413
+ // 3. live
5414
+ path29.resolve(projectRoot, ".cleargate", "delivery", "archive", ref)
5415
+ // 4. archived
5400
5416
  ];
5401
5417
  for (const candidate of candidates) {
5402
5418
  if (!candidate.startsWith(projectRoot)) continue;
@@ -5533,6 +5549,9 @@ function evalSection(parsed, doc) {
5533
5549
  case "listed-item":
5534
5550
  actualCount = (sectionContent.match(/^\s*- /gm) || []).length;
5535
5551
  break;
5552
+ case "declared-item":
5553
+ actualCount = countDeclaredItems(sectionContent);
5554
+ break;
5536
5555
  }
5537
5556
  const pass = applyCountOp(actualCount, parsed.count.op, parsed.count.n);
5538
5557
  const opStr = parsed.count.op === ">=" ? "\u2265" : parsed.count.op;
@@ -5549,9 +5568,39 @@ function applyCountOp(actual, op, n) {
5549
5568
  return actual > n;
5550
5569
  }
5551
5570
  }
5571
+ function countDeclaredItems(sectionContent) {
5572
+ const lines = sectionContent.split("\n");
5573
+ let count = 0;
5574
+ let inTable = false;
5575
+ for (const line of lines) {
5576
+ if (/^\s*- /.test(line)) {
5577
+ count++;
5578
+ inTable = false;
5579
+ continue;
5580
+ }
5581
+ if (/^\|.+\|/.test(line)) {
5582
+ if (/^\|[\s\-:]+\|[\s\-:|]*$/.test(line.replace(/\s/g, ""))) {
5583
+ inTable = true;
5584
+ continue;
5585
+ }
5586
+ if (inTable) {
5587
+ count++;
5588
+ }
5589
+ continue;
5590
+ }
5591
+ if (inTable && !/^\|/.test(line)) {
5592
+ inTable = false;
5593
+ }
5594
+ if (/^(\*{1,2}|_{1,2})?[A-Z][^|*\n]*(\*{1,2}|_{1,2})?:/.test(line.trim())) {
5595
+ count++;
5596
+ continue;
5597
+ }
5598
+ }
5599
+ return count;
5600
+ }
5552
5601
  function evalFileExists(parsed, projectRoot) {
5553
- const resolved = path28.resolve(projectRoot, parsed.path);
5554
- if (!resolved.startsWith(projectRoot + path28.sep) && resolved !== projectRoot) {
5602
+ const resolved = path29.resolve(projectRoot, parsed.path);
5603
+ if (!resolved.startsWith(projectRoot + path29.sep) && resolved !== projectRoot) {
5555
5604
  return {
5556
5605
  pass: false,
5557
5606
  detail: `path '${parsed.path}' resolves outside project root (sandbox violation)`
@@ -5565,7 +5614,7 @@ function evalFileExists(parsed, projectRoot) {
5565
5614
  }
5566
5615
  function evalLinkTargetExists(parsed, opts) {
5567
5616
  const projectRoot = opts?.projectRoot ?? process.cwd();
5568
- const wikiIndexPath = opts?.wikiIndexPath ?? path28.join(projectRoot, ".cleargate", "wiki", "index.md");
5617
+ const wikiIndexPath = opts?.wikiIndexPath ?? path29.join(projectRoot, ".cleargate", "wiki", "index.md");
5569
5618
  if (!wikiIndexPath.startsWith(projectRoot)) {
5570
5619
  return { pass: false, detail: "wikiIndexPath resolves outside project root" };
5571
5620
  }
@@ -5582,7 +5631,7 @@ function evalLinkTargetExists(parsed, opts) {
5582
5631
  };
5583
5632
  }
5584
5633
  function evalStatusOf(parsed, opts, projectRoot) {
5585
- const wikiIndexPath = opts?.wikiIndexPath ?? path28.join(projectRoot, ".cleargate", "wiki", "index.md");
5634
+ const wikiIndexPath = opts?.wikiIndexPath ?? path29.join(projectRoot, ".cleargate", "wiki", "index.md");
5586
5635
  if (!wikiIndexPath.startsWith(projectRoot)) {
5587
5636
  return { pass: false, detail: "wikiIndexPath resolves outside project root" };
5588
5637
  }
@@ -5599,7 +5648,7 @@ function evalStatusOf(parsed, opts, projectRoot) {
5599
5648
  return { pass: false, detail: `[[${parsed.id}]] not found in wiki index` };
5600
5649
  }
5601
5650
  const rawPath = rowMatch[1].trim();
5602
- const fullPath = path28.resolve(projectRoot, rawPath);
5651
+ const fullPath = path29.resolve(projectRoot, rawPath);
5603
5652
  if (!fullPath.startsWith(projectRoot)) {
5604
5653
  return { pass: false, detail: `wiki path for ${parsed.id} resolves outside project root` };
5605
5654
  }
@@ -5614,10 +5663,64 @@ function evalStatusOf(parsed, opts, projectRoot) {
5614
5663
  detail: pass ? `status-of([[${parsed.id}]]) == ${parsed.value}` : `status-of([[${parsed.id}]]) is '${status}', expected '${parsed.value}'`
5615
5664
  };
5616
5665
  }
5666
+ function evalExistingSurfacesVerified(doc, projectRoot) {
5667
+ const body = doc.body;
5668
+ const rawParts = body.split(/^(?=## )/m);
5669
+ let sectionContent;
5670
+ for (const part of rawParts) {
5671
+ if (part.startsWith("## Existing Surfaces")) {
5672
+ sectionContent = part;
5673
+ break;
5674
+ }
5675
+ }
5676
+ if (!sectionContent) {
5677
+ return {
5678
+ pass: true,
5679
+ detail: `not-applicable: ## Existing Surfaces section absent \u2014 reuse-audit-recorded already failing`
5680
+ };
5681
+ }
5682
+ const PATH_RE = /[a-zA-Z0-9_./-]+\.[a-zA-Z]{1,5}(?::[a-zA-Z_][a-zA-Z0-9_]*)?/g;
5683
+ const rawMatches = sectionContent.match(PATH_RE) ?? [];
5684
+ const paths = [...new Set(rawMatches.map((m) => m.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)$/, "")))];
5685
+ if (paths.length === 0) {
5686
+ const SENTINEL_RE = /no overlap found|no existing surface|no prior implementation|audit returned empty/i;
5687
+ if (SENTINEL_RE.test(sectionContent)) {
5688
+ return {
5689
+ pass: true,
5690
+ detail: `## Existing Surfaces contains no path citations; sentinel phrase present \u2014 audit explicitly empty`
5691
+ };
5692
+ }
5693
+ return {
5694
+ pass: false,
5695
+ detail: `'## Existing Surfaces' has no path citations and no "no overlap found" sentinel`
5696
+ };
5697
+ }
5698
+ const missing = [];
5699
+ for (const p of paths) {
5700
+ const resolved = path29.resolve(projectRoot, p);
5701
+ if (!resolved.startsWith(projectRoot + path29.sep) && resolved !== projectRoot) {
5702
+ missing.push(p);
5703
+ continue;
5704
+ }
5705
+ if (!fs27.existsSync(resolved)) {
5706
+ missing.push(p);
5707
+ }
5708
+ }
5709
+ if (missing.length > 0) {
5710
+ return {
5711
+ pass: false,
5712
+ detail: `cited paths do not exist on disk: ${missing.join(", ")}`
5713
+ };
5714
+ }
5715
+ return {
5716
+ pass: true,
5717
+ detail: `all ${paths.length} cited path${paths.length === 1 ? "" : "s"} exist on disk`
5718
+ };
5719
+ }
5617
5720
 
5618
5721
  // src/lib/frontmatter-cache.ts
5619
5722
  import * as fs28 from "fs/promises";
5620
- import yaml5 from "js-yaml";
5723
+ import yaml4 from "js-yaml";
5621
5724
  async function readCachedGate(absPath) {
5622
5725
  let raw;
5623
5726
  try {
@@ -5685,7 +5788,7 @@ function coerceCachedGate(val) {
5685
5788
  }
5686
5789
  if (typeof val === "string" && val.startsWith("{")) {
5687
5790
  try {
5688
- const parsed = yaml5.load(val, { schema: yaml5.CORE_SCHEMA });
5791
+ const parsed = yaml4.load(val, { schema: yaml4.CORE_SCHEMA });
5689
5792
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
5690
5793
  const p = parsed;
5691
5794
  return {
@@ -5708,7 +5811,7 @@ function loadGateBlocks(gatesDocPath) {
5708
5811
  let match;
5709
5812
  while ((match = fenceRe.exec(raw)) !== null) {
5710
5813
  const yamlContent = match[1];
5711
- const parsed = yaml6.load(yamlContent);
5814
+ const parsed = yaml5.load(yamlContent);
5712
5815
  const block = Array.isArray(parsed) ? parsed[0] : parsed;
5713
5816
  if (block && typeof block === "object" && "work_item_type" in block && "transition" in block && "severity" in block && "criteria" in block) {
5714
5817
  blocks.push(block);
@@ -5737,7 +5840,7 @@ async function gateCheckHandler(file, opts, cli) {
5737
5840
  const exitFn = cli?.exit ?? ((code) => process.exit(code));
5738
5841
  const cwd = cli?.cwd ?? process.cwd();
5739
5842
  const nowFn = cli?.now ?? (() => /* @__PURE__ */ new Date());
5740
- const absPath = path29.isAbsolute(file) ? file : path29.resolve(cwd, file);
5843
+ const absPath = path30.isAbsolute(file) ? file : path30.resolve(cwd, file);
5741
5844
  if (!fs29.existsSync(absPath)) {
5742
5845
  stderrFn(`[cleargate gate] error: file not found: ${absPath}`);
5743
5846
  return exitFn(1);
@@ -5763,7 +5866,7 @@ async function gateCheckHandler(file, opts, cli) {
5763
5866
  return exitFn(1);
5764
5867
  }
5765
5868
  const projectRoot = cwd;
5766
- const gatesDocPath = cli?.gatesDocPath ?? path29.join(projectRoot, ".cleargate", "knowledge", "readiness-gates.md");
5869
+ const gatesDocPath = cli?.gatesDocPath ?? path30.join(projectRoot, ".cleargate", "knowledge", "readiness-gates.md");
5767
5870
  if (!fs29.existsSync(gatesDocPath)) {
5768
5871
  stderrFn(`[cleargate gate] error: readiness-gates.md not found at: ${gatesDocPath}`);
5769
5872
  return exitFn(1);
@@ -5787,7 +5890,6 @@ async function gateCheckHandler(file, opts, cli) {
5787
5890
  const wikiIndexPath = cli?.wikiIndexPath;
5788
5891
  const parsedDoc = { fm, body, absPath };
5789
5892
  const evalOpts = { projectRoot, ...wikiIndexPath ? { wikiIndexPath } : {} };
5790
- const failingCriteria = [];
5791
5893
  const allResults = [];
5792
5894
  for (const criterion of gate2.criteria) {
5793
5895
  let result;
@@ -5796,9 +5898,32 @@ async function gateCheckHandler(file, opts, cli) {
5796
5898
  } catch (err) {
5797
5899
  result = { pass: false, detail: `predicate error: ${String(err)}` };
5798
5900
  }
5799
- allResults.push({ id: criterion.id, ...result });
5800
- if (!result.pass) {
5801
- failingCriteria.push({ id: criterion.id, detail: result.detail });
5901
+ allResults.push({ id: criterion.id, ...result, or_group: criterion.or_group });
5902
+ }
5903
+ const failingCriteria = [];
5904
+ const orGroups = /* @__PURE__ */ new Map();
5905
+ for (const r of allResults) {
5906
+ if (r.or_group) {
5907
+ const existing = orGroups.get(r.or_group) ?? [];
5908
+ existing.push(r);
5909
+ orGroups.set(r.or_group, existing);
5910
+ }
5911
+ }
5912
+ for (const r of allResults) {
5913
+ if (r.or_group) {
5914
+ const groupMembers = orGroups.get(r.or_group);
5915
+ const isFirstMember = groupMembers[0].id === r.id;
5916
+ if (isFirstMember) {
5917
+ const anyPasses = groupMembers.some((m) => m.pass);
5918
+ if (!anyPasses) {
5919
+ const details = groupMembers.map((m) => `${m.id}: ${m.detail}`).join("; ");
5920
+ failingCriteria.push({ id: r.or_group, detail: `OR-group failed \u2014 all alternatives failed: ${details}` });
5921
+ }
5922
+ }
5923
+ } else {
5924
+ if (!r.pass) {
5925
+ failingCriteria.push({ id: r.id, detail: r.detail });
5926
+ }
5802
5927
  }
5803
5928
  }
5804
5929
  const overallPass = failingCriteria.length === 0;
@@ -5815,15 +5940,15 @@ async function gateCheckHandler(file, opts, cli) {
5815
5940
  if (overallPass) {
5816
5941
  stdoutFn(`\u2705 ${detectedType}.${transition} passed (${gate2.criteria.length} criteria)`);
5817
5942
  } else {
5818
- for (const r of allResults) {
5819
- if (!r.pass) {
5820
- if (isAdvisory) {
5821
- stdoutFn(`\u26A0 ${r.id}: ${r.detail} (advisory)`);
5822
- } else {
5823
- stdoutFn(`\u274C ${r.id}: ${r.detail}`);
5824
- }
5943
+ for (const fc of failingCriteria) {
5944
+ if (isAdvisory) {
5945
+ stdoutFn(`\u26A0 ${fc.id}: ${fc.detail} (advisory)`);
5946
+ } else {
5947
+ stdoutFn(`\u274C ${fc.id}: ${fc.detail}`);
5825
5948
  }
5826
- if (opts.verbose) {
5949
+ }
5950
+ if (opts.verbose) {
5951
+ for (const r of allResults) {
5827
5952
  stdoutFn(` [${r.pass ? "pass" : "fail"}] ${r.id}: ${r.detail}`);
5828
5953
  }
5829
5954
  }
@@ -5837,7 +5962,7 @@ async function gateExplainHandler(file, cli) {
5837
5962
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
5838
5963
  const exitFn = cli?.exit ?? ((code) => process.exit(code));
5839
5964
  const cwd = cli?.cwd ?? process.cwd();
5840
- const absPath = path29.isAbsolute(file) ? file : path29.resolve(cwd, file);
5965
+ const absPath = path30.isAbsolute(file) ? file : path30.resolve(cwd, file);
5841
5966
  if (!fs29.existsSync(absPath)) {
5842
5967
  stderrFn(`[cleargate gate] error: file not found: ${absPath}`);
5843
5968
  return exitFn(1);
@@ -5870,7 +5995,7 @@ async function gateExplainHandler(file, cli) {
5870
5995
  function resolveRunScriptForGate(opts) {
5871
5996
  if (opts.runScriptPath) return opts.runScriptPath;
5872
5997
  const cwd = opts.cwd ?? process.cwd();
5873
- return path29.join(cwd, ".cleargate", "scripts", "run_script.sh");
5998
+ return path30.join(cwd, ".cleargate", "scripts", "run_script.sh");
5874
5999
  }
5875
6000
  function gateQaHandler(opts, cli) {
5876
6001
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
@@ -5886,9 +6011,11 @@ function gateQaHandler(opts, cli) {
5886
6011
  return printInertAndExit(stdoutFn, exitFn);
5887
6012
  }
5888
6013
  const runScript = resolveRunScriptForGate(cli ?? {});
6014
+ const qaCwd = cli?.cwd ?? process.cwd();
6015
+ const gateRunnerPath = resolveCleargateScript({ cwd: qaCwd }, "pre_gate_runner.sh");
5889
6016
  const result = spawnFn(
5890
6017
  "bash",
5891
- [runScript, "pre_gate_runner.sh", "qa", opts.worktree, opts.branch],
6018
+ [runScript, "bash", gateRunnerPath, "qa", opts.worktree, opts.branch],
5892
6019
  { stdio: "inherit" }
5893
6020
  );
5894
6021
  if (result.error) {
@@ -5912,9 +6039,11 @@ function gateArchHandler(opts, cli) {
5912
6039
  return printInertAndExit(stdoutFn, exitFn);
5913
6040
  }
5914
6041
  const runScript = resolveRunScriptForGate(cli ?? {});
6042
+ const archCwd = cli?.cwd ?? process.cwd();
6043
+ const archGateRunnerPath = resolveCleargateScript({ cwd: archCwd }, "pre_gate_runner.sh");
5915
6044
  const result = spawnFn(
5916
6045
  "bash",
5917
- [runScript, "pre_gate_runner.sh", "arch", opts.worktree, opts.branch],
6046
+ [runScript, "bash", archGateRunnerPath, "arch", opts.worktree, opts.branch],
5918
6047
  { stdio: "inherit" }
5919
6048
  );
5920
6049
  if (result.error) {
@@ -5962,319 +6091,10 @@ function gateRunHandler(name, opts, cli) {
5962
6091
  }
5963
6092
 
5964
6093
  // src/commands/sprint.ts
5965
- import * as fs31 from "fs";
5966
- import * as path31 from "path";
5967
- import { spawnSync as spawnSync11, execSync as execSync2 } from "child_process";
5968
- import yaml7 from "js-yaml";
5969
-
5970
- // src/lib/lifecycle-reconcile.ts
5971
6094
  import * as fs30 from "fs";
5972
- import * as path30 from "path";
5973
- import { spawnSync as spawnSync10 } from "child_process";
5974
- var VERB_STATUS_MAP = {
5975
- feat: {
5976
- types: ["STORY", "EPIC", "CR"],
5977
- expected: ["Done", "Completed"]
5978
- },
5979
- fix: {
5980
- types: ["BUG", "HOTFIX"],
5981
- expected: ["Verified", "Done", "Completed"]
5982
- }
5983
- };
5984
- var ID_PATTERN = /\b(STORY-\d{3}-\d{2}|(CR|BUG|EPIC|HOTFIX)-\d{3}|(PROPOSAL|PROP)-\d{3})\b/g;
5985
- function normalizeId(raw) {
5986
- return raw.replace(/^PROP-(\d+)$/, "PROPOSAL-$1");
5987
- }
5988
- function idType(id) {
5989
- if (/^STORY-\d{3}-\d{2}$/.test(id)) return "STORY";
5990
- if (/^CR-\d{3}$/.test(id)) return "CR";
5991
- if (/^BUG-\d{3}$/.test(id)) return "BUG";
5992
- if (/^EPIC-\d{3}$/.test(id)) return "EPIC";
5993
- if (/^PROPOSAL-\d{3}$/.test(id)) return "PROPOSAL";
5994
- if (/^HOTFIX-\d{3}$/.test(id)) return "HOTFIX";
5995
- return null;
5996
- }
5997
- function parseCommitMessage(msg) {
5998
- const lines = msg.split("\n");
5999
- const subject = lines[0] ?? "";
6000
- let firstBodyLine = "";
6001
- for (let i = 1; i < lines.length; i++) {
6002
- if (lines[i]?.trim()) {
6003
- firstBodyLine = lines[i];
6004
- break;
6005
- }
6006
- }
6007
- const verbMatch = /^(\w+)[(!]/.exec(subject) ?? /^(\w+):/.exec(subject);
6008
- const verb = verbMatch ? verbMatch[1].toLowerCase() : "";
6009
- const searchText = subject + (firstBodyLine ? "\n" + firstBodyLine : "");
6010
- const results = [];
6011
- const seen = /* @__PURE__ */ new Set();
6012
- let m;
6013
- ID_PATTERN.lastIndex = 0;
6014
- while ((m = ID_PATTERN.exec(searchText)) !== null) {
6015
- const rawId = m[0];
6016
- const id = normalizeId(rawId);
6017
- if (seen.has(id)) continue;
6018
- seen.add(id);
6019
- const type = idType(id);
6020
- if (!type) continue;
6021
- results.push({ verb, id, type });
6022
- }
6023
- return results;
6024
- }
6025
- function findArtifactFile(deliveryRoot, id) {
6026
- const prefix = `${id}_`;
6027
- const dirs = [
6028
- { rel: "pending-sync", inArchive: false },
6029
- { rel: "archive", inArchive: true }
6030
- ];
6031
- for (const { rel, inArchive } of dirs) {
6032
- const dir = path30.join(deliveryRoot, rel);
6033
- let entries;
6034
- try {
6035
- entries = fs30.readdirSync(dir);
6036
- } catch {
6037
- continue;
6038
- }
6039
- const match = entries.find(
6040
- (e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith(".md")
6041
- );
6042
- if (match) {
6043
- const absPath = path30.join(dir, match);
6044
- return { absPath, inArchive, relPath: `${rel}/${match}` };
6045
- }
6046
- }
6047
- return null;
6048
- }
6049
- function readArtifactStatus(absPath) {
6050
- let raw;
6051
- try {
6052
- raw = fs30.readFileSync(absPath, "utf8");
6053
- } catch {
6054
- return { status: null, carryOver: false };
6055
- }
6056
- try {
6057
- const { fm } = parseFrontmatter(raw);
6058
- const status = typeof fm["status"] === "string" ? fm["status"] : null;
6059
- const carryOver = fm["carry_over"] === true;
6060
- return { status, carryOver };
6061
- } catch {
6062
- return { status: null, carryOver: false };
6063
- }
6064
- }
6065
- function reconcileLifecycle(opts) {
6066
- const { since, until = /* @__PURE__ */ new Date(), deliveryRoot, repoRoot } = opts;
6067
- const gitRunner = opts.gitRunner ?? ((cmd, args) => {
6068
- const result = spawnSync10(cmd, args, { encoding: "utf8", cwd: repoRoot });
6069
- return result.stdout ?? "";
6070
- });
6071
- const sinceIso = since.toISOString();
6072
- const untilIso = until.toISOString();
6073
- const logOutput = gitRunner("git", [
6074
- "log",
6075
- `--after=${sinceIso}`,
6076
- `--before=${untilIso}`,
6077
- "--format=%H%x00%s%x00%b%x00---COMMIT---",
6078
- "--"
6079
- ]);
6080
- const idToItem = /* @__PURE__ */ new Map();
6081
- const cleanIds = /* @__PURE__ */ new Set();
6082
- if (logOutput.trim()) {
6083
- const rawCommits = logOutput.split("---COMMIT---\n").filter((c) => c.trim());
6084
- for (const raw of rawCommits) {
6085
- const [sha = "", subject = "", body = ""] = raw.split("\0");
6086
- const trimSha = sha.trim();
6087
- const trimSubject = subject.trim();
6088
- const trimBody = body.trim();
6089
- if (!trimSha || !trimSubject) continue;
6090
- const commitMsg = trimSubject + (trimBody ? "\n\n" + trimBody : "");
6091
- const parsed = parseCommitMessage(commitMsg);
6092
- for (const { verb, id, type } of parsed) {
6093
- if (verb === "merge" || verb === "chore" || verb === "docs" || verb === "refactor" || verb === "test" || verb === "file" || verb === "plan") {
6094
- continue;
6095
- }
6096
- if (type === "PROPOSAL") continue;
6097
- const verbConfig = VERB_STATUS_MAP[verb];
6098
- if (!verbConfig) continue;
6099
- const found = findArtifactFile(deliveryRoot, id);
6100
- if (!found) {
6101
- continue;
6102
- }
6103
- const { status, carryOver } = readArtifactStatus(found.absPath);
6104
- if (carryOver) continue;
6105
- let expectedStatuses;
6106
- if (verb === "feat" && type === "BUG") {
6107
- expectedStatuses = ["Verified", "Done", "Completed"];
6108
- } else if (!verbConfig.types.includes(type)) {
6109
- continue;
6110
- } else {
6111
- expectedStatuses = verbConfig.expected;
6112
- }
6113
- const isTerminal = status !== null && expectedStatuses.includes(status);
6114
- const isArchived = found.inArchive;
6115
- if (isTerminal && isArchived) {
6116
- cleanIds.add(id);
6117
- idToItem.delete(id);
6118
- } else if (!idToItem.has(id)) {
6119
- const expectedStr = expectedStatuses[0] ?? "Done";
6120
- idToItem.set(id, {
6121
- id,
6122
- type,
6123
- expected_status: expectedStr,
6124
- actual_status: status,
6125
- file_path: found.relPath,
6126
- in_archive: isArchived,
6127
- commit_shas: [trimSha],
6128
- carry_over: carryOver
6129
- });
6130
- } else {
6131
- const existing = idToItem.get(id);
6132
- if (!existing.commit_shas.includes(trimSha)) {
6133
- existing.commit_shas.push(trimSha);
6134
- }
6135
- }
6136
- }
6137
- }
6138
- }
6139
- for (const id of cleanIds) {
6140
- idToItem.delete(id);
6141
- }
6142
- const drift = Array.from(idToItem.values());
6143
- return { drift, clean: cleanIds.size };
6144
- }
6145
- function reconcileDecomposition(opts) {
6146
- const { sprintPlanPath, deliveryRoot } = opts;
6147
- let raw;
6148
- try {
6149
- raw = fs30.readFileSync(sprintPlanPath, "utf8");
6150
- } catch {
6151
- return { missing: [], clean: 0 };
6152
- }
6153
- let fm;
6154
- try {
6155
- ({ fm } = parseFrontmatter(raw));
6156
- } catch {
6157
- return { missing: [], clean: 0 };
6158
- }
6159
- const epics = Array.isArray(fm["epics"]) ? fm["epics"].map(String) : [];
6160
- const proposals = Array.isArray(fm["proposals"]) ? fm["proposals"].map(String) : [];
6161
- const pendingDir = path30.join(deliveryRoot, "pending-sync");
6162
- const archiveDir = path30.join(deliveryRoot, "archive");
6163
- function listMdFiles(dir) {
6164
- try {
6165
- return fs30.readdirSync(dir).filter((f) => f.endsWith(".md"));
6166
- } catch {
6167
- return [];
6168
- }
6169
- }
6170
- const pendingFiles = listMdFiles(pendingDir);
6171
- const archiveFiles = listMdFiles(archiveDir);
6172
- const allFiles = [...pendingFiles, ...archiveFiles];
6173
- const missing = [];
6174
- let clean = 0;
6175
- for (const epicId of epics) {
6176
- const epicFile = allFiles.find(
6177
- (f) => f.startsWith(`${epicId}_`) || f === `${epicId}.md`
6178
- );
6179
- if (!epicFile) {
6180
- missing.push({
6181
- id: epicId,
6182
- type: "epic",
6183
- reason: "file-missing",
6184
- expected_files: [`pending-sync/${epicId}_<name>.md`]
6185
- });
6186
- continue;
6187
- }
6188
- const childStories = findChildStories(
6189
- epicId,
6190
- pendingDir,
6191
- pendingFiles,
6192
- archiveDir,
6193
- archiveFiles
6194
- );
6195
- if (childStories.length === 0) {
6196
- missing.push({
6197
- id: epicId,
6198
- type: "epic",
6199
- reason: "no-child-stories",
6200
- expected_files: [
6201
- `pending-sync/${epicId.replace("EPIC-", "STORY-")}-01_<name>.md`
6202
- ]
6203
- });
6204
- } else {
6205
- clean++;
6206
- }
6207
- }
6208
- for (const proposalId of proposals) {
6209
- const decomposedEpic = findDecomposedEpic(
6210
- proposalId,
6211
- pendingDir,
6212
- pendingFiles
6213
- );
6214
- if (!decomposedEpic) {
6215
- missing.push({
6216
- id: proposalId,
6217
- type: "proposal",
6218
- reason: "no-decomposed-epic",
6219
- expected_files: [`pending-sync/EPIC-<NNN>_<name>.md with context_source citing ${proposalId}`]
6220
- });
6221
- } else {
6222
- clean++;
6223
- }
6224
- }
6225
- return { missing, clean };
6226
- }
6227
- function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveFiles) {
6228
- const results = [];
6229
- const epicNumMatch = /^EPIC-(\d+)$/.exec(epicId);
6230
- if (!epicNumMatch) return results;
6231
- const epicNum = epicNumMatch[1];
6232
- const storyPrefix = `STORY-${epicNum}-`;
6233
- for (const [files, dir] of [[pendingFiles, pendingDir], [archiveFiles, archiveDir]]) {
6234
- for (const f of files) {
6235
- if (!f.startsWith(storyPrefix) && !f.startsWith("STORY-")) continue;
6236
- if (!f.includes(storyPrefix)) continue;
6237
- const absPath = path30.join(dir, f);
6238
- try {
6239
- const raw = fs30.readFileSync(absPath, "utf8");
6240
- const { fm } = parseFrontmatter(raw);
6241
- const parentRef = fm["parent_epic_ref"];
6242
- if (parentRef === epicId) {
6243
- results.push(f);
6244
- }
6245
- } catch {
6246
- }
6247
- }
6248
- }
6249
- return results;
6250
- }
6251
- function findDecomposedEpic(proposalId, pendingDir, pendingFiles) {
6252
- for (const f of pendingFiles) {
6253
- if (!f.startsWith("EPIC-")) continue;
6254
- const absPath = path30.join(pendingDir, f);
6255
- try {
6256
- const raw = fs30.readFileSync(absPath, "utf8");
6257
- const { fm } = parseFrontmatter(raw);
6258
- const contextSource = fm["context_source"];
6259
- if (typeof contextSource === "string" && contextSource.includes(proposalId)) {
6260
- return f;
6261
- }
6262
- } catch {
6263
- }
6264
- }
6265
- return null;
6266
- }
6267
- function checkVerbMismatch(verb, type) {
6268
- if (verb === "feat" && type === "BUG") {
6269
- return `verb 'feat' unusual for BUG; expected 'fix'`;
6270
- }
6271
- if (verb === "fix" && (type === "STORY" || type === "EPIC" || type === "CR")) {
6272
- return `verb 'fix' unusual for ${type}; expected 'feat'`;
6273
- }
6274
- return null;
6275
- }
6276
-
6277
- // src/commands/sprint.ts
6095
+ import * as path31 from "path";
6096
+ import { spawnSync as spawnSync10, execSync as execSync2 } from "child_process";
6097
+ import yaml6 from "js-yaml";
6278
6098
  var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandoned", "Closed", "Resolved"]);
6279
6099
  function resolveRunScript(opts) {
6280
6100
  if (opts.runScriptPath) return opts.runScriptPath;
@@ -6288,7 +6108,7 @@ function sprintInitHandler(opts, cli) {
6288
6108
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
6289
6109
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
6290
6110
  const exitFn = cli?.exit ?? defaultExit;
6291
- const spawnFn = cli?.spawnFn ?? spawnSync11;
6111
+ const spawnFn = cli?.spawnFn ?? spawnSync10;
6292
6112
  const cwd = cli?.cwd ?? process.cwd();
6293
6113
  const allowDrift = opts.allowDrift ?? cli?.allowDrift ?? false;
6294
6114
  const mode = readSprintExecutionMode(opts.sprintId, {
@@ -6303,13 +6123,13 @@ function sprintInitHandler(opts, cli) {
6303
6123
  let sprintPlanPath = null;
6304
6124
  const pendingDir = path31.join(deliveryRoot, "pending-sync");
6305
6125
  try {
6306
- const entries = fs31.readdirSync(pendingDir);
6126
+ const entries = fs30.readdirSync(pendingDir);
6307
6127
  const sprintFile = entries.find(
6308
6128
  (e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
6309
6129
  );
6310
6130
  if (sprintFile) {
6311
6131
  sprintPlanPath = path31.join(pendingDir, sprintFile);
6312
- const raw = fs31.readFileSync(sprintPlanPath, "utf8");
6132
+ const raw = fs30.readFileSync(sprintPlanPath, "utf8");
6313
6133
  const { fm } = parseFileFrontmatter(raw);
6314
6134
  if (fm["lifecycle_init_mode"] === "block") {
6315
6135
  lifecycleInitMode = "block";
@@ -6343,7 +6163,7 @@ function sprintInitHandler(opts, cli) {
6343
6163
  stderrFn("[cleargate sprint init] lifecycle drift waived via --allow-drift flag");
6344
6164
  if (sprintPlanPath) {
6345
6165
  try {
6346
- const rawSprint = fs31.readFileSync(sprintPlanPath, "utf8");
6166
+ const rawSprint = fs30.readFileSync(sprintPlanPath, "utf8");
6347
6167
  const { fm, body } = parseFileFrontmatter(rawSprint);
6348
6168
  const waiverLine = `lifecycle waiver: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} for ${lifecycleResult.drift.map((d) => d.id).join(", ")}`;
6349
6169
  const currentContextSource = typeof fm["context_source"] === "string" ? fm["context_source"] : "";
@@ -6392,8 +6212,12 @@ ${waiverLine}` : waiverLine;
6392
6212
  }
6393
6213
  }
6394
6214
  const runScript = resolveRunScript(cli ?? {});
6395
- const args = ["init_sprint.mjs", opts.sprintId, "--stories", opts.stories];
6396
- const result = spawnFn("bash", [runScript, ...args], { stdio: "inherit" });
6215
+ const scriptPath = resolveCleargateScript({ cwd }, "init_sprint.mjs");
6216
+ const result = spawnFn(
6217
+ "bash",
6218
+ [runScript, "node", scriptPath, opts.sprintId, "--stories", opts.stories],
6219
+ { stdio: "inherit" }
6220
+ );
6397
6221
  if (result.error) {
6398
6222
  stderrFn(`[cleargate sprint init] error: ${result.error.message}`);
6399
6223
  return exitFn(1);
@@ -6408,7 +6232,7 @@ function sprintCloseHandler(opts, cli) {
6408
6232
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
6409
6233
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
6410
6234
  const exitFn = cli?.exit ?? defaultExit;
6411
- const spawnFn = cli?.spawnFn ?? spawnSync11;
6235
+ const spawnFn = cli?.spawnFn ?? spawnSync10;
6412
6236
  const mode = readSprintExecutionMode(opts.sprintId, {
6413
6237
  sprintFilePath: cli?.sprintFilePath,
6414
6238
  cwd: cli?.cwd
@@ -6417,11 +6241,13 @@ function sprintCloseHandler(opts, cli) {
6417
6241
  return printInertAndExit(stdoutFn, exitFn);
6418
6242
  }
6419
6243
  const runScript = resolveRunScript(cli ?? {});
6420
- const args = ["close_sprint.mjs", opts.sprintId];
6244
+ const closeCwd = cli?.cwd ?? process.cwd();
6245
+ const closeScriptPath = resolveCleargateScript({ cwd: closeCwd }, "close_sprint.mjs");
6246
+ const closeArgs = [runScript, "node", closeScriptPath, opts.sprintId];
6421
6247
  if (opts.assumeAck === true) {
6422
- args.push("--assume-ack");
6248
+ closeArgs.push("--assume-ack");
6423
6249
  }
6424
- const result = spawnFn("bash", [runScript, ...args], { stdio: "inherit" });
6250
+ const result = spawnFn("bash", closeArgs, { stdio: "inherit" });
6425
6251
  if (result.error) {
6426
6252
  stderrFn(`[cleargate sprint close] error: ${result.error.message}`);
6427
6253
  return exitFn(1);
@@ -6442,12 +6268,12 @@ function reconcileLifecycleCliHandler(opts, cli) {
6442
6268
  } else {
6443
6269
  try {
6444
6270
  const pendingDir = path31.join(deliveryRoot, "pending-sync");
6445
- const entries = fs31.readdirSync(pendingDir);
6271
+ const entries = fs30.readdirSync(pendingDir);
6446
6272
  const sprintFile = entries.find(
6447
6273
  (e) => (e.startsWith(`${opts.sprintId}_`) || e === `${opts.sprintId}.md`) && e.endsWith(".md")
6448
6274
  );
6449
6275
  if (sprintFile) {
6450
- const raw = fs31.readFileSync(path31.join(pendingDir, sprintFile), "utf8");
6276
+ const raw = fs30.readFileSync(path31.join(pendingDir, sprintFile), "utf8");
6451
6277
  const { fm } = parseFileFrontmatter(raw);
6452
6278
  const startDate = fm["start_date"];
6453
6279
  since = typeof startDate === "string" ? new Date(startDate) : new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3);
@@ -6504,7 +6330,7 @@ function parseFileFrontmatter(raw) {
6504
6330
  if (yamlText.trim() === "") return { fm: {}, body };
6505
6331
  let parsed;
6506
6332
  try {
6507
- parsed = yaml7.load(yamlText, { schema: yaml7.CORE_SCHEMA });
6333
+ parsed = yaml6.load(yamlText, { schema: yaml6.CORE_SCHEMA });
6508
6334
  } catch {
6509
6335
  return { fm: {}, body };
6510
6336
  }
@@ -6512,8 +6338,8 @@ function parseFileFrontmatter(raw) {
6512
6338
  return { fm: parsed, body };
6513
6339
  }
6514
6340
  function serializeFileContent(fm, body) {
6515
- const yamlBody = yaml7.dump(fm, {
6516
- schema: yaml7.CORE_SCHEMA,
6341
+ const yamlBody = yaml6.dump(fm, {
6342
+ schema: yaml6.CORE_SCHEMA,
6517
6343
  lineWidth: -1,
6518
6344
  noRefs: true,
6519
6345
  noCompatMode: true,
@@ -6528,8 +6354,8 @@ ${body}`;
6528
6354
  }
6529
6355
  function atomicWriteStr(filePath, content) {
6530
6356
  const tmp = `${filePath}.tmp.${process.pid}`;
6531
- fs31.writeFileSync(tmp, content, "utf8");
6532
- fs31.renameSync(tmp, filePath);
6357
+ fs30.writeFileSync(tmp, content, "utf8");
6358
+ fs30.renameSync(tmp, filePath);
6533
6359
  }
6534
6360
  function deriveSprintBranchForArchive(sprintId) {
6535
6361
  const match = /^SPRINT-(\d+)/.exec(sprintId);
@@ -6543,7 +6369,7 @@ function stampFile(raw, status, completedAt) {
6543
6369
  return serializeFileContent(fm, body);
6544
6370
  }
6545
6371
  function stampSprintClose(sprintPath, now) {
6546
- const previousContent = fs31.readFileSync(sprintPath, "utf8");
6372
+ const previousContent = fs30.readFileSync(sprintPath, "utf8");
6547
6373
  const { fm, body } = parseFileFrontmatter(previousContent);
6548
6374
  const currentStatus = typeof fm["status"] === "string" ? fm["status"] : "";
6549
6375
  const alreadyTerminal = TERMINAL_STATUSES2.has(currentStatus);
@@ -6570,7 +6396,7 @@ async function sprintArchiveHandler(opts, cli) {
6570
6396
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
6571
6397
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
6572
6398
  const exitFn = cli?.exit ?? defaultExit;
6573
- const spawnFn = cli?.spawnFn ?? spawnSync11;
6399
+ const spawnFn = cli?.spawnFn ?? spawnSync10;
6574
6400
  const cwd = cli?.cwd ?? process.cwd();
6575
6401
  const wikiBuildFn = cli?.wikiBuildFn ?? (async (wCwd, wStdout) => {
6576
6402
  const fakeExit = (code) => {
@@ -6604,13 +6430,13 @@ async function sprintArchiveHandler(opts, cli) {
6604
6430
  return printInertAndExit(stdoutFn, exitFn);
6605
6431
  }
6606
6432
  const stateFile = path31.join(cwd, ".cleargate", "sprint-runs", opts.sprintId, "state.json");
6607
- if (!fs31.existsSync(stateFile)) {
6433
+ if (!fs30.existsSync(stateFile)) {
6608
6434
  stderrFn(`[cleargate sprint archive] state.json not found at ${stateFile}`);
6609
6435
  return exitFn(1);
6610
6436
  }
6611
6437
  let state2;
6612
6438
  try {
6613
- state2 = JSON.parse(fs31.readFileSync(stateFile, "utf8"));
6439
+ state2 = JSON.parse(fs30.readFileSync(stateFile, "utf8"));
6614
6440
  } catch (err) {
6615
6441
  stderrFn(`[cleargate sprint archive] failed to parse state.json: ${err.message}`);
6616
6442
  return exitFn(1);
@@ -6625,15 +6451,15 @@ async function sprintArchiveHandler(opts, cli) {
6625
6451
  const pendingDir = path31.join(cwd, ".cleargate", "delivery", "pending-sync");
6626
6452
  const archiveDir = path31.join(cwd, ".cleargate", "delivery", "archive");
6627
6453
  let sprintFile = null;
6628
- for (const entry of fs31.readdirSync(pendingDir)) {
6454
+ for (const entry of fs30.readdirSync(pendingDir)) {
6629
6455
  if ((entry.startsWith(`${opts.sprintId}_`) || entry === `${opts.sprintId}.md`) && entry.endsWith(".md")) {
6630
6456
  sprintFile = path31.join(pendingDir, entry);
6631
6457
  break;
6632
6458
  }
6633
6459
  }
6634
6460
  let epicIds = [];
6635
- if (sprintFile && fs31.existsSync(sprintFile)) {
6636
- const { fm } = parseFileFrontmatter(fs31.readFileSync(sprintFile, "utf8"));
6461
+ if (sprintFile && fs30.existsSync(sprintFile)) {
6462
+ const { fm } = parseFileFrontmatter(fs30.readFileSync(sprintFile, "utf8"));
6637
6463
  const epics = fm["epics"];
6638
6464
  if (Array.isArray(epics)) {
6639
6465
  epicIds = epics.map(String);
@@ -6648,7 +6474,7 @@ async function sprintArchiveHandler(opts, cli) {
6648
6474
  });
6649
6475
  }
6650
6476
  for (const epicId of epicIds) {
6651
- for (const entry of fs31.readdirSync(pendingDir)) {
6477
+ for (const entry of fs30.readdirSync(pendingDir)) {
6652
6478
  if ((entry.startsWith(`${epicId}_`) || entry === `${epicId}.md`) && entry.endsWith(".md")) {
6653
6479
  plan.push({
6654
6480
  src: path31.join(pendingDir, entry),
@@ -6660,7 +6486,7 @@ async function sprintArchiveHandler(opts, cli) {
6660
6486
  }
6661
6487
  const storyKeys = storyKeysForEpic(stateStories, epicId);
6662
6488
  for (const storyId of storyKeys) {
6663
- for (const entry of fs31.readdirSync(pendingDir)) {
6489
+ for (const entry of fs30.readdirSync(pendingDir)) {
6664
6490
  if ((entry.startsWith(`${storyId}_`) || entry === `${storyId}.md`) && entry.endsWith(".md")) {
6665
6491
  plan.push({
6666
6492
  src: path31.join(pendingDir, entry),
@@ -6675,13 +6501,13 @@ async function sprintArchiveHandler(opts, cli) {
6675
6501
  const storyIdsInState = new Set(Object.keys(stateStories));
6676
6502
  const planSrcs = new Set(plan.map((p) => p.src));
6677
6503
  const orphans = [];
6678
- for (const entry of fs31.readdirSync(pendingDir)) {
6504
+ for (const entry of fs30.readdirSync(pendingDir)) {
6679
6505
  if (!entry.startsWith("STORY-") || !entry.endsWith(".md")) continue;
6680
6506
  const candidate = path31.join(pendingDir, entry);
6681
6507
  if (planSrcs.has(candidate)) continue;
6682
6508
  let raw;
6683
6509
  try {
6684
- raw = fs31.readFileSync(candidate, "utf8");
6510
+ raw = fs30.readFileSync(candidate, "utf8");
6685
6511
  } catch {
6686
6512
  continue;
6687
6513
  }
@@ -6719,8 +6545,8 @@ async function sprintArchiveHandler(opts, cli) {
6719
6545
  }
6720
6546
  let sprintFileSnapshot = null;
6721
6547
  const wikiRoot = path31.join(cwd, ".cleargate", "wiki");
6722
- const wikiInitialised = fs31.existsSync(wikiRoot);
6723
- if (sprintFile && fs31.existsSync(sprintFile)) {
6548
+ const wikiInitialised = fs30.existsSync(wikiRoot);
6549
+ if (sprintFile && fs30.existsSync(sprintFile)) {
6724
6550
  const { previousContent } = stampSprintClose(sprintFile, () => completedAt);
6725
6551
  sprintFileSnapshot = previousContent;
6726
6552
  if (wikiInitialised) {
@@ -6747,15 +6573,15 @@ async function sprintArchiveHandler(opts, cli) {
6747
6573
  }
6748
6574
  }
6749
6575
  for (const entry of plan) {
6750
- if (!fs31.existsSync(entry.src)) {
6576
+ if (!fs30.existsSync(entry.src)) {
6751
6577
  stderrFn(`[cleargate sprint archive] source not found: ${entry.src} \u2014 skipping`);
6752
6578
  continue;
6753
6579
  }
6754
- const raw = fs31.readFileSync(entry.src, "utf8");
6580
+ const raw = fs30.readFileSync(entry.src, "utf8");
6755
6581
  const stamped = stampFile(raw, entry.status, completedAt);
6756
6582
  const dest = path31.join(archiveDir, entry.destName);
6757
6583
  atomicWriteStr(entry.src, stamped);
6758
- fs31.renameSync(entry.src, dest);
6584
+ fs30.renameSync(entry.src, dest);
6759
6585
  stdoutFn(`archived: ${entry.destName}`);
6760
6586
  }
6761
6587
  try {
@@ -6819,9 +6645,9 @@ function checkPrevSprintCompleted(sprintId, cwd) {
6819
6645
  let resolvedPrevId = prevId;
6820
6646
  for (const pid of [prevId, prevIdAlt]) {
6821
6647
  const stateFile = path31.join(sprintRunsBase, pid, "state.json");
6822
- if (fs31.existsSync(stateFile)) {
6648
+ if (fs30.existsSync(stateFile)) {
6823
6649
  try {
6824
- const raw = fs31.readFileSync(stateFile, "utf8");
6650
+ const raw = fs30.readFileSync(stateFile, "utf8");
6825
6651
  stateJson = JSON.parse(raw);
6826
6652
  resolvedPrevId = pid;
6827
6653
  break;
@@ -6928,10 +6754,10 @@ function findSprintFile(sprintId, cwd) {
6928
6754
  path31.join(cwd, ".cleargate", "delivery", "archive")
6929
6755
  ];
6930
6756
  for (const dir of searchDirs) {
6931
- if (!fs31.existsSync(dir)) continue;
6757
+ if (!fs30.existsSync(dir)) continue;
6932
6758
  let entries;
6933
6759
  try {
6934
- entries = fs31.readdirSync(dir);
6760
+ entries = fs30.readdirSync(dir);
6935
6761
  } catch {
6936
6762
  continue;
6937
6763
  }
@@ -6953,7 +6779,7 @@ function findWorkItemFileLocal(cwd, workItemId) {
6953
6779
  for (const dir of searchDirs) {
6954
6780
  let entries;
6955
6781
  try {
6956
- entries = fs31.readdirSync(dir);
6782
+ entries = fs30.readdirSync(dir);
6957
6783
  } catch {
6958
6784
  continue;
6959
6785
  }
@@ -6965,7 +6791,7 @@ function findWorkItemFileLocal(cwd, workItemId) {
6965
6791
  function readCachedGateSync(absPath) {
6966
6792
  let raw;
6967
6793
  try {
6968
- raw = fs31.readFileSync(absPath, "utf8");
6794
+ raw = fs30.readFileSync(absPath, "utf8");
6969
6795
  } catch {
6970
6796
  return null;
6971
6797
  }
@@ -7041,7 +6867,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
7041
6867
  let fm;
7042
6868
  let raw;
7043
6869
  try {
7044
- raw = fs31.readFileSync(absPath, "utf8");
6870
+ raw = fs30.readFileSync(absPath, "utf8");
7045
6871
  ({ fm } = parseFrontmatter(raw));
7046
6872
  } catch {
7047
6873
  totalChecked++;
@@ -7085,7 +6911,7 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
7085
6911
  let sprintRaw;
7086
6912
  let sprintFm = {};
7087
6913
  try {
7088
- sprintRaw = fs31.readFileSync(sprintFilePath, "utf8");
6914
+ sprintRaw = fs30.readFileSync(sprintFilePath, "utf8");
7089
6915
  ({ fm: sprintFm } = parseFrontmatter(sprintRaw));
7090
6916
  } catch {
7091
6917
  }
@@ -7126,6 +6952,41 @@ function checkPerItemReadinessGates(sprintId, cwd, execFn, mode) {
7126
6952
  Run: cleargate gate check <file> -v for each`
7127
6953
  };
7128
6954
  }
6955
+ function refreshScopedGateCaches(sprintId, cwd, execFn) {
6956
+ const result = { refreshed: [], skipped: [], errors: [] };
6957
+ const sprintFilePath = findSprintFile(sprintId, cwd);
6958
+ if (!sprintFilePath) {
6959
+ return result;
6960
+ }
6961
+ const childIds = extractInScopeWorkItemIds(sprintFilePath, cwd, execFn);
6962
+ if (!childIds || childIds.length === 0) {
6963
+ return result;
6964
+ }
6965
+ for (const id of childIds) {
6966
+ const absPath = findWorkItemFileLocal(cwd, id);
6967
+ if (!absPath) {
6968
+ continue;
6969
+ }
6970
+ let status = "";
6971
+ try {
6972
+ const raw = fs30.readFileSync(absPath, "utf8");
6973
+ const { fm } = parseFrontmatter(raw);
6974
+ status = String(fm["status"] ?? "");
6975
+ } catch {
6976
+ }
6977
+ if (TERMINAL_STATUSES2.has(status)) {
6978
+ result.skipped.push(id);
6979
+ continue;
6980
+ }
6981
+ try {
6982
+ execFn(`cleargate gate check "${absPath}"`, { cwd, encoding: "utf8" });
6983
+ result.refreshed.push(id);
6984
+ } catch (err) {
6985
+ result.errors.push({ id, message: String(err) });
6986
+ }
6987
+ }
6988
+ return result;
6989
+ }
7129
6990
  function emitPunchList(sprintId, results, stdoutFn, stderrFn) {
7130
6991
  const failures = results.filter((r) => !r.pass && !r.skipped);
7131
6992
  if (failures.length === 0) {
@@ -7157,6 +7018,13 @@ function sprintPreflightHandler(opts, cli) {
7157
7018
  }
7158
7019
  const execFn = cli?.execFn ?? ((cmd, execOpts) => execSync2(cmd, { ...execOpts, stdio: "pipe" }));
7159
7020
  const mode = readSprintExecutionMode(opts.sprintId, { cwd });
7021
+ const refresh = refreshScopedGateCaches(opts.sprintId, cwd, execFn);
7022
+ stdoutFn(`Step 0: refreshed ${refresh.refreshed.length} items, ${refresh.errors.length} errors.
7023
+ `);
7024
+ for (const e of refresh.errors) {
7025
+ stdoutFn(` - ${e.id}: ${e.message}
7026
+ `);
7027
+ }
7160
7028
  const results = [
7161
7029
  checkPrevSprintCompleted(opts.sprintId, cwd),
7162
7030
  checkNoLeftoverWorktrees(cwd, execFn),
@@ -7173,9 +7041,9 @@ function sprintPreflightHandler(opts, cli) {
7173
7041
  }
7174
7042
 
7175
7043
  // src/commands/story.ts
7176
- import * as fs32 from "fs";
7044
+ import * as fs31 from "fs";
7177
7045
  import * as path32 from "path";
7178
- import { spawnSync as spawnSync12 } from "child_process";
7046
+ import { spawnSync as spawnSync11 } from "child_process";
7179
7047
  function defaultExit2(code) {
7180
7048
  return process.exit(code);
7181
7049
  }
@@ -7191,8 +7059,8 @@ function deriveSprintBranch(sprintId) {
7191
7059
  }
7192
7060
  function atomicWriteString(filePath, text) {
7193
7061
  const tmpFile = `${filePath}.tmp.${process.pid}`;
7194
- fs32.writeFileSync(tmpFile, text, "utf8");
7195
- fs32.renameSync(tmpFile, filePath);
7062
+ fs31.writeFileSync(tmpFile, text, "utf8");
7063
+ fs31.renameSync(tmpFile, filePath);
7196
7064
  }
7197
7065
  function stateJsonPath(cwd, sprintId) {
7198
7066
  return path32.join(cwd, ".cleargate", "sprint-runs", sprintId, "state.json");
@@ -7201,7 +7069,7 @@ function storyStartHandler(opts, cli) {
7201
7069
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
7202
7070
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
7203
7071
  const exitFn = cli?.exit ?? defaultExit2;
7204
- const spawnFn = cli?.spawnFn ?? spawnSync12;
7072
+ const spawnFn = cli?.spawnFn ?? spawnSync11;
7205
7073
  const cwd = cli?.cwd ?? process.cwd();
7206
7074
  const sprintId = cli?.sprintId ?? resolveSprintIdFromSentinel(cwd) ?? "SPRINT-UNKNOWN";
7207
7075
  const mode = readSprintExecutionMode(sprintId, {
@@ -7229,9 +7097,10 @@ function storyStartHandler(opts, cli) {
7229
7097
  return exitFn(step1.status ?? 1);
7230
7098
  }
7231
7099
  const runScript = resolveRunScript2(cli ?? { cwd });
7100
+ const updateStateScript = resolveCleargateScript({ cwd }, "update_state.mjs");
7232
7101
  const step2 = spawnFn(
7233
7102
  "bash",
7234
- [runScript, "update_state.mjs", opts.storyId, "Bouncing"],
7103
+ [runScript, "node", updateStateScript, opts.storyId, "Bouncing"],
7235
7104
  { stdio: "pipe", cwd, encoding: "utf8" }
7236
7105
  );
7237
7106
  if (step2.error) {
@@ -7244,13 +7113,13 @@ function storyStartHandler(opts, cli) {
7244
7113
  return exitFn(step2.status ?? 1);
7245
7114
  }
7246
7115
  const stateFile = stateJsonPath(cwd, sprintId);
7247
- if (!fs32.existsSync(stateFile)) {
7116
+ if (!fs31.existsSync(stateFile)) {
7248
7117
  stderrFn(`[cleargate story start] step 3: state.json not found at ${stateFile}`);
7249
7118
  return exitFn(1);
7250
7119
  }
7251
7120
  let state2;
7252
7121
  try {
7253
- state2 = JSON.parse(fs32.readFileSync(stateFile, "utf8"));
7122
+ state2 = JSON.parse(fs31.readFileSync(stateFile, "utf8"));
7254
7123
  } catch (err) {
7255
7124
  stderrFn(`[cleargate story start] step 3: failed to parse state.json: ${err.message}`);
7256
7125
  return exitFn(1);
@@ -7279,7 +7148,7 @@ function storyCompleteHandler(opts, cli) {
7279
7148
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
7280
7149
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
7281
7150
  const exitFn = cli?.exit ?? defaultExit2;
7282
- const spawnFn = cli?.spawnFn ?? spawnSync12;
7151
+ const spawnFn = cli?.spawnFn ?? spawnSync11;
7283
7152
  const cwd = cli?.cwd ?? process.cwd();
7284
7153
  const sprintId = cli?.sprintId ?? resolveSprintIdFromSentinel(cwd) ?? "SPRINT-UNKNOWN";
7285
7154
  const mode = readSprintExecutionMode(sprintId, {
@@ -7369,9 +7238,10 @@ function storyCompleteHandler(opts, cli) {
7369
7238
  return exitFn(step5.status ?? 1);
7370
7239
  }
7371
7240
  const runScript = resolveRunScript2(cli ?? { cwd });
7241
+ const updateStateDoneScript = resolveCleargateScript({ cwd }, "update_state.mjs");
7372
7242
  const step6 = spawnFn(
7373
7243
  "bash",
7374
- [runScript, "update_state.mjs", opts.storyId, "Done"],
7244
+ [runScript, "node", updateStateDoneScript, opts.storyId, "Done"],
7375
7245
  { stdio: "pipe", cwd, encoding: "utf8" }
7376
7246
  );
7377
7247
  if (step6.error) {
@@ -7389,7 +7259,7 @@ function storyCompleteHandler(opts, cli) {
7389
7259
 
7390
7260
  // src/commands/state.ts
7391
7261
  import * as path33 from "path";
7392
- import { spawnSync as spawnSync13 } from "child_process";
7262
+ import { spawnSync as spawnSync12 } from "child_process";
7393
7263
  function defaultExit3(code) {
7394
7264
  return process.exit(code);
7395
7265
  }
@@ -7402,7 +7272,7 @@ function stateUpdateHandler(opts, cli) {
7402
7272
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
7403
7273
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
7404
7274
  const exitFn = cli?.exit ?? defaultExit3;
7405
- const spawnFn = cli?.spawnFn ?? spawnSync13;
7275
+ const spawnFn = cli?.spawnFn ?? spawnSync12;
7406
7276
  const cwd = cli?.cwd;
7407
7277
  const sprintId = cli?.sprintId ?? resolveSprintIdFromSentinel(cwd) ?? "SPRINT-UNKNOWN";
7408
7278
  const mode = readSprintExecutionMode(sprintId, {
@@ -7413,9 +7283,11 @@ function stateUpdateHandler(opts, cli) {
7413
7283
  return printInertAndExit(stdoutFn, exitFn);
7414
7284
  }
7415
7285
  const runScript = resolveRunScript3(cli ?? {});
7286
+ const updateCwd = cli?.cwd ?? process.cwd();
7287
+ const updateScriptPath = resolveCleargateScript({ cwd: updateCwd }, "update_state.mjs");
7416
7288
  const result = spawnFn(
7417
7289
  "bash",
7418
- [runScript, "update_state.mjs", opts.storyId, opts.newState],
7290
+ [runScript, "node", updateScriptPath, opts.storyId, opts.newState],
7419
7291
  { stdio: "inherit" }
7420
7292
  );
7421
7293
  if (result.error) {
@@ -7429,7 +7301,7 @@ function stateValidateHandler(opts, cli) {
7429
7301
  const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
7430
7302
  const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
7431
7303
  const exitFn = cli?.exit ?? defaultExit3;
7432
- const spawnFn = cli?.spawnFn ?? spawnSync13;
7304
+ const spawnFn = cli?.spawnFn ?? spawnSync12;
7433
7305
  const mode = readSprintExecutionMode(opts.sprintId, {
7434
7306
  sprintFilePath: cli?.sprintFilePath,
7435
7307
  cwd: cli?.cwd
@@ -7438,9 +7310,11 @@ function stateValidateHandler(opts, cli) {
7438
7310
  return printInertAndExit(stdoutFn, exitFn);
7439
7311
  }
7440
7312
  const runScript = resolveRunScript3(cli ?? {});
7313
+ const validateCwd = cli?.cwd ?? process.cwd();
7314
+ const validateScriptPath = resolveCleargateScript({ cwd: validateCwd }, "validate_state.mjs");
7441
7315
  const result = spawnFn(
7442
7316
  "bash",
7443
- [runScript, "validate_state.mjs", opts.sprintId],
7317
+ [runScript, "node", validateScriptPath, opts.sprintId],
7444
7318
  { stdio: "inherit" }
7445
7319
  );
7446
7320
  if (result.error) {
@@ -7452,17 +7326,17 @@ function stateValidateHandler(opts, cli) {
7452
7326
  }
7453
7327
 
7454
7328
  // src/commands/stamp-tokens.ts
7455
- import * as fs34 from "fs";
7329
+ import * as fs33 from "fs";
7456
7330
  import * as path35 from "path";
7457
7331
 
7458
7332
  // src/lib/ledger-reader.ts
7459
- import * as fs33 from "fs";
7333
+ import * as fs32 from "fs";
7460
7334
  import * as path34 from "path";
7461
7335
  function findSprintRunsRoot(startDir) {
7462
7336
  let dir = startDir;
7463
7337
  while (true) {
7464
7338
  const candidate = path34.join(dir, ".cleargate", "sprint-runs");
7465
- if (fs33.existsSync(candidate)) {
7339
+ if (fs32.existsSync(candidate)) {
7466
7340
  return candidate;
7467
7341
  }
7468
7342
  const parent = path34.dirname(dir);
@@ -7518,13 +7392,13 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
7518
7392
  }
7519
7393
  sprintRunsRoot = found;
7520
7394
  }
7521
- if (!fs33.existsSync(sprintRunsRoot)) {
7395
+ if (!fs32.existsSync(sprintRunsRoot)) {
7522
7396
  return [];
7523
7397
  }
7524
7398
  let ledgerFiles;
7525
7399
  try {
7526
- const entries = fs33.readdirSync(sprintRunsRoot, { withFileTypes: true });
7527
- ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) => path34.join(sprintRunsRoot, e.name, "token-ledger.jsonl")).filter((f) => fs33.existsSync(f));
7400
+ const entries = fs32.readdirSync(sprintRunsRoot, { withFileTypes: true });
7401
+ ledgerFiles = entries.filter((e) => e.isDirectory()).map((e) => path34.join(sprintRunsRoot, e.name, "token-ledger.jsonl")).filter((f) => fs32.existsSync(f));
7528
7402
  } catch {
7529
7403
  return [];
7530
7404
  }
@@ -7532,7 +7406,7 @@ function readLedgerForWorkItem(workItemId, opts = {}) {
7532
7406
  for (const ledgerFile of ledgerFiles) {
7533
7407
  let content;
7534
7408
  try {
7535
- content = fs33.readFileSync(ledgerFile, "utf-8");
7409
+ content = fs32.readFileSync(ledgerFile, "utf-8");
7536
7410
  } catch {
7537
7411
  continue;
7538
7412
  }
@@ -7591,7 +7465,7 @@ async function stampTokensHandler(file, opts, cli) {
7591
7465
  }
7592
7466
  let rawContent;
7593
7467
  try {
7594
- rawContent = fs34.readFileSync(absPath, "utf-8");
7468
+ rawContent = fs33.readFileSync(absPath, "utf-8");
7595
7469
  } catch {
7596
7470
  stdoutFn(`[stamp-tokens] error: cannot read file: ${absPath}`);
7597
7471
  exitFn(1);
@@ -7663,7 +7537,7 @@ async function stampTokensHandler(file, opts, cli) {
7663
7537
  return;
7664
7538
  }
7665
7539
  try {
7666
- fs34.writeFileSync(absPath, serialized, "utf-8");
7540
+ fs33.writeFileSync(absPath, serialized, "utf-8");
7667
7541
  } catch {
7668
7542
  stdoutFn(`[stamp-tokens] error: cannot write file: ${absPath}`);
7669
7543
  exitFn(1);
@@ -7673,7 +7547,7 @@ async function stampTokensHandler(file, opts, cli) {
7673
7547
  exitFn(0);
7674
7548
  }
7675
7549
  function extractWorkItemId(fm, absPath) {
7676
- const idKeys = ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id"];
7550
+ const idKeys = ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id", "initiative_id", "sprint_id"];
7677
7551
  for (const key of idKeys) {
7678
7552
  const val = fm[key];
7679
7553
  if (typeof val === "string" && val.trim() !== "") {
@@ -7681,13 +7555,13 @@ function extractWorkItemId(fm, absPath) {
7681
7555
  }
7682
7556
  }
7683
7557
  const basename13 = path35.basename(absPath);
7684
- const match = basename13.match(/^(STORY|EPIC|PROPOSAL|CR|BUG)-\d+(-\d+)?/i);
7558
+ const match = basename13.match(/^(STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(-\d+)?/i);
7685
7559
  if (match) {
7686
7560
  return match[0].toUpperCase();
7687
7561
  }
7688
7562
  const typeFromPath = detectWorkItemType(absPath);
7689
7563
  if (typeFromPath) {
7690
- const idMatch = basename13.match(/((?:STORY|EPIC|PROPOSAL|CR|BUG)-\d+(?:-\d+)?)/i);
7564
+ const idMatch = basename13.match(/((?:STORY|EPIC|PROPOSAL|CR|BUG|INITIATIVE|SPRINT)-\d+(?:-\d+)?)/i);
7691
7565
  if (idMatch) {
7692
7566
  return idMatch[1].toUpperCase();
7693
7567
  }
@@ -7784,7 +7658,7 @@ ${body}`;
7784
7658
  }
7785
7659
 
7786
7660
  // src/commands/upgrade.ts
7787
- import * as fs35 from "fs";
7661
+ import * as fs34 from "fs";
7788
7662
  import * as fsp from "fs/promises";
7789
7663
  import * as path36 from "path";
7790
7664
 
@@ -8149,7 +8023,7 @@ async function upgradeHandler(flags, cli) {
8149
8023
  const pkgRoot = cli?.packageRoot ?? path36.join(path36.dirname(new URL(import.meta.url).pathname), "..", "..");
8150
8024
  const changelogPath = path36.join(pkgRoot, "CHANGELOG.md");
8151
8025
  try {
8152
- const changelogContent = fs35.readFileSync(changelogPath, "utf-8");
8026
+ const changelogContent = fs34.readFileSync(changelogPath, "utf-8");
8153
8027
  const sections = sliceChangelog(changelogContent, installedVersion, targetVersion);
8154
8028
  if (sections.length > 0) {
8155
8029
  const deltaText = sections.map((s) => s.body).join("\n\n");
@@ -8239,7 +8113,7 @@ async function upgradeHandler(flags, cli) {
8239
8113
  }
8240
8114
 
8241
8115
  // src/commands/uninstall.ts
8242
- import * as fs36 from "fs";
8116
+ import * as fs35 from "fs";
8243
8117
  import * as fsp2 from "fs/promises";
8244
8118
  import * as path37 from "path";
8245
8119
  import { execSync as execSync3 } from "child_process";
@@ -8268,9 +8142,9 @@ function shouldPreserve(entry, preserveSet, removeSet) {
8268
8142
  }
8269
8143
  function resolveProjectName(target) {
8270
8144
  const pkgPath = path37.join(target, "package.json");
8271
- if (fs36.existsSync(pkgPath)) {
8145
+ if (fs35.existsSync(pkgPath)) {
8272
8146
  try {
8273
- const raw = fs36.readFileSync(pkgPath, "utf-8");
8147
+ const raw = fs35.readFileSync(pkgPath, "utf-8");
8274
8148
  const parsed = JSON.parse(raw);
8275
8149
  if (parsed.name && typeof parsed.name === "string") {
8276
8150
  return parsed.name;
@@ -8308,7 +8182,7 @@ function detectUncommittedChanges(target, manifestPaths, gitRunner) {
8308
8182
  }
8309
8183
  async function removeFromPackageJson(target, dryRun) {
8310
8184
  const pkgPath = path37.join(target, "package.json");
8311
- if (!fs36.existsSync(pkgPath)) return false;
8185
+ if (!fs35.existsSync(pkgPath)) return false;
8312
8186
  let raw;
8313
8187
  try {
8314
8188
  raw = await fsp2.readFile(pkgPath, "utf-8");
@@ -8349,7 +8223,7 @@ async function removeFile(filePath) {
8349
8223
  }
8350
8224
  async function removeDir(dirPath) {
8351
8225
  try {
8352
- fs36.rmSync(dirPath, { recursive: true, force: true });
8226
+ fs35.rmSync(dirPath, { recursive: true, force: true });
8353
8227
  } catch {
8354
8228
  }
8355
8229
  }
@@ -8373,8 +8247,8 @@ async function uninstallHandler(opts) {
8373
8247
  const cleargateDir = path37.join(target, ".cleargate");
8374
8248
  const manifestPath = path37.join(cleargateDir, ".install-manifest.json");
8375
8249
  const uninstalledPath = path37.join(cleargateDir, ".uninstalled");
8376
- if (!fs36.existsSync(manifestPath)) {
8377
- if (fs36.existsSync(uninstalledPath)) {
8250
+ if (!fs35.existsSync(manifestPath)) {
8251
+ if (fs35.existsSync(uninstalledPath)) {
8378
8252
  stdout("already uninstalled");
8379
8253
  exit(0);
8380
8254
  return;
@@ -8383,7 +8257,7 @@ async function uninstallHandler(opts) {
8383
8257
  exit(0);
8384
8258
  return;
8385
8259
  }
8386
- if (fs36.existsSync(uninstalledPath) && !fs36.existsSync(manifestPath)) {
8260
+ if (fs35.existsSync(uninstalledPath) && !fs35.existsSync(manifestPath)) {
8387
8261
  stdout("already uninstalled");
8388
8262
  exit(0);
8389
8263
  return;
@@ -8407,8 +8281,8 @@ async function uninstallHandler(opts) {
8407
8281
  }
8408
8282
  const claudeMdPath = path37.join(target, "CLAUDE.md");
8409
8283
  let claudeMdContent = null;
8410
- if (fs36.existsSync(claudeMdPath)) {
8411
- claudeMdContent = fs36.readFileSync(claudeMdPath, "utf-8");
8284
+ if (fs35.existsSync(claudeMdPath)) {
8285
+ claudeMdContent = fs35.readFileSync(claudeMdPath, "utf-8");
8412
8286
  if (!claudeMdContent.includes(CLEARGATE_START)) {
8413
8287
  stderr("CLAUDE.md is missing <!-- CLEARGATE:START --> marker");
8414
8288
  exit(1);
@@ -8425,7 +8299,7 @@ async function uninstallHandler(opts) {
8425
8299
  const toSkip = [];
8426
8300
  for (const entry of snapshot.files) {
8427
8301
  const filePath = path37.join(target, entry.path);
8428
- if (!fs36.existsSync(filePath)) {
8302
+ if (!fs35.existsSync(filePath)) {
8429
8303
  toSkip.push(entry);
8430
8304
  continue;
8431
8305
  }
@@ -8499,9 +8373,9 @@ async function uninstallHandler(opts) {
8499
8373
  }
8500
8374
  }
8501
8375
  const settingsPath = path37.join(target, ".claude", "settings.json");
8502
- if (fs36.existsSync(settingsPath)) {
8376
+ if (fs35.existsSync(settingsPath)) {
8503
8377
  try {
8504
- const raw = fs36.readFileSync(settingsPath, "utf-8");
8378
+ const raw = fs35.readFileSync(settingsPath, "utf-8");
8505
8379
  const settings = JSON.parse(raw);
8506
8380
  const cleaned = removeClearGateHooks(settings);
8507
8381
  await writeAtomic3(settingsPath, JSON.stringify(cleaned, null, 2) + "\n");
@@ -8543,26 +8417,26 @@ import * as fsPromises8 from "fs/promises";
8543
8417
  import * as path45 from "path";
8544
8418
 
8545
8419
  // src/lib/sync-log.ts
8546
- import * as fs37 from "fs";
8420
+ import * as fs36 from "fs";
8547
8421
  import * as fsPromises2 from "fs/promises";
8548
8422
  import * as path38 from "path";
8549
8423
  function resolveActiveSprintDir(projectRoot, _opts) {
8550
8424
  const sprintRunsRoot = path38.join(projectRoot, ".cleargate", "sprint-runs");
8551
8425
  const offSprint = path38.join(sprintRunsRoot, "_off-sprint");
8552
- if (!fs37.existsSync(sprintRunsRoot)) {
8553
- fs37.mkdirSync(sprintRunsRoot, { recursive: true });
8554
- fs37.mkdirSync(offSprint, { recursive: true });
8426
+ if (!fs36.existsSync(sprintRunsRoot)) {
8427
+ fs36.mkdirSync(sprintRunsRoot, { recursive: true });
8428
+ fs36.mkdirSync(offSprint, { recursive: true });
8555
8429
  return offSprint;
8556
8430
  }
8557
- const entries = fs37.readdirSync(sprintRunsRoot, { withFileTypes: true });
8431
+ const entries = fs36.readdirSync(sprintRunsRoot, { withFileTypes: true });
8558
8432
  const sprintDirs = entries.filter((e) => e.isDirectory() && e.name !== "_off-sprint").map((e) => {
8559
8433
  const fullPath = path38.join(sprintRunsRoot, e.name);
8560
- const stat = fs37.statSync(fullPath);
8434
+ const stat = fs36.statSync(fullPath);
8561
8435
  return { name: e.name, fullPath, mtimeMs: stat.mtimeMs };
8562
8436
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
8563
8437
  if (sprintDirs.length === 0) {
8564
- if (!fs37.existsSync(offSprint)) {
8565
- fs37.mkdirSync(offSprint, { recursive: true });
8438
+ if (!fs36.existsSync(offSprint)) {
8439
+ fs36.mkdirSync(offSprint, { recursive: true });
8566
8440
  }
8567
8441
  return offSprint;
8568
8442
  }
@@ -8685,7 +8559,7 @@ function classify2(local, remote, since) {
8685
8559
  }
8686
8560
 
8687
8561
  // src/lib/merge-helper.ts
8688
- import { promises as fs38 } from "fs";
8562
+ import { promises as fs37 } from "fs";
8689
8563
  import * as os5 from "os";
8690
8564
  import * as path39 from "path";
8691
8565
  function promptFourChoice(opts) {
@@ -8765,16 +8639,16 @@ ${remote}
8765
8639
  >>>>>>> remote
8766
8640
  `;
8767
8641
  try {
8768
- await fs38.writeFile(tmpFile, markerContent, "utf-8");
8642
+ await fs37.writeFile(tmpFile, markerContent, "utf-8");
8769
8643
  await openInEditor(tmpFile, { editor: editor ?? process.env["EDITOR"] ?? "vi" });
8770
- const edited = await fs38.readFile(tmpFile, "utf-8");
8644
+ const edited = await fs37.readFile(tmpFile, "utf-8");
8771
8645
  if (containsConflictMarkers(edited)) {
8772
8646
  stdout("File still contains conflict markers \u2014 please resolve all conflicts.\n");
8773
8647
  continue;
8774
8648
  }
8775
8649
  return { resolution: "edited", body: edited };
8776
8650
  } finally {
8777
- await fs38.unlink(tmpFile).catch(() => {
8651
+ await fs37.unlink(tmpFile).catch(() => {
8778
8652
  });
8779
8653
  }
8780
8654
  }
@@ -9117,7 +8991,7 @@ async function hasAnyRemoteAuthored(projectRoot) {
9117
8991
  }
9118
8992
 
9119
8993
  // src/lib/active-criteria.ts
9120
- import * as fs39 from "fs";
8994
+ import * as fs38 from "fs";
9121
8995
  import * as fsPromises5 from "fs/promises";
9122
8996
  import * as path42 from "path";
9123
8997
  async function resolveActiveItems(projectRoot, localItems, nowFn = () => (/* @__PURE__ */ new Date()).toISOString()) {
@@ -9163,7 +9037,7 @@ async function findSprintFile2(projectRoot, sprintId) {
9163
9037
  const archive = path42.join(projectRoot, ".cleargate", "delivery", "archive");
9164
9038
  for (const dir of [pendingSync, archive]) {
9165
9039
  try {
9166
- const entries = fs39.readdirSync(dir, { withFileTypes: true });
9040
+ const entries = fs38.readdirSync(dir, { withFileTypes: true });
9167
9041
  for (const entry of entries) {
9168
9042
  if (entry.isFile() && entry.name.startsWith(sprintId) && entry.name.endsWith(".md")) {
9169
9043
  return path42.join(dir, entry.name);
@@ -9917,7 +9791,7 @@ async function syncWorkItems(opts) {
9917
9791
  }
9918
9792
 
9919
9793
  // src/lib/admin-url.ts
9920
- import * as fs40 from "fs";
9794
+ import * as fs39 from "fs";
9921
9795
  import * as os6 from "os";
9922
9796
  import * as path47 from "path";
9923
9797
  var DEFAULT_BASE = "https://admin.cleargate.soula.ge/";
@@ -9943,7 +9817,7 @@ function readLocalConfig() {
9943
9817
  const home = os6.homedir();
9944
9818
  if (!home) return null;
9945
9819
  const configPath = path47.join(home, ".cleargate", "config.json");
9946
- const raw = fs40.readFileSync(configPath, "utf8");
9820
+ const raw = fs39.readFileSync(configPath, "utf8");
9947
9821
  return JSON.parse(raw);
9948
9822
  }
9949
9823
 
@@ -10631,7 +10505,7 @@ function formatEntry(entry) {
10631
10505
  }
10632
10506
 
10633
10507
  // src/commands/admin-login.ts
10634
- import * as fs41 from "fs";
10508
+ import * as fs40 from "fs";
10635
10509
  import * as path51 from "path";
10636
10510
  import * as os7 from "os";
10637
10511
  var DEFAULT_MCP_URL = "http://localhost:3000";
@@ -10645,10 +10519,10 @@ function resolveAuthFilePath(opts) {
10645
10519
  }
10646
10520
  function writeAdminAuth(filePath, token) {
10647
10521
  const dir = path51.dirname(filePath);
10648
- fs41.mkdirSync(dir, { recursive: true });
10522
+ fs40.mkdirSync(dir, { recursive: true });
10649
10523
  const payload = JSON.stringify({ version: 1, token }, null, 2);
10650
- fs41.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
10651
- fs41.chmodSync(filePath, 384);
10524
+ fs40.writeFileSync(filePath, payload, { encoding: "utf8", mode: 384 });
10525
+ fs40.chmodSync(filePath, 384);
10652
10526
  }
10653
10527
  async function adminLoginHandler(opts = {}) {
10654
10528
  const fetchFn = opts.fetch ?? globalThis.fetch;
@@ -10757,7 +10631,7 @@ async function adminLoginHandler(opts = {}) {
10757
10631
  }
10758
10632
 
10759
10633
  // src/commands/hotfix.ts
10760
- import * as fs42 from "fs";
10634
+ import * as fs41 from "fs";
10761
10635
  import * as path52 from "path";
10762
10636
  function defaultExit4(code) {
10763
10637
  return process.exit(code);
@@ -10768,7 +10642,7 @@ function maxHotfixId(pendingDir) {
10768
10642
  let max = 0;
10769
10643
  let entries;
10770
10644
  try {
10771
- entries = fs42.readdirSync(pendingDir);
10645
+ entries = fs41.readdirSync(pendingDir);
10772
10646
  } catch {
10773
10647
  return 0;
10774
10648
  }
@@ -10788,7 +10662,7 @@ function countActiveHotfixes(repoRoot) {
10788
10662
  let count = 0;
10789
10663
  let pendingEntries = [];
10790
10664
  try {
10791
- pendingEntries = fs42.readdirSync(pendingDir);
10665
+ pendingEntries = fs41.readdirSync(pendingDir);
10792
10666
  } catch {
10793
10667
  }
10794
10668
  for (const entry of pendingEntries) {
@@ -10796,13 +10670,13 @@ function countActiveHotfixes(repoRoot) {
10796
10670
  }
10797
10671
  let archiveEntries = [];
10798
10672
  try {
10799
- archiveEntries = fs42.readdirSync(archiveDir);
10673
+ archiveEntries = fs41.readdirSync(archiveDir);
10800
10674
  } catch {
10801
10675
  }
10802
10676
  for (const entry of archiveEntries) {
10803
10677
  if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
10804
10678
  try {
10805
- const stat = fs42.statSync(path52.join(archiveDir, entry));
10679
+ const stat = fs41.statSync(path52.join(archiveDir, entry));
10806
10680
  if (stat.mtimeMs >= sevenDaysAgo) count++;
10807
10681
  } catch {
10808
10682
  }
@@ -10837,7 +10711,7 @@ function hotfixNewHandler(opts, cli) {
10837
10711
  const templatePath = resolveTemplatePath(repoRoot);
10838
10712
  let templateContent;
10839
10713
  try {
10840
- templateContent = fs42.readFileSync(templatePath, "utf8");
10714
+ templateContent = fs41.readFileSync(templatePath, "utf8");
10841
10715
  } catch {
10842
10716
  stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
10843
10717
  return exitFn(2);
@@ -10847,8 +10721,8 @@ function hotfixNewHandler(opts, cli) {
10847
10721
  const fileName = `${idStr}_${fileSlug}.md`;
10848
10722
  const outPath = path52.join(pendingDir, fileName);
10849
10723
  try {
10850
- fs42.mkdirSync(pendingDir, { recursive: true });
10851
- fs42.writeFileSync(outPath, content, "utf8");
10724
+ fs41.mkdirSync(pendingDir, { recursive: true });
10725
+ fs41.writeFileSync(outPath, content, "utf8");
10852
10726
  } catch (err) {
10853
10727
  const msg = err instanceof Error ? err.message : String(err);
10854
10728
  stderrFn(`[cleargate hotfix new] write failed: ${msg}`);