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.
Files changed (149) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/MANIFEST.json +71 -15
  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.md +4 -2
  25. package/templates/cleargate-planning/.claude/agents/developer.md +4 -11
  26. package/templates/cleargate-planning/.claude/agents/qa.md +14 -6
  27. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +2 -2
  28. package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +19 -1
  29. package/templates/cleargate-planning/.cleargate/config.example.yml +16 -0
  30. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.deferred-verify.red.node.test.ts +245 -0
  31. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +227 -0
  32. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +5 -4
  33. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +75 -2
  34. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +48 -0
  35. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +57 -1
  36. package/templates/cleargate-planning/.cleargate/scripts/provision_worktree_config.sh +155 -0
  37. package/templates/cleargate-planning/.cleargate/scripts/qa_red_lint.mjs +380 -0
  38. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +34 -1
  39. package/templates/cleargate-planning/.cleargate/scripts/test/cr077_eviction.red.sh +113 -0
  40. package/templates/cleargate-planning/.cleargate/scripts/test/cr078_init.test.sh +309 -0
  41. package/templates/cleargate-planning/.cleargate/scripts/test/cr079_provision.red.sh +262 -0
  42. package/templates/cleargate-planning/.cleargate/scripts/test/cr080_wrapper.test.sh +177 -0
  43. package/templates/cleargate-planning/.cleargate/scripts/test/cr081_qa_red_lint.red.sh +348 -0
  44. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/.session-totals.json +1 -0
  45. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/token-ledger.jsonl +27 -0
  46. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +17 -0
  47. package/templates/cleargate-planning/.cleargate/templates/story.md +1 -0
  48. package/templates/cleargate-planning/MANIFEST.json +71 -15
  49. package/dist/admin-api/index.cjs.map +0 -1
  50. package/dist/admin-api/index.js.map +0 -1
  51. package/dist/auth/factory.cjs.map +0 -1
  52. package/dist/auth/factory.js.map +0 -1
  53. package/dist/auth/require-token.cjs.map +0 -1
  54. package/dist/auth/require-token.js.map +0 -1
  55. package/dist/auth/token-store.cjs.map +0 -1
  56. package/dist/auth/token-store.js.map +0 -1
  57. package/dist/bootstrap-root-QKSA5V75.js.map +0 -1
  58. package/dist/chunk-5DI2Z3C2.js.map +0 -1
  59. package/dist/chunk-BTSZOEWC.js.map +0 -1
  60. package/dist/chunk-E3X7IE5E.js.map +0 -1
  61. package/dist/chunk-PDE37WFQ.js.map +0 -1
  62. package/dist/cli.cjs.map +0 -1
  63. package/dist/cli.js.map +0 -1
  64. package/dist/lib/ledger.cjs.map +0 -1
  65. package/dist/lib/ledger.js.map +0 -1
  66. package/dist/lib/lifecycle-reconcile.cjs.map +0 -1
  67. package/dist/lib/lifecycle-reconcile.js.map +0 -1
  68. package/dist/templates/cleargate-planning/.claude/agents/architect-reader.md +0 -61
  69. package/dist/templates/cleargate-planning/.claude/agents/architect-synth.md +0 -124
  70. package/dist/templates/cleargate-planning/.claude/agents/architect.md +0 -230
  71. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +0 -108
  72. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +0 -194
  73. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +0 -261
  74. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +0 -143
  75. package/dist/templates/cleargate-planning/.claude/agents/developer.md +0 -185
  76. package/dist/templates/cleargate-planning/.claude/agents/devops.md +0 -257
  77. package/dist/templates/cleargate-planning/.claude/agents/qa.md +0 -171
  78. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +0 -274
  79. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +0 -209
  80. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +0 -33
  81. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +0 -58
  82. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +0 -19
  83. package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +0 -162
  84. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-autonomy.sh +0 -58
  85. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +0 -148
  86. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +0 -75
  87. package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +0 -43
  88. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +0 -590
  89. package/dist/templates/cleargate-planning/.claude/settings.json +0 -68
  90. package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +0 -102
  91. package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +0 -742
  92. package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +0 -7
  93. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +0 -67
  94. package/dist/templates/cleargate-planning/.cleargate/config.yml +0 -18
  95. package/dist/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
  96. package/dist/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
  97. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +0 -551
  98. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +0 -878
  99. package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +0 -160
  100. package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +0 -213
  101. package/dist/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +0 -71
  102. package/dist/templates/cleargate-planning/.cleargate/scripts/_migrate-schema-v3.mjs +0 -120
  103. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +0 -265
  104. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +0 -1012
  105. package/dist/templates/cleargate-planning/.cleargate/scripts/collision_surface.sh +0 -114
  106. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +0 -62
  107. package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +0 -219
  108. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +0 -320
  109. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +0 -15
  110. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +0 -38
  111. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +0 -240
  112. package/dist/templates/cleargate-planning/.cleargate/scripts/launch_wave.mjs +0 -341
  113. package/dist/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +0 -54
  114. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +0 -206
  115. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +0 -371
  116. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +0 -280
  117. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +0 -378
  118. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +0 -888
  119. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +0 -209
  120. package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +0 -71
  121. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +0 -127
  122. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +0 -717
  123. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +0 -27
  124. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +0 -261
  125. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +0 -210
  126. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +0 -190
  127. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +0 -482
  128. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +0 -327
  129. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +0 -261
  130. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +0 -246
  131. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +0 -111
  132. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +0 -184
  133. package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +0 -172
  134. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +0 -126
  135. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +0 -130
  136. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +0 -137
  137. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +0 -166
  138. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +0 -111
  139. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +0 -122
  140. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +0 -50
  141. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +0 -224
  142. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +0 -213
  143. package/dist/templates/cleargate-planning/CLAUDE.md +0 -66
  144. package/dist/templates/cleargate-planning/MANIFEST.json +0 -503
  145. package/dist/templates/synthesis/active-sprint.md +0 -30
  146. package/dist/templates/synthesis/open-gates.md +0 -38
  147. package/dist/templates/synthesis/product-state.md +0 -31
  148. package/dist/templates/synthesis/roadmap.md +0 -63
  149. package/dist/whoami-EANGN46Z.js.map +0 -1
