cleargate 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/MANIFEST.json +13 -13
  2. package/dist/{chunk-HZPJ5QX4.js → chunk-EG6YGT2O.js} +315 -33
  3. package/dist/chunk-EG6YGT2O.js.map +1 -0
  4. package/dist/cli.cjs +612 -289
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.js +73 -37
  7. package/dist/cli.js.map +1 -1
  8. package/dist/lib/lifecycle-reconcile.cjs +318 -34
  9. package/dist/lib/lifecycle-reconcile.cjs.map +1 -1
  10. package/dist/lib/lifecycle-reconcile.d.cts +55 -4
  11. package/dist/lib/lifecycle-reconcile.d.ts +55 -4
  12. package/dist/lib/lifecycle-reconcile.js +7 -3
  13. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
  14. package/dist/templates/cleargate-planning/.claude/agents/developer.md +8 -4
  15. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
  16. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +73 -0
  17. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
  18. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
  19. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
  20. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
  21. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  22. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
  23. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
  24. package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
  25. package/dist/templates/cleargate-planning/MANIFEST.json +13 -13
  26. package/package.json +8 -9
  27. package/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
  28. package/templates/cleargate-planning/.claude/agents/developer.md +8 -4
  29. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
  30. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +73 -0
  31. package/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
  32. package/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
  33. package/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
  34. package/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
  35. package/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
  36. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
  37. package/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
  38. package/templates/cleargate-planning/CLAUDE.md +2 -0
  39. package/templates/cleargate-planning/MANIFEST.json +13 -13
  40. package/dist/chunk-HZPJ5QX4.js.map +0 -1
@@ -37,11 +37,13 @@ __export(lifecycle_reconcile_exports, {
37
37
  parseCommitMessage: () => parseCommitMessage,
38
38
  reconcileCrossSprintOrphans: () => reconcileCrossSprintOrphans,
39
39
  reconcileDecomposition: () => reconcileDecomposition,
40
- reconcileLifecycle: () => reconcileLifecycle
40
+ reconcileLifecycle: () => reconcileLifecycle,
41
+ rollUpParentStatus: () => rollUpParentStatus,
42
+ walkActiveParents: () => walkActiveParents
41
43
  });
42
44
  module.exports = __toCommonJS(lifecycle_reconcile_exports);
43
- var fs = __toESM(require("fs"), 1);
44
- var path = __toESM(require("path"), 1);
45
+ var fs2 = __toESM(require("fs"), 1);
46
+ var path2 = __toESM(require("path"), 1);
45
47
  var import_node_child_process = require("child_process");
46
48
 
47
49
  // src/wiki/parse-frontmatter.ts
@@ -83,25 +85,305 @@ function parseFrontmatter(raw) {
83
85
  return { fm: parsed, body };
84
86
  }
85
87
 
