cleargate 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/MANIFEST.json +72 -16
  3. package/dist/admin-api/index.cjs +0 -1
  4. package/dist/admin-api/index.js +1 -2
  5. package/dist/auth/factory.cjs +0 -1
  6. package/dist/auth/factory.js +2 -3
  7. package/dist/auth/require-token.cjs +0 -1
  8. package/dist/auth/require-token.js +1 -2
  9. package/dist/auth/token-store.cjs +0 -1
  10. package/dist/auth/token-store.js +1 -2
  11. package/dist/{bootstrap-root-QKSA5V75.js → bootstrap-root-2H5HVTCC.js} +1 -2
  12. package/dist/{chunk-PDE37WFQ.js → chunk-A7MSQUU7.js} +2 -3
  13. package/dist/{chunk-BTSZOEWC.js → chunk-P6KEDAK2.js} +0 -1
  14. package/dist/{chunk-E3X7IE5E.js → chunk-PY6FHGV5.js} +1 -2
  15. package/dist/{chunk-5DI2Z3C2.js → chunk-Y53ZZYYU.js} +1 -2
  16. package/dist/cli.cjs +1564 -1414
  17. package/dist/cli.js +1514 -1364
  18. package/dist/lib/ledger.cjs +0 -1
  19. package/dist/lib/ledger.js +1 -2
  20. package/dist/lib/lifecycle-reconcile.cjs +0 -1
  21. package/dist/lib/lifecycle-reconcile.js +2 -3
  22. package/dist/{whoami-EANGN46Z.js → whoami-JKQQPABQ.js} +3 -4
  23. package/package.json +4 -3
  24. package/templates/cleargate-planning/.claude/agents/architect-synth.md +2 -0
  25. package/templates/cleargate-planning/.claude/agents/architect.md +4 -2
  26. package/templates/cleargate-planning/.claude/agents/developer.md +4 -11
  27. package/templates/cleargate-planning/.claude/agents/qa.md +14 -6
  28. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +2 -2
  29. package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +19 -1
  30. package/templates/cleargate-planning/.cleargate/config.example.yml +16 -0
  31. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.deferred-verify.red.node.test.ts +245 -0
  32. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +227 -0
  33. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +5 -4
  34. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +75 -2
  35. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +48 -0
  36. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +57 -1
  37. package/templates/cleargate-planning/.cleargate/scripts/provision_worktree_config.sh +155 -0
  38. package/templates/cleargate-planning/.cleargate/scripts/qa_red_lint.mjs +380 -0
  39. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +34 -1
  40. package/templates/cleargate-planning/.cleargate/scripts/test/cr077_eviction.red.sh +113 -0
  41. package/templates/cleargate-planning/.cleargate/scripts/test/cr078_init.test.sh +309 -0
  42. package/templates/cleargate-planning/.cleargate/scripts/test/cr079_provision.red.sh +262 -0
  43. package/templates/cleargate-planning/.cleargate/scripts/test/cr080_wrapper.test.sh +177 -0
  44. package/templates/cleargate-planning/.cleargate/scripts/test/cr081_qa_red_lint.red.sh +348 -0
  45. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/.session-totals.json +1 -0
  46. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/token-ledger.jsonl +222 -0
  47. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +17 -0
  48. package/templates/cleargate-planning/.cleargate/templates/story.md +1 -0
  49. package/templates/cleargate-planning/MANIFEST.json +72 -16
  50. package/dist/admin-api/index.cjs.map +0 -1
  51. package/dist/admin-api/index.js.map +0 -1
  52. package/dist/auth/factory.cjs.map +0 -1
  53. package/dist/auth/factory.js.map +0 -1
  54. package/dist/auth/require-token.cjs.map +0 -1
  55. package/dist/auth/require-token.js.map +0 -1
  56. package/dist/auth/token-store.cjs.map +0 -1
  57. package/dist/auth/token-store.js.map +0 -1
  58. package/dist/bootstrap-root-QKSA5V75.js.map +0 -1
  59. package/dist/chunk-5DI2Z3C2.js.map +0 -1
  60. package/dist/chunk-BTSZOEWC.js.map +0 -1
  61. package/dist/chunk-E3X7IE5E.js.map +0 -1
  62. package/dist/chunk-PDE37WFQ.js.map +0 -1
  63. package/dist/cli.cjs.map +0 -1
  64. package/dist/cli.js.map +0 -1
  65. package/dist/lib/ledger.cjs.map +0 -1
  66. package/dist/lib/ledger.js.map +0 -1
  67. package/dist/lib/lifecycle-reconcile.cjs.map +0 -1
  68. package/dist/lib/lifecycle-reconcile.js.map +0 -1
  69. package/dist/templates/cleargate-planning/.claude/agents/architect-reader.md +0 -61
  70. package/dist/templates/cleargate-planning/.claude/agents/architect-synth.md +0 -124
  71. package/dist/templates/cleargate-planning/.claude/agents/architect.md +0 -230
  72. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +0 -108
  73. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +0 -194
  74. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +0 -261
  75. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +0 -143
  76. package/dist/templates/cleargate-planning/.claude/agents/developer.md +0 -185
  77. package/dist/templates/cleargate-planning/.claude/agents/devops.md +0 -257
  78. package/dist/templates/cleargate-planning/.claude/agents/qa.md +0 -171
  79. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +0 -274
  80. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +0 -209
  81. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +0 -33
  82. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +0 -58
  83. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +0 -19
  84. package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +0 -162
  85. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-autonomy.sh +0 -58
  86. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +0 -148
  87. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +0 -75
  88. package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +0 -43
  89. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +0 -590
  90. package/dist/templates/cleargate-planning/.claude/settings.json +0 -68
  91. package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +0 -102
  92. package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +0 -742
  93. package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +0 -7
  94. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +0 -67
  95. package/dist/templates/cleargate-planning/.cleargate/config.yml +0 -18
  96. package/dist/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
  97. package/dist/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
  98. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +0 -551
  99. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +0 -878
  100. package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +0 -160
  101. package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +0 -213
  102. package/dist/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +0 -71
  103. package/dist/templates/cleargate-planning/.cleargate/scripts/_migrate-schema-v3.mjs +0 -120
  104. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +0 -265
  105. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +0 -1012
  106. package/dist/templates/cleargate-planning/.cleargate/scripts/collision_surface.sh +0 -114
  107. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +0 -62
  108. package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +0 -219
  109. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +0 -320
  110. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +0 -15
  111. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +0 -38
  112. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +0 -240
  113. package/dist/templates/cleargate-planning/.cleargate/scripts/launch_wave.mjs +0 -341
  114. package/dist/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +0 -54
  115. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +0 -206
  116. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +0 -371
  117. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +0 -280
  118. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +0 -378
  119. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +0 -888
  120. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +0 -209
  121. package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +0 -71
  122. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +0 -127
  123. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +0 -717
  124. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +0 -27
  125. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +0 -261
  126. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +0 -210
  127. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +0 -190
  128. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +0 -482
  129. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +0 -327
  130. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +0 -261
  131. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +0 -246
  132. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +0 -111
  133. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +0 -184
  134. package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +0 -172
  135. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +0 -126
  136. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +0 -130
  137. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +0 -137
  138. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +0 -166
  139. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +0 -111
  140. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +0 -122
  141. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +0 -50
  142. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +0 -224
  143. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +0 -213
  144. package/dist/templates/cleargate-planning/CLAUDE.md +0 -66
  145. package/dist/templates/cleargate-planning/MANIFEST.json +0 -503
  146. package/dist/templates/synthesis/active-sprint.md +0 -30
  147. package/dist/templates/synthesis/open-gates.md +0 -38
  148. package/dist/templates/synthesis/product-state.md +0 -31
  149. package/dist/templates/synthesis/roadmap.md +0 -63
  150. package/dist/whoami-EANGN46Z.js.map +0 -1
