cleargate 0.2.1 → 0.4.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/MANIFEST.json +58 -16
  3. package/dist/admin-api/index.cjs +88 -1
  4. package/dist/admin-api/index.cjs.map +1 -1
  5. package/dist/admin-api/index.d.cts +105 -1
  6. package/dist/admin-api/index.d.ts +105 -1
  7. package/dist/admin-api/index.js +77 -1
  8. package/dist/admin-api/index.js.map +1 -1
  9. package/dist/bootstrap-root-FGWDICDT.js +130 -0
  10. package/dist/bootstrap-root-FGWDICDT.js.map +1 -0
  11. package/dist/chunk-OM4FAEA7.js +184 -0
  12. package/dist/chunk-OM4FAEA7.js.map +1 -0
  13. package/dist/cli.cjs +8530 -3957
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +5282 -1220
  16. package/dist/cli.js.map +1 -1
  17. package/dist/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  18. package/dist/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  19. package/dist/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  20. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  21. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  22. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  23. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  24. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  25. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  26. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  27. package/dist/templates/cleargate-planning/.claude/settings.json +11 -0
  28. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  29. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  30. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  31. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  32. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  33. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  34. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  35. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  36. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  37. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  38. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  39. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  40. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  41. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  42. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  43. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  44. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  45. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  46. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  47. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  48. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  49. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  50. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  51. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  52. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  53. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  54. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  55. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  56. package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  57. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  58. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  59. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  60. package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
  61. package/dist/templates/cleargate-planning/MANIFEST.json +58 -16
  62. package/dist/whoami-CX7CXJD5.js +76 -0
  63. package/dist/whoami-CX7CXJD5.js.map +1 -0
  64. package/package.json +6 -2
  65. package/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  66. package/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  67. package/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  68. package/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  69. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  70. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  71. package/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  72. package/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  73. package/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  74. package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  75. package/templates/cleargate-planning/.claude/settings.json +11 -0
  76. package/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  77. package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  78. package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  79. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  80. package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  81. package/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  82. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  83. package/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  84. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  85. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  86. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  87. package/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  88. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  89. package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  90. package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  91. package/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  92. package/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  93. package/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  94. package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  95. package/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  96. package/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  97. package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  98. package/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  99. package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  100. package/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  101. package/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  102. package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  103. package/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  104. package/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  105. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  106. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  107. package/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  108. package/templates/cleargate-planning/CLAUDE.md +2 -0
  109. package/templates/cleargate-planning/MANIFEST.json +58 -16
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * assert_story_files.mjs — Gate-2 story-file existence assertion
4
+ *
5
+ * Usage: node assert_story_files.mjs <sprint-file-path>
6
+ *
7
+ * Parses the "## 1. Consolidated Deliverables" section of a sprint file for
8
+ * STORY-\d+-\d+ IDs, then checks that each has a corresponding
9
+ * pending-sync/STORY-<id>_*.md file under the repo root.
10
+ *
11
+ * Exit 0: all story files present (prints summary to stdout)
12
+ * Exit 1: one or more missing (prints JSON {missing,present} to stderr)
13
+ * Exit 2: usage / parse error
14
+ *
15
+ * Env:
16
+ * CLEARGATE_REPO_ROOT override repo root (for test isolation)
17
+ */
18
+
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+
25
+ // Resolve repo root: .cleargate/scripts/ -> ../../ (two levels up)
26
+ const REPO_ROOT = process.env.CLEARGATE_REPO_ROOT
27
+ ? path.resolve(process.env.CLEARGATE_REPO_ROOT)
28
+ : path.resolve(__dirname, '..', '..');
29
+
30
+ function usage() {
31
+ process.stderr.write('Usage: node assert_story_files.mjs <sprint-file-path>\n');
32
+ process.exit(2);
33
+ }
34
+
35
+ /**
36
+ * Extract the "## 1. Consolidated Deliverables" section from sprint markdown.
37
+ * Returns the section text or null if not found.
38
+ *
39
+ * Strategy: split on ^## headings, find the one starting with "1. Consolidated Deliverables".
40
+ * This avoids regex lookahead pitfalls with end-of-string anchors in JS.
41
+ */
42
+ function extractDeliverablesSection(content) {
43
+ // Split on lines that start a new ## section (lookahead keeps delimiter in next part)
44
+ const parts = content.split(/^(?=## )/m);
45
+ const deliverables = parts.find((p) =>
46
+ /^## 1\.? Consolidated Deliverables\b/m.test(p)
47
+ );
48
+ if (!deliverables) return null;
49
+ // Strip the header line itself, return the rest
50
+ return deliverables.replace(/^## [^\n]*\n/, '');
51
+ }
52
+
53
+ /**
54
+ * Extract deduplicated STORY-\d+-\d+ IDs from a text block.
55
+ */
56
+ function extractStoryIds(text) {
57
+ const matches = text.match(/STORY-\d+-\d+/g) || [];
58
+ return [...new Set(matches)];
59
+ }
60
+
61
+ /**
62
+ * Check whether pending-sync contains a file matching STORY-<id>_*.md
63
+ * Returns the matching filename or null.
64
+ */
65
+ function findStoryFile(repoRoot, storyId) {
66
+ const pendingSync = path.join(repoRoot, '.cleargate', 'delivery', 'pending-sync');
67
+ let entries;
68
+ try {
69
+ entries = fs.readdirSync(pendingSync);
70
+ } catch {
71
+ return null;
72
+ }
73
+ const prefix = `${storyId}_`;
74
+ const match = entries.find(
75
+ (e) => e.startsWith(prefix) && e.endsWith('.md')
76
+ );
77
+ return match ? path.join(pendingSync, match) : null;
78
+ }
79
+
80
+ /**
81
+ * Main assertion logic.
82
+ * Returns { missing: string[], present: string[] }
83
+ */
84
+ function assertStoryFiles(sprintFilePath, repoRoot) {
85
+ let content;
86
+ try {
87
+ content = fs.readFileSync(sprintFilePath, 'utf8');
88
+ } catch (err) {
89
+ process.stderr.write(`Error: cannot read sprint file: ${err.message}\n`);
90
+ process.exit(2);
91
+ }
92
+
93
+ const section = extractDeliverablesSection(content);
94
+ if (section === null) {
95
+ process.stderr.write(
96
+ 'Error: "## 1. Consolidated Deliverables" section not found in sprint file\n'
97
+ );
98
+ process.exit(2);
99
+ }
100
+
101
+ const storyIds = extractStoryIds(section);
102
+ if (storyIds.length === 0) {
103
+ process.stderr.write('Warning: no STORY-IDs found in §1 Consolidated Deliverables\n');
104
+ // Return empty — no files to check, nothing is missing
105
+ return { missing: [], present: [] };
106
+ }
107
+
108
+ const missing = [];
109
+ const present = [];
110
+ for (const id of storyIds) {
111
+ const found = findStoryFile(repoRoot, id);
112
+ if (found) {
113
+ present.push(id);
114
+ } else {
115
+ missing.push(id);
116
+ }
117
+ }
118
+
119
+ return { missing, present };
120
+ }
121
+
122
+ function main() {
123
+ const args = process.argv.slice(2);
124
+ if (args.length === 0 || args[0].startsWith('--')) usage();
125
+
126
+ const sprintFilePath = path.resolve(args[0]);
127
+
128
+ const { missing, present } = assertStoryFiles(sprintFilePath, REPO_ROOT);
129
+
130
+ if (missing.length === 0) {
131
+ process.stdout.write(
132
+ `OK: all ${present.length} story file(s) present in pending-sync/\n`
133
+ );
134
+ process.exit(0);
135
+ } else {
136
+ process.stderr.write(
137
+ JSON.stringify({ missing, present }, null, 2) + '\n'
138
+ );
139
+ process.stderr.write(
140
+ `MISSING: ${missing.length} story file(s) not found in pending-sync/: ${missing.join(', ')}\n`
141
+ );
142
+ process.exit(1);
143
+ }
144
+ }
145
+
146
+ main();
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * close_sprint.mjs — Six-step sprint close pipeline
4
+ *
5
+ * Usage: node close_sprint.mjs <sprint-id> [--assume-ack]
6
+ * node close_sprint.mjs <sprint-id> --report-body-stdin (STORY-014-10)
7
+ *
8
+ * Steps:
9
+ * 1. Load and validate state.json via validateState
10
+ * 2. Refuse if any story state is not in TERMINAL_STATES (exit non-zero, list offenders)
11
+ * 3. Invoke prefill_report.mjs on all agent reports
12
+ * 4. Orchestrator spawns Reporter separately (script validates preconditions only)
13
+ * 5. On Reporter success + user ack (or --assume-ack flag), flip sprint_status -> "Completed"
14
+ * 6. Invoke suggest_improvements.mjs unconditionally
15
+ *
16
+ * Stdin fallback (STORY-014-10): when `--report-body-stdin` is passed, the script
17
+ * reads the full REPORT.md body from stdin and writes it atomically in lieu of
18
+ * waiting for a Reporter-produced file. Replaces the Step-4 gate; implies ack.
19
+ * Refuses empty stdin or pre-existing REPORT.md.
20
+ *
21
+ * Does NOT archive the sprint file (pending-sync -> archive stays human per EPIC-013 §4.5 step 7).
22
+ *
23
+ * Reuse: TERMINAL_STATES, VALID_STATES from constants.mjs
24
+ * validateState from validate_state.mjs
25
+ * atomicWrite pattern from update_state.mjs
26
+ */
27
+
28
+ import fs from 'node:fs';
29
+ import path from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+ import { execSync } from 'node:child_process';
32
+ import { TERMINAL_STATES } from './constants.mjs';
33
+ import { validateState } from './validate_state.mjs';
34
+
35
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
37
+ const SCRIPTS_DIR = __dirname;
38
+
39
+ function usage() {
40
+ process.stderr.write(
41
+ 'Usage: node close_sprint.mjs <sprint-id> [--assume-ack | --report-body-stdin]\n' +
42
+ '\n' +
43
+ 'Options:\n' +
44
+ ' --assume-ack Skip user acknowledgement prompt (for automated tests)\n' +
45
+ ' --report-body-stdin Read REPORT.md body from stdin; implies ack (STORY-014-10)\n'
46
+ );
47
+ process.exit(2);
48
+ }
49
+
50
+ /**
51
+ * Atomic write using tmp+rename pattern (per M1 update_state.mjs convention).
52
+ * @param {string} filePath
53
+ * @param {object} data
54
+ */
55
+ function atomicWrite(filePath, data) {
56
+ const tmpFile = `${filePath}.tmp.${process.pid}`;
57
+ fs.writeFileSync(tmpFile, JSON.stringify(data, null, 2) + '\n', 'utf8');
58
+ fs.renameSync(tmpFile, filePath);
59
+ }
60
+
61
+ /**
62
+ * Atomic write for a string body. Separate from atomicWrite() so we don't
63
+ * accidentally JSON.stringify a markdown body.
64
+ * @param {string} filePath
65
+ * @param {string} body
66
+ */
67
+ function atomicWriteString(filePath, body) {
68
+ const tmpFile = `${filePath}.tmp.${process.pid}`;
69
+ fs.writeFileSync(tmpFile, body, 'utf8');
70
+ fs.renameSync(tmpFile, filePath);
71
+ }
72
+
73
+ /**
74
+ * Invoke a script via node (for .mjs scripts in the same directory).
75
+ * Throws on non-zero exit.
76
+ * @param {string} scriptName
77
+ * @param {string[]} scriptArgs
78
+ * @param {object} env
79
+ */
80
+ function invokeScript(scriptName, scriptArgs, env) {
81
+ const scriptPath = path.join(SCRIPTS_DIR, scriptName);
82
+ if (!fs.existsSync(scriptPath)) {
83
+ throw new Error(`Script not found: ${scriptPath}`);
84
+ }
85
+ const argStr = scriptArgs.map(a => JSON.stringify(a)).join(' ');
86
+ const cmd = `node ${JSON.stringify(scriptPath)} ${argStr}`;
87
+ execSync(cmd, {
88
+ stdio: 'inherit',
89
+ env: Object.assign({}, process.env, env || {}),
90
+ });
91
+ }
92
+
93
+ function main() {
94
+ const args = process.argv.slice(2);
95
+
96
+ if (args.length < 1) usage();
97
+
98
+ const sprintId = args[0];
99
+ const reportBodyStdin = args.includes('--report-body-stdin');
100
+ const assumeAck = args.includes('--assume-ack') || reportBodyStdin;
101
+
102
+ const sprintDir = process.env.CLEARGATE_SPRINT_DIR
103
+ ? path.resolve(process.env.CLEARGATE_SPRINT_DIR)
104
+ : path.join(REPO_ROOT, '.cleargate', 'sprint-runs', sprintId);
105
+
106
+ if (!fs.existsSync(sprintDir)) {
107
+ process.stderr.write(`Error: sprint directory not found: ${sprintDir}\n`);
108
+ process.exit(1);
109
+ }
110
+
111
+ const stateFile = process.env.CLEARGATE_STATE_FILE
112
+ ? path.resolve(process.env.CLEARGATE_STATE_FILE)
113
+ : path.join(sprintDir, 'state.json');
114
+
115
+ // ── Step 1: Load and validate state.json ──────────────────────────────────
116
+ if (!fs.existsSync(stateFile)) {
117
+ process.stderr.write(
118
+ `Error: state.json not found at ${stateFile}\n` +
119
+ `Hint: run init_sprint.mjs ${sprintId} --stories <ids> first\n`
120
+ );
121
+ process.exit(1);
122
+ }
123
+
124
+ let state;
125
+ try {
126
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
127
+ } catch (err) {
128
+ process.stderr.write(`Error: failed to parse state.json: ${err.message}\n`);
129
+ process.exit(1);
130
+ }
131
+
132
+ const { valid, errors } = validateState(state);
133
+ if (!valid) {
134
+ process.stderr.write('Error: state.json validation failed:\n');
135
+ for (const e of errors) process.stderr.write(` - ${e}\n`);
136
+ process.exit(1);
137
+ }
138
+
139
+ // ── Step 2: Refuse if any story not in TERMINAL_STATES ────────────────────
140
+ const nonTerminal = [];
141
+ for (const [storyId, story] of Object.entries(state.stories || {})) {
142
+ if (!TERMINAL_STATES.includes(story.state)) {
143
+ nonTerminal.push(`${storyId}: ${story.state} — not terminal`);
144
+ }
145
+ }
146
+
147
+ if (nonTerminal.length > 0) {
148
+ process.stderr.write('Error: sprint cannot close — non-terminal stories:\n');
149
+ for (const msg of nonTerminal) {
150
+ process.stderr.write(` ${msg}\n`);
151
+ }
152
+ process.exit(1);
153
+ }
154
+
155
+ process.stdout.write(`Step 1-2 passed: all ${Object.keys(state.stories || {}).length} stories are terminal.\n`);
156
+
157
+ // ── Step 3: Invoke prefill_report.mjs ─────────────────────────────────────
158
+ process.stdout.write('Step 3: running prefill_report.mjs...\n');
159
+ try {
160
+ invokeScript('prefill_report.mjs', [sprintId], {
161
+ CLEARGATE_STATE_FILE: stateFile,
162
+ CLEARGATE_SPRINT_DIR: sprintDir,
163
+ });
164
+ } catch (err) {
165
+ process.stderr.write(`Error: prefill_report.mjs failed: ${err.message}\n`);
166
+ process.exit(1);
167
+ }
168
+
169
+ // ── Step 4: Orchestrator spawns Reporter separately ───────────────────────
170
+ // This script only validates preconditions; it does NOT fork the Reporter agent.
171
+ process.stdout.write(
172
+ 'Step 4: preconditions satisfied — orchestrator should now spawn the Reporter agent.\n' +
173
+ ' The Reporter writes REPORT.md using the sprint_report.md template.\n' +
174
+ ` Expected output: ${path.join(sprintDir, 'REPORT.md')}\n`
175
+ );
176
+
177
+ // Check if REPORT.md already exists (e.g., --assume-ack path in tests)
178
+ const reportFile = path.join(sprintDir, 'REPORT.md');
179
+
180
+ // ── Step 4.5 (STORY-014-10): --report-body-stdin fallback ────────────────
181
+ // Orchestrator pipes the Reporter's markdown body here when the Reporter's
182
+ // Write tool is blocked. Refuses empty stdin + pre-existing REPORT.md.
183
+ if (reportBodyStdin) {
184
+ if (fs.existsSync(reportFile)) {
185
+ process.stderr.write(
186
+ `Error: REPORT.md already exists at ${reportFile}\n` +
187
+ 'Delete it or skip --report-body-stdin mode to use the primary Reporter-write path.\n'
188
+ );
189
+ process.exit(1);
190
+ }
191
+ let body;
192
+ try {
193
+ body = fs.readFileSync(0, 'utf8');
194
+ } catch (err) {
195
+ process.stderr.write(`Error: failed to read stdin: ${err.message}\n`);
196
+ process.exit(1);
197
+ }
198
+ if (!body || body.trim().length === 0) {
199
+ process.stderr.write('Error: empty report body — refusing to write.\n');
200
+ process.exit(1);
201
+ }
202
+ atomicWriteString(reportFile, body);
203
+ process.stdout.write(
204
+ `Step 4.5 (stdin mode): REPORT.md written (${body.length} bytes) at ${reportFile}\n`
205
+ );
206
+ // Fall through to Step 5 + 6 unconditionally — stdin mode implies ack.
207
+ } else if (!assumeAck) {
208
+ if (!fs.existsSync(reportFile)) {
209
+ process.stdout.write(
210
+ '\nWaiting for Reporter to produce REPORT.md...\n' +
211
+ 'After Reporter succeeds, re-run with --assume-ack to complete the close.\n'
212
+ );
213
+ process.exit(0);
214
+ }
215
+ // In non-assume-ack mode with existing REPORT.md, prompt user
216
+ process.stdout.write(
217
+ `\nREPORT.md found at ${reportFile}\n` +
218
+ 'Review the report, then confirm close by re-running with --assume-ack\n'
219
+ );
220
+ process.exit(0);
221
+ }
222
+
223
+ // ── Step 5: Flip sprint_status to "Completed" ────────────────────────────
224
+ process.stdout.write('Step 5: flipping sprint_status to "Completed"...\n');
225
+ const now = new Date().toISOString();
226
+ state.sprint_status = 'Completed';
227
+ state.last_action = `close_sprint: sprint ${sprintId} completed`;
228
+ state.updated_at = now;
229
+ atomicWrite(stateFile, state);
230
+ process.stdout.write(`sprint_status flipped to "Completed" at ${now}\n`);
231
+
232
+ // ── Step 6: Invoke suggest_improvements.mjs unconditionally ───────────────
233
+ process.stdout.write('Step 6: running suggest_improvements.mjs...\n');
234
+ try {
235
+ invokeScript('suggest_improvements.mjs', [sprintId], {
236
+ CLEARGATE_STATE_FILE: stateFile,
237
+ CLEARGATE_SPRINT_DIR: sprintDir,
238
+ });
239
+ } catch (err) {
240
+ // suggest_improvements failure is non-fatal — log but do not abort
241
+ process.stderr.write(`Warning: suggest_improvements.mjs failed: ${err.message}\n`);
242
+ process.stderr.write('Sprint is still marked Completed; improvement suggestions may be incomplete.\n');
243
+ }
244
+
245
+ process.stdout.write(`\nSprint ${sprintId} close pipeline complete.\n`);
246
+ process.stdout.write(` state.json: sprint_status = Completed\n`);
247
+ process.stdout.write(` improvement-suggestions.md: ${path.join(sprintDir, 'improvement-suggestions.md')}\n`);
248
+ }
249
+
250
+ main();
@@ -0,0 +1,57 @@
1
+ /**
2
+ * ClearGate Execution Phase v2 — Constants
3
+ *
4
+ * state.json v1 Schema (LOCKED — any future field change must bump schema_version):
5
+ *
6
+ * {
7
+ * "schema_version": 1, // integer, mandatory
8
+ * "sprint_id": "S-NN", // string
9
+ * "execution_mode": "v1"|"v2", // string
10
+ * "sprint_status": "Active", // string
11
+ * "stories": {
12
+ * "STORY-NNN-NN": {
13
+ * "state": "Ready to Bounce", // one of VALID_STATES
14
+ * "qa_bounces": 0, // integer 0..BOUNCE_CAP
15
+ * "arch_bounces": 0, // integer 0..BOUNCE_CAP
16
+ * "worktree": null, // string|null — path to worktree checkout
17
+ * "updated_at": "<ISO-8601>", // string
18
+ * "notes": "" // string
19
+ * }
20
+ * },
21
+ * "last_action": "<string>", // human-readable last operation
22
+ * "updated_at": "<ISO-8601>" // string
23
+ * }
24
+ */
25
+
26
+ export const SCHEMA_VERSION = 1;
27
+
28
+ export const BOUNCE_CAP = 3;
29
+
30
+ export const VALID_STATES = [
31
+ 'Ready to Bounce',
32
+ 'Bouncing',
33
+ 'QA Passed',
34
+ 'Architect Passed',
35
+ 'Sprint Review',
36
+ 'Done',
37
+ 'Escalated',
38
+ 'Parking Lot',
39
+ ];
40
+
41
+ export const TERMINAL_STATES = ['Done', 'Escalated', 'Parking Lot'];
42
+
43
+ /**
44
+ * Canonical state-machine transitions table.
45
+ * Key: current state. Value: array of allowed next states.
46
+ * Terminal states have empty arrays (no transitions out).
47
+ */
48
+ export const STATE_TRANSITIONS = {
49
+ 'Ready to Bounce': ['Bouncing', 'Parking Lot'],
50
+ 'Bouncing': ['QA Passed', 'Ready to Bounce', 'Escalated', 'Parking Lot'],
51
+ 'QA Passed': ['Architect Passed', 'Ready to Bounce', 'Escalated', 'Parking Lot'],
52
+ 'Architect Passed': ['Sprint Review', 'Ready to Bounce', 'Escalated', 'Parking Lot'],
53
+ 'Sprint Review': ['Done', 'Ready to Bounce', 'Escalated', 'Parking Lot'],
54
+ 'Done': [],
55
+ 'Escalated': [],
56
+ 'Parking Lot': [],
57
+ };