cleargate 0.14.0 → 0.15.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/CHANGELOG.md +16 -0
- package/dist/MANIFEST.json +71 -15
- package/dist/admin-api/index.cjs +0 -1
- package/dist/admin-api/index.js +1 -2
- package/dist/auth/factory.cjs +0 -1
- package/dist/auth/factory.js +2 -3
- package/dist/auth/require-token.cjs +0 -1
- package/dist/auth/require-token.js +1 -2
- package/dist/auth/token-store.cjs +0 -1
- package/dist/auth/token-store.js +1 -2
- package/dist/{bootstrap-root-QKSA5V75.js → bootstrap-root-2H5HVTCC.js} +1 -2
- package/dist/{chunk-PDE37WFQ.js → chunk-A7MSQUU7.js} +2 -3
- package/dist/{chunk-BTSZOEWC.js → chunk-P6KEDAK2.js} +0 -1
- package/dist/{chunk-E3X7IE5E.js → chunk-PY6FHGV5.js} +1 -2
- package/dist/{chunk-5DI2Z3C2.js → chunk-Y53ZZYYU.js} +1 -2
- package/dist/cli.cjs +1564 -1414
- package/dist/cli.js +1514 -1364
- package/dist/lib/ledger.cjs +0 -1
- package/dist/lib/ledger.js +1 -2
- package/dist/lib/lifecycle-reconcile.cjs +0 -1
- package/dist/lib/lifecycle-reconcile.js +2 -3
- package/dist/{whoami-EANGN46Z.js → whoami-JKQQPABQ.js} +3 -4
- package/package.json +4 -3
- package/templates/cleargate-planning/.claude/agents/architect.md +4 -2
- package/templates/cleargate-planning/.claude/agents/developer.md +4 -11
- package/templates/cleargate-planning/.claude/agents/qa.md +14 -6
- package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +2 -2
- package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +19 -1
- package/templates/cleargate-planning/.cleargate/config.example.yml +16 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.deferred-verify.red.node.test.ts +245 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +227 -0
- package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +5 -4
- package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +75 -2
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +48 -0
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +57 -1
- package/templates/cleargate-planning/.cleargate/scripts/provision_worktree_config.sh +155 -0
- package/templates/cleargate-planning/.cleargate/scripts/qa_red_lint.mjs +380 -0
- package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +34 -1
- package/templates/cleargate-planning/.cleargate/scripts/test/cr077_eviction.red.sh +113 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr078_init.test.sh +309 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr079_provision.red.sh +262 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr080_wrapper.test.sh +177 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/cr081_qa_red_lint.red.sh +348 -0
- package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/.session-totals.json +1 -0
- package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/token-ledger.jsonl +27 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +17 -0
- package/templates/cleargate-planning/.cleargate/templates/story.md +1 -0
- package/templates/cleargate-planning/MANIFEST.json +71 -15
- package/dist/admin-api/index.cjs.map +0 -1
- package/dist/admin-api/index.js.map +0 -1
- package/dist/auth/factory.cjs.map +0 -1
- package/dist/auth/factory.js.map +0 -1
- package/dist/auth/require-token.cjs.map +0 -1
- package/dist/auth/require-token.js.map +0 -1
- package/dist/auth/token-store.cjs.map +0 -1
- package/dist/auth/token-store.js.map +0 -1
- package/dist/bootstrap-root-QKSA5V75.js.map +0 -1
- package/dist/chunk-5DI2Z3C2.js.map +0 -1
- package/dist/chunk-BTSZOEWC.js.map +0 -1
- package/dist/chunk-E3X7IE5E.js.map +0 -1
- package/dist/chunk-PDE37WFQ.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/lib/ledger.cjs.map +0 -1
- package/dist/lib/ledger.js.map +0 -1
- package/dist/lib/lifecycle-reconcile.cjs.map +0 -1
- package/dist/lib/lifecycle-reconcile.js.map +0 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect-reader.md +0 -61
- package/dist/templates/cleargate-planning/.claude/agents/architect-synth.md +0 -124
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +0 -230
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +0 -108
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +0 -194
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +0 -261
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +0 -143
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +0 -185
- package/dist/templates/cleargate-planning/.claude/agents/devops.md +0 -257
- package/dist/templates/cleargate-planning/.claude/agents/qa.md +0 -171
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +0 -274
- package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +0 -209
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +0 -33
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +0 -58
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +0 -19
- package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +0 -162
- package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-autonomy.sh +0 -58
- package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +0 -148
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +0 -75
- package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +0 -43
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +0 -590
- package/dist/templates/cleargate-planning/.claude/settings.json +0 -68
- package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +0 -102
- package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +0 -742
- package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +0 -7
- package/dist/templates/cleargate-planning/.cleargate/config.example.yml +0 -67
- package/dist/templates/cleargate-planning/.cleargate/config.yml +0 -18
- package/dist/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
- package/dist/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +0 -551
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +0 -878
- package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +0 -160
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +0 -213
- package/dist/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +0 -71
- package/dist/templates/cleargate-planning/.cleargate/scripts/_migrate-schema-v3.mjs +0 -120
- package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +0 -265
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +0 -1012
- package/dist/templates/cleargate-planning/.cleargate/scripts/collision_surface.sh +0 -114
- package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +0 -62
- package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +0 -219
- package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +0 -320
- package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +0 -15
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +0 -38
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +0 -240
- package/dist/templates/cleargate-planning/.cleargate/scripts/launch_wave.mjs +0 -341
- package/dist/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +0 -54
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +0 -206
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +0 -371
- package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +0 -280
- package/dist/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +0 -378
- package/dist/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +0 -888
- package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +0 -209
- package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +0 -71
- package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +0 -127
- package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +0 -717
- package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +0 -27
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +0 -261
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +0 -210
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +0 -190
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +0 -482
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +0 -327
- package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +0 -261
- package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +0 -246
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +0 -111
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +0 -184
- package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +0 -172
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +0 -126
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +0 -130
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +0 -137
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +0 -166
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +0 -111
- package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +0 -122
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +0 -50
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +0 -224
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +0 -213
- package/dist/templates/cleargate-planning/CLAUDE.md +0 -66
- package/dist/templates/cleargate-planning/MANIFEST.json +0 -503
- package/dist/templates/synthesis/active-sprint.md +0 -30
- package/dist/templates/synthesis/open-gates.md +0 -38
- package/dist/templates/synthesis/product-state.md +0 -31
- package/dist/templates/synthesis/roadmap.md +0 -63
- package/dist/whoami-EANGN46Z.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cleargate_version": "0.
|
|
3
|
-
"generated_at": "2026-06-
|
|
2
|
+
"cleargate_version": "0.15.0",
|
|
3
|
+
"generated_at": "2026-06-04T07:32:54.323Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": ".claude/agents/architect-reader.md",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"path": ".claude/agents/architect.md",
|
|
21
|
-
"sha256": "
|
|
21
|
+
"sha256": "846b4a358ae59cd14bbbd93bde6e14b5eeec771feb21ae21d06112cce8264669",
|
|
22
22
|
"tier": "agent",
|
|
23
23
|
"overwrite_policy": "always",
|
|
24
24
|
"preserve_on_uninstall": false
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": ".claude/agents/developer.md",
|
|
56
|
-
"sha256": "
|
|
56
|
+
"sha256": "a30c67845deb217eb03f83748bd80170a373315eab4ed9687222fcd50cb9a287",
|
|
57
57
|
"tier": "agent",
|
|
58
58
|
"overwrite_policy": "always",
|
|
59
59
|
"preserve_on_uninstall": false
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
"path": ".claude/agents/qa.md",
|
|
70
|
-
"sha256": "
|
|
70
|
+
"sha256": "438da28233d5954fe676eaa35b49fb024deb13841813d1409be30d0779a8af02",
|
|
71
71
|
"tier": "agent",
|
|
72
72
|
"overwrite_policy": "always",
|
|
73
73
|
"preserve_on_uninstall": false
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"path": ".claude/hooks/pending-task-sentinel.sh",
|
|
84
|
-
"sha256": "
|
|
84
|
+
"sha256": "cb9c37ceea8e93b6c86225442ec06993b55b3341b572b3218c29df5ea1fc1759",
|
|
85
85
|
"tier": "hook",
|
|
86
86
|
"overwrite_policy": "always",
|
|
87
87
|
"preserve_on_uninstall": false
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"path": ".claude/skills/sprint-execution/SKILL.md",
|
|
168
|
-
"sha256": "
|
|
168
|
+
"sha256": "a3d923bd1e36b2f2fbe5f7252d6f7c6ab5e6bc0b4fca228d02c9de79d674ba77",
|
|
169
169
|
"tier": "skill",
|
|
170
170
|
"overwrite_policy": "always",
|
|
171
171
|
"preserve_on_uninstall": false
|
|
@@ -226,9 +226,16 @@
|
|
|
226
226
|
"overwrite_policy": "always",
|
|
227
227
|
"preserve_on_uninstall": false
|
|
228
228
|
},
|
|
229
|
+
{
|
|
230
|
+
"path": ".cleargate/scripts/close_sprint.deferred-verify.red.node.test.ts",
|
|
231
|
+
"sha256": "d45d297206a7b57eeb12abd2fdea9d632a479f1aa8b5177f47c02ede67fcab58",
|
|
232
|
+
"tier": "script",
|
|
233
|
+
"overwrite_policy": "always",
|
|
234
|
+
"preserve_on_uninstall": false
|
|
235
|
+
},
|
|
229
236
|
{
|
|
230
237
|
"path": ".cleargate/scripts/close_sprint.mjs",
|
|
231
|
-
"sha256": "
|
|
238
|
+
"sha256": "894373451279678568fa2373f99126d0ad27c84a2a506cdd77e71c8e9b370647",
|
|
232
239
|
"tier": "script",
|
|
233
240
|
"overwrite_policy": "always",
|
|
234
241
|
"preserve_on_uninstall": false
|
|
@@ -263,7 +270,7 @@
|
|
|
263
270
|
},
|
|
264
271
|
{
|
|
265
272
|
"path": ".cleargate/scripts/gate-checks.json",
|
|
266
|
-
"sha256": "
|
|
273
|
+
"sha256": "fa8e2af31c709a589ae73a8ebfa6719120f32589d34cfca76ca1e8836d10461f",
|
|
267
274
|
"tier": "script",
|
|
268
275
|
"overwrite_policy": "always",
|
|
269
276
|
"preserve_on_uninstall": false
|
|
@@ -277,7 +284,7 @@
|
|
|
277
284
|
},
|
|
278
285
|
{
|
|
279
286
|
"path": ".cleargate/scripts/init_sprint.mjs",
|
|
280
|
-
"sha256": "
|
|
287
|
+
"sha256": "f06b6ada867f7fc693ef5d8b8fb765c3ac492668fbb875a9034c486b8967270a",
|
|
281
288
|
"tier": "script",
|
|
282
289
|
"overwrite_policy": "always",
|
|
283
290
|
"preserve_on_uninstall": false
|
|
@@ -298,14 +305,14 @@
|
|
|
298
305
|
},
|
|
299
306
|
{
|
|
300
307
|
"path": ".cleargate/scripts/pre_gate_common.sh",
|
|
301
|
-
"sha256": "
|
|
308
|
+
"sha256": "97405ff603b61843d032a95aadd0489505675201d727cb0fa5befb83410e6b0b",
|
|
302
309
|
"tier": "script",
|
|
303
310
|
"overwrite_policy": "always",
|
|
304
311
|
"preserve_on_uninstall": false
|
|
305
312
|
},
|
|
306
313
|
{
|
|
307
314
|
"path": ".cleargate/scripts/pre_gate_runner.sh",
|
|
308
|
-
"sha256": "
|
|
315
|
+
"sha256": "bd7e6050fd903f9a6898b92895b12a5c95d79b7306c4251c992ae33e70abecdc",
|
|
309
316
|
"tier": "script",
|
|
310
317
|
"overwrite_policy": "always",
|
|
311
318
|
"preserve_on_uninstall": false
|
|
@@ -331,9 +338,23 @@
|
|
|
331
338
|
"overwrite_policy": "always",
|
|
332
339
|
"preserve_on_uninstall": false
|
|
333
340
|
},
|
|
341
|
+
{
|
|
342
|
+
"path": ".cleargate/scripts/provision_worktree_config.sh",
|
|
343
|
+
"sha256": "3c33876fdeffc5b3391edab4a7b8e26c32ec37b0f7b60a0fff296e7640ff34b2",
|
|
344
|
+
"tier": "script",
|
|
345
|
+
"overwrite_policy": "always",
|
|
346
|
+
"preserve_on_uninstall": false
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"path": ".cleargate/scripts/qa_red_lint.mjs",
|
|
350
|
+
"sha256": "7f1bcbeb04c4588d5a8f4a9b925aaadaa399bd85feb94443bad3c546dec119b6",
|
|
351
|
+
"tier": "script",
|
|
352
|
+
"overwrite_policy": "always",
|
|
353
|
+
"preserve_on_uninstall": false
|
|
354
|
+
},
|
|
334
355
|
{
|
|
335
356
|
"path": ".cleargate/scripts/run_script.sh",
|
|
336
|
-
"sha256": "
|
|
357
|
+
"sha256": "e5c570e25a63ee75497a5c3596efe5e164fe736d6ddc750f74dfb21fd4daf710",
|
|
337
358
|
"tier": "script",
|
|
338
359
|
"overwrite_policy": "always",
|
|
339
360
|
"preserve_on_uninstall": false
|
|
@@ -373,6 +394,41 @@
|
|
|
373
394
|
"overwrite_policy": "always",
|
|
374
395
|
"preserve_on_uninstall": false
|
|
375
396
|
},
|
|
397
|
+
{
|
|
398
|
+
"path": ".cleargate/scripts/test/cr077_eviction.red.sh",
|
|
399
|
+
"sha256": "ecaa8104b925d5fd11a7d47a7bafe0f45199ebea79dc05c3f975fd46b545de6f",
|
|
400
|
+
"tier": "script",
|
|
401
|
+
"overwrite_policy": "always",
|
|
402
|
+
"preserve_on_uninstall": false
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"path": ".cleargate/scripts/test/cr078_init.test.sh",
|
|
406
|
+
"sha256": "955a7388f42aa89c4a9ff90d8bc27534249c3cd4be4ad01f1feb760d05fa4328",
|
|
407
|
+
"tier": "script",
|
|
408
|
+
"overwrite_policy": "always",
|
|
409
|
+
"preserve_on_uninstall": false
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
"path": ".cleargate/scripts/test/cr079_provision.red.sh",
|
|
413
|
+
"sha256": "b59ce43780433800ce7b8c4b8136689e1170c7a27e6bbf8cc4b1ebd0e25b08eb",
|
|
414
|
+
"tier": "script",
|
|
415
|
+
"overwrite_policy": "always",
|
|
416
|
+
"preserve_on_uninstall": false
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"path": ".cleargate/scripts/test/cr080_wrapper.test.sh",
|
|
420
|
+
"sha256": "d3953989d3be1540f7e2297c7869fae23d385e8ed2b64346a787775a98d2881e",
|
|
421
|
+
"tier": "script",
|
|
422
|
+
"overwrite_policy": "always",
|
|
423
|
+
"preserve_on_uninstall": false
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"path": ".cleargate/scripts/test/cr081_qa_red_lint.red.sh",
|
|
427
|
+
"sha256": "fa66c6d78489c55b5084e46151557716ff1145b0ac5f064dac507ff7259b3040",
|
|
428
|
+
"tier": "script",
|
|
429
|
+
"overwrite_policy": "always",
|
|
430
|
+
"preserve_on_uninstall": false
|
|
431
|
+
},
|
|
376
432
|
{
|
|
377
433
|
"path": ".cleargate/scripts/test/test_assert_story_files.sh",
|
|
378
434
|
"sha256": "6aace15a0de4d07b4b9e7454d2b38e266b7a80e265281135a050bd9f6be83aab",
|
|
@@ -480,7 +536,7 @@
|
|
|
480
536
|
},
|
|
481
537
|
{
|
|
482
538
|
"path": ".cleargate/templates/sprint_context.md",
|
|
483
|
-
"sha256": "
|
|
539
|
+
"sha256": "5f19079f19b4cbe373f4ed0e2425e7c2fe8978bd3b345d31d28a09ea19ef7bfc",
|
|
484
540
|
"tier": "template",
|
|
485
541
|
"overwrite_policy": "merge-3way",
|
|
486
542
|
"preserve_on_uninstall": false
|
|
@@ -494,7 +550,7 @@
|
|
|
494
550
|
},
|
|
495
551
|
{
|
|
496
552
|
"path": ".cleargate/templates/story.md",
|
|
497
|
-
"sha256": "
|
|
553
|
+
"sha256": "e64ba4260b975566c270bed7a8411db9df4579eb34fa5928732606aa838da935",
|
|
498
554
|
"tier": "template",
|
|
499
555
|
"overwrite_policy": "merge-3way",
|
|
500
556
|
"preserve_on_uninstall": false
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/admin-api/index.ts","../../src/admin-api/errors.ts","../../src/admin-api/responses.ts","../../src/admin-api/redact.ts","../../src/admin-api/client.ts","../../src/admin-api/admin-auth.ts"],"sourcesContent":["/**\n * Barrel export for admin-api module.\n * Consumed by cleargate-admin commands via cleargate/admin-api.\n */\nexport * from './client.js';\nexport * from './errors.js';\nexport * from './redact.js';\nexport * from './responses.js';\nexport * from './admin-auth.js';\n","/**\n * Typed error class for all admin API failures.\n * kind → exit code mapping lives in mcp/scripts/commands/_render-error.ts\n */\nexport class AdminApiError extends Error {\n constructor(\n public readonly kind:\n | 'network'\n | 'auth'\n | 'forbidden'\n | 'not_found'\n | 'invalid_request'\n | 'server'\n | 'response_shape',\n public readonly status: number | null,\n public readonly details: unknown,\n message: string,\n ) {\n super(message);\n this.name = 'AdminApiError';\n }\n}\n","/**\n * Vendored Zod response schemas — hand-authored from\n * mcp/src/admin-api/__snapshots__/openapi.test.ts.snap\n *\n * Snapshot drift is detected by cleargate-cli/test/admin-api/snapshot-drift.test.ts,\n * which reads the snapshot file at runtime and asserts field-set equality.\n */\nimport { z } from 'zod';\n\nexport const ProjectSchema = z\n .object({\n id: z.string(),\n name: z.string(),\n created_by: z.string(),\n created_at: z.string(),\n deleted_at: z.string().nullable(),\n })\n .strict();\n\nexport type Project = z.infer<typeof ProjectSchema>;\n\nexport const MemberSchema = z\n .object({\n id: z.string(),\n project_id: z.string(),\n email: z.string(),\n role: z.string(),\n display_name: z.string().nullable().optional(),\n created_at: z.string(),\n status: z.enum(['pending', 'active', 'expired']),\n })\n .strict();\n\nexport type Member = z.infer<typeof MemberSchema>;\n\nexport const InviteCreatedSchema = z\n .object({\n member: MemberSchema,\n invite_url: z.string(),\n invite_token: z.string(),\n invite_expires_in: z.number().int(),\n /** Whether the invite email was sent successfully (CR-062) */\n mail_sent: z.boolean(),\n })\n .strict();\n\nexport type InviteCreated = z.infer<typeof InviteCreatedSchema>;\n\nexport const TokenMetaSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n })\n .strict();\n\nexport type TokenMeta = z.infer<typeof TokenMetaSchema>;\n\n// TokenIssued = TokenMeta + plaintext token field (returned exactly once)\nexport const TokenIssuedSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n token: z.string(),\n })\n .strict();\n\nexport type TokenIssued = z.infer<typeof TokenIssuedSchema>;\n\nexport const AuthExchangeResponseSchema = z\n .object({\n admin_token: z.string(),\n expires_at: z.string(),\n })\n .strict();\n\nexport type AuthExchangeResponse = z.infer<typeof AuthExchangeResponseSchema>;\n\nexport const ErrorBodySchema = z\n .object({\n error: z.string(),\n details: z.record(z.string(), z.unknown()).optional(),\n })\n .strict();\n\nexport type ErrorBody = z.infer<typeof ErrorBodySchema>;\n\n// ── Items admin API (STORY-004-09) ───────────────────────────────────────────\n\nexport const ItemSummarySchema = z\n .object({\n id: z.string(),\n cleargate_id: z.string(),\n type: z.string(),\n title: z.string(),\n status: z.string(),\n remote_id: z.string().nullable(),\n last_pushed_at: z.string().nullable(),\n pushed_by_member_id: z.string().nullable(),\n version: z.number().int(),\n updated_at: z.string(),\n current_payload: z.record(z.string(), z.unknown()).default({}),\n })\n .strict();\n\nexport type ItemSummary = z.infer<typeof ItemSummarySchema>;\n\nexport const ItemsListResponseSchema = z\n .object({\n items: z.array(ItemSummarySchema),\n next_cursor: z.string().nullable(),\n })\n .strict();\n\nexport type ItemsListResponse = z.infer<typeof ItemsListResponseSchema>;\n\nexport const ItemVersionSchema = z\n .object({\n version: z.number().int(),\n pushed_by_member_id: z.string().nullable(),\n pushed_at: z.string(),\n status: z.string(),\n diff_summary: z.string().nullable(),\n })\n .strict();\n\nexport type ItemVersion = z.infer<typeof ItemVersionSchema>;\n\nexport const ItemVersionsResponseSchema = z\n .object({\n versions: z.array(ItemVersionSchema),\n })\n .strict();\n\nexport type ItemVersionsResponse = z.infer<typeof ItemVersionsResponseSchema>;\n\n// ── Device-flow schemas (STORY-005-06) ───────────────────────────────────────\n\nexport const DeviceStartResponseSchema = z\n .object({\n device_code: z.string(),\n user_code: z.string(),\n verification_uri: z.string(),\n expires_in: z.number().int(),\n interval: z.number().int(),\n })\n .strict();\n\nexport type DeviceStartResponse = z.infer<typeof DeviceStartResponseSchema>;\n\nexport const DevicePollPendingResponseSchema = z\n .object({\n pending: z.literal(true),\n retry_after: z.number().int().optional(),\n })\n .strict();\n\nexport type DevicePollPendingResponse = z.infer<typeof DevicePollPendingResponseSchema>;\n\nexport const DevicePollSuccessResponseSchema = z\n .object({\n pending: z.literal(false),\n admin_token: z.string(),\n expires_at: z.string(),\n admin_user_id: z.string(),\n })\n .strict();\n\nexport type DevicePollSuccessResponse = z.infer<typeof DevicePollSuccessResponseSchema>;\n\n// ── Admin users API (STORY-006-09) ───────────────────────────────────────────\n\nexport const AdminUserSchema = z\n .object({\n id: z.string(),\n github_handle: z.string(),\n github_user_id: z.string().nullable(),\n is_root: z.boolean(),\n disabled_at: z.string().nullable(),\n created_at: z.string(),\n created_by: z.string().nullable(),\n })\n .strict();\n\nexport type AdminUser = z.infer<typeof AdminUserSchema>;\n\nexport const AdminUsersListResponseSchema = z\n .object({\n admin_users: z.array(AdminUserSchema),\n })\n .strict();\n\nexport type AdminUsersListResponse = z.infer<typeof AdminUsersListResponseSchema>;\n\nexport const UsersMeResponseSchema = z\n .object({\n id: z.string(),\n github_handle: z.string(),\n is_root: z.boolean(),\n })\n .strict();\n\nexport type UsersMeResponse = z.infer<typeof UsersMeResponseSchema>;\n","/**\n * Recursively replaces sensitive key values with '<redacted>'.\n * Used in debug log paths — never in success output.\n * Keys stripped: token, refresh_token, invite_token\n */\nconst SENSITIVE_KEYS = new Set(['token', 'refresh_token', 'invite_token']);\n\nexport function redactSensitive(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map((item) => redactSensitive(item));\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key)) {\n result[key] = '<redacted>';\n } else {\n result[key] = redactSensitive(value);\n }\n }\n return result;\n }\n return obj;\n}\n","/**\n * AdminApiClient — typed HTTP client for the ClearGate admin API.\n *\n * Key implementation notes:\n * - CLI method args are camelCase; wire bodies are snake_case (converted internally)\n * - DELETE requests MUST omit Content-Type (Fastify 5 FST_ERR_CTP_EMPTY_JSON_BODY)\n * - All 2xx responses are validated through vendored Zod schemas\n * - Errors map to AdminApiError with kind → exit code table in D6\n */\nimport { AdminApiError } from './errors.js';\nimport {\n ProjectSchema,\n InviteCreatedSchema,\n TokenIssuedSchema,\n type Project,\n type InviteCreated,\n type TokenIssued,\n} from './responses.js';\nimport { redactSensitive } from './redact.js';\n\nexport interface AdminApiClientOptions {\n baseUrl: string;\n token: string;\n fetch?: typeof globalThis.fetch;\n warn?: (msg: string) => void;\n userAgent?: string;\n}\n\nexport interface AdminApiClient {\n createProject(input: { name: string }): Promise<Project>;\n inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated>;\n issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued>;\n revokeToken(input: { tokenId: string }): Promise<void>;\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\nclass AdminApiClientImpl implements AdminApiClient {\n private readonly baseUrl: string;\n private readonly token: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly warn: (msg: string) => void;\n private readonly userAgent: string;\n\n constructor(opts: AdminApiClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, '');\n this.token = opts.token;\n this.fetchFn = opts.fetch ?? globalThis.fetch;\n this.warn = opts.warn ?? defaultWarn;\n this.userAgent = opts.userAgent ?? `cleargate`;\n }\n\n private debugLog(method: string, path: string, status: number, body: unknown): void {\n if (process.env['CLEARGATE_LOG_LEVEL'] === 'debug') {\n const redacted = redactSensitive(body);\n this.warn(`[admin-api] ${method} ${path} → ${status} ${JSON.stringify(redacted)}`);\n }\n }\n\n private async request<T>(\n method: string,\n urlPath: string,\n body?: Record<string, unknown>,\n ): Promise<T> {\n const url = `${this.baseUrl}/admin-api/v1${urlPath}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': this.userAgent,\n Accept: 'application/json',\n };\n\n // CRITICAL: omit Content-Type on requests without body (DELETE)\n // Fastify 5 throws FST_ERR_CTP_EMPTY_JSON_BODY if Content-Type is set with empty body\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n throw new AdminApiError(\n 'network',\n null,\n err,\n `cannot reach ${this.baseUrl} (${err instanceof Error ? err.message : String(err)})`,\n );\n }\n\n // Parse response body when present\n let responseBody: unknown = null;\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n try {\n responseBody = await response.json();\n } catch {\n responseBody = null;\n }\n }\n\n this.debugLog(method, urlPath, response.status, responseBody);\n\n // Map HTTP status to AdminApiError kinds\n if (!response.ok) {\n const errBody = responseBody as { error?: string; details?: unknown } | null;\n if (response.status === 401) {\n throw new AdminApiError('auth', 401, responseBody, 'Admin token rejected.');\n }\n if (response.status === 403) {\n throw new AdminApiError('forbidden', 403, responseBody, 'Token is not admin-role.');\n }\n if (response.status === 404) {\n throw new AdminApiError('not_found', 404, responseBody, 'Not found.');\n }\n if (response.status === 400 || response.status === 409) {\n throw new AdminApiError(\n 'invalid_request',\n response.status,\n errBody?.details ?? responseBody,\n `Invalid request: ${errBody?.error ?? 'unknown'}`,\n );\n }\n if (response.status >= 500) {\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Server error ${response.status}.`,\n );\n }\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Unexpected status ${response.status}.`,\n );\n }\n\n return responseBody as T;\n }\n\n async createProject(input: { name: string }): Promise<Project> {\n const raw = await this.request<unknown>('POST', '/projects', { name: input.name });\n const parsed = ProjectSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated> {\n const body: Record<string, unknown> = {\n email: input.email,\n role: input.role,\n };\n if (input.displayName !== undefined) {\n body['display_name'] = input.displayName;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/members`,\n body,\n );\n const parsed = InviteCreatedSchema.safeParse(raw);\n if (!parsed.success) {\n // Try MemberSchema in case the server returned a member-only response\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued> {\n const body: Record<string, unknown> = {\n member_id: input.memberId,\n name: input.name,\n };\n if (input.expiresAt !== undefined) {\n body['expires_at'] = input.expiresAt;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/tokens`,\n body,\n );\n const parsed = TokenIssuedSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async revokeToken(input: { tokenId: string }): Promise<void> {\n // No body — Content-Type must be omitted (Fastify 5 CTP quirk)\n await this.request<unknown>('DELETE', `/tokens/${input.tokenId}`, undefined);\n }\n}\n\nexport function createAdminApiClient(opts: AdminApiClientOptions): AdminApiClient {\n return new AdminApiClientImpl(opts);\n}\n","/**\n * Loads an admin JWT for use with cleargate-admin CLI commands.\n *\n * Load order:\n * 1. CLEARGATE_ADMIN_TOKEN env var (wins immediately — file is not read)\n * 2. ~/.cleargate/admin-auth.json (shape: { version: 1, token: string })\n *\n * DISTINCT from FileTokenStore: that file holds user profile → refresh-token maps.\n * This file holds a single admin JWT acquired out-of-band via dev-issue-token.\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nexport const AdminAuthFileSchema = z\n .object({\n version: z.literal(1),\n token: z.string().min(1),\n })\n .strict();\n\nexport interface LoadAdminAuthOptions {\n env?: NodeJS.ProcessEnv;\n filePath?: string;\n homedir?: () => string;\n warn?: (msg: string) => void;\n}\n\nexport interface AdminAuth {\n token: string;\n source: 'env' | 'file';\n}\n\nconst MISSING_TOKEN_ERROR =\n 'No admin token. Set CLEARGATE_ADMIN_TOKEN or write ~/.cleargate/admin-auth.json (chmod 600). See README §admin-jwt.';\n\nexport function loadAdminAuth(opts?: LoadAdminAuthOptions): AdminAuth {\n const env = opts?.env ?? process.env;\n const warn = opts?.warn ?? ((msg: string) => process.stderr.write(msg + '\\n'));\n\n // Env wins — file is not read at all when env is set\n const envToken = env['CLEARGATE_ADMIN_TOKEN'];\n if (envToken) {\n return { token: envToken, source: 'env' };\n }\n\n // Resolve file path\n const homedirFn = opts?.homedir ?? os.homedir;\n const filePath =\n opts?.filePath ?? path.join(homedirFn(), '.cleargate', 'admin-auth.json');\n\n // Try file\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch (err) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(MISSING_TOKEN_ERROR);\n }\n throw new Error(`Failed to read admin-auth file at ${filePath}: ${String(err)}`);\n }\n\n // Check file permissions (warn if too permissive)\n try {\n const stat = fs.statSync(filePath);\n const mode = stat.mode & 0o777;\n if (mode & 0o077) {\n warn(\n `cleargate-admin: warning: ${filePath} is group/world readable (mode ${(mode).toString(8).padStart(3, '0')}). Run: chmod 600 ${filePath}`,\n );\n }\n } catch {\n // If we can't stat the file, ignore — the read already succeeded\n }\n\n // Parse JSON\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Failed to parse admin-auth file at ${filePath}: invalid JSON`);\n }\n\n // Validate with strict schema\n const result = AdminAuthFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `Invalid admin-auth file at ${filePath}: ${result.error.message}`,\n );\n }\n\n return { token: result.data.token, source: 'file' };\n}\n"],"mappings":"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","names":["path","import_zod"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/admin-api/index.ts","../../src/admin-api/client.ts","../../src/admin-api/errors.ts","../../src/admin-api/responses.ts","../../src/admin-api/redact.ts","../../src/admin-api/admin-auth.ts"],"sourcesContent":["/**\n * Barrel export for admin-api module.\n * Consumed by cleargate-admin commands via cleargate/admin-api.\n */\nexport * from './client.js';\nexport * from './errors.js';\nexport * from './redact.js';\nexport * from './responses.js';\nexport * from './admin-auth.js';\n","/**\n * AdminApiClient — typed HTTP client for the ClearGate admin API.\n *\n * Key implementation notes:\n * - CLI method args are camelCase; wire bodies are snake_case (converted internally)\n * - DELETE requests MUST omit Content-Type (Fastify 5 FST_ERR_CTP_EMPTY_JSON_BODY)\n * - All 2xx responses are validated through vendored Zod schemas\n * - Errors map to AdminApiError with kind → exit code table in D6\n */\nimport { AdminApiError } from './errors.js';\nimport {\n ProjectSchema,\n InviteCreatedSchema,\n TokenIssuedSchema,\n type Project,\n type InviteCreated,\n type TokenIssued,\n} from './responses.js';\nimport { redactSensitive } from './redact.js';\n\nexport interface AdminApiClientOptions {\n baseUrl: string;\n token: string;\n fetch?: typeof globalThis.fetch;\n warn?: (msg: string) => void;\n userAgent?: string;\n}\n\nexport interface AdminApiClient {\n createProject(input: { name: string }): Promise<Project>;\n inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated>;\n issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued>;\n revokeToken(input: { tokenId: string }): Promise<void>;\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\nclass AdminApiClientImpl implements AdminApiClient {\n private readonly baseUrl: string;\n private readonly token: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly warn: (msg: string) => void;\n private readonly userAgent: string;\n\n constructor(opts: AdminApiClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, '');\n this.token = opts.token;\n this.fetchFn = opts.fetch ?? globalThis.fetch;\n this.warn = opts.warn ?? defaultWarn;\n this.userAgent = opts.userAgent ?? `cleargate`;\n }\n\n private debugLog(method: string, path: string, status: number, body: unknown): void {\n if (process.env['CLEARGATE_LOG_LEVEL'] === 'debug') {\n const redacted = redactSensitive(body);\n this.warn(`[admin-api] ${method} ${path} → ${status} ${JSON.stringify(redacted)}`);\n }\n }\n\n private async request<T>(\n method: string,\n urlPath: string,\n body?: Record<string, unknown>,\n ): Promise<T> {\n const url = `${this.baseUrl}/admin-api/v1${urlPath}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': this.userAgent,\n Accept: 'application/json',\n };\n\n // CRITICAL: omit Content-Type on requests without body (DELETE)\n // Fastify 5 throws FST_ERR_CTP_EMPTY_JSON_BODY if Content-Type is set with empty body\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n throw new AdminApiError(\n 'network',\n null,\n err,\n `cannot reach ${this.baseUrl} (${err instanceof Error ? err.message : String(err)})`,\n );\n }\n\n // Parse response body when present\n let responseBody: unknown = null;\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n try {\n responseBody = await response.json();\n } catch {\n responseBody = null;\n }\n }\n\n this.debugLog(method, urlPath, response.status, responseBody);\n\n // Map HTTP status to AdminApiError kinds\n if (!response.ok) {\n const errBody = responseBody as { error?: string; details?: unknown } | null;\n if (response.status === 401) {\n throw new AdminApiError('auth', 401, responseBody, 'Admin token rejected.');\n }\n if (response.status === 403) {\n throw new AdminApiError('forbidden', 403, responseBody, 'Token is not admin-role.');\n }\n if (response.status === 404) {\n throw new AdminApiError('not_found', 404, responseBody, 'Not found.');\n }\n if (response.status === 400 || response.status === 409) {\n throw new AdminApiError(\n 'invalid_request',\n response.status,\n errBody?.details ?? responseBody,\n `Invalid request: ${errBody?.error ?? 'unknown'}`,\n );\n }\n if (response.status >= 500) {\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Server error ${response.status}.`,\n );\n }\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Unexpected status ${response.status}.`,\n );\n }\n\n return responseBody as T;\n }\n\n async createProject(input: { name: string }): Promise<Project> {\n const raw = await this.request<unknown>('POST', '/projects', { name: input.name });\n const parsed = ProjectSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated> {\n const body: Record<string, unknown> = {\n email: input.email,\n role: input.role,\n };\n if (input.displayName !== undefined) {\n body['display_name'] = input.displayName;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/members`,\n body,\n );\n const parsed = InviteCreatedSchema.safeParse(raw);\n if (!parsed.success) {\n // Try MemberSchema in case the server returned a member-only response\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued> {\n const body: Record<string, unknown> = {\n member_id: input.memberId,\n name: input.name,\n };\n if (input.expiresAt !== undefined) {\n body['expires_at'] = input.expiresAt;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/tokens`,\n body,\n );\n const parsed = TokenIssuedSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async revokeToken(input: { tokenId: string }): Promise<void> {\n // No body — Content-Type must be omitted (Fastify 5 CTP quirk)\n await this.request<unknown>('DELETE', `/tokens/${input.tokenId}`, undefined);\n }\n}\n\nexport function createAdminApiClient(opts: AdminApiClientOptions): AdminApiClient {\n return new AdminApiClientImpl(opts);\n}\n","/**\n * Typed error class for all admin API failures.\n * kind → exit code mapping lives in mcp/scripts/commands/_render-error.ts\n */\nexport class AdminApiError extends Error {\n constructor(\n public readonly kind:\n | 'network'\n | 'auth'\n | 'forbidden'\n | 'not_found'\n | 'invalid_request'\n | 'server'\n | 'response_shape',\n public readonly status: number | null,\n public readonly details: unknown,\n message: string,\n ) {\n super(message);\n this.name = 'AdminApiError';\n }\n}\n","/**\n * Vendored Zod response schemas — hand-authored from\n * mcp/src/admin-api/__snapshots__/openapi.test.ts.snap\n *\n * Snapshot drift is detected by cleargate-cli/test/admin-api/snapshot-drift.test.ts,\n * which reads the snapshot file at runtime and asserts field-set equality.\n */\nimport { z } from 'zod';\n\nexport const ProjectSchema = z\n .object({\n id: z.string(),\n name: z.string(),\n created_by: z.string(),\n created_at: z.string(),\n deleted_at: z.string().nullable(),\n })\n .strict();\n\nexport type Project = z.infer<typeof ProjectSchema>;\n\nexport const MemberSchema = z\n .object({\n id: z.string(),\n project_id: z.string(),\n email: z.string(),\n role: z.string(),\n display_name: z.string().nullable().optional(),\n created_at: z.string(),\n status: z.enum(['pending', 'active', 'expired']),\n })\n .strict();\n\nexport type Member = z.infer<typeof MemberSchema>;\n\nexport const InviteCreatedSchema = z\n .object({\n member: MemberSchema,\n invite_url: z.string(),\n invite_token: z.string(),\n invite_expires_in: z.number().int(),\n /** Whether the invite email was sent successfully (CR-062) */\n mail_sent: z.boolean(),\n })\n .strict();\n\nexport type InviteCreated = z.infer<typeof InviteCreatedSchema>;\n\nexport const TokenMetaSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n })\n .strict();\n\nexport type TokenMeta = z.infer<typeof TokenMetaSchema>;\n\n// TokenIssued = TokenMeta + plaintext token field (returned exactly once)\nexport const TokenIssuedSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n token: z.string(),\n })\n .strict();\n\nexport type TokenIssued = z.infer<typeof TokenIssuedSchema>;\n\nexport const AuthExchangeResponseSchema = z\n .object({\n admin_token: z.string(),\n expires_at: z.string(),\n })\n .strict();\n\nexport type AuthExchangeResponse = z.infer<typeof AuthExchangeResponseSchema>;\n\nexport const ErrorBodySchema = z\n .object({\n error: z.string(),\n details: z.record(z.string(), z.unknown()).optional(),\n })\n .strict();\n\nexport type ErrorBody = z.infer<typeof ErrorBodySchema>;\n\n// ── Items admin API (STORY-004-09) ───────────────────────────────────────────\n\nexport const ItemSummarySchema = z\n .object({\n id: z.string(),\n cleargate_id: z.string(),\n type: z.string(),\n title: z.string(),\n status: z.string(),\n remote_id: z.string().nullable(),\n last_pushed_at: z.string().nullable(),\n pushed_by_member_id: z.string().nullable(),\n version: z.number().int(),\n updated_at: z.string(),\n current_payload: z.record(z.string(), z.unknown()).default({}),\n })\n .strict();\n\nexport type ItemSummary = z.infer<typeof ItemSummarySchema>;\n\nexport const ItemsListResponseSchema = z\n .object({\n items: z.array(ItemSummarySchema),\n next_cursor: z.string().nullable(),\n })\n .strict();\n\nexport type ItemsListResponse = z.infer<typeof ItemsListResponseSchema>;\n\nexport const ItemVersionSchema = z\n .object({\n version: z.number().int(),\n pushed_by_member_id: z.string().nullable(),\n pushed_at: z.string(),\n status: z.string(),\n diff_summary: z.string().nullable(),\n })\n .strict();\n\nexport type ItemVersion = z.infer<typeof ItemVersionSchema>;\n\nexport const ItemVersionsResponseSchema = z\n .object({\n versions: z.array(ItemVersionSchema),\n })\n .strict();\n\nexport type ItemVersionsResponse = z.infer<typeof ItemVersionsResponseSchema>;\n\n// ── Device-flow schemas (STORY-005-06) ───────────────────────────────────────\n\nexport const DeviceStartResponseSchema = z\n .object({\n device_code: z.string(),\n user_code: z.string(),\n verification_uri: z.string(),\n expires_in: z.number().int(),\n interval: z.number().int(),\n })\n .strict();\n\nexport type DeviceStartResponse = z.infer<typeof DeviceStartResponseSchema>;\n\nexport const DevicePollPendingResponseSchema = z\n .object({\n pending: z.literal(true),\n retry_after: z.number().int().optional(),\n })\n .strict();\n\nexport type DevicePollPendingResponse = z.infer<typeof DevicePollPendingResponseSchema>;\n\nexport const DevicePollSuccessResponseSchema = z\n .object({\n pending: z.literal(false),\n admin_token: z.string(),\n expires_at: z.string(),\n admin_user_id: z.string(),\n })\n .strict();\n\nexport type DevicePollSuccessResponse = z.infer<typeof DevicePollSuccessResponseSchema>;\n\n// ── Admin users API (STORY-006-09) ───────────────────────────────────────────\n\nexport const AdminUserSchema = z\n .object({\n id: z.string(),\n github_handle: z.string(),\n github_user_id: z.string().nullable(),\n is_root: z.boolean(),\n disabled_at: z.string().nullable(),\n created_at: z.string(),\n created_by: z.string().nullable(),\n })\n .strict();\n\nexport type AdminUser = z.infer<typeof AdminUserSchema>;\n\nexport const AdminUsersListResponseSchema = z\n .object({\n admin_users: z.array(AdminUserSchema),\n })\n .strict();\n\nexport type AdminUsersListResponse = z.infer<typeof AdminUsersListResponseSchema>;\n\nexport const UsersMeResponseSchema = z\n .object({\n id: z.string(),\n github_handle: z.string(),\n is_root: z.boolean(),\n })\n .strict();\n\nexport type UsersMeResponse = z.infer<typeof UsersMeResponseSchema>;\n","/**\n * Recursively replaces sensitive key values with '<redacted>'.\n * Used in debug log paths — never in success output.\n * Keys stripped: token, refresh_token, invite_token\n */\nconst SENSITIVE_KEYS = new Set(['token', 'refresh_token', 'invite_token']);\n\nexport function redactSensitive(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map((item) => redactSensitive(item));\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key)) {\n result[key] = '<redacted>';\n } else {\n result[key] = redactSensitive(value);\n }\n }\n return result;\n }\n return obj;\n}\n","/**\n * Loads an admin JWT for use with cleargate-admin CLI commands.\n *\n * Load order:\n * 1. CLEARGATE_ADMIN_TOKEN env var (wins immediately — file is not read)\n * 2. ~/.cleargate/admin-auth.json (shape: { version: 1, token: string })\n *\n * DISTINCT from FileTokenStore: that file holds user profile → refresh-token maps.\n * This file holds a single admin JWT acquired out-of-band via dev-issue-token.\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nexport const AdminAuthFileSchema = z\n .object({\n version: z.literal(1),\n token: z.string().min(1),\n })\n .strict();\n\nexport interface LoadAdminAuthOptions {\n env?: NodeJS.ProcessEnv;\n filePath?: string;\n homedir?: () => string;\n warn?: (msg: string) => void;\n}\n\nexport interface AdminAuth {\n token: string;\n source: 'env' | 'file';\n}\n\nconst MISSING_TOKEN_ERROR =\n 'No admin token. Set CLEARGATE_ADMIN_TOKEN or write ~/.cleargate/admin-auth.json (chmod 600). See README §admin-jwt.';\n\nexport function loadAdminAuth(opts?: LoadAdminAuthOptions): AdminAuth {\n const env = opts?.env ?? process.env;\n const warn = opts?.warn ?? ((msg: string) => process.stderr.write(msg + '\\n'));\n\n // Env wins — file is not read at all when env is set\n const envToken = env['CLEARGATE_ADMIN_TOKEN'];\n if (envToken) {\n return { token: envToken, source: 'env' };\n }\n\n // Resolve file path\n const homedirFn = opts?.homedir ?? os.homedir;\n const filePath =\n opts?.filePath ?? path.join(homedirFn(), '.cleargate', 'admin-auth.json');\n\n // Try file\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch (err) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(MISSING_TOKEN_ERROR);\n }\n throw new Error(`Failed to read admin-auth file at ${filePath}: ${String(err)}`);\n }\n\n // Check file permissions (warn if too permissive)\n try {\n const stat = fs.statSync(filePath);\n const mode = stat.mode & 0o777;\n if (mode & 0o077) {\n warn(\n `cleargate-admin: warning: ${filePath} is group/world readable (mode ${(mode).toString(8).padStart(3, '0')}). Run: chmod 600 ${filePath}`,\n );\n }\n } catch {\n // If we can't stat the file, ignore — the read already succeeded\n }\n\n // Parse JSON\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Failed to parse admin-auth file at ${filePath}: invalid JSON`);\n }\n\n // Validate with strict schema\n const result = AdminAuthFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `Invalid admin-auth file at ${filePath}: ${result.error.message}`,\n );\n }\n\n return { token: result.data.token, source: 'file' };\n}\n"],"mappings":"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","names":["path","z"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/factory.ts","../../src/auth/keychain-store.ts","../../src/auth/file-store.ts"],"sourcesContent":["import * as os from 'node:os';\nimport * as path from 'node:path';\nimport { KeychainTokenStore } from './keychain-store.js';\nimport { FileTokenStore } from './file-store.js';\nimport type { TokenStore, TokenStoreFactoryOptions } from './token-store.js';\n\nconst DEFAULT_KEYCHAIN_SERVICE = 'cleargate';\n\nfunction resolveFilePath(opts: TokenStoreFactoryOptions): string {\n if (opts.filePath) return opts.filePath;\n const home = os.homedir();\n if (!home) {\n throw new Error(\n 'Cannot determine home directory. Set opts.filePath explicitly or ensure os.homedir() returns a non-empty string.',\n );\n }\n return path.join(home, '.cleargate', 'auth.json');\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\n/**\n * Creates a TokenStore, selecting the keychain backend when available and\n * falling back to file storage with a stderr warning when the OS keychain\n * cannot be accessed.\n */\nexport async function createTokenStore(\n opts: TokenStoreFactoryOptions = {},\n): Promise<TokenStore> {\n const filePath = resolveFilePath(opts);\n const service = opts.keychainService ?? DEFAULT_KEYCHAIN_SERVICE;\n const warn = opts.warn ?? defaultWarn;\n\n // Short-circuit if backend is forced (test seam, skips probe)\n if (opts.forceBackend === 'file') {\n return new FileTokenStore(filePath);\n }\n if (opts.forceBackend === 'keychain') {\n return new KeychainTokenStore(service);\n }\n\n // Probe the keychain to determine availability\n try {\n const { Entry } = await import('@napi-rs/keyring');\n new Entry(service, '__cleargate_probe__').getPassword();\n // Probe succeeded (returned string | null cleanly) — use keychain\n return new KeychainTokenStore(service);\n } catch {\n // Constructor threw (native module load failed, libsecret missing on Linux)\n // OR getPassword() threw (dbus not running, prompt cancelled)\n // Either way, keychain is unavailable for this CLI invocation\n warn(\n `cleargate: OS keychain unavailable, falling back to file storage at ${filePath}. Run with --log-level=debug for details.`,\n );\n return new FileTokenStore(filePath);\n }\n}\n","import { Entry } from '@napi-rs/keyring';\nimport type { TokenStore } from './token-store.js';\n\nexport class KeychainTokenStore implements TokenStore {\n readonly backend = 'keychain' as const;\n\n constructor(private readonly service: string) {}\n\n async save(profile: string, token: string): Promise<void> {\n new Entry(this.service, profile).setPassword(token);\n }\n\n async load(profile: string): Promise<string | null> {\n try {\n const result = new Entry(this.service, profile).getPassword();\n // getPassword() returns string | null per @napi-rs/keyring@1.2.0 index.d.ts:124\n // Despite the docstring claiming it throws NoEntry, the return type wins.\n // Handle both: null return AND potential thrown NoEntry (platform-specific).\n return result ?? null;\n } catch {\n // NoEntry or other keychain error — treat as absent\n return null;\n }\n }\n\n async remove(profile: string): Promise<void> {\n try {\n new Entry(this.service, profile).deletePassword();\n } catch {\n // Entry didn't exist or other keychain error — idempotent, swallow\n }\n }\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport type { TokenStore } from './token-store.js';\n\nconst ProfileEntrySchema = z.object({ refreshToken: z.string().min(1) }).strict();\n\nexport const AuthFileSchema = z\n .object({\n version: z.literal(1),\n profiles: z.record(z.string().min(1), ProfileEntrySchema),\n })\n .strict();\n\ntype AuthFile = z.infer<typeof AuthFileSchema>;\n\nconst EMPTY_AUTH_FILE: AuthFile = { version: 1, profiles: {} };\n\nexport class FileTokenStore implements TokenStore {\n readonly backend = 'file' as const;\n\n constructor(private readonly filePath: string) {}\n\n async save(profile: string, token: string): Promise<void> {\n const current = await this.readFile();\n const updated: AuthFile = {\n ...current,\n profiles: {\n ...current.profiles,\n [profile]: { refreshToken: token },\n },\n };\n await this.writeFile(updated);\n }\n\n async load(profile: string): Promise<string | null> {\n const data = await this.readFile();\n return data.profiles[profile]?.refreshToken ?? null;\n }\n\n async remove(profile: string): Promise<void> {\n let current: AuthFile;\n try {\n current = await this.readFile();\n } catch {\n // File doesn't exist or unreadable — no-op since there's nothing to remove\n return;\n }\n if (!(profile in current.profiles)) {\n return; // Profile doesn't exist — idempotent\n }\n const { [profile]: _removed, ...rest } = current.profiles;\n const updated: AuthFile = { ...current, profiles: rest };\n await this.writeFile(updated);\n }\n\n private async readFile(): Promise<AuthFile> {\n let raw: string;\n try {\n raw = await fs.readFile(this.filePath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return EMPTY_AUTH_FILE;\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n `Failed to parse auth file at ${this.filePath}: invalid JSON`,\n );\n }\n\n const result = AuthFileSchema.safeParse(parsed);\n if (!result.success) {\n // Check for version mismatch specifically\n const versionCheck = (parsed as Record<string, unknown>)?.['version'];\n if (versionCheck !== 1) {\n throw new Error(\n `Invalid auth file at ${this.filePath}: unsupported version ${String(versionCheck)}. Please upgrade \\`cleargate\\` to read this file.`,\n );\n }\n throw new Error(\n `Invalid auth file at ${this.filePath}: ${result.error.message}`,\n );\n }\n\n return result.data;\n }\n\n private async writeFile(data: AuthFile): Promise<void> {\n const dir = path.dirname(this.filePath);\n await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n // Explicit chmod after mkdir — mkdir only sets mode on newly created dirs\n await fs.chmod(dir, 0o700).catch(() => {\n // If chmod fails on existing dir, that's acceptable — we don't want to\n // surprise users who have set custom modes on ~/.cleargate/\n });\n\n const json = JSON.stringify(data, null, 2);\n const tmpPath = path.join(dir, '.auth.json.tmp');\n\n // Atomic write: write to tmp then rename to avoid partial-write corruption\n await fs.writeFile(tmpPath, json, { mode: 0o600 });\n // Explicit chmod after writeFile — writeFile only sets mode on file creation\n await fs.chmod(tmpPath, 0o600);\n await fs.rename(tmpPath, this.filePath);\n // After rename, chmod the final path to ensure it stays 0600\n await fs.chmod(this.filePath, 0o600);\n }\n}\n"],"mappings":"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","names":["path","Entry"]}
|
package/dist/auth/factory.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/require-token.ts"],"sourcesContent":["import type { TokenStore } from './token-store.js';\n\n/**\n * Asserts a refresh token is present for the given profile.\n * Throws a user-friendly error if the token is missing.\n *\n * Mirrors requireMcpUrl from config.ts — single throw site for \"missing token\".\n */\nexport async function requireToken(\n profile: string,\n store: TokenStore,\n): Promise<string> {\n const token = await store.load(profile);\n if (token === null) {\n throw new Error(\n `No refresh token for profile \"${profile}\". Run \\`cleargate join <invite-url>\\` first.`,\n );\n }\n return token;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,eAAsB,aACpB,SACA,OACiB;AACjB,QAAM,QAAQ,MAAM,MAAM,KAAK,OAAO;AACtC,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI;AAAA,MACR,iCAAiC,OAAO;AAAA,IAC1C;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/require-token.ts"],"sourcesContent":["import type { TokenStore } from './token-store.js';\n\n/**\n * Asserts a refresh token is present for the given profile.\n * Throws a user-friendly error if the token is missing.\n *\n * Mirrors requireMcpUrl from config.ts — single throw site for \"missing token\".\n */\nexport async function requireToken(\n profile: string,\n store: TokenStore,\n): Promise<string> {\n const token = await store.load(profile);\n if (token === null) {\n throw new Error(\n `No refresh token for profile \"${profile}\". Run \\`cleargate join <invite-url>\\` first.`,\n );\n }\n return token;\n}\n"],"mappings":";;;;;;;;AAAA;AAQA,eAAsB,aACpB,SACA,OACiB;AACjB,QAAM,QAAQ,MAAM,MAAM,KAAK,OAAO;AACtC,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI;AAAA,MACR,iCAAiC,OAAO;AAAA,IAC1C;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/token-store.ts"],"sourcesContent":["/**\n * TokenStore — interface + factory options types only.\n * Zero runtime imports (consumers can import types cheaply).\n */\n\nexport interface TokenStore {\n /** Persist a refresh token for the named profile. Overwrites any existing value. */\n save(profile: string, token: string): Promise<void>;\n /** Return the stored refresh token for the profile, or null if none. */\n load(profile: string): Promise<string | null>;\n /** Remove the stored token for the profile. Idempotent — no error if absent. */\n remove(profile: string): Promise<void>;\n /** Backend identifier for diagnostics. */\n readonly backend: 'keychain' | 'file';\n}\n\nexport interface TokenStoreFactoryOptions {\n /** Override file path for FileTokenStore (test seam). Default: ~/.cleargate/auth.json */\n filePath?: string;\n /** Override keychain service name (test seam). Default: \"cleargate\". */\n keychainService?: string;\n /** Force a backend, bypassing detection. Used by tests. */\n forceBackend?: 'keychain' | 'file';\n /** stderr writer for the \"keychain unavailable\" warning. Default: process.stderr.write. */\n warn?: (msg: string) => void;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/token-store.ts"],"sourcesContent":["/**\n * TokenStore — interface + factory options types only.\n * Zero runtime imports (consumers can import types cheaply).\n */\n\nexport interface TokenStore {\n /** Persist a refresh token for the named profile. Overwrites any existing value. */\n save(profile: string, token: string): Promise<void>;\n /** Return the stored refresh token for the profile, or null if none. */\n load(profile: string): Promise<string | null>;\n /** Remove the stored token for the profile. Idempotent — no error if absent. */\n remove(profile: string): Promise<void>;\n /** Backend identifier for diagnostics. */\n readonly backend: 'keychain' | 'file';\n}\n\nexport interface TokenStoreFactoryOptions {\n /** Override file path for FileTokenStore (test seam). Default: ~/.cleargate/auth.json */\n filePath?: string;\n /** Override keychain service name (test seam). Default: \"cleargate\". */\n keychainService?: string;\n /** Force a backend, bypassing detection. Used by tests. */\n forceBackend?: 'keychain' | 'file';\n /** stderr writer for the \"keychain unavailable\" warning. Default: process.stderr.write. */\n warn?: (msg: string) => void;\n}\n"],"mappings":";;;;;;;;AAAA;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/bootstrap-root.ts"],"sourcesContent":["/**\n * `cleargate admin bootstrap-root <handle>` — seed the first root admin.\n *\n * Idempotent: running the same handle twice is safe. Provides a `--force` flag\n * to either promote an existing non-root user or insert a second root when one\n * already exists.\n *\n * Exit codes:\n * 0 — success (including idempotent no-op)\n * 1 — validation error (bad handle, second-root guard refused)\n * 2 — missing DATABASE_URL\n * 3 — connection / query failure\n *\n * DATABASE_URL password is scrubbed from all error output.\n *\n * STORY-011-03.\n */\n\nimport pg from 'pg';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n// GitHub handle grammar: starts with alphanumeric, ends with alphanumeric,\n// allows hyphens in the middle, max 39 chars total.\n// Single-char handles (no middle section) are allowed.\n// Trailing hyphens are rejected by requiring the last char to be alphanumeric\n// when the handle is longer than 1 char.\nconst HANDLE_RE = /^[A-Za-z0-9]([A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;\n\nexport function isValidHandle(handle: string): boolean {\n return HANDLE_RE.test(handle);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Password scrubber\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SCRUB = /(postgres(?:ql)?:\\/\\/[^:/@]+):[^@/]+@/gi;\n\nexport function scrubPassword(s: string): string {\n return s.replace(SCRUB, '$1:***@');\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Minimal pg client surface needed by this handler. */\nexport interface PgClientLike {\n // connect() returns Promise<void> or Promise<Client> depending on pg version.\n // We accept either by using Promise<unknown>.\n connect(): Promise<unknown>;\n query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[] }>;\n end(): Promise<void>;\n}\n\nexport interface BootstrapRootOptions {\n handle: string;\n databaseUrl?: string;\n force?: boolean;\n env?: NodeJS.ProcessEnv;\n stdout?: (s: string) => void;\n stderr?: (s: string) => void;\n exit?: (code: number) => never;\n /** Test seam — factory receives the resolved connection string */\n pgClientFactory?: (url: string) => PgClientLike;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal result — avoids exception-based control flow bleeding into the\n// SQL error catch block (exit seam throws in tests; we must not catch those).\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface SqlResult {\n exitCode: number;\n kind: 'stdout' | 'stderr';\n message: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Main handler\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport async function bootstrapRootHandler(opts: BootstrapRootOptions): Promise<void> {\n const env = opts.env ?? process.env;\n const stdoutFn = opts.stdout ?? ((s: string) => process.stdout.write(s + '\\n'));\n const stderrFn = opts.stderr ?? ((s: string) => process.stderr.write(s + '\\n'));\n const exitFn = opts.exit ?? ((code: number): never => process.exit(code));\n const force = opts.force ?? false;\n\n // ── 1. Handle validation ────────────────────────────────────────────────────\n const { handle } = opts;\n if (!isValidHandle(handle)) {\n stderrFn(`cleargate: error: '${handle}' is not a valid GitHub handle`);\n return exitFn(1);\n }\n\n // ── 2. Resolve DATABASE_URL ─────────────────────────────────────────────────\n const url = opts.databaseUrl ?? env['DATABASE_URL'];\n if (!url) {\n stderrFn('cleargate: error: DATABASE_URL is required (set env or pass --database-url)');\n return exitFn(2);\n }\n\n // ── 3. Connect ──────────────────────────────────────────────────────────────\n const clientFactory =\n opts.pgClientFactory ??\n ((connStr: string): PgClientLike => new pg.Client({ connectionString: connStr }));\n\n const client = clientFactory(url);\n\n try {\n await client.connect();\n } catch (err) {\n const raw = err instanceof Error ? `${err.message} ${err.stack ?? ''}` : String(err);\n stderrFn(`cleargate: error: cannot reach database (${scrubPassword(raw.trim())})`);\n return exitFn(3);\n }\n\n // ── 4. Run SQL (returns a SqlResult, never throws for logic branches) ────────\n // By returning a value rather than calling exitFn() inside runSql(), we ensure\n // that if the test exit-seam throws, the throw propagates out of the whole\n // handler rather than being caught here as a \"database error\".\n let result: SqlResult;\n let sqlError: unknown = undefined;\n\n try {\n result = await runSql(client, handle, force);\n } catch (err) {\n sqlError = err;\n result = { exitCode: 3, kind: 'stderr', message: '' }; // filled below\n }\n\n // ── 5. Close connection ─────────────────────────────────────────────────────\n try {\n await client.end();\n } catch {\n // ignore cleanup error\n }\n\n // ── 6. Handle SQL error ─────────────────────────────────────────────────────\n if (sqlError !== undefined) {\n const err = sqlError;\n const raw = err instanceof Error ? `${err.message} ${err.stack ?? ''}` : String(err);\n stderrFn(`cleargate: error: cannot reach database (${scrubPassword(raw.trim())})`);\n return exitFn(3);\n }\n\n // ── 7. Emit message and exit ────────────────────────────────────────────────\n if (result!.kind === 'stdout') {\n stdoutFn(result!.message);\n } else {\n stderrFn(result!.message);\n }\n return exitFn(result!.exitCode);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SQL logic — returns SqlResult, never calls exitFn\n// ─────────────────────────────────────────────────────────────────────────────\n\nasync function runSql(\n client: PgClientLike,\n handle: string,\n force: boolean,\n): Promise<SqlResult> {\n await client.query('BEGIN');\n\n // Step 1: does the handle already exist?\n const existing = await client.query(\n 'SELECT id, is_root FROM admin_users WHERE github_handle = $1',\n [handle],\n );\n\n if (existing.rows.length > 0) {\n const row = existing.rows[0]!;\n\n if (row['is_root'] === true) {\n // Branch A: already a root admin — idempotent no-op\n await client.query('COMMIT');\n return {\n exitCode: 0,\n kind: 'stdout',\n message: `Root admin '${handle}' already exists; no change.`,\n };\n }\n\n // Row exists but is_root=false\n if (force) {\n // Branch B: promote\n await client.query(\n 'UPDATE admin_users SET is_root = true WHERE github_handle = $1',\n [handle],\n );\n await client.query('COMMIT');\n return {\n exitCode: 0,\n kind: 'stdout',\n message: `Promoted '${handle}' to root admin.`,\n };\n }\n\n // Branch C: existing non-root, no --force\n await client.query('ROLLBACK');\n return {\n exitCode: 1,\n kind: 'stderr',\n message: `cleargate: error: '${handle}' exists but is not a root admin; pass --force to promote`,\n };\n }\n\n // No row for this handle — check if another root exists\n if (!force) {\n const rootCount = await client.query(\n 'SELECT count(*) AS cnt FROM admin_users WHERE is_root = true',\n );\n const cnt = Number((rootCount.rows[0] as { cnt: string })['cnt'] ?? 0);\n\n if (cnt >= 1) {\n // Branch D: refuse second root without --force\n await client.query('ROLLBACK');\n return {\n exitCode: 1,\n kind: 'stderr',\n message:\n 'cleargate: error: refusing to create a second root admin; pass --force to override',\n };\n }\n }\n\n // Branch E: insert (first root, or --force allows a second root)\n await client.query(\n `INSERT INTO admin_users (github_handle, is_root)\n VALUES ($1, true)\n ON CONFLICT (github_handle) DO UPDATE SET is_root = EXCLUDED.is_root`,\n [handle],\n );\n await client.query('COMMIT');\n return {\n exitCode: 0,\n kind: 'stdout',\n message: `Bootstrapped root admin '${handle}'.`,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/lifecycle-reconcile.ts","../src/wiki/parse-frontmatter.ts","../src/lib/parent-rollup.ts"],"sourcesContent":["/**\n * lifecycle-reconcile.ts — CR-017 Lifecycle Status Reconciliation + Decomposition Gate\n *\n * Public API:\n * reconcileLifecycle(opts) → { drift: DriftItem[], clean: number }\n * reconcileDecomposition(opts) → { missing: MissingDecomp[], clean: number }\n * reconcileCurrentSprintStories(opts) → ReconcileCurrentSprintResult (BUG-032)\n * parseCommitMessage(msg) → Array<{ verb, id, type }>\n * VERB_STATUS_MAP — verb-to-expected-status table\n *\n * TERMINAL_STATES referenced from .cleargate/scripts/constants.mjs:45.\n * Do NOT redefine; duplicate literal with source citation.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { parseFrontmatter } from '../wiki/parse-frontmatter.js';\n\n// ─── Constants ─────────────────────────────────────────────────────────────────\n\n/**\n * Terminal statuses for artifact lifecycle (post-CR-067).\n * Source: .cleargate/scripts/constants.mjs:45 TERMINAL_STATES.\n * NOTE: These are the *artifact* terminal statuses, not state.json story states.\n *\n * CR-067 keep/remove decisions:\n * Completed — KEEP (sole canonical terminal post-CR-067 vocab unification)\n * Abandoned — KEEP (explicit non-completion terminal; needed for cleargate_id audit)\n * Closed — KEEP (issue-specific terminal; not subject to CR-067 vocab scope)\n * Resolved — KEEP (bug-specific terminal; not subject to CR-067 vocab scope)\n * Done — REMOVE (CR-067 unified vocab; all Done artifacts migrated to Completed)\n * Verified — REMOVE (CR-067 unified vocab; all Verified artifacts migrated to Completed)\n * Escalated — REMOVE (state.json story-state vocab, not artifact status; lives in TERMINAL_STATE_JSON)\n * Parking Lot — REMOVE (state.json story-state vocab, not artifact status; lives in TERMINAL_STATE_JSON)\n */\nexport const ARTIFACT_TERMINAL_STATUSES = new Set([\n 'Completed',\n 'Abandoned',\n 'Closed',\n 'Resolved',\n]);\n\n/**\n * Canonical single expected status for all artifact gate-checks (post-CR-067).\n * All per-verb expected[] arrays reference this constant.\n */\nconst ARTIFACT_GATE_EXPECTED = ['Completed'] as const;\n\n/**\n * Verb-to-expected-status map (v1).\n * Key: verb pattern (lower-case), Value: { types, expected }.\n * types: which artifact types this verb applies to.\n * expected: accepted terminal statuses for this verb.\n */\nexport const VERB_STATUS_MAP: Readonly<Record<string, { types: string[]; expected: string[] }>> = {\n feat: {\n types: ['STORY', 'EPIC', 'CR'],\n expected: [...ARTIFACT_GATE_EXPECTED],\n },\n fix: {\n types: ['BUG', 'HOTFIX'],\n expected: [...ARTIFACT_GATE_EXPECTED],\n },\n};\n\n// ─── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface DriftItem {\n id: string;\n type: 'STORY' | 'CR' | 'BUG' | 'EPIC' | 'PROPOSAL' | 'HOTFIX';\n expected_status: string;\n actual_status: string | null;\n file_path: string | null;\n in_archive: boolean;\n commit_shas: string[];\n carry_over: boolean;\n}\n\nexport interface ReconcileLifecycleResult {\n drift: DriftItem[];\n clean: number;\n}\n\nexport interface ReconcileLifecycleOpts {\n since: Date;\n until?: Date;\n deliveryRoot: string;\n repoRoot: string;\n /** Test seam: replace spawnSync git calls */\n gitRunner?: (cmd: string, args: string[]) => string;\n}\n\nexport interface MissingDecomp {\n id: string;\n type: 'epic' | 'proposal';\n reason: 'no-child-stories' | 'no-decomposed-epic' | 'file-missing';\n expected_files: string[];\n}\n\nexport interface ReconcileDecompositionResult {\n missing: MissingDecomp[];\n clean: number;\n}\n\nexport interface ReconcileDecompositionOpts {\n sprintPlanPath: string;\n deliveryRoot: string;\n}\n\n// ─── ID shape regex (longest-alternative-first per BUG-010 + assert_story_files.mjs) ──\n\nconst ID_PATTERN = /\\b(STORY-\\d{3}-\\d{2}|(CR|BUG|EPIC|HOTFIX)-\\d{3}|(PROPOSAL|PROP)-\\d{3})\\b/g;\n\n/** Artifact type names recognized by the reconciler */\ntype ArtifactType = 'STORY' | 'CR' | 'BUG' | 'EPIC' | 'PROPOSAL' | 'HOTFIX';\n\nfunction normalizeId(raw: string): string {\n // PROP-NNN → PROPOSAL-NNN (BUG-009 lesson)\n return raw.replace(/^PROP-(\\d+)$/, 'PROPOSAL-$1');\n}\n\nfunction idType(id: string): ArtifactType | null {\n if (/^STORY-\\d{3}-\\d{2}$/.test(id)) return 'STORY';\n if (/^CR-\\d{3}$/.test(id)) return 'CR';\n if (/^BUG-\\d{3}$/.test(id)) return 'BUG';\n if (/^EPIC-\\d{3}$/.test(id)) return 'EPIC';\n if (/^PROPOSAL-\\d{3}$/.test(id)) return 'PROPOSAL';\n if (/^HOTFIX-\\d{3}$/.test(id)) return 'HOTFIX';\n return null;\n}\n\n// ─── parseCommitMessage ────────────────────────────────────────────────────────\n\n/**\n * Parse a commit message (subject + optional first body line) for work-item IDs.\n * Returns one entry per ID found with the verb inferred from conventional prefix.\n *\n * commit format: `<verb>(<scope>): <description>\\n\\n<body>`\n * multi-ID: `fix(cli)!: BUG-001 fix + CR-001 align`\n * merge: `merge: STORY-001-01 → main`\n */\nexport function parseCommitMessage(\n msg: string,\n): Array<{ verb: string; id: string; type: string }> {\n const lines = msg.split('\\n');\n const subject = lines[0] ?? '';\n\n // First non-empty body line (if any) after the blank separator\n let firstBodyLine = '';\n for (let i = 1; i < lines.length; i++) {\n if (lines[i]?.trim()) {\n firstBodyLine = lines[i]!;\n break;\n }\n }\n\n // Extract verb from subject: `feat(...)`, `fix(...)`, `merge:`, `chore(...)`, etc.\n const verbMatch = /^(\\w+)[(!]/.exec(subject) ?? /^(\\w+):/.exec(subject);\n const verb = verbMatch ? verbMatch[1]!.toLowerCase() : '';\n\n // Scan subject + first body line for IDs\n const searchText = subject + (firstBodyLine ? '\\n' + firstBodyLine : '');\n const results: Array<{ verb: string; id: string; type: string }> = [];\n const seen = new Set<string>();\n\n let m: RegExpExecArray | null;\n ID_PATTERN.lastIndex = 0;\n while ((m = ID_PATTERN.exec(searchText)) !== null) {\n const rawId = m[0]!;\n const id = normalizeId(rawId);\n if (seen.has(id)) continue;\n seen.add(id);\n const type = idType(id);\n if (!type) continue;\n results.push({ verb, id, type });\n }\n\n return results;\n}\n\n// ─── File finders ─────────────────────────────────────────────────────────────\n\ninterface FoundFile {\n absPath: string;\n inArchive: boolean;\n relPath: string; // relative to deliveryRoot\n}\n\nfunction findArtifactFile(deliveryRoot: string, id: string): FoundFile | null {\n const prefix = `${id}_`;\n const dirs: Array<{ rel: string; inArchive: boolean }> = [\n { rel: 'pending-sync', inArchive: false },\n { rel: 'archive', inArchive: true },\n ];\n for (const { rel, inArchive } of dirs) {\n const dir = path.join(deliveryRoot, rel);\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n continue;\n }\n // match `ID_*.md` OR `ID.md`\n const match = entries.find(\n (e) => (e.startsWith(prefix) || e === `${id}.md`) && e.endsWith('.md'),\n );\n if (match) {\n const absPath = path.join(dir, match);\n return { absPath, inArchive, relPath: `${rel}/${match}` };\n }\n }\n return null;\n}\n\nfunction readArtifactStatus(absPath: string): { status: string | null; carryOver: boolean } {\n let raw: string;\n try {\n raw = fs.readFileSync(absPath, 'utf8');\n } catch {\n return { status: null, carryOver: false };\n }\n try {\n const { fm } = parseFrontmatter(raw);\n const status = typeof fm['status'] === 'string' ? fm['status'] : null;\n const carryOver = fm['carry_over'] === true;\n return { status, carryOver };\n } catch {\n return { status: null, carryOver: false };\n }\n}\n\n// ─── reconcileLifecycle ────────────────────────────────────────────────────────\n\n/**\n * Scan git log in [since, until] range and reconcile artifact statuses.\n *\n * For each commit touching feat/fix verbs with IDs:\n * - Find the artifact file in pending-sync or archive\n * - Check if status is at expected terminal status\n * - Report drift items for non-terminal artifacts\n * - Skip artifacts with carry_over: true\n */\nexport function reconcileLifecycle(opts: ReconcileLifecycleOpts): ReconcileLifecycleResult {\n const { since, until = new Date(), deliveryRoot, repoRoot } = opts;\n\n const gitRunner =\n opts.gitRunner ??\n ((cmd: string, args: string[]) => {\n const result = spawnSync(cmd, args, { encoding: 'utf8', cwd: repoRoot });\n return (result.stdout ?? '') as string;\n });\n\n // git log --format=\"%H %s%n%b%n---COMMIT---\" --after=<since> --before=<until>\n const sinceIso = since.toISOString();\n const untilIso = until.toISOString();\n const logOutput = gitRunner('git', [\n 'log',\n `--after=${sinceIso}`,\n `--before=${untilIso}`,\n '--format=%H%x00%s%x00%b%x00---COMMIT---',\n '--',\n ]);\n\n // Map: id → DriftItem (accumulates SHAs for bundled-commit grouping)\n // We track each id independently; bundled-commit = multiple SHAs per id\n const idToItem = new Map<string, DriftItem>();\n // Track ids that were found CLEAN (fully reconciled)\n const cleanIds = new Set<string>();\n\n if (logOutput.trim()) {\n // Split by commit separator\n const rawCommits = logOutput.split('---COMMIT---\\n').filter((c) => c.trim());\n\n for (const raw of rawCommits) {\n // Each commit entry: sha\\0subject\\0body\\0\n const [sha = '', subject = '', body = ''] = raw.split('\\x00');\n const trimSha = sha.trim();\n const trimSubject = subject.trim();\n const trimBody = body.trim();\n\n if (!trimSha || !trimSubject) continue;\n\n const commitMsg = trimSubject + (trimBody ? '\\n\\n' + trimBody : '');\n const parsed = parseCommitMessage(commitMsg);\n\n for (const { verb, id, type } of parsed) {\n // Skip merge, chore, docs, refactor, test, file, plan verbs (no expectation)\n if (verb === 'merge' || verb === 'chore' || verb === 'docs' || verb === 'refactor'\n || verb === 'test' || verb === 'file' || verb === 'plan') {\n continue;\n }\n\n // Skip PROPOSAL types — proposals aren't shipped via feat/fix commits\n if (type === 'PROPOSAL') continue;\n\n const verbConfig = VERB_STATUS_MAP[verb];\n if (!verbConfig) continue;\n\n // Verb mismatch: feat(BUG-NNN) → soft warning only, handled at call site\n // We still need to find the file and check status for the call site to report\n\n // Find the artifact file\n const found = findArtifactFile(deliveryRoot, id);\n if (!found) {\n // Unknown ID — log once at info level (no drift)\n // We skip unknown IDs (no file found); call site logs info\n continue;\n }\n\n // Read status + carry_over from CURRENT frontmatter\n const { status, carryOver } = readArtifactStatus(found.absPath);\n\n // carry_over: true → skip silently\n if (carryOver) continue;\n\n // Determine expected statuses for this (verb, type) pair\n let expectedStatuses: string[];\n if (verb === 'feat' && type === 'BUG') {\n // verb mismatch — soft warning, does not block; still check status\n // Use Completed as expected for BUG even with feat verb (post-CR-067)\n expectedStatuses = [...ARTIFACT_GATE_EXPECTED];\n } else if (!verbConfig.types.includes(type)) {\n // Type not covered by this verb's map — skip\n continue;\n } else {\n expectedStatuses = verbConfig.expected;\n }\n\n const isTerminal = status !== null && expectedStatuses.includes(status);\n const isArchived = found.inArchive;\n\n if (isTerminal && isArchived) {\n // Clean\n cleanIds.add(id);\n // If we previously recorded drift for this id (from another commit), remove it\n // (Most recent status check wins — carry_over already handled above)\n idToItem.delete(id);\n } else if (!idToItem.has(id)) {\n // New drift item\n const expectedStr = expectedStatuses[0] ?? 'Completed';\n idToItem.set(id, {\n id,\n type: type as DriftItem['type'],\n expected_status: expectedStr,\n actual_status: status,\n file_path: found.relPath,\n in_archive: isArchived,\n commit_shas: [trimSha],\n carry_over: carryOver,\n });\n } else {\n // Existing drift item — add SHA if not already present\n const existing = idToItem.get(id)!;\n if (!existing.commit_shas.includes(trimSha)) {\n existing.commit_shas.push(trimSha);\n }\n }\n }\n }\n }\n\n // Remove from drift any IDs that ended up in cleanIds\n for (const id of cleanIds) {\n idToItem.delete(id);\n }\n\n const drift = Array.from(idToItem.values());\n return { drift, clean: cleanIds.size };\n}\n\n// ─── reconcileCrossSprintOrphans ──────────────────────────────────────────────\n\n/**\n * Orphan drift item: a file in pending-sync/ with a non-terminal status\n * that has been marked Done (or another terminal state) in a closed sprint's\n * state.json — indicating it was completed but never archived.\n */\nexport interface OrphanDriftItem {\n id: string;\n type: 'CR' | 'STORY' | 'BUG' | 'EPIC' | 'HOTFIX';\n pending_sync_status: string;\n state_json_state: string;\n state_json_sprint: string;\n file_path: string;\n}\n\nexport interface ReconcileOrphansOpts {\n /** Path to .cleargate/delivery */\n deliveryRoot: string;\n /** Path to .cleargate/sprint-runs */\n sprintRunsRoot: string;\n}\n\nexport interface ReconcileOrphansResult {\n drift: OrphanDriftItem[];\n clean: number;\n}\n\n/**\n * Detect cross-sprint orphan drift: items in pending-sync/ with status: Ready\n * (or any non-terminal status) that are recorded as Done in a closed sprint's\n * state.json. These were completed but never archived at sprint close.\n *\n * Active-sprint exclusion: reads .active sentinel to identify the current\n * sprint and skips that sprint's state.json (in-flight items are not orphans).\n *\n * Scope: only scans pending-sync/*.md files matching the work-item-ID pattern.\n * Does NOT scan .script-incidents/ or any subdirectory.\n */\nexport function reconcileCrossSprintOrphans(opts: ReconcileOrphansOpts): ReconcileOrphansResult {\n const { deliveryRoot, sprintRunsRoot } = opts;\n\n // Terminal states from state.json (story-level states, not artifact statuses)\n const TERMINAL_STATE_JSON = new Set(['Done', 'Escalated', 'Parking Lot']);\n\n // Read the active sprint sentinel (to exclude it from orphan detection)\n let activeSprintId: string | null = null;\n try {\n activeSprintId = fs.readFileSync(path.join(sprintRunsRoot, '.active'), 'utf8').trim();\n } catch {\n // No .active file — no active sprint; scan all sprints\n }\n\n // Collect all pending-sync *.md files (no subdirectory traversal)\n const pendingDir = path.join(deliveryRoot, 'pending-sync');\n let pendingFiles: string[];\n try {\n pendingFiles = fs.readdirSync(pendingDir).filter(\n (f) => f.endsWith('.md') && !f.startsWith('.'),\n );\n } catch {\n pendingFiles = [];\n }\n\n // Build a map: id → { status, filePath } for each pending-sync item\n interface PendingItem {\n status: string;\n filePath: string;\n type: OrphanDriftItem['type'];\n }\n const pendingMap = new Map<string, PendingItem>();\n\n for (const fileName of pendingFiles) {\n const absPath = path.join(pendingDir, fileName);\n const { status } = readArtifactStatus(absPath);\n if (status === null) continue;\n // Skip already-terminal items in pending-sync (shouldn't be there but be safe)\n if (ARTIFACT_TERMINAL_STATUSES.has(status)) continue;\n\n // Extract ID from filename: filenames use <ID>_<slug>.md or <ID>.md format.\n // ID_PATTERN uses \\b word-boundaries which don't fire between a digit and '_'\n // (since '_' is a word char), so we extract the prefix before the first '_' or '.'.\n const fileNameNoExt = fileName.endsWith('.md') ? fileName.slice(0, -3) : fileName;\n const prefixPart = fileNameNoExt.split('_')[0] ?? fileNameNoExt;\n const rawId = prefixPart;\n const id = normalizeId(rawId);\n const type = idType(id);\n if (!type || type === 'PROPOSAL') continue;\n\n pendingMap.set(id, {\n status,\n filePath: path.join('pending-sync', fileName),\n type: type as OrphanDriftItem['type'],\n });\n }\n\n if (pendingMap.size === 0) {\n return { drift: [], clean: 0 };\n }\n\n // Walk sprint-runs directories for state.json files\n let sprintDirs: string[];\n try {\n sprintDirs = fs.readdirSync(sprintRunsRoot).filter((entry) => {\n // Skip the .active sentinel file and any hidden files\n if (entry.startsWith('.')) return false;\n // Skip non-directories (e.g. files in root)\n try {\n return fs.statSync(path.join(sprintRunsRoot, entry)).isDirectory();\n } catch {\n return false;\n }\n });\n } catch {\n sprintDirs = [];\n }\n\n const drift: OrphanDriftItem[] = [];\n // Track which IDs we've flagged to avoid duplicates (first sprint that shows Done wins)\n const flagged = new Set<string>();\n let clean = 0;\n\n for (const sprintDir of sprintDirs) {\n // Skip the active sprint\n if (activeSprintId && sprintDir === activeSprintId) continue;\n\n const stateFile = path.join(sprintRunsRoot, sprintDir, 'state.json');\n let stateJson: Record<string, unknown>;\n try {\n const raw = fs.readFileSync(stateFile, 'utf8');\n stateJson = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n continue;\n }\n\n const stories = stateJson['stories'] as Record<string, { state: string }> | undefined;\n if (!stories || typeof stories !== 'object') continue;\n\n for (const [id, storyEntry] of Object.entries(stories)) {\n // Skip if already flagged from an earlier sprint\n if (flagged.has(id)) continue;\n\n const pending = pendingMap.get(id);\n if (!pending) continue; // not in pending-sync\n\n const stateInJson = storyEntry?.state ?? '';\n if (TERMINAL_STATE_JSON.has(stateInJson)) {\n // This item is Done in a closed sprint but still in pending-sync — orphan drift\n flagged.add(id);\n drift.push({\n id,\n type: pending.type,\n pending_sync_status: pending.status,\n state_json_state: stateInJson,\n state_json_sprint: sprintDir,\n file_path: pending.filePath,\n });\n } else {\n // Item is in pending-sync AND in state.json but NOT terminal — correctly in-flight\n clean++;\n }\n }\n }\n\n return { drift, clean };\n}\n\n// ─── reconcileDecomposition ───────────────────────────────────────────────────\n\n/**\n * Read the sprint plan's epics: and proposals: frontmatter arrays and verify\n * that each referenced epic has ≥1 child story file, and each proposal has\n * a decomposed epic.\n */\nexport function reconcileDecomposition(opts: ReconcileDecompositionOpts): ReconcileDecompositionResult {\n const { sprintPlanPath, deliveryRoot } = opts;\n\n // Parse sprint plan frontmatter\n let raw: string;\n try {\n raw = fs.readFileSync(sprintPlanPath, 'utf8');\n } catch {\n return { missing: [], clean: 0 };\n }\n\n let fm: Record<string, unknown>;\n try {\n ({ fm } = parseFrontmatter(raw));\n } catch {\n return { missing: [], clean: 0 };\n }\n\n const epics: string[] = Array.isArray(fm['epics']) ? fm['epics'].map(String) : [];\n const proposals: string[] = Array.isArray(fm['proposals']) ? fm['proposals'].map(String) : [];\n\n const pendingDir = path.join(deliveryRoot, 'pending-sync');\n const archiveDir = path.join(deliveryRoot, 'archive');\n\n // Read both dirs for all .md files\n function listMdFiles(dir: string): string[] {\n try {\n return fs.readdirSync(dir).filter((f) => f.endsWith('.md'));\n } catch {\n return [];\n }\n }\n const pendingFiles = listMdFiles(pendingDir);\n const archiveFiles = listMdFiles(archiveDir);\n const allFiles = [...pendingFiles, ...archiveFiles];\n\n const missing: MissingDecomp[] = [];\n let clean = 0;\n\n // Check epics\n for (const epicId of epics) {\n // Find the epic file\n const epicFile = allFiles.find(\n (f) => f.startsWith(`${epicId}_`) || f === `${epicId}.md`,\n );\n if (!epicFile) {\n missing.push({\n id: epicId,\n type: 'epic',\n reason: 'file-missing',\n expected_files: [`pending-sync/${epicId}_<name>.md`],\n });\n continue;\n }\n\n // Find child stories: any STORY-*.md with parent_epic_ref: epicId\n const childStories = findChildStories(\n epicId,\n pendingDir,\n pendingFiles,\n archiveDir,\n archiveFiles,\n );\n\n if (childStories.length === 0) {\n missing.push({\n id: epicId,\n type: 'epic',\n reason: 'no-child-stories',\n expected_files: [\n `pending-sync/${epicId.replace('EPIC-', 'STORY-')}-01_<name>.md`,\n ],\n });\n } else {\n clean++;\n }\n }\n\n // Check proposals\n for (const proposalId of proposals) {\n // Find a decomposed epic that cites this proposal in context_source\n const decomposedEpic = findDecomposedEpic(\n proposalId,\n pendingDir,\n pendingFiles,\n );\n if (!decomposedEpic) {\n missing.push({\n id: proposalId,\n type: 'proposal',\n reason: 'no-decomposed-epic',\n expected_files: [`pending-sync/EPIC-<NNN>_<name>.md with context_source citing ${proposalId}`],\n });\n } else {\n clean++;\n }\n }\n\n return { missing, clean };\n}\n\n/**\n * Find story files in pending-sync or archive that have parent_epic_ref: epicId.\n */\nfunction findChildStories(\n epicId: string,\n pendingDir: string,\n pendingFiles: string[],\n archiveDir: string,\n archiveFiles: string[],\n): string[] {\n const results: string[] = [];\n const epicNumMatch = /^EPIC-(\\d+)$/.exec(epicId);\n if (!epicNumMatch) return results;\n const epicNum = epicNumMatch[1]!;\n\n const storyPrefix = `STORY-${epicNum}-`;\n\n for (const [files, dir] of [[pendingFiles, pendingDir], [archiveFiles, archiveDir]] as const) {\n for (const f of files) {\n if (!f.startsWith(storyPrefix) && !f.startsWith('STORY-')) continue;\n // Quick filename match first\n if (!f.includes(storyPrefix)) continue;\n const absPath = path.join(dir, f);\n try {\n const raw = fs.readFileSync(absPath, 'utf8');\n const { fm } = parseFrontmatter(raw);\n const parentRef = fm['parent_epic_ref'];\n if (parentRef === epicId) {\n results.push(f);\n }\n } catch {\n // skip malformed files\n }\n }\n }\n return results;\n}\n\n/**\n * Find an epic file in pending-sync whose context_source cites proposalId.\n */\nfunction findDecomposedEpic(\n proposalId: string,\n pendingDir: string,\n pendingFiles: string[],\n): string | null {\n for (const f of pendingFiles) {\n if (!f.startsWith('EPIC-')) continue;\n const absPath = path.join(pendingDir, f);\n try {\n const raw = fs.readFileSync(absPath, 'utf8');\n const { fm } = parseFrontmatter(raw);\n const contextSource = fm['context_source'];\n if (\n typeof contextSource === 'string' &&\n contextSource.includes(proposalId)\n ) {\n return f;\n }\n } catch {\n // skip\n }\n }\n return null;\n}\n\n// ─── Verb mismatch checker (exported for test use) ────────────────────────────\n\n/**\n * Check if a (verb, type) combination is a mismatch (soft warning only in v1).\n * Returns a warning message or null if no mismatch.\n */\nexport function checkVerbMismatch(verb: string, type: string): string | null {\n if (verb === 'feat' && type === 'BUG') {\n return `verb 'feat' unusual for BUG; expected 'fix'`;\n }\n if (verb === 'fix' && (type === 'STORY' || type === 'EPIC' || type === 'CR')) {\n return `verb 'fix' unusual for ${type}; expected 'feat'`;\n }\n return null;\n}\n\n// ─── reconcileCurrentSprintStories (BUG-032) ─────────────────────────────────\n\n/**\n * Result item for a story processed by reconcileCurrentSprintStories.\n */\nexport interface CurrentSprintStoryFlip {\n id: string;\n type: OrphanDriftItem['type'];\n old_status: string;\n new_status: 'Completed';\n file_path: string;\n archived: boolean;\n}\n\nexport interface ReconcileCurrentSprintResult {\n flipped: CurrentSprintStoryFlip[];\n skipped_already_terminal: number;\n skipped_not_done: number;\n}\n\nexport interface ReconcileCurrentSprintOpts {\n /** Path to .cleargate/delivery */\n deliveryRoot: string;\n /** Path to .cleargate/sprint-runs */\n sprintRunsRoot: string;\n /** The sprint ID being closed (e.g. \"SPRINT-30\") */\n sprintId: string;\n /**\n * When true: scan ALL closed sprints (sprint_status: Completed) and flip\n * any Done stories whose frontmatter is still non-terminal.\n * Used by `cleargate sprint reconcile-lifecycle --retroactive`.\n */\n retroactive?: boolean;\n}\n\n/**\n * Atomic in-place frontmatter field rewrite (raw-bytes regex-replace).\n * Follows FLASHCARD 2026-04-24 #frontmatter #write-back pattern.\n * Rewrites a single YAML field in the --- block, preserving all other content.\n *\n * @param filePath - absolute path to the markdown file\n * @param field - YAML key name (e.g. 'status', 'approved')\n * @param rawValue - raw YAML value string (e.g. '\"Completed\"', 'true')\n */\nfunction setFrontmatterFieldAtomic(filePath: string, field: string, rawValue: string): void {\n const raw = fs.readFileSync(filePath, 'utf8');\n const fm = raw.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!fm) throw new Error(`No frontmatter in ${filePath}`);\n let newFm = fm[1];\n const fieldRx = new RegExp(`^${field}:.*$`, 'm');\n if (fieldRx.test(newFm)) {\n newFm = newFm.replace(fieldRx, `${field}: ${rawValue}`);\n } else {\n // Field not present — append it to the frontmatter block\n newFm = newFm.trimEnd() + `\\n${field}: ${rawValue}`;\n }\n const newRaw = raw.replace(fm[1], newFm);\n const tmp = filePath + '.tmp.' + process.pid;\n fs.writeFileSync(tmp, newRaw, 'utf8');\n fs.renameSync(tmp, filePath);\n}\n\n/**\n * Flip all Done-state stories in the closing sprint from their current frontmatter\n * status to `status: Completed, approved: true`, then move the file from\n * pending-sync/ to archive/.\n *\n * Root cause addressed (BUG-032): `reconcileCrossSprintOrphans` explicitly skips\n * the active sprint via the `.active` sentinel. No existing step flips same-sprint\n * story frontmatter at close time. This function fills that gap.\n *\n * Idempotence: stories already at a terminal status (Completed, Abandoned, etc.)\n * are skipped silently. Running this function twice produces the same result.\n *\n * When `retroactive: true`: scans ALL sprint-runs directories (not just the\n * provided sprintId) and flips any stories in Done-state sprints whose frontmatter\n * is still non-terminal. Used by `cleargate sprint reconcile-lifecycle --retroactive`.\n *\n * Implementation note: iterates over state.json story IDs and uses `findArtifactFile`\n * to locate the pending-sync file. This handles both standard `STORY-\\d{3}-\\d{2}`\n * and non-standard (test fixture) IDs like `STORY-TEST-01` correctly.\n */\nexport function reconcileCurrentSprintStories(\n opts: ReconcileCurrentSprintOpts,\n): ReconcileCurrentSprintResult {\n const { deliveryRoot, sprintRunsRoot, sprintId, retroactive = false } = opts;\n\n const TERMINAL_STATE_JSON = new Set(['Done', 'Escalated', 'Parking Lot']);\n const archiveDir = path.join(deliveryRoot, 'archive');\n\n // Determine which sprint dirs to scan\n let sprintDirsToScan: string[];\n if (retroactive) {\n // Retroactive: scan ALL sprint-runs dirs that are Completed\n try {\n sprintDirsToScan = fs.readdirSync(sprintRunsRoot).filter((entry) => {\n if (entry.startsWith('.')) return false;\n try {\n return fs.statSync(path.join(sprintRunsRoot, entry)).isDirectory();\n } catch {\n return false;\n }\n });\n } catch {\n sprintDirsToScan = [];\n }\n // Filter to Completed sprints only\n sprintDirsToScan = sprintDirsToScan.filter((dir) => {\n const stateFile = path.join(sprintRunsRoot, dir, 'state.json');\n try {\n const raw = fs.readFileSync(stateFile, 'utf8');\n const s = JSON.parse(raw) as Record<string, unknown>;\n return s['sprint_status'] === 'Completed';\n } catch {\n return false;\n }\n });\n } else {\n // Normal close: scan only the current sprint\n sprintDirsToScan = [sprintId];\n }\n\n const flipped: CurrentSprintStoryFlip[] = [];\n let skipped_already_terminal = 0;\n let skipped_not_done = 0;\n const flippedIds = new Set<string>();\n\n for (const dir of sprintDirsToScan) {\n const stateFile = path.join(sprintRunsRoot, dir, 'state.json');\n let stateJson: Record<string, unknown>;\n try {\n const raw = fs.readFileSync(stateFile, 'utf8');\n stateJson = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n continue;\n }\n\n const stories = stateJson['stories'] as Record<string, { state: string }> | undefined;\n if (!stories || typeof stories !== 'object') continue;\n\n for (const [storyId, storyEntry] of Object.entries(stories)) {\n if (flippedIds.has(storyId)) continue; // already handled by an earlier sprint dir\n\n const stateInJson = storyEntry?.state ?? '';\n if (!TERMINAL_STATE_JSON.has(stateInJson)) {\n skipped_not_done++;\n continue;\n }\n\n // State is Done (or other terminal) in state.json — find the pending-sync file\n // Use findArtifactFile which handles ID_*.md and ID.md patterns for any ID format\n const found = findArtifactFile(deliveryRoot, storyId);\n if (!found) continue; // file not in pending-sync or archive — nothing to flip\n\n // Skip if file is already in archive (idempotence guard — already flipped)\n if (found.inArchive) {\n // Read status to check if it's already terminal\n const { status } = readArtifactStatus(found.absPath);\n if (status !== null && ARTIFACT_TERMINAL_STATUSES.has(status)) {\n skipped_already_terminal++;\n }\n // Either way, skip — already archived\n continue;\n }\n\n // File is in pending-sync — read current status\n const { status: currentStatus } = readArtifactStatus(found.absPath);\n if (currentStatus !== null && ARTIFACT_TERMINAL_STATUSES.has(currentStatus)) {\n // Already terminal in pending-sync (unusual but safe to skip)\n skipped_already_terminal++;\n continue;\n }\n\n // Compute destination path in archive/\n const fileName = path.basename(found.absPath);\n const destPath = path.join(archiveDir, fileName);\n const srcPath = found.absPath;\n\n // 1. Set status: \"Completed\" in frontmatter (Done state is authoritative)\n setFrontmatterFieldAtomic(srcPath, 'status', '\"Completed\"');\n // 2. Set approved: true in frontmatter\n setFrontmatterFieldAtomic(srcPath, 'approved', 'true');\n // 3. Move file from pending-sync/ to archive/\n fs.mkdirSync(archiveDir, { recursive: true });\n\n // Idempotence: if file already in archive, do not double-move\n if (!fs.existsSync(destPath)) {\n fs.renameSync(srcPath, destPath);\n } else {\n // Archive already has this file — remove the pending-sync copy\n fs.rmSync(srcPath, { force: true });\n }\n\n flippedIds.add(storyId);\n flipped.push({\n id: storyId,\n type: (idType(storyId) ?? 'STORY') as OrphanDriftItem['type'],\n old_status: currentStatus ?? 'unknown',\n new_status: 'Completed',\n file_path: path.join('archive', fileName),\n archived: true,\n });\n }\n }\n\n return { flipped, skipped_already_terminal, skipped_not_done };\n}\n\n// ─── Parent rollup re-exports (STORY-066-01) ──────────────────────────────────\n\nexport { rollUpParentStatus, walkActiveParents, type RollupResult } from './parent-rollup.js';\n","/**\n * YAML frontmatter parser backed by js-yaml with CORE_SCHEMA (YAML 1.2 core).\n *\n * Parses `---\\n<yaml>\\n---\\n<body>` into a typed frontmatter map + body string.\n * Preserves native types (null, boolean, number, string), nested maps, and\n * arrays. Uses CORE_SCHEMA so ISO-8601 timestamp strings are NOT coerced to\n * Date objects (YAML 1.1's quirk).\n *\n * Historical note: an earlier hand-rolled parser flattened indented nested\n * maps into top-level keys and stringified null/boolean scalars. See\n * BUG-001 and FLASHCARD entry `#yaml #frontmatter`.\n */\n\nimport yaml from 'js-yaml';\n\nexport function parseFrontmatter(raw: string): { fm: Record<string, unknown>; body: string } {\n const lines = raw.split('\\n');\n if (lines[0] !== '---') {\n throw new Error('parseFrontmatter: input does not start with ---');\n }\n let closeIdx = -1;\n for (let i = 1; i < lines.length; i++) {\n if (lines[i] === '---') { closeIdx = i; break; }\n }\n if (closeIdx === -1) {\n throw new Error('parseFrontmatter: missing closing ---');\n }\n\n const yamlText = lines.slice(1, closeIdx).join('\\n');\n const bodyLines = lines.slice(closeIdx + 1);\n // strip one leading blank line if present\n if (bodyLines[0] === '') bodyLines.shift();\n const body = bodyLines.join('\\n');\n\n if (yamlText.trim() === '') {\n return { fm: {}, body };\n }\n\n let parsed: unknown;\n try {\n parsed = yaml.load(yamlText, { schema: yaml.CORE_SCHEMA });\n } catch (err) {\n throw new Error(`parseFrontmatter: invalid YAML: ${(err as Error).message}`);\n }\n\n if (parsed === null || parsed === undefined) {\n return { fm: {}, body };\n }\n if (typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('parseFrontmatter: frontmatter is not a YAML mapping');\n }\n\n return { fm: parsed as Record<string, unknown>, body };\n}\n","/**\n * parent-rollup.ts — CR-066 parent status rollup library\n *\n * Pure library: no I/O side-effects beyond reading frontmatter from disk.\n * Writing flips is the responsibility of the caller (STORY-066-02).\n *\n * Public API:\n * rollUpParentStatus(parentFilePath, opts) → Promise<RollupResult>\n * walkActiveParents(opts) → Promise<RollupResult[]>\n * RollupResult (interface)\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parseFrontmatter } from '../wiki/parse-frontmatter.js';\nimport { ARTIFACT_TERMINAL_STATUSES } from './lifecycle-reconcile.js';\n\n// ─── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface RollupResult {\n parent_id: string;\n parent_path: string;\n current_status: string;\n proposed_status: 'Completed' | null;\n coverage: 'full' | 'partial' | 'zero' | 'sub-epic-partial';\n terminal_children: string[];\n pending_children: string[];\n verdict: 'auto-flip' | 'halt-partial' | 'halt-zero-children' | 'skip-deferred' | 'no-op';\n halt_reason?: string;\n}\n\nexport interface WalkActiveParentsOpts {\n deliveryRoot: string;\n archiveRoot: string;\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────────────\n\n/**\n * Safely parse frontmatter from a file path.\n * Returns null on any read or parse error.\n */\nfunction readFm(filePath: string): Record<string, unknown> | null {\n try {\n const raw = fs.readFileSync(filePath, 'utf8');\n const { fm } = parseFrontmatter(raw);\n return fm;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract the canonical ID from frontmatter, checking all known ID-key conventions\n * in priority order before falling back to the filename stem.\n *\n * Key priority order mirrors template conventions:\n * story_id (story.md) → epic_id (epic.md) → sprint_id (Sprint Plan Template.md)\n * → bug_id (Bug.md) → cr_id (CR.md) → initiative_id (initiative.md)\n * → hotfix_id (hotfix.md)\n *\n * Filename stem fallback: takes the first underscore-delimited segment so that\n * files named \"EPIC-010_Multi_Participant_MCP_Sync.md\" resolve to \"EPIC-010\".\n */\nfunction extractId(fm: Record<string, unknown>, filePath: string): string {\n for (const key of [\n 'story_id',\n 'epic_id',\n 'sprint_id',\n 'bug_id',\n 'cr_id',\n 'initiative_id',\n 'hotfix_id',\n ]) {\n const val = fm[key];\n if (typeof val === 'string' && val.trim() !== '') return val.trim();\n }\n // Fallback: parse from filename stem (first underscore-delimited segment)\n const stem = path.basename(filePath, '.md');\n return stem.split('_')[0] ?? stem;\n}\n\n/**\n * Enumerate all children of a parent across both archive and pending-sync.\n * Children are identified by `parent_cleargate_id` OR `parent_epic_ref` frontmatter\n * matching the parentId.\n *\n * Caching is done via the fmCache map (keyed by absolute path) to avoid\n * re-reading files during recursive sub-epic walks.\n */\nfunction enumerateChildren(\n parentId: string,\n deliveryRoot: string,\n archiveRoot: string,\n fmCache: Map<string, Record<string, unknown>>\n): { id: string; status: string }[] {\n const pendingSyncDir = path.join(deliveryRoot, 'pending-sync');\n const results: { id: string; status: string }[] = [];\n\n const pools: string[] = [];\n if (fs.existsSync(archiveRoot)) pools.push(archiveRoot);\n if (fs.existsSync(pendingSyncDir)) pools.push(pendingSyncDir);\n\n for (const dir of pools) {\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n entries = [];\n }\n\n for (const entry of entries) {\n if (!entry.endsWith('.md')) continue;\n const absPath = path.join(dir, entry);\n\n let fm = fmCache.get(absPath);\n if (fm === undefined) {\n const parsed = readFm(absPath);\n if (parsed === null) continue;\n fm = parsed;\n fmCache.set(absPath, fm);\n }\n\n // Match by parent_cleargate_id or parent_epic_ref\n const parentCleargateId = fm['parent_cleargate_id'];\n const parentEpicRef = fm['parent_epic_ref'];\n\n const isChild =\n (typeof parentCleargateId === 'string' && parentCleargateId.trim() === parentId) ||\n (typeof parentEpicRef === 'string' && parentEpicRef.trim() === parentId);\n\n if (!isChild) continue;\n\n const childId = extractId(fm, absPath);\n const status = typeof fm['status'] === 'string' ? fm['status'].trim() : '';\n results.push({ id: childId, status });\n }\n }\n\n return results;\n}\n\n// ─── Core rollup logic ────────────────────────────────────────────────────────\n\n/**\n * Internal implementation with cycle-detection via visited set.\n */\nasync function rollUpParentStatusInternal(\n parentFilePath: string,\n opts: WalkActiveParentsOpts,\n visited: Set<string>,\n fmCache: Map<string, Record<string, unknown>>\n): Promise<RollupResult> {\n const { deliveryRoot, archiveRoot } = opts;\n\n // Read parent frontmatter\n let fm = fmCache.get(parentFilePath);\n if (fm === undefined) {\n const raw = readFm(parentFilePath);\n if (raw === null) {\n throw new Error(`parent-rollup: cannot read frontmatter from ${parentFilePath}`);\n }\n fm = raw;\n fmCache.set(parentFilePath, fm);\n }\n\n const parentId = extractId(fm, parentFilePath);\n const currentStatus = typeof fm['status'] === 'string' ? fm['status'].trim() : '';\n\n // Short-circuit: already terminal\n if (ARTIFACT_TERMINAL_STATUSES.has(currentStatus)) {\n return {\n parent_id: parentId,\n parent_path: parentFilePath,\n current_status: currentStatus,\n proposed_status: null,\n coverage: 'full',\n terminal_children: [],\n pending_children: [],\n verdict: 'no-op',\n };\n }\n\n // Cycle detection (before recursing into sub_epics)\n if (visited.has(parentId)) {\n throw new Error(`parent-rollup: sub_epics cycle detected at ${parentId}`);\n }\n visited.add(parentId);\n\n // Sub-epic recursion path\n const subEpicsField = fm['sub_epics'];\n const subEpics: string[] =\n Array.isArray(subEpicsField) && subEpicsField.length > 0\n ? (subEpicsField as unknown[]).filter((s): s is string => typeof s === 'string')\n : [];\n\n if (subEpics.length > 0) {\n // Recurse into sub-epics\n const pendingSyncDir = path.join(deliveryRoot, 'pending-sync');\n\n const terminalSubEpics: string[] = [];\n const pendingSubEpics: string[] = [];\n\n for (const subEpicId of subEpics) {\n // Locate the sub-epic file — search pending-sync and archive\n let subEpicPath: string | null = null;\n const candidateDirs = [pendingSyncDir, archiveRoot];\n for (const dir of candidateDirs) {\n if (!fs.existsSync(dir)) continue;\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n entries = [];\n }\n for (const entry of entries) {\n if (!entry.endsWith('.md')) continue;\n const absPath = path.join(dir, entry);\n let subFm = fmCache.get(absPath);\n if (subFm === undefined) {\n const parsed = readFm(absPath);\n if (parsed === null) continue;\n subFm = parsed;\n fmCache.set(absPath, subFm);\n }\n const entryId = extractId(subFm, absPath);\n if (entryId === subEpicId) {\n subEpicPath = absPath;\n break;\n }\n }\n if (subEpicPath !== null) break;\n }\n\n if (subEpicPath === null) {\n // Sub-epic file not found; treat as pending\n pendingSubEpics.push(subEpicId);\n continue;\n }\n\n // Read sub-epic frontmatter to check for DEFERRED\n let subFm = fmCache.get(subEpicPath);\n if (subFm === undefined) {\n const parsed = readFm(subEpicPath);\n if (parsed === null) {\n pendingSubEpics.push(subEpicId);\n continue;\n }\n subFm = parsed;\n fmCache.set(subEpicPath, subFm);\n }\n\n const subStatus = typeof subFm['status'] === 'string' ? subFm['status'].trim() : '';\n\n // Exclude DEFERRED sub-epics from denominator entirely\n if (subStatus === 'DEFERRED') {\n continue;\n }\n\n // Already terminal (e.g. Completed) counts as done — no further recursion needed\n if (ARTIFACT_TERMINAL_STATUSES.has(subStatus)) {\n terminalSubEpics.push(subEpicId);\n continue;\n }\n\n // Recurse: make a snapshot of visited before entering sub-epic, restore after\n // (so sibling sub-epics don't block each other)\n const visitedSnapshot = new Set(visited);\n const subResult = await rollUpParentStatusInternal(\n subEpicPath,\n opts,\n visitedSnapshot,\n fmCache\n );\n\n if (subResult.verdict === 'auto-flip' || subResult.verdict === 'no-op') {\n terminalSubEpics.push(subEpicId);\n } else {\n pendingSubEpics.push(subEpicId);\n }\n }\n\n // Remove parentId from visited since we're returning up the stack\n visited.delete(parentId);\n\n const total = terminalSubEpics.length + pendingSubEpics.length;\n\n if (total === 0) {\n // All sub-epics were DEFERRED (excluded) or none exist — treat as zero-children\n return {\n parent_id: parentId,\n parent_path: parentFilePath,\n current_status: currentStatus,\n proposed_status: null,\n coverage: 'zero',\n terminal_children: [],\n pending_children: [],\n verdict: 'halt-zero-children',\n halt_reason: `${parentId}: 0 children drafted; not reconcilable — decompose or abandon`,\n };\n }\n\n if (pendingSubEpics.length === 0) {\n return {\n parent_id: parentId,\n parent_path: parentFilePath,\n current_status: currentStatus,\n proposed_status: 'Completed',\n coverage: 'full',\n terminal_children: terminalSubEpics,\n pending_children: [],\n verdict: 'auto-flip',\n };\n }\n\n return {\n parent_id: parentId,\n parent_path: parentFilePath,\n current_status: currentStatus,\n proposed_status: null,\n coverage: 'sub-epic-partial',\n terminal_children: terminalSubEpics,\n pending_children: pendingSubEpics,\n verdict: 'halt-partial',\n halt_reason: `${parentId}: ${terminalSubEpics.length}/${total} sub-epics terminal — pending: ${pendingSubEpics.join(', ')}`,\n };\n }\n\n // Leaf-epic / sprint: enumerate children from archive + pending-sync\n const children = enumerateChildren(parentId, deliveryRoot, archiveRoot, fmCache);\n\n visited.delete(parentId);\n\n if (children.length === 0) {\n return {\n parent_id: parentId,\n parent_path: parentFilePath,\n current_status: currentStatus,\n proposed_status: null,\n coverage: 'zero',\n terminal_children: [],\n pending_children: [],\n verdict: 'halt-zero-children',\n halt_reason: `${parentId}: 0 children drafted; not reconcilable — decompose or abandon`,\n };\n }\n\n const terminalChildren: string[] = [];\n const pendingChildren: string[] = [];\n\n for (const child of children) {\n if (ARTIFACT_TERMINAL_STATUSES.has(child.status)) {\n terminalChildren.push(child.id);\n } else {\n pendingChildren.push(child.id);\n }\n }\n\n const total = terminalChildren.length + pendingChildren.length;\n\n if (pendingChildren.length === 0) {\n return {\n parent_id: parentId,\n parent_path: parentFilePath,\n current_status: currentStatus,\n proposed_status: 'Completed',\n coverage: 'full',\n terminal_children: terminalChildren,\n pending_children: [],\n verdict: 'auto-flip',\n };\n }\n\n return {\n parent_id: parentId,\n parent_path: parentFilePath,\n current_status: currentStatus,\n proposed_status: null,\n coverage: 'partial',\n terminal_children: terminalChildren,\n pending_children: pendingChildren,\n verdict: 'halt-partial',\n halt_reason: `${parentId}: ${terminalChildren.length}/${total} children terminal — pending: ${pendingChildren.join(', ')}`,\n };\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Roll up the status of a single parent (Epic or Sprint).\n *\n * @param parentFilePath — absolute path to the parent .md file\n * @param opts — deliveryRoot: root of the delivery tree; archiveRoot: absolute path to archive/\n * @returns RollupResult with verdict, coverage, and child lists\n */\nexport async function rollUpParentStatus(\n parentFilePath: string,\n opts: WalkActiveParentsOpts\n): Promise<RollupResult> {\n const visited = new Set<string>();\n const fmCache = new Map<string, Record<string, unknown>>();\n return rollUpParentStatusInternal(parentFilePath, opts, visited, fmCache);\n}\n\n/**\n * Walk all active parents (EPIC-*.md + SPRINT-*.md) in deliveryRoot/pending-sync/\n * and return one RollupResult per parent.\n *\n * Already-terminal parents (status: Completed/Done/etc.) emit verdict: 'no-op'.\n */\nexport async function walkActiveParents(\n opts: WalkActiveParentsOpts\n): Promise<RollupResult[]> {\n const { deliveryRoot } = opts;\n const pendingSyncDir = path.join(deliveryRoot, 'pending-sync');\n\n let entries: string[];\n try {\n entries = fs.readdirSync(pendingSyncDir);\n } catch {\n return [];\n }\n\n const parentFiles = entries.filter(\n (e) =>\n e.endsWith('.md') &&\n (e.startsWith('EPIC-') || e.startsWith('SPRINT-'))\n );\n\n const results: RollupResult[] = [];\n const fmCache = new Map<string, Record<string, unknown>>();\n\n for (const entry of parentFiles) {\n const absPath = path.join(pendingSyncDir, entry);\n try {\n const visited = new Set<string>();\n const result = await rollUpParentStatusInternal(absPath, opts, visited, fmCache);\n results.push(result);\n } catch (err) {\n // Propagate cycle errors; skip unreadable files\n if (err instanceof Error && err.message.includes('sub_epics cycle detected')) {\n throw err;\n }\n // Other errors (e.g. unreadable) — skip silently\n }\n }\n\n return results;\n}\n"],"mappings":"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","names":["fs","path","subFm","total"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAF9B,IAIM,aACA,YAEO,WACA;AARb;AAAA;AAAA;AAIA,IAAM,cAAc,MAAM,cAAc,YAAY,GAAG;AACvD,IAAM,aAAa,MAAM,KAAK,QAAQ,YAAY,CAAC;AAE5C,IAAM,YAA4B,2BAAW;AAC7C,IAAM,aAA6B,4BAAY;AAAA;AAAA;","names":[]}
|