cleargate 0.10.0 → 0.11.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.
- package/CHANGELOG.md +27 -0
- package/README.md +11 -1
- package/dist/MANIFEST.json +40 -26
- package/dist/chunk-HZPJ5QX4.js +459 -0
- package/dist/chunk-HZPJ5QX4.js.map +1 -0
- package/dist/cli.cjs +421 -204
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +389 -515
- package/dist/cli.js.map +1 -1
- package/dist/lib/lifecycle-reconcile.cjs +497 -0
- package/dist/lib/lifecycle-reconcile.cjs.map +1 -0
- package/dist/lib/lifecycle-reconcile.d.cts +136 -0
- package/dist/lib/lifecycle-reconcile.d.ts +136 -0
- package/dist/lib/lifecycle-reconcile.js +20 -0
- package/dist/lib/lifecycle-reconcile.js.map +1 -0
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +55 -2
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +22 -0
- package/dist/templates/cleargate-planning/.claude/agents/devops.md +249 -0
- package/dist/templates/cleargate-planning/.claude/agents/qa.md +41 -0
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +44 -8
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +21 -0
- package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +12 -1
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +21 -1
- package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +200 -29
- package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +160 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +41 -9
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +98 -16
- package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +3 -3
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +86 -10
- package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +173 -87
- package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +150 -22
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +20 -20
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +32 -8
- package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +12 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +3 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +3 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +3 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +3 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +8 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +3 -0
- package/dist/templates/cleargate-planning/CLAUDE.md +3 -1
- package/dist/templates/cleargate-planning/MANIFEST.json +40 -26
- package/package.json +8 -5
- package/templates/cleargate-planning/.claude/agents/architect.md +55 -2
- package/templates/cleargate-planning/.claude/agents/developer.md +22 -0
- package/templates/cleargate-planning/.claude/agents/devops.md +249 -0
- package/templates/cleargate-planning/.claude/agents/qa.md +41 -0
- package/templates/cleargate-planning/.claude/agents/reporter.md +44 -8
- package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +21 -0
- package/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +12 -1
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +21 -1
- package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +200 -29
- package/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +160 -0
- package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +41 -9
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +98 -16
- package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +3 -3
- package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +86 -10
- package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +173 -87
- package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +150 -22
- package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +20 -20
- package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +32 -8
- package/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +12 -1
- package/templates/cleargate-planning/.cleargate/templates/Bug.md +3 -0
- package/templates/cleargate-planning/.cleargate/templates/CR.md +3 -0
- package/templates/cleargate-planning/.cleargate/templates/epic.md +3 -0
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +3 -0
- package/templates/cleargate-planning/.cleargate/templates/initiative.md +1 -1
- package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +8 -0
- package/templates/cleargate-planning/.cleargate/templates/story.md +3 -0
- package/templates/cleargate-planning/CLAUDE.md +3 -1
- package/templates/cleargate-planning/MANIFEST.json +40 -26
|
@@ -6,7 +6,7 @@ This file is the single source of truth for ClearGate's machine-checkable readin
|
|
|
6
6
|
|
|
7
7
|
## Predicate Vocabulary
|
|
8
8
|
|
|
9
|
-
There are exactly **
|
|
9
|
+
There are exactly **7 predicate shapes**. No other shapes are recognized; a check string that does not match one of these forms throws a parse error at evaluation time.
|
|
10
10
|
|
|
11
11
|
**1. `frontmatter(<ref>).<field> <op> <value>`**
|
|
12
12
|
Reads a frontmatter field from a document. `<ref>` is either `.` (the document being evaluated) or a frontmatter key whose value is a relative path to another document (e.g. `context_source`). `<op>` is one of `==`, `!=`, `>=`, `<=`. `<value>` is a literal string, number, or boolean. Example: `frontmatter(context_source).approved == true` reads the file named by the evaluated document's `context_source` key and asserts its `approved` field equals `true`.
|
|
@@ -15,7 +15,13 @@ Reads a frontmatter field from a document. `<ref>` is either `.` (the document b
|
|
|
15
15
|
Performs a case-sensitive substring search on the document body (everything after the frontmatter block). The negated form `body does not contain` passes when the string is absent. Example: `body does not contain 'TBD'` fails if the literal string `TBD` appears anywhere in the body.
|
|
16
16
|
|
|
17
17
|
**3. `section(<N>) has <count> <item-type>`**
|
|
18
|
-
Splits the document body on `## ` heading boundaries (1-indexed) and counts items of a given type within section N. `<count>` is an expression like `≥1`, `≥3`, or `0` (exact zero). `<item-type>` is one of
|
|
18
|
+
Splits the document body on `## ` heading boundaries (1-indexed) and counts items of a given type within section N. `<count>` is an expression like `≥1`, `≥3`, or `0` (exact zero). `<item-type>` is one of:
|
|
19
|
+
- `checked-checkbox` — lines matching `- [x]`
|
|
20
|
+
- `unchecked-checkbox` — lines matching `- [ ]`
|
|
21
|
+
- `listed-item` — lines matching `- ` regardless of checkbox state (bullet-precise; use when checkbox/task-list semantics are required, e.g. DoD)
|
|
22
|
+
- `declared-item` — any line that declares a structured item: bullet lines (`- ...`), table data rows (`| ... |` lines following a `|---|`-style separator within the section), or definition-list terms (lines matching `**Item:**`, `Item:`, `*Item*:` etc.). Use `declared-item` when the gate cares only that the author declared at least N entries in section N, regardless of presentation format (table vs bullet vs def-list).
|
|
23
|
+
|
|
24
|
+
Example: `section(2) has ≥1 checked-checkbox` asserts that the second `##` section contains at least one checked markdown checkbox. Example: `section(3) has ≥1 declared-item` passes when §3 contains at least one bullet, table data row, or definition-list term.
|
|
19
25
|
|
|
20
26
|
**4. `file-exists(<path>)`**
|
|
21
27
|
Asserts that a file exists on disk at the given path, resolved relative to the project root. Example: `file-exists(.cleargate/knowledge/cleargate-protocol.md)` passes when that file is present in the working tree.
|
|
@@ -26,6 +32,9 @@ Reads `.cleargate/wiki/index.md` and asserts that the wiki index contains a refe
|
|
|
26
32
|
**6. `status-of(<[[ID]]>) == <value>`**
|
|
27
33
|
Resolves the given ID via the wiki index, reads that page's compiled frontmatter `status:` field, and compares it to `<value>`. Status values in the live corpus are textual strings (`Draft`, `Ready`, `Active`, `Done`) — not emoji. Example: `status-of([[EPIC-008]]) == Active` passes when EPIC-008's wiki page has `status: Active`. Note: this predicate returns `unknown` (evaluates to fail) when the wiki index is stale and the item is not yet compiled. Run `cleargate wiki build` before relying on `status-of` predicates.
|
|
28
34
|
|
|
35
|
+
**7. `existing-surfaces-verified`**
|
|
36
|
+
Closed-set predicate (no parameters). Locates the `## Existing Surfaces` section in the document body, extracts path-shaped substrings via regex, asserts each cited path exists on disk relative to the project root. Passes when section is absent (defers to `reuse-audit-recorded`) OR all cited paths exist OR section contains a "no overlap found" / "no existing surface" / "no prior implementation" / "audit returned empty" sentinel. Sandbox-rejected paths (escaping project root) are treated as missing. Example: `existing-surfaces-verified` against an Epic body whose `## Existing Surfaces` cites `cleargate-cli/src/lib/work-item-type.ts:detectWorkItemTypeFromFm` passes when that path exists.
|
|
37
|
+
|
|
29
38
|
---
|
|
30
39
|
|
|
31
40
|
## Severity Model
|
|
@@ -60,20 +69,26 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
60
69
|
transition: ready-for-decomposition
|
|
61
70
|
severity: enforcing
|
|
62
71
|
criteria:
|
|
63
|
-
- id:
|
|
72
|
+
- id: parent-approved-proposal
|
|
64
73
|
check: "frontmatter(context_source).approved == true"
|
|
74
|
+
or_group: parent-approved
|
|
75
|
+
- id: parent-approved-initiative
|
|
76
|
+
check: "frontmatter(context_source).status == 'Triaged'"
|
|
77
|
+
or_group: parent-approved
|
|
65
78
|
- id: no-tbds
|
|
66
79
|
check: "body does not contain marker 'TBD'"
|
|
67
80
|
- id: scope-in-populated
|
|
68
|
-
check: "section(
|
|
81
|
+
check: "section(3) has ≥1 declared-item"
|
|
69
82
|
- id: affected-files-declared
|
|
70
|
-
check: "section(
|
|
83
|
+
check: "section(5) has ≥1 declared-item"
|
|
71
84
|
- id: interrogation-resolved
|
|
72
85
|
check: "body does not contain 'Unresolved'"
|
|
73
86
|
- id: discovery-checked
|
|
74
87
|
check: "frontmatter(.).context_source != null"
|
|
75
88
|
- id: reuse-audit-recorded
|
|
76
89
|
check: "body contains '## Existing Surfaces'"
|
|
90
|
+
- id: existing-surfaces-verified
|
|
91
|
+
check: "existing-surfaces-verified"
|
|
77
92
|
- id: simplest-form-justified
|
|
78
93
|
check: "body contains '## Why not simpler?'"
|
|
79
94
|
```
|
|
@@ -107,7 +122,7 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
107
122
|
- id: no-tbds
|
|
108
123
|
check: "body does not contain marker 'TBD'"
|
|
109
124
|
- id: implementation-files-declared
|
|
110
|
-
check: "section(3) has ≥1
|
|
125
|
+
check: "section(3) has ≥1 declared-item"
|
|
111
126
|
- id: dod-declared
|
|
112
127
|
check: "section(4) has ≥1 listed-item"
|
|
113
128
|
- id: gherkin-present
|
|
@@ -116,6 +131,8 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
116
131
|
check: "frontmatter(.).context_source != null"
|
|
117
132
|
- id: reuse-audit-recorded
|
|
118
133
|
check: "body contains '## Existing Surfaces'"
|
|
134
|
+
- id: existing-surfaces-verified
|
|
135
|
+
check: "existing-surfaces-verified"
|
|
119
136
|
- id: simplest-form-justified
|
|
120
137
|
check: "body contains '## Why not simpler?'"
|
|
121
138
|
```
|
|
@@ -126,15 +143,17 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
126
143
|
severity: enforcing
|
|
127
144
|
criteria:
|
|
128
145
|
- id: blast-radius-populated
|
|
129
|
-
check: "section(2) has ≥1
|
|
146
|
+
check: "section(2) has ≥1 declared-item"
|
|
130
147
|
- id: no-tbds
|
|
131
148
|
check: "body does not contain marker 'TBD'"
|
|
132
149
|
- id: sandbox-paths-declared
|
|
133
|
-
check: "section(
|
|
150
|
+
check: "section(3) has ≥1 declared-item"
|
|
134
151
|
- id: discovery-checked
|
|
135
152
|
check: "frontmatter(.).context_source != null"
|
|
136
153
|
- id: reuse-audit-recorded
|
|
137
154
|
check: "body contains '## Existing Surfaces'"
|
|
155
|
+
- id: existing-surfaces-verified
|
|
156
|
+
check: "existing-surfaces-verified"
|
|
138
157
|
```
|
|
139
158
|
|
|
140
159
|
```yaml
|
|
@@ -143,7 +162,7 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
143
162
|
severity: enforcing
|
|
144
163
|
criteria:
|
|
145
164
|
- id: repro-steps-deterministic
|
|
146
|
-
check: "section(2) has ≥3
|
|
165
|
+
check: "section(2) has ≥3 declared-item"
|
|
147
166
|
- id: severity-set
|
|
148
167
|
check: "frontmatter(.).severity != null"
|
|
149
168
|
- id: no-tbds
|
|
@@ -162,3 +181,16 @@ The asymmetry exists because Proposal documents are human-authored strategy arti
|
|
|
162
181
|
- id: discovery-checked
|
|
163
182
|
check: "frontmatter(.).context_source != null"
|
|
164
183
|
```
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
- work_item_type: initiative
|
|
187
|
+
transition: ready-for-decomposition
|
|
188
|
+
severity: advisory
|
|
189
|
+
criteria:
|
|
190
|
+
- id: no-tbds
|
|
191
|
+
check: "body does not contain marker 'TBD'"
|
|
192
|
+
- id: user-flow-populated
|
|
193
|
+
check: "section(1) has ≥1 listed-item"
|
|
194
|
+
- id: success-criteria-populated
|
|
195
|
+
check: "section(5) has ≥1 listed-item"
|
|
196
|
+
```
|
|
@@ -34,9 +34,10 @@
|
|
|
34
34
|
* atomicWrite pattern from update_state.mjs
|
|
35
35
|
*
|
|
36
36
|
* Test seams (CR-022 M1):
|
|
37
|
-
* CLEARGATE_SKIP_LIFECYCLE_CHECK=1 — skip Step 2.6 lifecycle reconciliation
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* CLEARGATE_SKIP_LIFECYCLE_CHECK=1 — skip Step 2.6 lifecycle reconciliation AND Step 2.6b
|
|
38
|
+
* cross-sprint orphan drift check entirely (test
|
|
39
|
+
* environments where the CLI binary is present but
|
|
40
|
+
* real git history would produce drift false-positives).
|
|
40
41
|
* CLEARGATE_SKIP_WORKTREE_CHECK=1 — skip Step 2.7 entirely (test environments that cannot
|
|
41
42
|
* run git worktree list from a real git root).
|
|
42
43
|
* CLEARGATE_FORCE_WORKTREE_PATHS=p1,p2 — comma-separated fake worktree paths injected into
|
|
@@ -59,6 +60,9 @@
|
|
|
59
60
|
* --flashcard-cleanup scan in suggest_improvements.mjs.
|
|
60
61
|
* CLEARGATE_FLASHCARD_LOOKBACK=<N> — override 3-sprint default lookback for
|
|
61
62
|
* --flashcard-cleanup scan.
|
|
63
|
+
* CLEARGATE_SKIP_BUNDLE_CHECK=1 — skip Step 3.5 bundle generation + size check entirely
|
|
64
|
+
* (CR-036 test seam; analogous to CLEARGATE_SKIP_MERGE_CHECK).
|
|
65
|
+
* Never use in production — Step 3.5 is v2-fatal in production.
|
|
62
66
|
*/
|
|
63
67
|
|
|
64
68
|
import fs from 'node:fs';
|
|
@@ -153,7 +157,7 @@ function invokeScript(scriptName, scriptArgs, env) {
|
|
|
153
157
|
});
|
|
154
158
|
}
|
|
155
159
|
|
|
156
|
-
function main() {
|
|
160
|
+
async function main() {
|
|
157
161
|
const args = process.argv.slice(2);
|
|
158
162
|
|
|
159
163
|
if (args.length < 1) usage();
|
|
@@ -348,6 +352,61 @@ function main() {
|
|
|
348
352
|
}
|
|
349
353
|
}
|
|
350
354
|
|
|
355
|
+
// ── Step 2.6b: Cross-Sprint Orphan Drift Check (CR-048) ─────────────────────
|
|
356
|
+
// Detect items in pending-sync/ with non-terminal status whose state.json entry
|
|
357
|
+
// in any closed sprint shows Done — i.e., completed but never archived.
|
|
358
|
+
// v2: drift > 0 blocks close. v1: warn-only.
|
|
359
|
+
// Test seam: CLEARGATE_SKIP_LIFECYCLE_CHECK=1 also skips this step.
|
|
360
|
+
process.stdout.write('Step 2.6b: checking for cross-sprint orphan drift...\n');
|
|
361
|
+
if (process.env.CLEARGATE_SKIP_LIFECYCLE_CHECK !== '1') {
|
|
362
|
+
try {
|
|
363
|
+
const cliBin26b = path.join(REPO_ROOT, 'cleargate-cli', 'dist', 'cli.js');
|
|
364
|
+
if (fs.existsSync(cliBin26b)) {
|
|
365
|
+
// Dynamic import the compiled reconciler function
|
|
366
|
+
const reconcilerMod = await import(
|
|
367
|
+
path.join(REPO_ROOT, 'cleargate-cli', 'dist', 'lib', 'lifecycle-reconcile.js')
|
|
368
|
+
).catch(() => null);
|
|
369
|
+
|
|
370
|
+
if (reconcilerMod && typeof reconcilerMod.reconcileCrossSprintOrphans === 'function') {
|
|
371
|
+
const deliveryRoot = path.join(REPO_ROOT, '.cleargate', 'delivery');
|
|
372
|
+
const sprintRunsRoot = path.join(REPO_ROOT, '.cleargate', 'sprint-runs');
|
|
373
|
+
const orphanResult = reconcilerMod.reconcileCrossSprintOrphans({ deliveryRoot, sprintRunsRoot });
|
|
374
|
+
|
|
375
|
+
if (orphanResult.drift.length > 0) {
|
|
376
|
+
process.stderr.write(
|
|
377
|
+
`Step 2.6b: ${orphanResult.drift.length} cross-sprint orphan(s) detected:\n`
|
|
378
|
+
);
|
|
379
|
+
for (const item of orphanResult.drift) {
|
|
380
|
+
process.stderr.write(
|
|
381
|
+
` ${item.id} — status: ${item.pending_sync_status} in pending-sync, ` +
|
|
382
|
+
`state: ${item.state_json_state} in ${item.state_json_sprint}\n`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (isV2) {
|
|
386
|
+
process.stderr.write(
|
|
387
|
+
'close_sprint: Step 2.6b FAILED — orphan drift blocks sprint close under v2.\n' +
|
|
388
|
+
' Archive the listed items and re-run close_sprint.mjs.\n'
|
|
389
|
+
);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
} else {
|
|
392
|
+
process.stdout.write('Step 2.6b warning (v1): orphan drift detected above — remediate before next sprint.\n');
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
process.stdout.write('Step 2.6b passed: no cross-sprint orphan drift.\n');
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
process.stdout.write('Step 2.6b skipped: reconcileCrossSprintOrphans not available in built CLI.\n');
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
process.stdout.write('Step 2.6b skipped: CLI binary not found (non-fatal).\n');
|
|
402
|
+
}
|
|
403
|
+
} catch (step26bErr) {
|
|
404
|
+
process.stderr.write(`Step 2.6b warning: orphan check unavailable: ${step26bErr.message}\n`);
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
process.stdout.write('Step 2.6b skipped: CLEARGATE_SKIP_LIFECYCLE_CHECK=1 set (test seam).\n');
|
|
408
|
+
}
|
|
409
|
+
|
|
351
410
|
// ── Step 2.7: Worktree-Closed Check (CR-022 M1) ──────────────────────────
|
|
352
411
|
// Block close if any .worktrees/STORY-* path is present.
|
|
353
412
|
// v2 enforcing (exit 1); v1 advisory (warn + continue).
|
|
@@ -511,17 +570,40 @@ function main() {
|
|
|
511
570
|
}
|
|
512
571
|
|
|
513
572
|
// ── Step 3.5: Build curated Reporter context bundle ───────────────────────
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
process.stdout.write(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
573
|
+
const bundlePath = path.join(sprintDir, '.reporter-context.md');
|
|
574
|
+
const isEnforcingV2 = isV2 && state.execution_mode === 'v2';
|
|
575
|
+
const MIN_BUNDLE_BYTES = 2048;
|
|
576
|
+
if (process.env.CLEARGATE_SKIP_BUNDLE_CHECK === '1') {
|
|
577
|
+
process.stdout.write('Step 3.5 skipped: CLEARGATE_SKIP_BUNDLE_CHECK=1 set (test seam).\n');
|
|
578
|
+
} else {
|
|
579
|
+
process.stdout.write('Step 3.5: building Reporter context bundle...\n');
|
|
580
|
+
try {
|
|
581
|
+
invokeScript('prep_reporter_context.mjs', [sprintId], {
|
|
582
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
583
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
584
|
+
});
|
|
585
|
+
if (!fs.existsSync(bundlePath)) {
|
|
586
|
+
throw new Error(`bundle not written at ${bundlePath}`);
|
|
587
|
+
}
|
|
588
|
+
const bundleSize = fs.statSync(bundlePath).size;
|
|
589
|
+
if (bundleSize < MIN_BUNDLE_BYTES) {
|
|
590
|
+
throw new Error(`bundle too small (${bundleSize}B < ${MIN_BUNDLE_BYTES}B): ${bundlePath}`);
|
|
591
|
+
}
|
|
592
|
+
process.stdout.write(`Step 3.5 passed: ${bundlePath} ready (${Math.round(bundleSize / 1024)}KB).\n`);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
const msg = /** @type {Error} */ (err).message;
|
|
595
|
+
if (isEnforcingV2) {
|
|
596
|
+
process.stderr.write(
|
|
597
|
+
`close_sprint: Step 3.5 FAILED (v2 hard-block): ${msg}\n` +
|
|
598
|
+
` Cannot dispatch Reporter without bundle. Fix prep_reporter_context.mjs or run with execution_mode: v1.\n` +
|
|
599
|
+
` Diagnostic: node .cleargate/scripts/prep_reporter_context.mjs ${sprintId}\n`
|
|
600
|
+
);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
} else {
|
|
603
|
+
process.stderr.write(`Step 3.5 warning (v1 advisory): ${msg}\n`);
|
|
604
|
+
process.stderr.write('Reporter will fall back to broad-fetch context loading.\n');
|
|
605
|
+
}
|
|
606
|
+
}
|
|
525
607
|
}
|
|
526
608
|
|
|
527
609
|
// ── Step 4: Orchestrator spawns Reporter separately ───────────────────────
|
|
@@ -700,4 +782,4 @@ function main() {
|
|
|
700
782
|
}
|
|
701
783
|
}
|
|
702
784
|
|
|
703
|
-
main();
|
|
785
|
+
await main();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"qa": {
|
|
4
|
-
"typecheck": "npm run typecheck",
|
|
4
|
+
"typecheck": "cd cleargate-cli && npm run typecheck",
|
|
5
5
|
"debug_patterns": ["console.log", "console.debug", "debugger"],
|
|
6
6
|
"todo_patterns": ["TODO", "FIXME", "XXX"],
|
|
7
|
-
"test": "npm test"
|
|
7
|
+
"test": "cd cleargate-cli && npm test"
|
|
8
8
|
},
|
|
9
9
|
"arch": {
|
|
10
|
-
"typecheck": "npm run typecheck",
|
|
10
|
+
"typecheck": "cd cleargate-cli && npm run typecheck",
|
|
11
11
|
"new_deps_check": true,
|
|
12
12
|
"stray_env_files": [".env", ".env.local", ".env.production"],
|
|
13
13
|
"file_count_report": true
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* init_sprint.mjs — Initialize a sprint state.json
|
|
4
4
|
*
|
|
5
|
-
* Usage: node init_sprint.mjs <sprint-id> --stories ID1,ID2,... [--force]
|
|
5
|
+
* Usage: node init_sprint.mjs <sprint-id> --stories ID1,ID2,... [--force] [--preserve-bounces]
|
|
6
|
+
*
|
|
7
|
+
* --preserve-bounces requires --force; reads existing state.json and carries
|
|
8
|
+
* forward qa_bounces / arch_bounces / state / lane / worktree per matching
|
|
9
|
+
* story-id. Items not in the new --stories list are dropped. Useful when
|
|
10
|
+
* mid-sprint dogfood / rerun must not lose kickback history.
|
|
6
11
|
*
|
|
7
12
|
* Creates .cleargate/sprint-runs/<sprint-id>/state.json with initial state
|
|
8
13
|
* "Ready to Bounce" for each story. Refuses if state.json already exists
|
|
@@ -117,6 +122,7 @@ function main() {
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
const force = args.includes('--force');
|
|
125
|
+
const preserveBounces = args.includes('--preserve-bounces');
|
|
120
126
|
|
|
121
127
|
const sprintDir = path.join(REPO_ROOT, '.cleargate', 'sprint-runs', sprintId);
|
|
122
128
|
const stateFile = path.join(sprintDir, 'state.json');
|
|
@@ -128,6 +134,20 @@ function main() {
|
|
|
128
134
|
process.exit(1);
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
// --- CR-049-followup: Preserve bounce counters when --force re-inits an in-flight sprint ---
|
|
138
|
+
// Default --force overwrites everything (initial design). With --preserve-bounces,
|
|
139
|
+
// qa_bounces / arch_bounces / state are read from the existing state.json and merged
|
|
140
|
+
// back per-story. Only Ready-to-Bounce default fields get reset.
|
|
141
|
+
let preserved = {};
|
|
142
|
+
if (force && preserveBounces && fs.existsSync(stateFile)) {
|
|
143
|
+
try {
|
|
144
|
+
const existing = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
145
|
+
preserved = existing.stories || {};
|
|
146
|
+
} catch (err) {
|
|
147
|
+
process.stderr.write(`WARN: --preserve-bounces could not read existing state.json: ${err.message}\n`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
131
151
|
// --- Read execution_mode from sprint frontmatter ---
|
|
132
152
|
const sprintFilePath = findSprintFile(REPO_ROOT, sprintId);
|
|
133
153
|
const executionMode = sprintFilePath ? readExecutionMode(sprintFilePath) : 'v1';
|
|
@@ -159,17 +179,18 @@ function main() {
|
|
|
159
179
|
const now = new Date().toISOString();
|
|
160
180
|
const stories = {};
|
|
161
181
|
for (const id of storyIds) {
|
|
182
|
+
const carry = preserved[id] || {};
|
|
162
183
|
stories[id] = {
|
|
163
|
-
state: 'Ready to Bounce',
|
|
164
|
-
qa_bounces: 0,
|
|
165
|
-
arch_bounces: 0,
|
|
166
|
-
worktree: null,
|
|
184
|
+
state: carry.state ?? 'Ready to Bounce',
|
|
185
|
+
qa_bounces: carry.qa_bounces ?? 0,
|
|
186
|
+
arch_bounces: carry.arch_bounces ?? 0,
|
|
187
|
+
worktree: carry.worktree ?? null,
|
|
167
188
|
updated_at: now,
|
|
168
|
-
notes: '',
|
|
169
|
-
lane: 'standard',
|
|
170
|
-
lane_assigned_by: 'migration-default',
|
|
171
|
-
lane_demoted_at: null,
|
|
172
|
-
lane_demotion_reason: null,
|
|
189
|
+
notes: carry.notes ?? '',
|
|
190
|
+
lane: carry.lane ?? 'standard',
|
|
191
|
+
lane_assigned_by: carry.lane_assigned_by ?? 'migration-default',
|
|
192
|
+
lane_demoted_at: carry.lane_demoted_at ?? null,
|
|
193
|
+
lane_demotion_reason: carry.lane_demotion_reason ?? null,
|
|
173
194
|
};
|
|
174
195
|
}
|
|
175
196
|
|
|
@@ -189,6 +210,61 @@ function main() {
|
|
|
189
210
|
fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
190
211
|
fs.renameSync(tmpFile, stateFile);
|
|
191
212
|
|
|
213
|
+
// --- CR-045: Write sprint-context.md from template ---
|
|
214
|
+
// Reads .cleargate/templates/sprint_context.md, substitutes frontmatter fields,
|
|
215
|
+
// optionally splices the sprint goal from the sprint plan §0, and writes atomically.
|
|
216
|
+
// Skip if file already exists and --force was not passed (idempotency-safe re-init).
|
|
217
|
+
const ctxTemplate = path.join(REPO_ROOT, '.cleargate', 'templates', 'sprint_context.md');
|
|
218
|
+
const ctxOut = path.join(sprintDir, 'sprint-context.md');
|
|
219
|
+
|
|
220
|
+
if (!fs.existsSync(ctxOut) || force) {
|
|
221
|
+
let ctxContent;
|
|
222
|
+
try {
|
|
223
|
+
ctxContent = fs.readFileSync(ctxTemplate, 'utf8');
|
|
224
|
+
} catch {
|
|
225
|
+
// Template absent — log warning and continue (non-fatal)
|
|
226
|
+
process.stderr.write(
|
|
227
|
+
`WARN: sprint-context.md template not found at ${ctxTemplate}; skipping sprint-context.md write.\n`
|
|
228
|
+
);
|
|
229
|
+
ctxContent = null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (ctxContent !== null) {
|
|
233
|
+
// Substitute frontmatter placeholders
|
|
234
|
+
ctxContent = ctxContent.replace(/sprint_id:\s*["']?S-NN["']?/, `sprint_id: "${sprintId}"`);
|
|
235
|
+
ctxContent = ctxContent.replace(/created_at:\s*["']?YYYY-MM-DDTHH:MM:SSZ["']?/, `created_at: "${now}"`);
|
|
236
|
+
ctxContent = ctxContent.replace(/last_updated:\s*["']?YYYY-MM-DDTHH:MM:SSZ["']?/, `last_updated: "${now}"`);
|
|
237
|
+
|
|
238
|
+
// Optionally extract sprint goal from sprint plan §0.
|
|
239
|
+
// Regex matches `- **Sprint Goal:** <text>` within first 200 lines (after H1).
|
|
240
|
+
// Falls back to placeholder if absent — non-fatal.
|
|
241
|
+
if (sprintFilePath) {
|
|
242
|
+
try {
|
|
243
|
+
const planContent = fs.readFileSync(sprintFilePath, 'utf8');
|
|
244
|
+
const planLines = planContent.split('\n').slice(0, 200);
|
|
245
|
+
const goalLine = planLines.find((l) => /^- \*\*Sprint Goal:\*\* (.+)$/.test(l.trim()));
|
|
246
|
+
if (goalLine) {
|
|
247
|
+
const goalMatch = goalLine.trim().match(/^- \*\*Sprint Goal:\*\* (.+)$/);
|
|
248
|
+
if (goalMatch) {
|
|
249
|
+
const goalText = goalMatch[1].trim();
|
|
250
|
+
ctxContent = ctxContent.replace(
|
|
251
|
+
'_(populated by orchestrator from sprint plan §0 at kickoff)_',
|
|
252
|
+
goalText
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// Goal extraction failed — leave placeholder; non-fatal
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Write atomically via tmpFile + renameSync (mirrors state.json pattern)
|
|
262
|
+
const ctxTmp = `${ctxOut}.tmp.${process.pid}`;
|
|
263
|
+
fs.writeFileSync(ctxTmp, ctxContent, 'utf8');
|
|
264
|
+
fs.renameSync(ctxTmp, ctxOut);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
192
268
|
process.stdout.write(`Initialized state.json for sprint ${sprintId} with ${storyIds.length} stories\n`);
|
|
193
269
|
}
|
|
194
270
|
|