cleargate 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/MANIFEST.json +59 -17
- package/dist/admin-api/index.cjs +88 -1
- package/dist/admin-api/index.cjs.map +1 -1
- package/dist/admin-api/index.d.cts +105 -1
- package/dist/admin-api/index.d.ts +105 -1
- package/dist/admin-api/index.js +77 -1
- package/dist/admin-api/index.js.map +1 -1
- package/dist/bootstrap-root-FGWDICDT.js +130 -0
- package/dist/bootstrap-root-FGWDICDT.js.map +1 -0
- package/dist/chunk-OM4FAEA7.js +184 -0
- package/dist/chunk-OM4FAEA7.js.map +1 -0
- package/dist/cli.cjs +7995 -3984
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4062 -561
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +72 -0
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +45 -3
- package/dist/templates/cleargate-planning/.claude/agents/qa.md +7 -3
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +72 -75
- package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
- package/dist/templates/cleargate-planning/.claude/settings.json +11 -0
- package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +31 -12
- package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +2 -1
- package/dist/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +29 -0
- package/dist/templates/cleargate-planning/CLAUDE.md +3 -0
- package/dist/templates/cleargate-planning/MANIFEST.json +59 -17
- package/dist/whoami-CX7CXJD5.js +76 -0
- package/dist/whoami-CX7CXJD5.js.map +1 -0
- package/package.json +6 -2
- package/templates/cleargate-planning/.claude/agents/architect.md +72 -0
- package/templates/cleargate-planning/.claude/agents/developer.md +45 -3
- package/templates/cleargate-planning/.claude/agents/qa.md +7 -3
- package/templates/cleargate-planning/.claude/agents/reporter.md +72 -75
- package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
- package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
- package/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
- package/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
- package/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
- package/templates/cleargate-planning/.claude/settings.json +11 -0
- package/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +31 -12
- package/templates/cleargate-planning/.cleargate/FLASHCARD.md +2 -1
- package/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
- package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
- package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
- package/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
- package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
- package/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
- package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
- package/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
- package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
- package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
- package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
- package/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
- package/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
- package/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
- package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
- package/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
- package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
- package/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
- package/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
- package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
- package/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
- package/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
- package/templates/cleargate-planning/.cleargate/templates/story.md +29 -0
- package/templates/cleargate-planning/CLAUDE.md +3 -0
- package/templates/cleargate-planning/MANIFEST.json +59 -17
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* prefill_report.mjs — Backfill missing YAML frontmatter fields in agent reports
|
|
4
|
+
*
|
|
5
|
+
* Usage: node prefill_report.mjs <sprint-id>
|
|
6
|
+
* or: CLEARGATE_STATE_FILE=... node prefill_report.mjs <sprint-id>
|
|
7
|
+
*
|
|
8
|
+
* Reads:
|
|
9
|
+
* - state.json (v1 schema) for story metadata (bounce counts, states)
|
|
10
|
+
* - token-ledger.jsonl for commit_sha attribution
|
|
11
|
+
* - All STORY-<id>-dev.md and STORY-<id>-qa.md in sprint-runs/<id>/reports/
|
|
12
|
+
*
|
|
13
|
+
* Backfills missing deterministic YAML frontmatter fields:
|
|
14
|
+
* - story_id, sprint_id, commit_sha, qa_bounces, arch_bounces
|
|
15
|
+
*
|
|
16
|
+
* Atomic write (tmp+rename per M1 pattern). Idempotent: re-run on a
|
|
17
|
+
* fully-prefilled report is a no-op.
|
|
18
|
+
*
|
|
19
|
+
* SubagentStop hook attribution note (FLASHCARD 2026-04-19 #reporting #hooks #ledger):
|
|
20
|
+
* Ledger rows may lack story_id; those are attributed to "unassigned" bucket
|
|
21
|
+
* and do not prevent backfill of reports that can be matched by filename.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { validateState } from './validate_state.mjs';
|
|
28
|
+
|
|
29
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a minimal YAML frontmatter block from markdown content.
|
|
34
|
+
* Returns { frontmatter: string, body: string, fields: object }
|
|
35
|
+
* Only parses simple key: value pairs (no nested objects).
|
|
36
|
+
* @param {string} content
|
|
37
|
+
* @returns {{ hasFrontmatter: boolean, frontmatter: string, body: string, fields: object }}
|
|
38
|
+
*/
|
|
39
|
+
function parseFrontmatter(content) {
|
|
40
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
41
|
+
if (!fmMatch) {
|
|
42
|
+
return { hasFrontmatter: false, frontmatter: '', body: content, fields: {} };
|
|
43
|
+
}
|
|
44
|
+
const rawFm = fmMatch[1];
|
|
45
|
+
const body = fmMatch[2];
|
|
46
|
+
const fields = {};
|
|
47
|
+
for (const line of rawFm.split('\n')) {
|
|
48
|
+
const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*):\s*(.*)$/);
|
|
49
|
+
if (m) {
|
|
50
|
+
const key = m[1];
|
|
51
|
+
let val = m[2].trim();
|
|
52
|
+
// Strip surrounding quotes
|
|
53
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
54
|
+
val = val.slice(1, -1);
|
|
55
|
+
}
|
|
56
|
+
// Parse nulls
|
|
57
|
+
if (val === 'null' || val === '') val = null;
|
|
58
|
+
fields[key] = val;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { hasFrontmatter: true, frontmatter: rawFm, body, fields };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Serialize fields back to YAML frontmatter lines (simple key: value).
|
|
66
|
+
* @param {object} fields
|
|
67
|
+
* @param {string} originalFrontmatter - preserve original order and formatting
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
function serializeFrontmatter(fields, originalFrontmatter) {
|
|
71
|
+
const lines = originalFrontmatter.split('\n');
|
|
72
|
+
const updatedLines = [];
|
|
73
|
+
const processedKeys = new Set();
|
|
74
|
+
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*):/);
|
|
77
|
+
if (m) {
|
|
78
|
+
const key = m[1];
|
|
79
|
+
processedKeys.add(key);
|
|
80
|
+
if (key in fields && fields[key] !== null && fields[key] !== undefined) {
|
|
81
|
+
const val = fields[key];
|
|
82
|
+
updatedLines.push(`${key}: "${val}"`);
|
|
83
|
+
} else {
|
|
84
|
+
updatedLines.push(line);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
updatedLines.push(line);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Append any new fields not in the original
|
|
92
|
+
for (const [key, val] of Object.entries(fields)) {
|
|
93
|
+
if (!processedKeys.has(key) && val !== null && val !== undefined) {
|
|
94
|
+
updatedLines.push(`${key}: "${val}"`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return updatedLines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Atomic write to a file using tmp+rename pattern.
|
|
103
|
+
* @param {string} filePath
|
|
104
|
+
* @param {string} content
|
|
105
|
+
*/
|
|
106
|
+
function atomicWrite(filePath, content) {
|
|
107
|
+
const tmpFile = `${filePath}.tmp.${process.pid}`;
|
|
108
|
+
fs.writeFileSync(tmpFile, content, 'utf8');
|
|
109
|
+
fs.renameSync(tmpFile, filePath);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Parse JSONL file, returning an array of parsed objects.
|
|
114
|
+
* Tolerates malformed lines (skips them with a warning).
|
|
115
|
+
* @param {string} filePath
|
|
116
|
+
* @returns {object[]}
|
|
117
|
+
*/
|
|
118
|
+
function parseJsonl(filePath) {
|
|
119
|
+
if (!fs.existsSync(filePath)) return [];
|
|
120
|
+
const lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(l => l.trim());
|
|
121
|
+
const results = [];
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
try {
|
|
124
|
+
results.push(JSON.parse(line));
|
|
125
|
+
} catch {
|
|
126
|
+
process.stderr.write(`Warning: skipping malformed JSONL line: ${line.slice(0, 80)}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function main() {
|
|
133
|
+
const args = process.argv.slice(2);
|
|
134
|
+
if (args.length < 1) {
|
|
135
|
+
process.stderr.write('Usage: node prefill_report.mjs <sprint-id>\n');
|
|
136
|
+
process.exit(2);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const sprintId = args[0];
|
|
140
|
+
const sprintRunsDir = path.join(REPO_ROOT, '.cleargate', 'sprint-runs');
|
|
141
|
+
const sprintDir = process.env.CLEARGATE_SPRINT_DIR
|
|
142
|
+
? path.resolve(process.env.CLEARGATE_SPRINT_DIR)
|
|
143
|
+
: path.join(sprintRunsDir, sprintId);
|
|
144
|
+
|
|
145
|
+
if (!fs.existsSync(sprintDir)) {
|
|
146
|
+
process.stderr.write(`Error: sprint directory not found: ${sprintDir}\n`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Load state.json
|
|
151
|
+
const stateFile = process.env.CLEARGATE_STATE_FILE
|
|
152
|
+
? path.resolve(process.env.CLEARGATE_STATE_FILE)
|
|
153
|
+
: path.join(sprintDir, 'state.json');
|
|
154
|
+
|
|
155
|
+
if (!fs.existsSync(stateFile)) {
|
|
156
|
+
process.stderr.write(`Error: state.json not found at ${stateFile}\n`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let state;
|
|
161
|
+
try {
|
|
162
|
+
state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
163
|
+
} catch (err) {
|
|
164
|
+
process.stderr.write(`Error: failed to parse state.json: ${err.message}\n`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const { valid, errors } = validateState(state);
|
|
169
|
+
if (!valid) {
|
|
170
|
+
process.stderr.write('Error: state.json is invalid:\n');
|
|
171
|
+
for (const e of errors) process.stderr.write(` - ${e}\n`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Load token-ledger.jsonl — build a map of story_id -> commit_sha
|
|
176
|
+
// Rows lacking story_id are attributed to 'unassigned' (per FLASHCARD 2026-04-19 #reporting #hooks #ledger)
|
|
177
|
+
const ledgerFile = path.join(sprintDir, 'token-ledger.jsonl');
|
|
178
|
+
const ledgerRows = parseJsonl(ledgerFile);
|
|
179
|
+
const storyCommits = {};
|
|
180
|
+
for (const row of ledgerRows) {
|
|
181
|
+
const sid = row.story_id || 'unassigned';
|
|
182
|
+
if (sid !== 'unassigned' && row.commit_sha) {
|
|
183
|
+
storyCommits[sid] = row.commit_sha;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Find all agent reports in the sprint dir (reports/ subdirectory + top level)
|
|
188
|
+
const reportsDir = path.join(sprintDir, 'reports');
|
|
189
|
+
const reportFiles = [];
|
|
190
|
+
|
|
191
|
+
// Check reports subdirectory
|
|
192
|
+
if (fs.existsSync(reportsDir)) {
|
|
193
|
+
for (const f of fs.readdirSync(reportsDir)) {
|
|
194
|
+
if (/^STORY-.*-(dev|qa)\.md$/.test(f)) {
|
|
195
|
+
reportFiles.push(path.join(reportsDir, f));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check sprint dir directly
|
|
201
|
+
for (const f of fs.readdirSync(sprintDir)) {
|
|
202
|
+
if (/^STORY-.*-(dev|qa)\.md$/.test(f)) {
|
|
203
|
+
reportFiles.push(path.join(sprintDir, f));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (reportFiles.length === 0) {
|
|
208
|
+
process.stdout.write(`No agent reports found in ${sprintDir}; nothing to prefill.\n`);
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let prefilled = 0;
|
|
213
|
+
let noops = 0;
|
|
214
|
+
|
|
215
|
+
for (const reportPath of reportFiles) {
|
|
216
|
+
const filename = path.basename(reportPath);
|
|
217
|
+
// Extract story_id from filename: STORY-NNN-NN-dev.md or STORY-NNN-NN-qa.md
|
|
218
|
+
const storyMatch = filename.match(/^(STORY-\d+-\d+)-(dev|qa)\.md$/);
|
|
219
|
+
if (!storyMatch) continue;
|
|
220
|
+
|
|
221
|
+
const storyId = storyMatch[1];
|
|
222
|
+
const content = fs.readFileSync(reportPath, 'utf8');
|
|
223
|
+
const { hasFrontmatter, frontmatter, body, fields } = parseFrontmatter(content);
|
|
224
|
+
|
|
225
|
+
if (!hasFrontmatter) {
|
|
226
|
+
process.stdout.write(`Skipping ${filename}: no frontmatter found.\n`);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Determine what needs backfill
|
|
231
|
+
const updates = {};
|
|
232
|
+
let needsUpdate = false;
|
|
233
|
+
|
|
234
|
+
if (!fields.story_id) {
|
|
235
|
+
updates.story_id = storyId;
|
|
236
|
+
needsUpdate = true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!fields.sprint_id) {
|
|
240
|
+
updates.sprint_id = state.sprint_id || sprintId;
|
|
241
|
+
needsUpdate = true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!fields.commit_sha && storyCommits[storyId]) {
|
|
245
|
+
updates.commit_sha = storyCommits[storyId];
|
|
246
|
+
needsUpdate = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const storyEntry = state.stories && state.stories[storyId];
|
|
250
|
+
if (storyEntry) {
|
|
251
|
+
if (fields.qa_bounces === null || fields.qa_bounces === undefined) {
|
|
252
|
+
updates.qa_bounces = String(storyEntry.qa_bounces || 0);
|
|
253
|
+
needsUpdate = true;
|
|
254
|
+
}
|
|
255
|
+
if (fields.arch_bounces === null || fields.arch_bounces === undefined) {
|
|
256
|
+
updates.arch_bounces = String(storyEntry.arch_bounces || 0);
|
|
257
|
+
needsUpdate = true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!needsUpdate) {
|
|
262
|
+
noops++;
|
|
263
|
+
process.stdout.write(`No-op: ${filename} already fully prefilled.\n`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Merge updates into existing fields
|
|
268
|
+
const mergedFields = Object.assign({}, fields, updates);
|
|
269
|
+
const newFrontmatter = serializeFrontmatter(mergedFields, frontmatter);
|
|
270
|
+
const newContent = `---\n${newFrontmatter}\n---\n${body}`;
|
|
271
|
+
|
|
272
|
+
atomicWrite(reportPath, newContent);
|
|
273
|
+
prefilled++;
|
|
274
|
+
process.stdout.write(`Prefilled ${filename}: ${Object.keys(updates).join(', ')}\n`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
process.stdout.write(`\nDone. prefilled=${prefilled} noops=${noops}\n`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
main();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# run_script.sh — Wrapper that captures stdout/stderr separately and prints a
|
|
3
|
+
# structured diagnostic block on non-zero exit.
|
|
4
|
+
# Usage: run_script.sh <script-name> [args...]
|
|
5
|
+
# Supported extensions: .mjs (runs via node), .sh (runs via bash)
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
|
|
10
|
+
# ---------------------------------------------------------------------------
|
|
11
|
+
# Usage guard
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
if [[ $# -lt 1 ]]; then
|
|
14
|
+
echo "Usage: run_script.sh <script-name> [args...]" >&2
|
|
15
|
+
exit 2
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
SCRIPT_NAME="$1"
|
|
19
|
+
shift
|
|
20
|
+
SCRIPT_ARGS=()
|
|
21
|
+
if [[ $# -gt 0 ]]; then
|
|
22
|
+
SCRIPT_ARGS=("$@")
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Resolve path — script may be an absolute path or relative to SCRIPT_DIR
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
if [[ "$SCRIPT_NAME" == /* ]]; then
|
|
29
|
+
SCRIPT_PATH="$SCRIPT_NAME"
|
|
30
|
+
else
|
|
31
|
+
SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_NAME}"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Extension routing
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
EXT="${SCRIPT_NAME##*.}"
|
|
38
|
+
case "$EXT" in
|
|
39
|
+
mjs) RUNNER="node" ;;
|
|
40
|
+
sh) RUNNER="bash" ;;
|
|
41
|
+
*)
|
|
42
|
+
echo "unsupported extension: .${EXT}" >&2
|
|
43
|
+
exit 2
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Check script exists
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
if [[ ! -f "$SCRIPT_PATH" ]]; then
|
|
51
|
+
echo "run_script.sh: script not found: ${SCRIPT_PATH}" >&2
|
|
52
|
+
exit 2
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Capture stdout + stderr to temp files
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
STDOUT_FILE="$(mktemp)"
|
|
59
|
+
STDERR_FILE="$(mktemp)"
|
|
60
|
+
trap 'rm -f "$STDOUT_FILE" "$STDERR_FILE"' EXIT
|
|
61
|
+
|
|
62
|
+
EXIT_CODE=0
|
|
63
|
+
if [[ ${#SCRIPT_ARGS[@]} -gt 0 ]]; then
|
|
64
|
+
"$RUNNER" "$SCRIPT_PATH" "${SCRIPT_ARGS[@]}" > "$STDOUT_FILE" 2> "$STDERR_FILE" || EXIT_CODE=$?
|
|
65
|
+
else
|
|
66
|
+
"$RUNNER" "$SCRIPT_PATH" > "$STDOUT_FILE" 2> "$STDERR_FILE" || EXIT_CODE=$?
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# On success: pass through and exit 0
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
if [[ $EXIT_CODE -eq 0 ]]; then
|
|
73
|
+
cat "$STDOUT_FILE"
|
|
74
|
+
cat "$STDERR_FILE" >&2
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# On failure: pass stdout through, then print structured diagnostic to stderr
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
cat "$STDOUT_FILE"
|
|
82
|
+
|
|
83
|
+
# Root-cause heuristic (6-branch)
|
|
84
|
+
STDERR_CONTENT="$(cat "$STDERR_FILE")"
|
|
85
|
+
ROOT_CAUSE="unknown error"
|
|
86
|
+
SUGGESTED_FIX="check the script output above for details"
|
|
87
|
+
|
|
88
|
+
if echo "$STDERR_CONTENT" | grep -q "state\.json not found"; then
|
|
89
|
+
ROOT_CAUSE="state.json not found — sprint may not be initialized"
|
|
90
|
+
SUGGESTED_FIX="run: node .cleargate/scripts/init_sprint.mjs <sprint-id> --stories <ids>"
|
|
91
|
+
elif echo "$STDERR_CONTENT" | grep -qi "ENOENT"; then
|
|
92
|
+
ROOT_CAUSE="missing file (ENOENT)"
|
|
93
|
+
SUGGESTED_FIX="verify all required files exist at the expected paths"
|
|
94
|
+
elif echo "$STDERR_CONTENT" | grep -qi "EACCES"; then
|
|
95
|
+
ROOT_CAUSE="permission denied (EACCES)"
|
|
96
|
+
SUGGESTED_FIX="chmod 755 the target file or directory"
|
|
97
|
+
elif echo "$STDERR_CONTENT" | grep -qi "SyntaxError"; then
|
|
98
|
+
ROOT_CAUSE="JavaScript syntax error"
|
|
99
|
+
SUGGESTED_FIX="fix the syntax error in the script; run: node --check <file>"
|
|
100
|
+
elif echo "$STDERR_CONTENT" | grep -qi "Cannot find module"; then
|
|
101
|
+
ROOT_CAUSE="missing module (import resolution failure)"
|
|
102
|
+
SUGGESTED_FIX="run npm install in the relevant package directory"
|
|
103
|
+
elif echo "$STDERR_CONTENT" | grep -qi "command not found"; then
|
|
104
|
+
ROOT_CAUSE="required command not found on PATH"
|
|
105
|
+
SUGGESTED_FIX="install the missing tool or add it to PATH"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
echo ""
|
|
110
|
+
echo "## Script Incident"
|
|
111
|
+
echo "Script: ${SCRIPT_NAME}"
|
|
112
|
+
echo "Runner: ${RUNNER}"
|
|
113
|
+
echo "Exit code: ${EXIT_CODE}"
|
|
114
|
+
echo ""
|
|
115
|
+
echo "### First 10 lines of stderr:"
|
|
116
|
+
echo "$STDERR_CONTENT" | head -10
|
|
117
|
+
echo ""
|
|
118
|
+
echo "Root cause: ${ROOT_CAUSE}"
|
|
119
|
+
echo "Suggested fix: ${SUGGESTED_FIX}"
|
|
120
|
+
echo "## End Incident"
|
|
121
|
+
} >&2
|
|
122
|
+
|
|
123
|
+
exit $EXIT_CODE
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://cleargate.dev/schemas/state.v1.json",
|
|
4
|
+
"title": "ClearGate Sprint State v1",
|
|
5
|
+
"description": "Schema for .cleargate/sprint-runs/<sprint-id>/state.json. schema_version=1 is LOCKED — any field addition or removal must bump schema_version.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"sprint_id",
|
|
10
|
+
"execution_mode",
|
|
11
|
+
"sprint_status",
|
|
12
|
+
"stories",
|
|
13
|
+
"last_action",
|
|
14
|
+
"updated_at"
|
|
15
|
+
],
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"properties": {
|
|
18
|
+
"schema_version": {
|
|
19
|
+
"type": "integer",
|
|
20
|
+
"const": 1,
|
|
21
|
+
"description": "Schema version. Must be 1 for v1 state files. Bump on any schema change."
|
|
22
|
+
},
|
|
23
|
+
"sprint_id": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Sprint identifier, e.g. S-09",
|
|
26
|
+
"examples": ["S-09", "S-10"]
|
|
27
|
+
},
|
|
28
|
+
"execution_mode": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": ["v1", "v2"],
|
|
31
|
+
"description": "Execution mode for the sprint. v1 = legacy sequential; v2 = worktree-parallel."
|
|
32
|
+
},
|
|
33
|
+
"sprint_status": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Overall sprint status",
|
|
36
|
+
"examples": ["Active", "Closed"]
|
|
37
|
+
},
|
|
38
|
+
"stories": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"description": "Map of story ID to story state entry",
|
|
41
|
+
"additionalProperties": {
|
|
42
|
+
"$ref": "#/definitions/StoryEntry"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"last_action": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Human-readable description of the last operation performed"
|
|
48
|
+
},
|
|
49
|
+
"updated_at": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"format": "date-time",
|
|
52
|
+
"description": "ISO-8601 timestamp of last top-level update"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"definitions": {
|
|
56
|
+
"StoryEntry": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"required": [
|
|
59
|
+
"state",
|
|
60
|
+
"qa_bounces",
|
|
61
|
+
"arch_bounces",
|
|
62
|
+
"worktree",
|
|
63
|
+
"updated_at",
|
|
64
|
+
"notes"
|
|
65
|
+
],
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"properties": {
|
|
68
|
+
"state": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"enum": [
|
|
71
|
+
"Ready to Bounce",
|
|
72
|
+
"Bouncing",
|
|
73
|
+
"QA Passed",
|
|
74
|
+
"Architect Passed",
|
|
75
|
+
"Sprint Review",
|
|
76
|
+
"Done",
|
|
77
|
+
"Escalated",
|
|
78
|
+
"Parking Lot"
|
|
79
|
+
],
|
|
80
|
+
"description": "Current lifecycle state of the story"
|
|
81
|
+
},
|
|
82
|
+
"qa_bounces": {
|
|
83
|
+
"type": "integer",
|
|
84
|
+
"minimum": 0,
|
|
85
|
+
"maximum": 3,
|
|
86
|
+
"description": "Number of QA bounce cycles. Reaching 3 triggers auto-escalation."
|
|
87
|
+
},
|
|
88
|
+
"arch_bounces": {
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"minimum": 0,
|
|
91
|
+
"maximum": 3,
|
|
92
|
+
"description": "Number of Architect bounce cycles. Reaching 3 triggers auto-escalation."
|
|
93
|
+
},
|
|
94
|
+
"worktree": {
|
|
95
|
+
"type": ["string", "null"],
|
|
96
|
+
"description": "Path to the story's git worktree checkout, or null if not in a worktree"
|
|
97
|
+
},
|
|
98
|
+
"updated_at": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"format": "date-time",
|
|
101
|
+
"description": "ISO-8601 timestamp of last update to this story entry"
|
|
102
|
+
},
|
|
103
|
+
"notes": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"description": "Free-form notes for this story (escalation reason, QA comments, etc.)"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|