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.
- package/dist/MANIFEST.json +13 -13
- package/dist/{chunk-HZPJ5QX4.js → chunk-EG6YGT2O.js} +315 -33
- package/dist/chunk-EG6YGT2O.js.map +1 -0
- package/dist/cli.cjs +612 -289
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +73 -37
- package/dist/cli.js.map +1 -1
- package/dist/lib/lifecycle-reconcile.cjs +318 -34
- package/dist/lib/lifecycle-reconcile.cjs.map +1 -1
- package/dist/lib/lifecycle-reconcile.d.cts +55 -4
- package/dist/lib/lifecycle-reconcile.d.ts +55 -4
- package/dist/lib/lifecycle-reconcile.js +7 -3
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +8 -4
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +73 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
- package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
- package/dist/templates/cleargate-planning/MANIFEST.json +13 -13
- package/package.json +8 -9
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +1 -1
- package/templates/cleargate-planning/.claude/agents/developer.md +8 -4
- package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +2 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +73 -0
- package/templates/cleargate-planning/.cleargate/templates/Bug.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/CR.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/epic.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/story.md +1 -1
- package/templates/cleargate-planning/CLAUDE.md +2 -0
- package/templates/cleargate-planning/MANIFEST.json +13 -13
- package/dist/chunk-HZPJ5QX4.js.map +0 -1
package/dist/MANIFEST.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cleargate_version": "0.
|
|
3
|
-
"generated_at": "2026-05-
|
|
2
|
+
"cleargate_version": "0.13.0",
|
|
3
|
+
"generated_at": "2026-05-18T17:11:10.027Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": ".claude/agents/architect.md",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": ".claude/agents/cleargate-wiki-lint.md",
|
|
28
|
-
"sha256": "
|
|
28
|
+
"sha256": "2a9212d81df9a68e167ec7a18093a73c6da0208f13685c6887d5bd832b56fb3d",
|
|
29
29
|
"tier": "agent",
|
|
30
30
|
"overwrite_policy": "always",
|
|
31
31
|
"preserve_on_uninstall": false
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"path": ".claude/agents/developer.md",
|
|
42
|
-
"sha256": "
|
|
42
|
+
"sha256": "db7963778d68654f2dc96658d60433fa79305a10eec937807f2f0ed0cd05ce89",
|
|
43
43
|
"tier": "agent",
|
|
44
44
|
"overwrite_policy": "always",
|
|
45
45
|
"preserve_on_uninstall": false
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
"path": ".claude/hooks/pre-commit-surface-gate.sh",
|
|
77
|
-
"sha256": "
|
|
77
|
+
"sha256": "8dd817fbe75ee53753e2fe1fc2ccd2b6efdb2b3c0b6ad3191bebd9640afdd6f8",
|
|
78
78
|
"tier": "hook",
|
|
79
79
|
"overwrite_policy": "always",
|
|
80
80
|
"preserve_on_uninstall": false
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
"path": ".cleargate/scripts/close_sprint.mjs",
|
|
203
|
-
"sha256": "
|
|
203
|
+
"sha256": "b14b65f15c5ad57a845df89c069b5417195651b7d3aa7f7f5736416d0db0b868",
|
|
204
204
|
"tier": "script",
|
|
205
205
|
"overwrite_policy": "always",
|
|
206
206
|
"preserve_on_uninstall": false
|
|
@@ -396,35 +396,35 @@
|
|
|
396
396
|
},
|
|
397
397
|
{
|
|
398
398
|
"path": ".cleargate/templates/Bug.md",
|
|
399
|
-
"sha256": "
|
|
399
|
+
"sha256": "cebca344b6b820525c603444cf52626e120ebaa1ac28da099dc19c9cff39302c",
|
|
400
400
|
"tier": "template",
|
|
401
401
|
"overwrite_policy": "merge-3way",
|
|
402
402
|
"preserve_on_uninstall": false
|
|
403
403
|
},
|
|
404
404
|
{
|
|
405
405
|
"path": ".cleargate/templates/CR.md",
|
|
406
|
-
"sha256": "
|
|
406
|
+
"sha256": "ea5acf2087808e0d52806a87a6a7f1ced7473b591857f322fab5285adc80d25a",
|
|
407
407
|
"tier": "template",
|
|
408
408
|
"overwrite_policy": "merge-3way",
|
|
409
409
|
"preserve_on_uninstall": false
|
|
410
410
|
},
|
|
411
411
|
{
|
|
412
412
|
"path": ".cleargate/templates/epic.md",
|
|
413
|
-
"sha256": "
|
|
413
|
+
"sha256": "f9cf44db19288f0756b76bc8b13a075d4089990324db6febd072f5cf93d59cd0",
|
|
414
414
|
"tier": "template",
|
|
415
415
|
"overwrite_policy": "merge-3way",
|
|
416
416
|
"preserve_on_uninstall": false
|
|
417
417
|
},
|
|
418
418
|
{
|
|
419
419
|
"path": ".cleargate/templates/hotfix.md",
|
|
420
|
-
"sha256": "
|
|
420
|
+
"sha256": "de788497b4d224500036773f854801c056d73b08c3c0d66fdb641f17a1610bca",
|
|
421
421
|
"tier": "template",
|
|
422
422
|
"overwrite_policy": "merge-3way",
|
|
423
423
|
"preserve_on_uninstall": false
|
|
424
424
|
},
|
|
425
425
|
{
|
|
426
426
|
"path": ".cleargate/templates/initiative.md",
|
|
427
|
-
"sha256": "
|
|
427
|
+
"sha256": "1170e595f5813c62f86212d6ca1955d84465f396c81aaf34498b8e2d4595d681",
|
|
428
428
|
"tier": "template",
|
|
429
429
|
"overwrite_policy": "merge-3way",
|
|
430
430
|
"preserve_on_uninstall": false
|
|
@@ -445,14 +445,14 @@
|
|
|
445
445
|
},
|
|
446
446
|
{
|
|
447
447
|
"path": ".cleargate/templates/sprint_report.md",
|
|
448
|
-
"sha256": "
|
|
448
|
+
"sha256": "5914d54080f6110be5a5e905e3312811f3d0f80978121b1e15707d5be05cc5b1",
|
|
449
449
|
"tier": "template",
|
|
450
450
|
"overwrite_policy": "merge-3way",
|
|
451
451
|
"preserve_on_uninstall": false
|
|
452
452
|
},
|
|
453
453
|
{
|
|
454
454
|
"path": ".cleargate/templates/story.md",
|
|
455
|
-
"sha256": "
|
|
455
|
+
"sha256": "0badf01a080bca552a06fb64becd9b8c88fbf104e30f32667263522c3ff81051",
|
|
456
456
|
"tier": "template",
|
|
457
457
|
"overwrite_policy": "merge-3way",
|
|
458
458
|
"preserve_on_uninstall": false
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/lib/lifecycle-reconcile.ts
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
4
|
+
import * as fs2 from "fs";
|
|
5
|
+
import * as path2 from "path";
|
|
6
6
|
import { spawnSync } from "child_process";
|
|
7
7
|
|
|
8
8
|
// src/wiki/parse-frontmatter.ts
|
|
@@ -44,25 +44,305 @@ function parseFrontmatter(raw) {
|
|
|
44
44
|
return { fm: parsed, body };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// src/lib/parent-rollup.ts
|
|
48
|
+
import * as fs from "fs";
|
|
49
|
+
import * as path from "path";
|
|
50
|
+
function readFm(filePath) {
|
|
51
|
+
try {
|
|
52
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
53
|
+
const { fm } = parseFrontmatter(raw);
|
|
54
|
+
return fm;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function extractId(fm, filePath) {
|
|
60
|
+
for (const key of [
|
|
61
|
+
"story_id",
|
|
62
|
+
"epic_id",
|
|
63
|
+
"sprint_id",
|
|
64
|
+
"bug_id",
|
|
65
|
+
"cr_id",
|
|
66
|
+
"initiative_id",
|
|
67
|
+
"hotfix_id"
|
|
68
|
+
]) {
|
|
69
|
+
const val = fm[key];
|
|
70
|
+
if (typeof val === "string" && val.trim() !== "") return val.trim();
|
|
71
|
+
}
|
|
72
|
+
const stem = path.basename(filePath, ".md");
|
|
73
|
+
return stem.split("_")[0] ?? stem;
|
|
74
|
+
}
|
|
75
|
+
function enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache) {
|
|
76
|
+
const pendingSyncDir = path.join(deliveryRoot, "pending-sync");
|
|
77
|
+
const results = [];
|
|
78
|
+
const pools = [];
|
|
79
|
+
if (fs.existsSync(archiveRoot)) pools.push(archiveRoot);
|
|
80
|
+
if (fs.existsSync(pendingSyncDir)) pools.push(pendingSyncDir);
|
|
81
|
+
for (const dir of pools) {
|
|
82
|
+
let entries;
|
|
83
|
+
try {
|
|
84
|
+
entries = fs.readdirSync(dir);
|
|
85
|
+
} catch {
|
|
86
|
+
entries = [];
|
|
87
|
+
}
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (!entry.endsWith(".md")) continue;
|
|
90
|
+
const absPath = path.join(dir, entry);
|
|
91
|
+
let fm = fmCache.get(absPath);
|
|
92
|
+
if (fm === void 0) {
|
|
93
|
+
const parsed = readFm(absPath);
|
|
94
|
+
if (parsed === null) continue;
|
|
95
|
+
fm = parsed;
|
|
96
|
+
fmCache.set(absPath, fm);
|
|
97
|
+
}
|
|
98
|
+
const parentCleargateId = fm["parent_cleargate_id"];
|
|
99
|
+
const parentEpicRef = fm["parent_epic_ref"];
|
|
100
|
+
const isChild = typeof parentCleargateId === "string" && parentCleargateId.trim() === parentId || typeof parentEpicRef === "string" && parentEpicRef.trim() === parentId;
|
|
101
|
+
if (!isChild) continue;
|
|
102
|
+
const childId = extractId(fm, absPath);
|
|
103
|
+
const status = typeof fm["status"] === "string" ? fm["status"].trim() : "";
|
|
104
|
+
results.push({ id: childId, status });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
async function rollUpParentStatusInternal(parentFilePath, opts, visited, fmCache) {
|
|
110
|
+
const { deliveryRoot, archiveRoot } = opts;
|
|
111
|
+
let fm = fmCache.get(parentFilePath);
|
|
112
|
+
if (fm === void 0) {
|
|
113
|
+
const raw = readFm(parentFilePath);
|
|
114
|
+
if (raw === null) {
|
|
115
|
+
throw new Error(`parent-rollup: cannot read frontmatter from ${parentFilePath}`);
|
|
116
|
+
}
|
|
117
|
+
fm = raw;
|
|
118
|
+
fmCache.set(parentFilePath, fm);
|
|
119
|
+
}
|
|
120
|
+
const parentId = extractId(fm, parentFilePath);
|
|
121
|
+
const currentStatus = typeof fm["status"] === "string" ? fm["status"].trim() : "";
|
|
122
|
+
if (ARTIFACT_TERMINAL_STATUSES.has(currentStatus)) {
|
|
123
|
+
return {
|
|
124
|
+
parent_id: parentId,
|
|
125
|
+
parent_path: parentFilePath,
|
|
126
|
+
current_status: currentStatus,
|
|
127
|
+
proposed_status: null,
|
|
128
|
+
coverage: "full",
|
|
129
|
+
terminal_children: [],
|
|
130
|
+
pending_children: [],
|
|
131
|
+
verdict: "no-op"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (visited.has(parentId)) {
|
|
135
|
+
throw new Error(`parent-rollup: sub_epics cycle detected at ${parentId}`);
|
|
136
|
+
}
|
|
137
|
+
visited.add(parentId);
|
|
138
|
+
const subEpicsField = fm["sub_epics"];
|
|
139
|
+
const subEpics = Array.isArray(subEpicsField) && subEpicsField.length > 0 ? subEpicsField.filter((s) => typeof s === "string") : [];
|
|
140
|
+
if (subEpics.length > 0) {
|
|
141
|
+
const pendingSyncDir = path.join(deliveryRoot, "pending-sync");
|
|
142
|
+
const terminalSubEpics = [];
|
|
143
|
+
const pendingSubEpics = [];
|
|
144
|
+
for (const subEpicId of subEpics) {
|
|
145
|
+
let subEpicPath = null;
|
|
146
|
+
const candidateDirs = [pendingSyncDir, archiveRoot];
|
|
147
|
+
for (const dir of candidateDirs) {
|
|
148
|
+
if (!fs.existsSync(dir)) continue;
|
|
149
|
+
let entries;
|
|
150
|
+
try {
|
|
151
|
+
entries = fs.readdirSync(dir);
|
|
152
|
+
} catch {
|
|
153
|
+
entries = [];
|
|
154
|
+
}
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (!entry.endsWith(".md")) continue;
|
|
157
|
+
const absPath = path.join(dir, entry);
|
|
158
|
+
let subFm2 = fmCache.get(absPath);
|
|
159
|
+
if (subFm2 === void 0) {
|
|
160
|
+
const parsed = readFm(absPath);
|
|
161
|
+
if (parsed === null) continue;
|
|
162
|
+
subFm2 = parsed;
|
|
163
|
+
fmCache.set(absPath, subFm2);
|
|
164
|
+
}
|
|
165
|
+
const entryId = extractId(subFm2, absPath);
|
|
166
|
+
if (entryId === subEpicId) {
|
|
167
|
+
subEpicPath = absPath;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (subEpicPath !== null) break;
|
|
172
|
+
}
|
|
173
|
+
if (subEpicPath === null) {
|
|
174
|
+
pendingSubEpics.push(subEpicId);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
let subFm = fmCache.get(subEpicPath);
|
|
178
|
+
if (subFm === void 0) {
|
|
179
|
+
const parsed = readFm(subEpicPath);
|
|
180
|
+
if (parsed === null) {
|
|
181
|
+
pendingSubEpics.push(subEpicId);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
subFm = parsed;
|
|
185
|
+
fmCache.set(subEpicPath, subFm);
|
|
186
|
+
}
|
|
187
|
+
const subStatus = typeof subFm["status"] === "string" ? subFm["status"].trim() : "";
|
|
188
|
+
if (subStatus === "DEFERRED") {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (ARTIFACT_TERMINAL_STATUSES.has(subStatus)) {
|
|
192
|
+
terminalSubEpics.push(subEpicId);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const visitedSnapshot = new Set(visited);
|
|
196
|
+
const subResult = await rollUpParentStatusInternal(
|
|
197
|
+
subEpicPath,
|
|
198
|
+
opts,
|
|
199
|
+
visitedSnapshot,
|
|
200
|
+
fmCache
|
|
201
|
+
);
|
|
202
|
+
if (subResult.verdict === "auto-flip" || subResult.verdict === "no-op") {
|
|
203
|
+
terminalSubEpics.push(subEpicId);
|
|
204
|
+
} else {
|
|
205
|
+
pendingSubEpics.push(subEpicId);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
visited.delete(parentId);
|
|
209
|
+
const total2 = terminalSubEpics.length + pendingSubEpics.length;
|
|
210
|
+
if (total2 === 0) {
|
|
211
|
+
return {
|
|
212
|
+
parent_id: parentId,
|
|
213
|
+
parent_path: parentFilePath,
|
|
214
|
+
current_status: currentStatus,
|
|
215
|
+
proposed_status: null,
|
|
216
|
+
coverage: "zero",
|
|
217
|
+
terminal_children: [],
|
|
218
|
+
pending_children: [],
|
|
219
|
+
verdict: "halt-zero-children",
|
|
220
|
+
halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (pendingSubEpics.length === 0) {
|
|
224
|
+
return {
|
|
225
|
+
parent_id: parentId,
|
|
226
|
+
parent_path: parentFilePath,
|
|
227
|
+
current_status: currentStatus,
|
|
228
|
+
proposed_status: "Completed",
|
|
229
|
+
coverage: "full",
|
|
230
|
+
terminal_children: terminalSubEpics,
|
|
231
|
+
pending_children: [],
|
|
232
|
+
verdict: "auto-flip"
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
parent_id: parentId,
|
|
237
|
+
parent_path: parentFilePath,
|
|
238
|
+
current_status: currentStatus,
|
|
239
|
+
proposed_status: null,
|
|
240
|
+
coverage: "sub-epic-partial",
|
|
241
|
+
terminal_children: terminalSubEpics,
|
|
242
|
+
pending_children: pendingSubEpics,
|
|
243
|
+
verdict: "halt-partial",
|
|
244
|
+
halt_reason: `${parentId}: ${terminalSubEpics.length}/${total2} sub-epics terminal \u2014 pending: ${pendingSubEpics.join(", ")}`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const children = enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache);
|
|
248
|
+
visited.delete(parentId);
|
|
249
|
+
if (children.length === 0) {
|
|
250
|
+
return {
|
|
251
|
+
parent_id: parentId,
|
|
252
|
+
parent_path: parentFilePath,
|
|
253
|
+
current_status: currentStatus,
|
|
254
|
+
proposed_status: null,
|
|
255
|
+
coverage: "zero",
|
|
256
|
+
terminal_children: [],
|
|
257
|
+
pending_children: [],
|
|
258
|
+
verdict: "halt-zero-children",
|
|
259
|
+
halt_reason: `${parentId}: 0 children drafted; not reconcilable \u2014 decompose or abandon`
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const terminalChildren = [];
|
|
263
|
+
const pendingChildren = [];
|
|
264
|
+
for (const child of children) {
|
|
265
|
+
if (ARTIFACT_TERMINAL_STATUSES.has(child.status)) {
|
|
266
|
+
terminalChildren.push(child.id);
|
|
267
|
+
} else {
|
|
268
|
+
pendingChildren.push(child.id);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const total = terminalChildren.length + pendingChildren.length;
|
|
272
|
+
if (pendingChildren.length === 0) {
|
|
273
|
+
return {
|
|
274
|
+
parent_id: parentId,
|
|
275
|
+
parent_path: parentFilePath,
|
|
276
|
+
current_status: currentStatus,
|
|
277
|
+
proposed_status: "Completed",
|
|
278
|
+
coverage: "full",
|
|
279
|
+
terminal_children: terminalChildren,
|
|
280
|
+
pending_children: [],
|
|
281
|
+
verdict: "auto-flip"
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
parent_id: parentId,
|
|
286
|
+
parent_path: parentFilePath,
|
|
287
|
+
current_status: currentStatus,
|
|
288
|
+
proposed_status: null,
|
|
289
|
+
coverage: "partial",
|
|
290
|
+
terminal_children: terminalChildren,
|
|
291
|
+
pending_children: pendingChildren,
|
|
292
|
+
verdict: "halt-partial",
|
|
293
|
+
halt_reason: `${parentId}: ${terminalChildren.length}/${total} children terminal \u2014 pending: ${pendingChildren.join(", ")}`
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
async function rollUpParentStatus(parentFilePath, opts) {
|
|
297
|
+
const visited = /* @__PURE__ */ new Set();
|
|
298
|
+
const fmCache = /* @__PURE__ */ new Map();
|
|
299
|
+
return rollUpParentStatusInternal(parentFilePath, opts, visited, fmCache);
|
|
300
|
+
}
|
|
301
|
+
async function walkActiveParents(opts) {
|
|
302
|
+
const { deliveryRoot } = opts;
|
|
303
|
+
const pendingSyncDir = path.join(deliveryRoot, "pending-sync");
|
|
304
|
+
let entries;
|
|
305
|
+
try {
|
|
306
|
+
entries = fs.readdirSync(pendingSyncDir);
|
|
307
|
+
} catch {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
const parentFiles = entries.filter(
|
|
311
|
+
(e) => e.endsWith(".md") && (e.startsWith("EPIC-") || e.startsWith("SPRINT-"))
|
|
312
|
+
);
|
|
313
|
+
const results = [];
|
|
314
|
+
const fmCache = /* @__PURE__ */ new Map();
|
|
315
|
+
for (const entry of parentFiles) {
|
|
316
|
+
const absPath = path.join(pendingSyncDir, entry);
|
|
317
|
+
try {
|
|
318
|
+
const visited = /* @__PURE__ */ new Set();
|
|
319
|
+
const result = await rollUpParentStatusInternal(absPath, opts, visited, fmCache);
|
|
320
|
+
results.push(result);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
if (err instanceof Error && err.message.includes("sub_epics cycle detected")) {
|
|
323
|
+
throw err;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return results;
|
|
328
|
+
}
|
|
329
|
+
|
|
47
330
|
// src/lib/lifecycle-reconcile.ts
|
|
48
331
|
var ARTIFACT_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
49
|
-
"Done",
|
|
50
332
|
"Completed",
|
|
51
|
-
"Verified",
|
|
52
333
|
"Abandoned",
|
|
53
334
|
"Closed",
|
|
54
|
-
"Resolved"
|
|
55
|
-
"Escalated",
|
|
56
|
-
"Parking Lot"
|
|
335
|
+
"Resolved"
|
|
57
336
|
]);
|
|
337
|
+
var ARTIFACT_GATE_EXPECTED = ["Completed"];
|
|
58
338
|
var VERB_STATUS_MAP = {
|
|
59
339
|
feat: {
|
|
60
340
|
types: ["STORY", "EPIC", "CR"],
|
|
61
|
-
expected: [
|
|
341
|
+
expected: [...ARTIFACT_GATE_EXPECTED]
|
|
62
342
|
},
|
|
63
343
|
fix: {
|
|
64
344
|
types: ["BUG", "HOTFIX"],
|
|
65
|
-
expected: [
|
|
345
|
+
expected: [...ARTIFACT_GATE_EXPECTED]
|
|
66
346
|
}
|
|
67
347
|
};
|
|
68
348
|
var ID_PATTERN = /\b(STORY-\d{3}-\d{2}|(CR|BUG|EPIC|HOTFIX)-\d{3}|(PROPOSAL|PROP)-\d{3})\b/g;
|
|
@@ -113,10 +393,10 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
113
393
|
{ rel: "archive", inArchive: true }
|
|
114
394
|
];
|
|
115
395
|
for (const { rel, inArchive } of dirs) {
|
|
116
|
-
const dir =
|
|
396
|
+
const dir = path2.join(deliveryRoot, rel);
|
|
117
397
|
let entries;
|
|
118
398
|
try {
|
|
119
|
-
entries =
|
|
399
|
+
entries = fs2.readdirSync(dir);
|
|
120
400
|
} catch {
|
|
121
401
|
continue;
|
|
122
402
|
}
|
|
@@ -124,7 +404,7 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
124
404
|
(e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith(".md")
|
|
125
405
|
);
|
|
126
406
|
if (match) {
|
|
127
|
-
const absPath =
|
|
407
|
+
const absPath = path2.join(dir, match);
|
|
128
408
|
return { absPath, inArchive, relPath: `${rel}/${match}` };
|
|
129
409
|
}
|
|
130
410
|
}
|
|
@@ -133,7 +413,7 @@ function findArtifactFile(deliveryRoot, id) {
|
|
|
133
413
|
function readArtifactStatus(absPath) {
|
|
134
414
|
let raw;
|
|
135
415
|
try {
|
|
136
|
-
raw =
|
|
416
|
+
raw = fs2.readFileSync(absPath, "utf8");
|
|
137
417
|
} catch {
|
|
138
418
|
return { status: null, carryOver: false };
|
|
139
419
|
}
|
|
@@ -188,7 +468,7 @@ function reconcileLifecycle(opts) {
|
|
|
188
468
|
if (carryOver) continue;
|
|
189
469
|
let expectedStatuses;
|
|
190
470
|
if (verb === "feat" && type === "BUG") {
|
|
191
|
-
expectedStatuses = [
|
|
471
|
+
expectedStatuses = [...ARTIFACT_GATE_EXPECTED];
|
|
192
472
|
} else if (!verbConfig.types.includes(type)) {
|
|
193
473
|
continue;
|
|
194
474
|
} else {
|
|
@@ -200,7 +480,7 @@ function reconcileLifecycle(opts) {
|
|
|
200
480
|
cleanIds.add(id);
|
|
201
481
|
idToItem.delete(id);
|
|
202
482
|
} else if (!idToItem.has(id)) {
|
|
203
|
-
const expectedStr = expectedStatuses[0] ?? "
|
|
483
|
+
const expectedStr = expectedStatuses[0] ?? "Completed";
|
|
204
484
|
idToItem.set(id, {
|
|
205
485
|
id,
|
|
206
486
|
type,
|
|
@@ -231,13 +511,13 @@ function reconcileCrossSprintOrphans(opts) {
|
|
|
231
511
|
const TERMINAL_STATE_JSON = /* @__PURE__ */ new Set(["Done", "Escalated", "Parking Lot"]);
|
|
232
512
|
let activeSprintId = null;
|
|
233
513
|
try {
|
|
234
|
-
activeSprintId =
|
|
514
|
+
activeSprintId = fs2.readFileSync(path2.join(sprintRunsRoot, ".active"), "utf8").trim();
|
|
235
515
|
} catch {
|
|
236
516
|
}
|
|
237
|
-
const pendingDir =
|
|
517
|
+
const pendingDir = path2.join(deliveryRoot, "pending-sync");
|
|
238
518
|
let pendingFiles;
|
|
239
519
|
try {
|
|
240
|
-
pendingFiles =
|
|
520
|
+
pendingFiles = fs2.readdirSync(pendingDir).filter(
|
|
241
521
|
(f) => f.endsWith(".md") && !f.startsWith(".")
|
|
242
522
|
);
|
|
243
523
|
} catch {
|
|
@@ -245,7 +525,7 @@ function reconcileCrossSprintOrphans(opts) {
|
|
|
245
525
|
}
|
|
246
526
|
const pendingMap = /* @__PURE__ */ new Map();
|
|
247
527
|
for (const fileName of pendingFiles) {
|
|
248
|
-
const absPath =
|
|
528
|
+
const absPath = path2.join(pendingDir, fileName);
|
|
249
529
|
const { status } = readArtifactStatus(absPath);
|
|
250
530
|
if (status === null) continue;
|
|
251
531
|
if (ARTIFACT_TERMINAL_STATUSES.has(status)) continue;
|
|
@@ -257,7 +537,7 @@ function reconcileCrossSprintOrphans(opts) {
|
|
|
257
537
|
if (!type || type === "PROPOSAL") continue;
|
|
258
538
|
pendingMap.set(id, {
|
|
259
539
|
status,
|
|
260
|
-
filePath:
|
|
540
|
+
filePath: path2.join("pending-sync", fileName),
|
|
261
541
|
type
|
|
262
542
|
});
|
|
263
543
|
}
|
|
@@ -266,10 +546,10 @@ function reconcileCrossSprintOrphans(opts) {
|
|
|
266
546
|
}
|
|
267
547
|
let sprintDirs;
|
|
268
548
|
try {
|
|
269
|
-
sprintDirs =
|
|
549
|
+
sprintDirs = fs2.readdirSync(sprintRunsRoot).filter((entry) => {
|
|
270
550
|
if (entry.startsWith(".")) return false;
|
|
271
551
|
try {
|
|
272
|
-
return
|
|
552
|
+
return fs2.statSync(path2.join(sprintRunsRoot, entry)).isDirectory();
|
|
273
553
|
} catch {
|
|
274
554
|
return false;
|
|
275
555
|
}
|
|
@@ -282,10 +562,10 @@ function reconcileCrossSprintOrphans(opts) {
|
|
|
282
562
|
let clean = 0;
|
|
283
563
|
for (const sprintDir of sprintDirs) {
|
|
284
564
|
if (activeSprintId && sprintDir === activeSprintId) continue;
|
|
285
|
-
const stateFile =
|
|
565
|
+
const stateFile = path2.join(sprintRunsRoot, sprintDir, "state.json");
|
|
286
566
|
let stateJson;
|
|
287
567
|
try {
|
|
288
|
-
const raw =
|
|
568
|
+
const raw = fs2.readFileSync(stateFile, "utf8");
|
|
289
569
|
stateJson = JSON.parse(raw);
|
|
290
570
|
} catch {
|
|
291
571
|
continue;
|
|
@@ -318,7 +598,7 @@ function reconcileDecomposition(opts) {
|
|
|
318
598
|
const { sprintPlanPath, deliveryRoot } = opts;
|
|
319
599
|
let raw;
|
|
320
600
|
try {
|
|
321
|
-
raw =
|
|
601
|
+
raw = fs2.readFileSync(sprintPlanPath, "utf8");
|
|
322
602
|
} catch {
|
|
323
603
|
return { missing: [], clean: 0 };
|
|
324
604
|
}
|
|
@@ -330,11 +610,11 @@ function reconcileDecomposition(opts) {
|
|
|
330
610
|
}
|
|
331
611
|
const epics = Array.isArray(fm["epics"]) ? fm["epics"].map(String) : [];
|
|
332
612
|
const proposals = Array.isArray(fm["proposals"]) ? fm["proposals"].map(String) : [];
|
|
333
|
-
const pendingDir =
|
|
334
|
-
const archiveDir =
|
|
613
|
+
const pendingDir = path2.join(deliveryRoot, "pending-sync");
|
|
614
|
+
const archiveDir = path2.join(deliveryRoot, "archive");
|
|
335
615
|
function listMdFiles(dir) {
|
|
336
616
|
try {
|
|
337
|
-
return
|
|
617
|
+
return fs2.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
338
618
|
} catch {
|
|
339
619
|
return [];
|
|
340
620
|
}
|
|
@@ -406,9 +686,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
|
|
|
406
686
|
for (const f of files) {
|
|
407
687
|
if (!f.startsWith(storyPrefix) && !f.startsWith("STORY-")) continue;
|
|
408
688
|
if (!f.includes(storyPrefix)) continue;
|
|
409
|
-
const absPath =
|
|
689
|
+
const absPath = path2.join(dir, f);
|
|
410
690
|
try {
|
|
411
|
-
const raw =
|
|
691
|
+
const raw = fs2.readFileSync(absPath, "utf8");
|
|
412
692
|
const { fm } = parseFrontmatter(raw);
|
|
413
693
|
const parentRef = fm["parent_epic_ref"];
|
|
414
694
|
if (parentRef === epicId) {
|
|
@@ -423,9 +703,9 @@ function findChildStories(epicId, pendingDir, pendingFiles, archiveDir, archiveF
|
|
|
423
703
|
function findDecomposedEpic(proposalId, pendingDir, pendingFiles) {
|
|
424
704
|
for (const f of pendingFiles) {
|
|
425
705
|
if (!f.startsWith("EPIC-")) continue;
|
|
426
|
-
const absPath =
|
|
706
|
+
const absPath = path2.join(pendingDir, f);
|
|
427
707
|
try {
|
|
428
|
-
const raw =
|
|
708
|
+
const raw = fs2.readFileSync(absPath, "utf8");
|
|
429
709
|
const { fm } = parseFrontmatter(raw);
|
|
430
710
|
const contextSource = fm["context_source"];
|
|
431
711
|
if (typeof contextSource === "string" && contextSource.includes(proposalId)) {
|
|
@@ -448,6 +728,8 @@ function checkVerbMismatch(verb, type) {
|
|
|
448
728
|
|
|
449
729
|
export {
|
|
450
730
|
parseFrontmatter,
|
|
731
|
+
rollUpParentStatus,
|
|
732
|
+
walkActiveParents,
|
|
451
733
|
ARTIFACT_TERMINAL_STATUSES,
|
|
452
734
|
VERB_STATUS_MAP,
|
|
453
735
|
parseCommitMessage,
|
|
@@ -456,4 +738,4 @@ export {
|
|
|
456
738
|
reconcileDecomposition,
|
|
457
739
|
checkVerbMismatch
|
|
458
740
|
};
|
|
459
|
-
//# sourceMappingURL=chunk-
|
|
741
|
+
//# sourceMappingURL=chunk-EG6YGT2O.js.map
|