88
+ // src/lib/parent-rollup.ts
89
+ var fs = __toESM(require("fs"), 1);
90
+ var path = __toESM(require("path"), 1);
91
+ function readFm(filePath) {
92
+ try {
93
+ const raw = fs.readFileSync(filePath, "utf8");
94
+ const { fm } = parseFrontmatter(raw);
95
+ return fm;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ function extractId(fm, filePath) {
101
+ for (const key of [
102
+ "story_id",
103
+ "epic_id",
104
+ "sprint_id",
105
+ "bug_id",
106
+ "cr_id",
107
+ "initiative_id",
108
+ "hotfix_id"
109
+ ]) {
110
+ const val = fm[key];
111
+ if (typeof val === "string" && val.trim() !== "") return val.trim();
112
+ }
113
+ const stem = path.basename(filePath, ".md");
114
+ return stem.split("_")[0] ?? stem;
115
+ }
116
+ function enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache) {
117
+ const pendingSyncDir = path.join(deliveryRoot, "pending-sync");
118
+ const results = [];
119
+ const pools = [];
120
+ if (fs.existsSync(archiveRoot)) pools.push(archiveRoot);
121
+ if (fs.existsSync(pendingSyncDir)) pools.push(pendingSyncDir);
122
+ for (const dir of pools) {
123
+ let entries;
124
+ try {
125
+ entries = fs.readdirSync(dir);
126
+ } catch {
127
+ entries = [];
128
+ }
129
+ for (const entry of entries) {
130
+ if (!entry.endsWith(".md")) continue;
131
+ const absPath = path.join(dir, entry);
132
+ let fm = fmCache.get(absPath);
133
+ if (fm === void 0) {
134
+ const parsed = readFm(absPath);
135
+ if (parsed === null) continue;
136
+ fm = parsed;
137
+ fmCache.set(absPath, fm);
138
+ }
139
+ const parentCleargateId = fm["parent_cleargate_id"];
140
+ const parentEpicRef = fm["parent_epic_ref"];
141
+ const isChild = typeof parentCleargateId === "string" && parentCleargateId.trim() === parentId || typeof parentEpicRef === "string" && parentEpicRef.trim() === parentId;
142
+ if (!isChild) continue;
143
+ const childId = extractId(fm, absPath);
144
+ const status = typeof fm["status"] === "string" ? fm["status"].trim() : "";
145
+ results.push({ id: childId, status });
146
+ }
147
+ }
148
+ return results;
149
+ }
150
+ async function rollUpParentStatusInternal(parentFilePath, opts, visited, fmCache) {
151
+ const { deliveryRoot, archiveRoot } = opts;
152
+ let fm = fmCache.get(parentFilePath);
153
+ if (fm === void 0) {
154
+ const raw = readFm(parentFilePath);
155
+ if (raw === null) {
156
+ throw new Error(`parent-rollup: cannot read frontmatter from ${parentFilePath}`);
157
+ }
158
+ fm = raw;
159
+ fmCache.set(parentFilePath, fm);
160
+ }
161
+ const parentId = extractId(fm, parentFilePath);
162
+ const currentStatus = typeof fm["status"] === "string" ? fm["status"].trim() : "";
163
+ if (ARTIFACT_TERMINAL_STATUSES.has(currentStatus)) {
164
+ return {
165
+ parent_id: parentId,
166
+ parent_path: parentFilePath,
167
+ current_status: currentStatus,
168
+ proposed_status: null,
169
+ coverage: "full",
170
+ terminal_children: [],
171
+ pending_children: [],
172
+ verdict: "no-op"
173
+ };
174
+ }
175
+ if (visited.has(parentId)) {
176
+ throw new Error(`parent-rollup: sub_epics cycle detected at ${parentId}`);
177
+ }
178
+ visited.add(parentId);
179
+ const subEpicsField = fm["sub_epics"];
180
+ const subEpics = Array.isArray(subEpicsField) && subEpicsField.length > 0 ? subEpicsField.filter((s) => typeof s === "string") : [];
181
+ if (subEpics.length > 0) {
182
+ const pendingSyncDir = path.join(deliveryRoot, "pending-sync");
183
+ const terminalSubEpics = [];
184
+ const pendingSubEpics = [];
185
+ for (const subEpicId of subEpics) {
186
+ let subEpicPath = null;
187
+ const candidateDirs = [pendingSyncDir, archiveRoot];
188
+ for (const dir of candidateDirs) {
189
+ if (!fs.existsSync(dir)) continue;
190
+ let entries;
191
+ try {
192
+ entries = fs.readdirSync(dir);
193
+ } catch {
194
+ entries = [];
195
+ }
196
+ for (const entry of entries) {
197
+ if (!entry.endsWith(".md")) continue;
198
+ const absPath = path.join(dir, entry);
199
+ let subFm2 = fmCache.get(absPath);
200
+ if (subFm2 === void 0) {
201
+ const parsed = readFm(absPath);
202
+ if (parsed === null) continue;
203
+ subFm2 = parsed;
204
+ fmCache.set(absPath, subFm2);
205
+ }
206
+ const entryId = extractId(subFm2, absPath);
207
+ if (entryId === subEpicId) {
208
+ subEpicPath = absPath;
209
+ break;
210
+ }
211
+ }
212
+ if (subEpicPath !== null) break;
213
+ }
214
+ if (subEpicPath === null) {
215
+ pendingSubEpics.push(subEpicId);
216
+ continue;
217
+ }
218
+ let subFm = fmCache.get(subEpicPath);
219
+ if (subFm === void 0) {
220
+ const parsed = readFm(subEpicPath);
221
+ if (parsed === null) {
222
+ pendingSubEpics.push(subEpicId);
223
+ continue;
224
+ }
225
+ subFm = parsed;
226
+ fmCache.set(subEpicPath, subFm);
227
+ }
228
+ const subStatus = typeof subFm["status"] === "string" ? subFm["status"].trim() : "";
229
+ if (subStatus === "DEFERRED") {
230
+ continue;
231
+ }
232
+ if (ARTIFACT_TERMINAL_STATUSES.has(subStatus)) {
233
+ terminalSubEpics.push(subEpicId);
234
+ continue;
235
+ }
236
+ const visitedSnapshot = new Set(visited);
237
+ const subResult = await rollUpParentStatusInternal(
238
+ subEpicPath,
239
+ opts,
240
+ visitedSnapshot,
241
+ fmCache
242
+ );
243
+ if (subResult.verdict === "auto-flip" || subResult.verdict === "no-op") {
244
+ terminalSubEpics.push(subEpicId);
245
+ } else {
246
+ pendingSubEpics.push(subEpicId);
247
+ }
248
+ }
249
+ visited.delete(parentId);
250
+ const total2 = terminalSubEpics.length + pendingSubEpics.length;
251
+ if (total2 === 0) {
252
+ return {
253
+ parent_id: parentId,
254
+ parent_path: parentFilePath,
255
+ current_status: currentStatus,
256
+ proposed_status: null,
257
+ coverage: "zero",
258
+ terminal_children: [],
259
+ pending_children: [],
260
+ verdict: "halt-zero-children",
261
+ halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
262
+ };
263
+ }
264
+ if (pendingSubEpics.length === 0) {
265
+ return {
266
+ parent_id: parentId,
267
+ parent_path: parentFilePath,
268
+ current_status: currentStatus,
269
+ proposed_status: "Completed",
270
+ coverage: "full",
271
+ terminal_children: terminalSubEpics,
272
+ pending_children: [],
273
+ verdict: "auto-flip"
274
+ };
275
+ }
276
+ return {
277
+ parent_id: parentId,
278
+ parent_path: parentFilePath,
279
+ current_status: currentStatus,
280
+ proposed_status: null,
281
+ coverage: "sub-epic-partial",
282
+ terminal_children: terminalSubEpics,
283
+ pending_children: pendingSubEpics,
284
+ verdict: "halt-partial",
285
+ halt_reason: `${parentId}: ${terminalSubEpics.length}/${total2} sub-epics terminal \u2014 pending: ${pendingSubEpics.join(", ")}`
286
+ };
287
+ }
288
+ const children = enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache);
289
+ visited.delete(parentId);
290
+ if (children.length === 0) {
291
+ return {
292
+ parent_id: parentId,
293
+ parent_path: parentFilePath,
294
+ current_status: currentStatus,
295
+ proposed_status: null,
296
+ coverage: "zero",
297
+ terminal_children: [],
298
+ pending_children: [],
299
+ verdict: "halt-zero-children",
300
+ halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
301
+ };
302
+ }
303
+ const terminalChildren = [];
304
+ const pendingChildren = [];
305
+ for (const child of children) {
306
+ if (ARTIFACT_TERMINAL_STATUSES.has(child.status)) {
307
+ terminalChildren.push(child.id);
308
+ } else {
309
+ pendingChildren.push(child.id);
310
+ }
311
+ }
312
+ const total = terminalChildren.length + pendingChildren.length;
313
+ if (pendingChildren.length === 0) {
314
+ return {
315
+ parent_id: parentId,
316
+ parent_path: parentFilePath,
317
+ current_status: currentStatus,
318
+ proposed_status: "Completed",
319
+ coverage: "full",
320
+ terminal_children: terminalChildren,
321
+ pending_children: [],
322
+ verdict: "auto-flip"
323
+ };
324
+ }
325
+ return {
326
+ parent_id: parentId,
327
+ parent_path: parentFilePath,
328
+ current_status: currentStatus,
329
+ proposed_status: null,
330
+ coverage: "partial",
331
+ terminal_children: terminalChildren,
332
+ pending_children: pendingChildren,
333
+ verdict: "halt-partial",
334
+ halt_reason: `${parentId}: ${terminalChildren.length}/${total} children terminal \u2014 pending: ${pendingChildren.join(", ")}`
335
+ };
336
+ }
337
+ async function rollUpParentStatus(parentFilePath, opts) {
338
+ const visited = /* @__PURE__ */ new Set();
339
+ const fmCache = /* @__PURE__ */ new Map();
340
+ return rollUpParentStatusInternal(parentFilePath, opts, visited, fmCache);
341
+ }
342
+ async function walkActiveParents(opts) {
343
+ const { deliveryRoot } = opts;
344
+ const pendingSyncDir = path.join(deliveryRoot, "pending-sync");
345
+ let entries;
346
+ try {
347
+ entries = fs.readdirSync(pendingSyncDir);
348
+ } catch {
349
+ return [];
350
+ }
351
+ const parentFiles = entries.filter(
352
+ (e) => e.endsWith(".md") && (e.startsWith("EPIC-") || e.startsWith("SPRINT-"))
353
+ );
354
+ const results = [];
355
+ const fmCache = /* @__PURE__ */ new Map();
356
+ for (const entry of parentFiles) {
357
+ const absPath = path.join(pendingSyncDir, entry);
358
+ try {
359
+ const visited = /* @__PURE__ */ new Set();
360
+ const result = await rollUpParentStatusInternal(absPath, opts, visited, fmCache);
361
+ results.push(result);
362
+ } catch (err) {
363
+ if (err instanceof Error && err.message.includes("sub_epics cycle detected")) {
364
+ throw err;
365
+ }
366
+ }
367
+ }
368
+ return results;
369
+ }
370
+
86
371
  // src/lib/lifecycle-reconcile.ts