@@ -22,6 +22,23 @@ Frozen dependency versions for this sprint. Orchestrator populates from `package
22
22
  | TypeScript | `^5.8.0` |
23
23
  | (add rows per workspace below) | |
24
24
 
25
+ ## Test Stack
26
+
27
+ Repo-derived test conventions. Written best-effort by `cleargate init` (detector); the
28
+ orchestrator may correct any field per-sprint. Agents read this block as OVERRIDING their
29
+ built-in defaults — Developer/QA/Architect use these values, not any hardcoded runner.
30
+
31
+ | Field | Value |
32
+ |-------|-------|
33
+ | Backend runner | _(e.g. `pytest -q` · `go test ./...` · `npm test` · populate at init)_ |
34
+ | Frontend runner | _(e.g. js unit runner — blank if single-stack)_ |
35
+ | Typecheck command | _(e.g. `mypy .` · `tsc --noEmit` · blank if none)_ |
36
+ | Red-test naming | _(e.g. `*.red.test.tsx` · `test_*_red.py` · `*_red_test.go` · populate at init)_ |
37
+
38
+ _If unresolved at init: leave the table stubbed. The pre-gate scan emits a one-line
39
+ "test_stack unresolved — populate sprint_context.md §Test Stack" advisory and treats the
40
+ typecheck/test gate as advisory (not FAIL) until populated. (§0.5 backstop decision.)_
41
+
25
42
  ## Cross-Cutting Rules
26
43
 
27
44
  Sprint-wide architecture rules and UI/API tokens that every story must honour. Populated from the parent Epic's `<architecture_rules>` block.
@@ -68,6 +68,7 @@ parallel_eligible: "y"
68
68
  expected_bounce_exposure: "low"
69
69
  lane: "standard"
70
70
  db_write_set: [] # advisory-v1 (EPIC-033 wave-planner axis 4): list table names this story writes to; default [] = no DB collision contribution; absent treated as [] by architect-synth predicate
71
+ deferred_verification: [] # optional. Heavy checks owed, run post-merge. Each: { description: <human string>, command: <shell, run from repo root>, blocks: close }. Absent/empty = no obligation (common case; fully backward-compatible).
71
72
  created_at: "2026-04-17T00:00:00Z"
72
73
  updated_at: "2026-04-17T00:00:00Z"
73
74
  created_at_version: "strategy-phase-pre-init"
@@ -1,6 +1,6 @@
1
1
  {
2
- "cleargate_version": "0.14.0",
3
- "generated_at": "2026-06-02T09:52:18.119Z",
2
+ "cleargate_version": "0.15.1",
3
+ "generated_at": "2026-06-06T20:46:55.418Z",
4
4
  "files": [
5
5
  {
6
6
  "path": ".claude/agents/architect-reader.md",
@@ -11,14 +11,14 @@
11
11
  },
12
12
  {
13
13
  "path": ".claude/agents/architect-synth.md",
14
- "sha256": "066aeb48dea5628e440cc8f0ab7fae8421759a1cafee282b40337aba8091c466",
14
+ "sha256": "7ba1037bbe61cf587b6a71f744e2a43cf97e162ff233915a2a01e740dc2892d4",
15
15
  "tier": "agent",
16
16
  "overwrite_policy": "always",
17
17
  "preserve_on_uninstall": false
18
18
  },
19
19
  {
20
20
  "path": ".claude/agents/architect.md",
21
- "sha256": "20b770935167393be7a59fdd9e62c7ba638e84e070f167f445cbfa3701a39c12",
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": "1a2ca593ed4dae8d7f3e44840fcd527330fe468dd696cb2a15993e7c330721d0",
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": "3e6b9f84bfd61a29d41147f76e2f01c7ab582067b26c62d58581c52978c5bd46",
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": "d62404e290dbb04602a75858f03555a9d141839e3ec02512781beb444e6202ac",
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": "2196d887f592aa2056b6b57f9696aa413920bfcb3dfd10167ab43f173f246711",
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": "398c32d3a54dbf0f5e4f769dd02a45ea5de51627ca0cea74a52fdb1c314848a6",
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": "9c520e63ac582289b6905a1c5704326590923eeef90d1e02801d1a57b4262877",
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": "1cdd9e8ebb8951a4142b83222d98b671c33ac9f72e238f58cb9bf1dbf29ce3cd",
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": "fd9af416b6db636901354fca64e60adcce9d4ce71dac96f2d718562a4bdb27e0",
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": "7f221089a353391f6e917c03a6813a26b6650a8b4e7e1c6a2bd845fa4ae081ca",
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": "f2bd931d3b7bb4d30f5da9c116714345480163e9afb4c244c1a1c074410c15d0",
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": "82a95b8e5e9a5842a9dcad68bc3ed7405819a616d13d616cdb80083c32cc1fc3",
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": "eeafb3e6d20df4faa0828a20760dfcb20266ee67b428c4219e0b4231913252d4",
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAoB;AACpB,IAAAA,QAAsB;;;ACDtB,qBAAsB;AAGf,IAAM,qBAAN,MAA+C;AAAA,EAGpD,YAA6B,SAAiB;AAAjB;AAAA,EAAkB;AAAA,EAAlB;AAAA,EAFpB,UAAU;AAAA,EAInB,MAAM,KAAK,SAAiB,OAA8B;AACxD,QAAI,qBAAM,KAAK,SAAS,OAAO,EAAE,YAAY,KAAK;AAAA,EACpD;AAAA,EAEA,MAAM,KAAK,SAAyC;AAClD,QAAI;AACF,YAAM,SAAS,IAAI,qBAAM,KAAK,SAAS,OAAO,EAAE,YAAY;AAI5D,aAAO,UAAU;AAAA,IACnB,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,SAAgC;AAC3C,QAAI;AACF,UAAI,qBAAM,KAAK,SAAS,OAAO,EAAE,eAAe;AAAA,IAClD,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AChCA,SAAoB;AACpB,WAAsB;AACtB,iBAAkB;AAGlB,IAAM,qBAAqB,aAAE,OAAO,EAAE,cAAc,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO;AAEzE,IAAM,iBAAiB,aAC3B,OAAO;AAAA,EACN,SAAS,aAAE,QAAQ,CAAC;AAAA,EACpB,UAAU,aAAE,OAAO,aAAE,OAAO,EAAE,IAAI,CAAC,GAAG,kBAAkB;AAC1D,CAAC,EACA,OAAO;AAIV,IAAM,kBAA4B,EAAE,SAAS,GAAG,UAAU,CAAC,EAAE;AAEtD,IAAM,iBAAN,MAA2C;AAAA,EAGhD,YAA6B,UAAkB;AAAlB;AAAA,EAAmB;AAAA,EAAnB;AAAA,EAFpB,UAAU;AAAA,EAInB,MAAM,KAAK,SAAiB,OAA8B;AACxD,UAAM,UAAU,MAAM,KAAK,SAAS;AACpC,UAAM,UAAoB;AAAA,MACxB,GAAG;AAAA,MACH,UAAU;AAAA,QACR,GAAG,QAAQ;AAAA,QACX,CAAC,OAAO,GAAG,EAAE,cAAc,MAAM;AAAA,MACnC;AAAA,IACF;AACA,UAAM,KAAK,UAAU,OAAO;AAAA,EAC9B;AAAA,EAEA,MAAM,KAAK,SAAyC;AAClD,UAAM,OAAO,MAAM,KAAK,SAAS;AACjC,WAAO,KAAK,SAAS,OAAO,GAAG,gBAAgB;AAAA,EACjD;AAAA,EAEA,MAAM,OAAO,SAAgC;AAC3C,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,KAAK,SAAS;AAAA,IAChC,QAAQ;AAEN;AAAA,IACF;AACA,QAAI,EAAE,WAAW,QAAQ,WAAW;AAClC;AAAA,IACF;AACA,UAAM,EAAE,CAAC,OAAO,GAAG,UAAU,GAAG,KAAK,IAAI,QAAQ;AACjD,UAAM,UAAoB,EAAE,GAAG,SAAS,UAAU,KAAK;AACvD,UAAM,KAAK,UAAU,OAAO;AAAA,EAC9B;AAAA,EAEA,MAAc,WAA8B;AAC1C,QAAI;AACJ,QAAI;AACF,YAAM,MAAS,YAAS,KAAK,UAAU,MAAM;AAAA,IAC/C,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,UAAU;AACpD,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,GAAG;AAAA,IACzB,QAAQ;AACN,YAAM,IAAI;AAAA,QACR,gCAAgC,KAAK,QAAQ;AAAA,MAC/C;AAAA,IACF;AAEA,UAAM,SAAS,eAAe,UAAU,MAAM;AAC9C,QAAI,CAAC,OAAO,SAAS;AAEnB,YAAM,eAAgB,SAAqC,SAAS;AACpE,UAAI,iBAAiB,GAAG;AACtB,cAAM,IAAI;AAAA,UACR,wBAAwB,KAAK,QAAQ,yBAAyB,OAAO,YAAY,CAAC;AAAA,QACpF;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,wBAAwB,KAAK,QAAQ,KAAK,OAAO,MAAM,OAAO;AAAA,MAChE;AAAA,IACF;AAEA,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAc,UAAU,MAA+B;AACrD,UAAM,MAAW,aAAQ,KAAK,QAAQ;AACtC,UAAS,SAAM,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAEpD,UAAS,SAAM,KAAK,GAAK,EAAE,MAAM,MAAM;AAAA,IAGvC,CAAC;AAED,UAAM,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AACzC,UAAM,UAAe,UAAK,KAAK,gBAAgB;AAG/C,UAAS,aAAU,SAAS,MAAM,EAAE,MAAM,IAAM,CAAC;AAEjD,UAAS,SAAM,SAAS,GAAK;AAC7B,UAAS,UAAO,SAAS,KAAK,QAAQ;AAEtC,UAAS,SAAM,KAAK,UAAU,GAAK;AAAA,EACrC;AACF;;;AF3GA,IAAM,2BAA2B;AAEjC,SAAS,gBAAgB,MAAwC;AAC/D,MAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,QAAM,OAAU,WAAQ;AACxB,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAY,WAAK,MAAM,cAAc,WAAW;AAClD;AAEA,SAAS,YAAY,KAAmB;AACtC,UAAQ,OAAO,MAAM,MAAM,IAAI;AACjC;AAOA,eAAsB,iBACpB,OAAiC,CAAC,GACb;AACrB,QAAM,WAAW,gBAAgB,IAAI;AACrC,QAAM,UAAU,KAAK,mBAAmB;AACxC,QAAM,OAAO,KAAK,QAAQ;AAG1B,MAAI,KAAK,iBAAiB,QAAQ;AAChC,WAAO,IAAI,eAAe,QAAQ;AAAA,EACpC;AACA,MAAI,KAAK,iBAAiB,YAAY;AACpC,WAAO,IAAI,mBAAmB,OAAO;AAAA,EACvC;AAGA,MAAI;AACF,UAAM,EAAE,OAAAC,OAAM,IAAI,MAAM,OAAO,kBAAkB;AACjD,QAAIA,OAAM,SAAS,qBAAqB,EAAE,YAAY;AAEtD,WAAO,IAAI,mBAAmB,OAAO;AAAA,EACvC,QAAQ;AAIN;AAAA,MACE,uEAAuE,QAAQ;AAAA,IACjF;AACA,WAAO,IAAI,eAAe,QAAQ;AAAA,EACpC;AACF;","names":["path","Entry"]}
@@ -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":[]}