@@ -1,341 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * launch_wave.mjs — STORY-033-04 (EPIC-033 capstone): Parallel-Wave Execution + Barrier.
4
- *
5
- * Drives a `parallel()` Workflow of one worktree-isolated segment per story in an
6
- * Architect-planned wave (waves.json, produced by STORY-033-03), mints a stable per-thunk
7
- * RUN_ID for each segment, and returns the array of schema-typed segment verdicts to the
8
- * Orchestrator at the barrier. The Orchestrator consolidates serially: validate → process
9
- * flashcards (between-wave) → merge GREEN stories one worktree at a time → restore env.
10
- *
11
- * SPRINT-32 constraint (SDR §2.1): this sprint BUILDS the wave capability but runs SERIALLY.
12
- * No live `parallel()` dispatch executes here. The launcher ships for the NEXT (self-hosting)
13
- * sprint. The testable JS surface is the exported `validateVerdicts` barrier validator (plus
14
- * the helper constructors); the `parallel()` dispatch itself is exercised only when the
15
- * next sprint runs under `execution_mode: v2-parallel`.
16
- *
17
- * Spike facts honored (STORY-033-01-spike-result.md, FLASHCARD #workflow #token-ledger #worktree):
18
- * - Per-agent SubagentStop attribution is DEAD under workflows → the barrier writes one
19
- * ledger row per segment from `verdict.tokens`, keyed by RUN_ID (STORY-033-02 owns the write).
20
- * - `PreToolUse:Task` never fires under workflows → no dispatch-marker auto-attribution.
21
- * - Per-thunk child env is NOT settable → the Orchestrator sets `SKIP_FLASHCARD_GATE=1` in
22
- * its OWN env before launch (inherited by all children) and restores it at the barrier.
23
- * - `isolation:'worktree'` checks out tracked-files-only off the wrong base → segments use
24
- * ClearGate's OWN `git worktree add .worktrees/STORY-X -b story/STORY-X sprint/S-NN`.
25
- * - `resumeFromRunId` caches completed GREEN agents (0 tokens on replay) → complete-then-resume
26
- * re-dispatches only the ESCALATED/BLOCKED segment.
27
- *
28
- * Kill-switch: `execution_mode: v2-serial` in sprint frontmatter OR `CLEARGATE_PARALLEL_WAVES=off`
29
- * in the session env routes to today's serial Phase C loop with ZERO behavior change — no
30
- * launch_wave.mjs invocation occurs on that path.
31
- *
32
- * Verdict schema (discriminated union, STORY-033-04 §1.2 — inherits protocol §23):
33
- * {
34
- * verdict: 'GREEN' | 'ESCALATED' | 'BLOCKED',
35
- * storyId: string,
36
- * runId: string,
37
- * devSha: string,
38
- * qaSha: string,
39
- * archSha?: string,
40
- * flashcards_flagged: string[],
41
- * counters: { qa_bounces: number, arch_bounces: number, breaker_hits: number },
42
- * tokens: { input, output, cache_creation, cache_read, model },
43
- * blocker?: { type, message } // REQUIRED iff verdict !== 'GREEN'
44
- * }
45
- * blocker.type ∈ the five §22 true-blocker kinds.
46
- */
47
-
48
- import crypto from 'node:crypto';
49
-
50
- // ---------------------------------------------------------------------------
51
- // Constants — the verdict discriminated-union vocabulary (protocol §23).
52
- // ---------------------------------------------------------------------------
53
-
54
- /** The three terminal verdict states a segment may return at the barrier. */
55
- export const VERDICT_KINDS = Object.freeze(['GREEN', 'ESCALATED', 'BLOCKED']);
56
-
57
- /**
58
- * The five protocol-§22 true-blocker kinds. A non-GREEN verdict's `blocker.type`
59
- * MUST be one of these (inherited verbatim from §22 — do not redefine here).
60
- */
61
- export const BLOCKER_TYPES = Object.freeze([
62
- 'destructive', // force-push, git reset --hard, drop table, delete untracked file, kill process
63
- 'secret', // reading .env / fetching a secret / persisting credential material
64
- 'user-intent', // a genuine "what does the user want?" not inferable from goal/Gherkin
65
- 'technical-impossibility', // required infra unavailable (test DB unreachable, MCP down)
66
- 'spec-contradiction', // story Gherkin self-contradicts or two wave stories collide on a shared surface
67
- ]);
68
-
69
- /** Required scalar fields present on EVERY verdict regardless of discriminant. */
70
- const REQUIRED_SCALAR_FIELDS = Object.freeze(['storyId', 'runId', 'devSha', 'qaSha']);
71
-
72
- /** Required `tokens` sub-fields (per-segment cost the barrier writes to the ledger). */
73
- const REQUIRED_TOKEN_FIELDS = Object.freeze([
74
- 'input',
75
- 'output',
76
- 'cache_creation',
77
- 'cache_read',
78
- 'model',
79
- ]);
80
-
81
- /** Required `counters` sub-fields. */
82
- const REQUIRED_COUNTER_FIELDS = Object.freeze(['qa_bounces', 'arch_bounces', 'breaker_hits']);
83
-
84
- // ---------------------------------------------------------------------------
85
- // RUN_ID minting — one stable, distinct id per wave segment.
86
- // ---------------------------------------------------------------------------
87
-
88
- /**
89
- * Mint a stable, distinct RUN_ID for a wave segment. Stable so a resumeFromRunId replay
90
- * can re-key the same segment; distinct so sibling segments do not collide on the
91
- * RUN_ID-keyed ledger row STORY-033-02 writes.
92
- *
93
- * @param {string} storyId The story this segment executes (e.g. "STORY-033-04").
94
- * @param {string} sprintId The active sprint (e.g. "SPRINT-32").
95
- * @returns {string} A run id of the form "run-<sprint>-<story>-<8hex>".
96
- */
97
- export function mintRunId(storyId, sprintId) {
98
- const entropy = crypto.randomBytes(4).toString('hex');
99
- const story = String(storyId || 'STORY-UNKNOWN').replace(/[^A-Za-z0-9-]/g, '');
100
- const sprint = String(sprintId || 'SPRINT-UNKNOWN').replace(/[^A-Za-z0-9-]/g, '');
101
- return `run-${sprint}-${story}-${entropy}`;
102
- }
103
-
104
- // ---------------------------------------------------------------------------
105
- // Worktree command construction (spike decision 2 — ClearGate-managed worktrees).
106
- // ---------------------------------------------------------------------------
107
-
108
- /**
109
- * Build the bash `git worktree add` command for a segment. ClearGate manages its OWN
110
- * worktrees (NOT the Workflow tool's `isolation:'worktree'`, which checks out
111
- * tracked-files-only off the wrong base — spike decision 2). Worktrees are pre-created
112
- * SERIALLY at wave launch to avoid concurrent `git worktree add` racing the repo index.
113
- *
114
- * @param {string} storyId e.g. "STORY-033-04"
115
- * @param {string} sprintBranch e.g. "sprint/S-32"
116
- * @returns {string} the bash command string
117
- */
118
- export function worktreeAddCommand(storyId, sprintBranch) {
119
- const story = String(storyId);
120
- return `git worktree add .worktrees/${story} -b story/${story} ${sprintBranch}`;
121
- }
122
-
123
- // ---------------------------------------------------------------------------
124
- // Kill-switch — does this run go parallel, or revert to the serial Phase C loop?
125
- // ---------------------------------------------------------------------------
126
-
127
- /**
128
- * Decide whether the wave loop should run in parallel. Returns false (serial) when EITHER
129
- * the sprint frontmatter `execution_mode` is `v2-serial` OR the session env carries
130
- * `CLEARGATE_PARALLEL_WAVES=off`. Either revert path runs today's serial five-dispatch
131
- * Phase C loop with ZERO behavior change — no launch_wave.mjs invocation occurs.
132
- *
133
- * @param {string|undefined} executionMode sprint-frontmatter `execution_mode` value
134
- * @param {NodeJS.ProcessEnv} [env] session env (defaults to process.env)
135
- * @returns {boolean} true → parallel waves; false → serial loop
136
- */
137
- export function shouldRunParallel(executionMode, env = process.env) {
138
- if ((env.CLEARGATE_PARALLEL_WAVES || '').toLowerCase() === 'off') return false;
139
- if (executionMode === 'v2-serial') return false;
140
- return executionMode === 'v2-parallel';
141
- }
142
-
143
- // ---------------------------------------------------------------------------
144
- // Barrier verdict validator (EXPORTED — STORY-033-04 §1.2 / §4.1).
145
- // ---------------------------------------------------------------------------
146
-
147
- /**
148
- * Internal: validate a single verdict, returning a human-readable reason string when it
149
- * is malformed, or null when it is well-formed. Never throws — the array-level
150
- * `validateVerdicts` owns the throw so it can name the offending storyId.
151
- *
152
- * @param {unknown} v
153
- * @returns {string|null} malformation reason, or null if valid
154
- */
155
- function verdictMalformationReason(v) {
156
- if (v === null || typeof v !== 'object') {
157
- return 'verdict is not an object';
158
- }
159
- const verdict = /** @type {Record<string, unknown>} */ (v);
160
-
161
- // Discriminant must be one of the three known verdict kinds.
162
- if (!VERDICT_KINDS.includes(/** @type {string} */ (verdict.verdict))) {
163
- return `'verdict' must be one of ${VERDICT_KINDS.join(', ')} (got ${JSON.stringify(verdict.verdict)})`;
164
- }
165
-
166
- // Required scalar fields.
167
- for (const field of REQUIRED_SCALAR_FIELDS) {
168
- if (typeof verdict[field] !== 'string' || verdict[field] === '') {
169
- return `missing or empty required field '${field}'`;
170
- }
171
- }
172
-
173
- // flashcards_flagged must be an array (may be empty).
174
- if (!Array.isArray(verdict.flashcards_flagged)) {
175
- return `'flashcards_flagged' must be an array`;
176
- }
177
-
178
- // counters object with the three required numeric sub-fields.
179
- const counters = verdict.counters;
180
- if (counters === null || typeof counters !== 'object') {
181
- return `missing required 'counters' object`;
182
- }
183
- for (const field of REQUIRED_COUNTER_FIELDS) {
184
- if (typeof (/** @type {Record<string, unknown>} */ (counters))[field] !== 'number') {
185
- return `'counters.${field}' must be a number`;
186
- }
187
- }
188
-
189
- // tokens object with the five required sub-fields (per-segment cost the ledger consumes).
190
- const tokens = verdict.tokens;
191
- if (tokens === null || typeof tokens !== 'object') {
192
- return `missing required 'tokens' object`;
193
- }
194
- for (const field of REQUIRED_TOKEN_FIELDS) {
195
- const val = (/** @type {Record<string, unknown>} */ (tokens))[field];
196
- if (field === 'model') {
197
- if (typeof val !== 'string' || val === '') return `'tokens.model' must be a non-empty string`;
198
- } else if (typeof val !== 'number') {
199
- return `'tokens.${field}' must be a number`;
200
- }
201
- }
202
-
203
- // blocker is REQUIRED iff verdict !== GREEN (§1.2).
204
- if (verdict.verdict !== 'GREEN') {
205
- const blocker = verdict.blocker;
206
- if (blocker === null || blocker === undefined || typeof blocker !== 'object') {
207
- return `non-GREEN verdict (${verdict.verdict}) is missing the required 'blocker' object`;
208
- }
209
- const b = /** @type {Record<string, unknown>} */ (blocker);
210
- if (!BLOCKER_TYPES.includes(/** @type {string} */ (b.type))) {
211
- return `'blocker.type' must be one of the §22 true-blocker kinds ${BLOCKER_TYPES.join(', ')} (got ${JSON.stringify(b.type)})`;
212
- }
213
- if (typeof b.message !== 'string' || b.message === '') {
214
- return `'blocker.message' must be a non-empty string`;
215
- }
216
- }
217
-
218
- return null;
219
- }
220
-
221
- /**
222
- * Barrier validator. Accepts a well-formed verdict array; raises a validation Error NAMING
223
- * the offending `storyId` on the first malformed verdict (missing required field, unknown
224
- * discriminant, or non-GREEN without a valid `blocker`). The named segment is the one the
225
- * Orchestrator marks ESCALATED (no ledger row); sibling GREEN verdicts consolidate.
226
- *
227
- * @param {unknown[]} verdicts
228
- * @returns {void}
229
- * @throws {Error} when any verdict is malformed — message contains the offending storyId.
230
- */
231
- export function validateVerdicts(verdicts) {
232
- if (!Array.isArray(verdicts)) {
233
- throw new Error(
234
- `validateVerdicts: expected an array of segment verdicts, got ${typeof verdicts}.`,
235
- );
236
- }
237
-
238
- for (const v of verdicts) {
239
- const reason = verdictMalformationReason(v);
240
- if (reason !== null) {
241
- // Name the offending storyId so the Orchestrator can mark exactly that segment
242
- // ESCALATED. Fall back to a placeholder when the verdict has no usable storyId.
243
- const offendingId =
244
- v !== null && typeof v === 'object' && typeof v.storyId === 'string' && v.storyId !== ''
245
- ? v.storyId
246
- : '<unknown-storyId>';
247
- throw new Error(`Malformed segment verdict for ${offendingId}: ${reason}.`);
248
- }
249
- }
250
- }
251
-
252
- // ---------------------------------------------------------------------------
253
- // launchWave — the parallel() driver (next-sprint surface; not exercised this sprint).
254
- // ---------------------------------------------------------------------------
255
-
256
- /**
257
- * Drive a `parallel()` Workflow of one segment per story in the wave. Each segment mints a
258
- * stable RUN_ID, runs the linear per-story pipeline inside its own ClearGate-managed
259
- * `.worktrees/STORY-X`, accumulates `tokens` from the outset, and RETURNS a verdict object —
260
- * it never blocks (a §22 true-blocker becomes a BLOCKED verdict with `blocker.type`, never an
261
- * `AskUserQuestion`).
262
- *
263
- * Flashcard write-gate (BUG-034 — code-enforced restore). Per the spike, per-thunk child env
264
- * is not settable, so the WRITE-gate is suppressed by setting `SKIP_FLASHCARD_GATE=1` in the
265
- * env inherited by every segment for the duration of the `parallel()` dispatch. This launcher
266
- * OWNS that save/set/restore in a `try/finally`: the prior value is snapshotted, `=1` is set
267
- * before dispatch, and the EXACT prior value is restored (deleted iff it was unset) in a
268
- * `finally` that runs even when `parallel()` or `validateVerdicts()` throws. Previously this
269
- * was an Orchestrator prose obligation (SKILL.md §C.0.1); a mid-barrier throw left the gate
270
- * suppressed session-wide, leaking into the serial fallback EPIC-033 promises is untouched.
271
- *
272
- * `validateVerdicts()` (the designed throw site) runs INSIDE the protected region so the
273
- * `finally` always restores the gate before the validation Error propagates to the caller.
274
- *
275
- * The `parallel`/`segmentRunner` seams are injected so this is testable without a live
276
- * Workflow runtime (SPRINT-32 runs serial — no real dispatch). When omitted, the function
277
- * documents the shape and performs NO env mutation; the live launcher wires the Workflow
278
- * `parallel()` primitive.
279
- *
280
- * @param {object} args
281
- * @param {string} args.sprintId
282
- * @param {string} args.sprintBranch
283
- * @param {{ wave: string, stories: string[], parallel: boolean }} args.wave one wave from waves.json
284
- * @param {(thunks: Array<() => Promise<object>>) => Promise<object[]>} [args.parallel] parallel() seam
285
- * @param {(story: string, runId: string) => Promise<object>} [args.segmentRunner] per-segment seam
286
- * @param {NodeJS.ProcessEnv} [args.env] env handle for the gate save/restore (defaults to process.env)
287
- * @returns {Promise<object[]>} the validated array of segment verdicts
288
- * @throws {Error} from validateVerdicts when any verdict is malformed — gate is restored first.
289
- */
290
- export async function launchWave({ sprintId, sprintBranch, wave, parallel, segmentRunner, env = process.env }) {
291
- const stories = (wave && Array.isArray(wave.stories) ? wave.stories : []).slice();
292
-
293
- // Pre-create each wave story's worktree SERIALLY (spike risk-mitigation: concurrent
294
- // `git worktree add` races the repo index). The actual exec is the Orchestrator's job;
295
- // here we surface the commands so the launcher / tests can assert their construction.
296
- const worktreeCommands = stories.map((story) => worktreeAddCommand(story, sprintBranch));
297
-
298
- // Build one thunk per story; each mints its own distinct RUN_ID.
299
- const segmentPlan = stories.map((story) => ({ story, runId: mintRunId(story, sprintId) }));
300
-
301
- if (typeof parallel !== 'function' || typeof segmentRunner !== 'function') {
302
- // No live runtime seam supplied (the SPRINT-32 serial-build case). Return the plan so a
303
- // caller can inspect the worktree commands + minted RUN_IDs without dispatching agents.
304
- // NO env mutation here — nothing is dispatched, so the gate need not be suppressed.
305
- return segmentPlan.map((seg) => ({
306
- storyId: seg.story,
307
- runId: seg.runId,
308
- worktreeCommand: worktreeAddCommand(seg.story, sprintBranch),
309
- planned: true,
310
- }));
311
- }
312
-
313
- // BUG-034: snapshot the PRIOR gate value, suppress it for the dispatch, restore it EXACTLY
314
- // in a `finally` — even if parallel()/validateVerdicts() throws. `hasOwnProperty` (not a
315
- // truthiness check) distinguishes "unset" from "set to empty string" so restore is exact.
316
- const hadPriorGate = Object.prototype.hasOwnProperty.call(env, 'SKIP_FLASHCARD_GATE');
317
- const priorGate = env.SKIP_FLASHCARD_GATE;
318
- env.SKIP_FLASHCARD_GATE = '1';
319
- try {
320
- const thunks = segmentPlan.map((seg) => () => segmentRunner(seg.story, seg.runId));
321
- const verdicts = await parallel(thunks);
322
- // Designed throw site (names the offending storyId). Inside the try so the finally below
323
- // restores the gate before the Error propagates to the Orchestrator.
324
- validateVerdicts(verdicts);
325
- void worktreeCommands; // pre-created by the Orchestrator before the parallel() call
326
- return verdicts;
327
- } finally {
328
- if (hadPriorGate) env.SKIP_FLASHCARD_GATE = priorGate;
329
- else delete env.SKIP_FLASHCARD_GATE;
330
- }
331
- }
332
-
333
- // When run directly (`node launch_wave.mjs`) print a short usage note — there is no live
334
- // wave to drive in this sprint, so direct execution is informational only.
335
- if (import.meta.url === `file://${process.argv[1]}`) {
336
- process.stdout.write(
337
- 'launch_wave.mjs — parallel-wave launcher (EPIC-033). ' +
338
- 'Exports validateVerdicts, mintRunId, worktreeAddCommand, shouldRunParallel, launchWave. ' +
339
- 'Not invoked under execution_mode: v2-serial or CLEARGATE_PARALLEL_WAVES=off (serial Phase C loop).\n',
340
- );
341
- }
@@ -1,54 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * report-filename.mjs — Shared helper for computing the sprint report filename.
4
- *
5
- * Named export only — no default.
6
- *
7
- * Dependencies: node:path, node:fs only. No third-party deps.
8
- *
9
- * Design notes:
10
- * - Helper is pure given its arguments + a filesystem read (when opts.forRead=true).
11
- * - Never throws — callers decide whether to fs.readFileSync and handle ENOENT.
12
- * - Never reads env vars. Sprint dir resolution stays in callers.
13
- * (Each consumer owns CLEARGATE_SPRINT_DIR resolution.)
14
- */
15
-
16
- import path from 'node:path';
17
- import fs from 'node:fs';
18
-
19
- /**
20
- * Compute the report filename for a given sprint directory + sprint ID.
21
- *
22
- * New naming (SPRINT-18+): SPRINT-<#>_REPORT.md where <#> is the numeric
23
- * portion of the sprint-id (e.g. "18" for "SPRINT-18").
24
- * No-numeric-portion ids (e.g. "SPRINT-TEST") → plain REPORT.md.
25
- *
26
- * Backwards-compat read-fallback: when opts.forRead === true AND the new-name
27
- * file is absent BUT legacy REPORT.md exists, return the legacy path.
28
- * Covers SPRINT-01..17 archives written before STORY-025-03's naming change.
29
- * MUST NOT rename or rewrite those pre-existing files.
30
- *
31
- * @param {string} sprintDirPath absolute path to the sprint directory
32
- * @param {string} sprintId e.g. "SPRINT-18" or "SPRINT-TEST"
33
- * @param {{ forRead?: boolean }} [opts]
34
- * @returns {string} absolute path to the report file
35
- */
36
- export function reportFilename(sprintDirPath, sprintId, opts) {
37
- const numMatch = sprintId.match(/^SPRINT-(\d+)$/);
38
- if (!numMatch) {
39
- // No numeric portion — use plain REPORT.md (e.g. SPRINT-TEST)
40
- return path.join(sprintDirPath, 'REPORT.md');
41
- }
42
- const sprintNumber = numMatch[1];
43
- const newName = path.join(sprintDirPath, `SPRINT-${sprintNumber}_REPORT.md`);
44
-
45
- // Read-fallback: if the new-name file doesn't exist but legacy REPORT.md does, use legacy.
46
- if (opts?.forRead) {
47
- const legacyName = path.join(sprintDirPath, 'REPORT.md');
48
- if (!fs.existsSync(newName) && fs.existsSync(legacyName)) {
49
- return legacyName;
50
- }
51
- }
52
-
53
- return newName;
54
- }
@@ -1,206 +0,0 @@
1
- #!/usr/bin/env bash
2
- # pre_gate_common.sh — Shared helpers for pre_gate_runner.sh
3
- # Sourced by pre_gate_runner.sh. Do NOT execute directly.
4
- # Handles Node+TS stacks only (no Python/Rust/Go/Java/Swift detectors).
5
- set -euo pipefail
6
-
7
- # ---------------------------------------------------------------------------
8
- # read_config_field <field_path> <config_file>
9
- # Uses node -p to parse JSON and extract a field.
10
- # field_path: dot-separated path, e.g. "qa.typecheck"
11
- # ---------------------------------------------------------------------------
12
- read_config_field() {
13
- local field_path="$1"
14
- local config_file="$2"
15
- node -p "
16
- const c = JSON.parse(require('fs').readFileSync('${config_file}', 'utf8'));
17
- const parts = '${field_path}'.split('.');
18
- let v = c;
19
- for (const p of parts) { v = v[p]; }
20
- Array.isArray(v) ? JSON.stringify(v) : (v === undefined ? '' : String(v));
21
- " 2>/dev/null || echo ""
22
- }
23
-
24
- # ---------------------------------------------------------------------------
25
- # get_modified_files <worktree_path>
26
- # Lists files modified in the current git index vs HEAD.
27
- # ---------------------------------------------------------------------------
28
- get_modified_files() {
29
- local worktree="$1"
30
- git -C "$worktree" diff --name-only HEAD 2>/dev/null || true
31
- git -C "$worktree" diff --cached --name-only 2>/dev/null || true
32
- }
33
-
34
- # ---------------------------------------------------------------------------
35
- # get_staged_diff <worktree_path>
36
- # Returns unified diff of staged changes.
37
- # ---------------------------------------------------------------------------
38
- get_staged_diff() {
39
- local worktree="$1"
40
- git -C "$worktree" diff --cached 2>/dev/null || git -C "$worktree" diff HEAD 2>/dev/null || true
41
- }
42
-
43
- # ---------------------------------------------------------------------------
44
- # record_result <report_file> <check_name> <status> [details]
45
- # Appends one result line to the report.
46
- # status: PASS | FAIL | WARN | INFO
47
- # ---------------------------------------------------------------------------
48
- record_result() {
49
- local report_file="$1"
50
- local check_name="$2"
51
- local status="$3"
52
- local details="${4:-}"
53
- echo "[${status}] ${check_name}: ${details}" >> "$report_file"
54
- }
55
-
56
- # ---------------------------------------------------------------------------
57
- # print_summary <report_file>
58
- # Prints count of PASS/FAIL/WARN lines from the report.
59
- # ---------------------------------------------------------------------------
60
- print_summary() {
61
- local report_file="$1"
62
- local pass_count fail_count warn_count
63
- pass_count=$(grep -c '^\[PASS\]' "$report_file" 2>/dev/null || echo 0)
64
- fail_count=$(grep -c '^\[FAIL\]' "$report_file" 2>/dev/null || echo 0)
65
- warn_count=$(grep -c '^\[WARN\]' "$report_file" 2>/dev/null || echo 0)
66
- echo "Summary: ${pass_count} passed, ${fail_count} failed, ${warn_count} warnings"
67
- }
68
-
69
- # ---------------------------------------------------------------------------
70
- # write_report <report_file> <mode> <worktree_path> <branch>
71
- # Writes the report header.
72
- # ---------------------------------------------------------------------------
73
- write_report_header() {
74
- local report_file="$1"
75
- local mode="$2"
76
- local worktree="$3"
77
- local branch="$4"
78
- mkdir -p "$(dirname "$report_file")"
79
- {
80
- echo "# ClearGate Pre-Gate Scan Report"
81
- echo "Mode: ${mode}"
82
- echo "Worktree: ${worktree}"
83
- echo "Branch: ${branch}"
84
- echo "Timestamp: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
85
- echo "---"
86
- } > "$report_file"
87
- }
88
-
89
- # ---------------------------------------------------------------------------
90
- # detect_stack <worktree_path>
91
- # Returns "node-ts" if package.json + tsconfig.json found, else "unknown".
92
- # Node+TS only — no Python/Rust/Go/Java/Swift detectors.
93
- # ---------------------------------------------------------------------------
94
- detect_stack() {
95
- local worktree="$1"
96
- if [[ -f "${worktree}/package.json" ]]; then
97
- if [[ -f "${worktree}/tsconfig.json" ]]; then
98
- echo "node-ts"
99
- else
100
- echo "node"
101
- fi
102
- else
103
- echo "unknown"
104
- fi
105
- }
106
-
107
- # ---------------------------------------------------------------------------
108
- # resolve_story_id_from_branch <branch>
109
- # Extracts story/CR/bug ID from a branch name like story/STORY-022-04.
110
- # Pattern (per M4 plan §1): (story|cr|bug)/([A-Z-]+-[0-9]+(-[0-9]+)?)
111
- # Returns the ID (group 2) on stdout, or empty string on no match.
112
- # Cross-OS: bash 3.2+, POSIX ERE grep -E, quoted expansions, no sed -E extensions.
113
- # ---------------------------------------------------------------------------
114
- resolve_story_id_from_branch() {
115
- local branch="$1"
116
- # Match the full pattern and extract just the ID part after the slash
117
- local matched
118
- matched="$(printf '%s\n' "${branch}" | grep -oE '(story|cr|bug)/[A-Z][A-Z-]*[A-Z]-[0-9]+(-[0-9]+)?' | head -1 || true)"
119
- if [[ -n "${matched}" ]]; then
120
- # Strip the (story|cr|bug)/ prefix — everything up to and including first /
121
- printf '%s\n' "${matched#*/}"
122
- else
123
- printf ''
124
- fi
125
- }
126
-
127
- # ---------------------------------------------------------------------------
128
- # resolve_lane <state_json_path> <story_id>
129
- # Reads the lane field for a story from state.json.
130
- # Returns "standard" if story absent, lane absent, or jq fails.
131
- # jq 1.5+ syntax. Cross-OS portable.
132
- # ---------------------------------------------------------------------------
133
- resolve_lane() {
134
- local state_json="$1"
135
- local story_id="$2"
136
- if [[ ! -f "${state_json}" ]]; then
137
- printf 'standard\n'
138
- return
139
- fi
140
- local lane
141
- lane="$(jq -r --arg sid "${story_id}" '.stories[$sid].lane // "standard"' "${state_json}" 2>/dev/null || printf 'standard')"
142
- if [[ -z "${lane}" || "${lane}" = "null" ]]; then
143
- lane="standard"
144
- fi
145
- printf '%s\n' "${lane}"
146
- }
147
-
148
- # ---------------------------------------------------------------------------
149
- # append_ld_event <sprint_md_path> <story_id> <reason>
150
- # Appends an LD event row to the sprint markdown §4 Events section.
151
- # If "## 4. Events" heading absent, creates it with table header first.
152
- # Idempotent by design (orchestrator calls once per demotion).
153
- # Cross-OS: bash 3.2+, portable date, printf.
154
- # ---------------------------------------------------------------------------
155
- append_ld_event() {
156
- local sprint_md="$1"
157
- local story_id="$2"
158
- local reason="$3"
159
- local timestamp
160
- timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
161
- # Truncate reason to 80 chars
162
- reason="$(printf '%s' "${reason}" | cut -c1-80)"
163
-
164
- if [[ ! -f "${sprint_md}" ]]; then
165
- printf 'append_ld_event: sprint markdown not found: %s\n' "${sprint_md}" >&2
166
- return 1
167
- fi
168
-
169
- local row
170
- row="$(printf '| LD | %s | %s | %s |' "${story_id}" "${timestamp}" "${reason}")"
171
-
172
- if grep -q '^## 4\. Events' "${sprint_md}" 2>/dev/null; then
173
- # Section exists: append row only
174
- printf '\n%s\n' "${row}" >> "${sprint_md}"
175
- else
176
- # Section absent: append heading + table header + row
177
- printf '\n## 4. Events\n\n| Event | Story | Timestamp | Reason |\n|---|---|---|---|\n%s\n' "${row}" >> "${sprint_md}"
178
- fi
179
- }
180
-
181
- # ---------------------------------------------------------------------------
182
- # diff_package_json <worktree_path> <branch>
183
- # Prints new runtime deps (non-dev) introduced vs <branch>^.
184
- # Returns lines like: "new runtime dep: <name>"
185
- # ---------------------------------------------------------------------------
186
- diff_package_json() {
187
- local worktree="$1"
188
- local branch="$2"
189
-
190
- # Get old package.json from branch parent (branch^ in the worktree's git)
191
- local old_json
192
- old_json=$(git -C "$worktree" show "${branch}^:package.json" 2>/dev/null || echo '{"dependencies":{}}')
193
-
194
- local new_json
195
- new_json=$(cat "${worktree}/package.json" 2>/dev/null || echo '{"dependencies":{}}')
196
-
197
- # Extract dep keys using node
198
- node -e "
199
- const oldPkg = JSON.parse($(echo "$old_json" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>process.stdout.write(JSON.stringify(d)))"));
200
- const newPkg = JSON.parse($(echo "$new_json" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>process.stdout.write(JSON.stringify(d)))"));
201
- const oldDeps = Object.keys(oldPkg.dependencies || {});
202
- const newDeps = Object.keys(newPkg.dependencies || {});
203
- const added = newDeps.filter(d => !oldDeps.includes(d));
204
- added.forEach(d => console.log('new runtime dep: ' + d));
205
- " 2>/dev/null || true
206
- }