87
372
  var ARTIFACT_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
88
- "Done",
89
373
  "Completed",
90
- "Verified",
91
374
  "Abandoned",
92
375
  "Closed",
93
- "Resolved",
94
- "Escalated",
95
- "Parking Lot"
376
+ "Resolved"
96
377
  ]);
378
+ var ARTIFACT_GATE_EXPECTED = ["Completed"];
97
379
  var VERB_STATUS_MAP = {
98
380
  feat: {
99
381
  types: ["STORY", "EPIC", "CR"],
100
- expected: ["Done", "Completed"]
382
+ expected: [...ARTIFACT_GATE_EXPECTED]
101
383
  },
102
384
  fix: {
103
385
  types: ["BUG", "HOTFIX"],
104
- expected: ["Verified", "Done", "Completed"]
386
+ expected: [...ARTIFACT_GATE_EXPECTED]
105
387
  }
106
388
  };
107
389
  var ID_PATTERN = /\b(STORY-\d{3}-\d{2}|(CR|BUG|EPIC|HOTFIX)-\d{3}|(PROPOSAL|PROP)-\d{3})\b/g;
@@ -152,10 +434,10 @@ function findArtifactFile(deliveryRoot, id) {
152
434
  { rel: "archive", inArchive: true }
153
435
  ];
154
436
  for (const { rel, inArchive } of dirs) {
155
- const dir = path.join(deliveryRoot, rel);
437
+ const dir = path2.join(deliveryRoot, rel);
156
438
  let entries;
157
439
  try {
158
- entries = fs.readdirSync(dir);
440
+ entries = fs2.readdirSync(dir);
159
441
  } catch {
160
442
  continue;
161
443
  }
@@ -163,7 +445,7 @@ function findArtifactFile(deliveryRoot, id) {
163
445
  (e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith(".md")
164
446
  );
165
447
  if (match) {
166
- const absPath = path.join(dir, match);
448
+ const absPath = path2.join(dir, match);
167
449
  return { absPath, inArchive, relPath: `${rel}/${match}` };
168
450
  }
169
451
  }
@@ -172,7 +454,7 @@ function findArtifactFile(deliveryRoot, id) {
172
454
  function readArtifactStatus(absPath) {
173
455
  let raw;
174
456
  try {
175
- raw = fs.readFileSync(absPath, "utf8");
457
+ raw = fs2.readFileSync(absPath, "utf8");
176
458
  } catch {
177
459
  return { status: null, carryOver: false };
178
460
  }
@@ -227,7 +509,7 @@ function reconcileLifecycle(opts) {
227
509
  if (carryOver) continue;
228
510
  let expectedStatuses;
229
511
  if (verb === "feat" && type === "BUG") {
230
- expectedStatuses = ["Verified", "Done", "Completed"];
512
+ expectedStatuses = [...ARTIFACT_GATE_EXPECTED];
231
513
  } else if (!verbConfig.types.includes(type)) {
232
514
  continue;
233
515
  } else {
@@ -239,7 +521,7 @@ function reconcileLifecycle(opts) {
239
521
  cleanIds.add(id);
240
522
  idToItem.delete(id);
241
523
  } else if (!idToItem.has(id)) {
242
- const expectedStr = expectedStatuses[0] ?? "Done";
524
+ const expectedStr = expectedStatuses[0] ?? "Completed";
243
525
  idToItem.set(id, {
244
526
  id,
245
527
  type,
@@ -270,13 +552,13 @@ function reconcileCrossSprintOrphans(opts) {
270
552
  const TERMINAL_STATE_JSON = /* @__PURE__ */ new Set(["Done", "Escalated", "Parking Lot"]);
271
553
  let activeSprintId = null;
272
554
  try {
273
- activeSprintId = fs.readFileSync(path.join(sprintRunsRoot, ".active"), "utf8").trim();
555
+ activeSprintId = fs2.readFileSync(path2.join(sprintRunsRoot, ".active"), "utf8").trim();
274
556
  } catch {
275
557
  }
276
- const pendingDir = path.join(deliveryRoot, "pending-sync");
558
+ const pendingDir = path2.join(deliveryRoot, "pending-sync");
277
559
  let pendingFiles;
278
560
  try {
279
- pendingFiles = fs.readdirSync(pendingDir).filter(
561
+ pendingFiles = fs2.readdirSync(pendingDir).filter(
280
562
  (f) => f.endsWith(".md") && !f.startsWith(".")
281
563
  );
282
564
  } catch {
@@ -284,7 +566,7 @@ function reconcileCrossSprintOrphans(opts) {
284
566
  }
285
567
  const pendingMap = /* @__PURE__ */ new Map();
286
568
  for (const fileName of pendingFiles) {
287
- const absPath = path.join(pendingDir, fileName);
569
+ const absPath = path2.join(pendingDir, fileName);
288
570
  const { status } = readArtifactStatus(absPath);
289
571
  if (status === null) continue;
290
572
  if (ARTIFACT_TERMINAL_STATUSES.has(status)) continue;
@@ -296,7 +578,7 @@ function reconcileCrossSprintOrphans(opts) {
296
578
  if (!type || type === "PROPOSAL") continue;
297
579
  pendingMap.set(id, {
298
580
  status,
299
- filePath: path.join("pending-sync", fileName),
581
+ filePath: path2.join("pending-sync", fileName),
300
582
  type
301
583
  });
302
584
  }
@@ -305,10 +587,10 @@ function reconcileCrossSprintOrphans(opts) {
305
587
  }
306
588
  let sprintDirs;
307
589
  try {
308
- sprintDirs = fs.readdirSync(sprintRunsRoot).filter((entry) => {
590
+ sprintDirs = fs2.readdirSync(sprintRunsRoot).filter((entry) => {
309
591
  if (entry.startsWith(".")) return false;
310
592
  try {
311
- return fs.statSync(path.join(sprintRunsRoot, entry)).isDirectory();
593
+ return fs2.statSync(path2.join(sprintRunsRoot, entry)).isDirectory();
312
594
  } catch {
313
595
  return false;
314
596
  }
@@ -321,10 +603,10 @@ function reconcileCrossSprintOrphans(opts) {
321
603
  let clean = 0;
322
604
  for (const sprintDir of sprintDirs) {
323
605
  if (activeSprintId && sprintDir === activeSprintId) continue;
324
- const stateFile = path.join(sprintRunsRoot, sprintDir, "state.json");
606
+ const stateFile = path2.join(sprintRunsRoot, sprintDir, "state.json");
325
607
  let stateJson;
326
608
  try {
327
- const raw = fs.readFileSync(stateFile, "utf8");
609
+ const raw = fs2.readFileSync(stateFile, "utf8");
328
610
  stateJson = JSON.parse(raw);
329
611
  } catch {
330
612
  continue;
@@ -357,7 +639,7 @@ function reconcileDecomposition(opts) {
357
639
  const { sprintPlanPath, deliveryRoot } = opts;
358
640
  let raw;
359
641
  try {
360
- raw = fs.readFileSync(sprintPlanPath, "utf8");
642
+ raw = fs2.readFileSync(sprintPlanPath, "utf8");
361
643
  } catch {
362
644
  return { missing: [], clean: 0 };
363
645
  }
@@ -369,11 +651,11 @@ function reconcileDecomposition(opts) {
369
651
  }
370
652
  const epics = Array.isArray(fm["epics"]) ? fm["epics"].map(String) : [];
371
653
  const proposals = Array.isArray(fm["proposals"]) ? fm["proposals"].map(String) : [];
372
- const pendingDir = path.join(deliveryRoot, "pending-sync");
373
- const archiveDir = path.join(deliveryRoot, "archive");
654
+ const pendingDir = path2.join(deliveryRoot, "pending-sync");
655
+ const archiveDir = path2.join(deliveryRoot, "archive");
374
656
  function listMdFiles(dir) {
375
657
  try {
376
- return fs.readdirSync(dir).filter((f) => f.endsWith(".md"));
658
+ return fs2.readdirSync(dir).filter((f) => f.endsWith(".md"));
377
659
  } catch {
378
660
  return [];
379
661
  }
@@ -445,9 +727,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
445
727
  for (const f of files) {
446
728
  if (!f.startsWith(storyPrefix) && !f.startsWith("STORY-")) continue;
447
729
  if (!f.includes(storyPrefix)) continue;
448
- const absPath = path.join(dir, f);
730
+ const absPath = path2.join(dir, f);
449
731
  try {
450
- const raw = fs.readFileSync(absPath, "utf8");
732
+ const raw = fs2.readFileSync(absPath, "utf8");
451
733
  const { fm } = parseFrontmatter(raw);
452
734
  const parentRef = fm["parent_epic_ref"];
453
735
  if (parentRef === epicId) {
@@ -462,9 +744,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
462
744
  function findDecomposedEpic(proposalId, pendingDir, pendingFiles) {
463
745
  for (const f of pendingFiles) {
464
746
  if (!f.startsWith("EPIC-")) continue;
465
- const absPath = path.join(pendingDir, f);
747
+ const absPath = path2.join(pendingDir, f);
466
748
  try {
467
- const raw = fs.readFileSync(absPath, "utf8");
749
+ const raw = fs2.readFileSync(absPath, "utf8");
468
750
  const { fm } = parseFrontmatter(raw);
469
751
  const contextSource = fm["context_source"];
470
752
  if (typeof contextSource === "string" && contextSource.includes(proposalId)) {
@@ -492,6 +774,8 @@ function checkVerbMismatch(verb, type) {
492
774
  parseCommitMessage,
493
775
  reconcileCrossSprintOrphans,
494
776
  reconcileDecomposition,
495
- reconcileLifecycle
777
+ reconcileLifecycle,
778
+ rollUpParentStatus,
779
+ walkActiveParents
496
780
  });
497
781
  //# sourceMappingURL=lifecycle-reconcile.cjs.map