cleargate 0.8.2 → 0.10.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/CHANGELOG.md +190 -0
- package/README.md +11 -0
- package/dist/MANIFEST.json +259 -28
- package/dist/{chunk-OM4FAEA7.js → chunk-Q3BTSXCK.js} +69 -3
- package/dist/chunk-Q3BTSXCK.js.map +1 -0
- package/dist/cli.cjs +2621 -548
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2548 -560
- package/dist/cli.js.map +1 -1
- package/dist/lib/ledger.cjs +120 -0
- package/dist/lib/ledger.cjs.map +1 -0
- package/dist/lib/ledger.d.cts +64 -0
- package/dist/lib/ledger.d.ts +64 -0
- package/dist/lib/ledger.js +96 -0
- package/dist/lib/ledger.js.map +1 -0
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +10 -8
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +108 -0
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +49 -3
- package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +6 -1
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +29 -2
- package/dist/templates/cleargate-planning/.claude/agents/qa.md +50 -1
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +31 -9
- package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +148 -0
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +6 -0
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +314 -96
- package/dist/templates/cleargate-planning/.claude/settings.json +4 -0
- package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +473 -0
- package/dist/templates/cleargate-planning/.cleargate/config.example.yml +19 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +542 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +102 -428
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +31 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +71 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +24 -2
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +387 -27
- package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +219 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +54 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +378 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +888 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +71 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +355 -13
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +20 -20
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +482 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +125 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +24 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +32 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +48 -14
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +37 -3
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +50 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +98 -29
- package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +17 -4
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +23 -4
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +55 -3
- package/dist/templates/cleargate-planning/CLAUDE.md +28 -10
- package/dist/templates/cleargate-planning/MANIFEST.json +259 -28
- package/dist/{whoami-CX7CXJD5.js → whoami-W4U6DPVG.js} +17 -17
- package/dist/whoami-W4U6DPVG.js.map +1 -0
- package/package.json +13 -2
- package/templates/cleargate-planning/.claude/agents/architect.md +10 -8
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +108 -0
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +49 -3
- package/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +6 -1
- package/templates/cleargate-planning/.claude/agents/developer.md +29 -2
- package/templates/cleargate-planning/.claude/agents/qa.md +50 -1
- package/templates/cleargate-planning/.claude/agents/reporter.md +31 -9
- package/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +148 -0
- package/templates/cleargate-planning/.claude/hooks/session-start.sh +6 -0
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +314 -96
- package/templates/cleargate-planning/.claude/settings.json +4 -0
- package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +473 -0
- package/templates/cleargate-planning/.cleargate/config.example.yml +19 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +542 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +102 -428
- package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +31 -0
- package/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +71 -0
- package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +24 -2
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +387 -27
- package/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +219 -0
- package/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +54 -0
- package/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +378 -0
- package/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +888 -0
- package/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +71 -0
- package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +355 -13
- package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +20 -20
- package/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +482 -0
- package/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +125 -0
- package/templates/cleargate-planning/.cleargate/templates/Bug.md +24 -1
- package/templates/cleargate-planning/.cleargate/templates/CR.md +32 -1
- package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +48 -14
- package/templates/cleargate-planning/.cleargate/templates/epic.md +37 -3
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +50 -0
- package/templates/cleargate-planning/.cleargate/templates/initiative.md +98 -29
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +23 -4
- package/templates/cleargate-planning/.cleargate/templates/story.md +55 -3
- package/templates/cleargate-planning/CLAUDE.md +28 -10
- package/templates/cleargate-planning/MANIFEST.json +259 -28
- package/dist/chunk-OM4FAEA7.js.map +0 -1
- package/dist/whoami-CX7CXJD5.js.map +0 -1
- package/templates/cleargate-planning/.cleargate/templates/proposal.md +0 -61
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* close_sprint.mjs —
|
|
3
|
+
* close_sprint.mjs — Eight-step sprint close pipeline
|
|
4
4
|
*
|
|
5
5
|
* Usage: node close_sprint.mjs <sprint-id> [--assume-ack]
|
|
6
6
|
* node close_sprint.mjs <sprint-id> --report-body-stdin (STORY-014-10)
|
|
@@ -9,20 +9,56 @@
|
|
|
9
9
|
* 1. Load and validate state.json via validateState
|
|
10
10
|
* 2. Refuse if any story state is not in TERMINAL_STATES (exit non-zero, list offenders)
|
|
11
11
|
* 3. Invoke prefill_report.mjs on all agent reports
|
|
12
|
+
* 3.5 Build curated Reporter context bundle via prep_reporter_context.mjs (non-fatal)
|
|
12
13
|
* 4. Orchestrator spawns Reporter separately (script validates preconditions only)
|
|
13
14
|
* 5. On Reporter success + user ack (or --assume-ack flag), flip sprint_status -> "Completed"
|
|
14
15
|
* 6. Invoke suggest_improvements.mjs unconditionally
|
|
16
|
+
* 7. Auto-push per-artifact status updates to MCP via cleargate sync work-items (non-fatal)
|
|
17
|
+
* 8. Verbose post-close handoff list (6-item next-steps block to stdout)
|
|
18
|
+
*
|
|
19
|
+
* Report filename: SPRINT-<#>_REPORT.md for new sprints (SPRINT-18+).
|
|
20
|
+
* Backwards-compat: if SPRINT-<#>_REPORT.md is absent but REPORT.md exists (legacy
|
|
21
|
+
* SPRINT-01..17), fall back to REPORT.md for read operations. New writes always
|
|
22
|
+
* use SPRINT-<#>_REPORT.md when the sprint-id carries a numeric portion.
|
|
23
|
+
* If the sprint-id has no numeric portion (e.g. SPRINT-TEST), plain REPORT.md is used.
|
|
15
24
|
*
|
|
16
25
|
* Stdin fallback (STORY-014-10): when `--report-body-stdin` is passed, the script
|
|
17
|
-
* reads the full
|
|
26
|
+
* reads the full SPRINT-<#>_REPORT.md body from stdin and writes it atomically in lieu of
|
|
18
27
|
* waiting for a Reporter-produced file. Replaces the Step-4 gate; implies ack.
|
|
19
|
-
* Refuses empty stdin or pre-existing
|
|
28
|
+
* Refuses empty stdin or pre-existing report file.
|
|
20
29
|
*
|
|
21
30
|
* Does NOT archive the sprint file (pending-sync -> archive stays human per EPIC-013 §4.5 step 7).
|
|
22
31
|
*
|
|
23
32
|
* Reuse: TERMINAL_STATES, VALID_STATES from constants.mjs
|
|
24
33
|
* validateState from validate_state.mjs
|
|
25
34
|
* atomicWrite pattern from update_state.mjs
|
|
35
|
+
*
|
|
36
|
+
* Test seams (CR-022 M1):
|
|
37
|
+
* CLEARGATE_SKIP_LIFECYCLE_CHECK=1 — skip Step 2.6 lifecycle reconciliation entirely
|
|
38
|
+
* (test environments where the CLI binary is present
|
|
39
|
+
* but real git history would produce drift false-positives).
|
|
40
|
+
* CLEARGATE_SKIP_WORKTREE_CHECK=1 — skip Step 2.7 entirely (test environments that cannot
|
|
41
|
+
* run git worktree list from a real git root).
|
|
42
|
+
* CLEARGATE_FORCE_WORKTREE_PATHS=p1,p2 — comma-separated fake worktree paths injected into
|
|
43
|
+
* Step 2.7 instead of running git worktree list.
|
|
44
|
+
* Used to exercise the v2 block / v1 advisory paths
|
|
45
|
+
* without a real .worktrees/STORY-* directory.
|
|
46
|
+
* CLEARGATE_SKIP_MERGE_CHECK=1 — skip Step 2.8 entirely (test environments where git
|
|
47
|
+
* refs are absent or merge state is irrelevant).
|
|
48
|
+
* CLEARGATE_FORCE_MERGE_STATUS=merged|unmerged — inject merge status for Step 2.8 without
|
|
49
|
+
* running git merge-base. Used to exercise
|
|
50
|
+
* the v2 block / v1 advisory paths.
|
|
51
|
+
* CLEARGATE_REPO_ROOT=<path> — override REPO_ROOT for Step 2.8 git commands
|
|
52
|
+
* (used in tests that need a controlled git repo).
|
|
53
|
+
* CLEARGATE_SKIP_SPRINT_TRENDS=1 — skip Step 6.5 entirely (test environments).
|
|
54
|
+
* CLEARGATE_SKIP_SKILL_CANDIDATES=1 — skip Step 6.6 entirely (test environments).
|
|
55
|
+
* CLEARGATE_SKIP_FLASHCARD_CLEANUP=1 — skip Step 6.7 entirely (test environments).
|
|
56
|
+
* CLEARGATE_SPRINT_RUNS_DIR=<path> — override .cleargate/sprint-runs/ root for
|
|
57
|
+
* sibling-sprint counting in sprint_trends.mjs.
|
|
58
|
+
* CLEARGATE_FLASHCARD_PATH=<path> — override .cleargate/FLASHCARD.md path for
|
|
59
|
+
* --flashcard-cleanup scan in suggest_improvements.mjs.
|
|
60
|
+
* CLEARGATE_FLASHCARD_LOOKBACK=<N> — override 3-sprint default lookback for
|
|
61
|
+
* --flashcard-cleanup scan.
|
|
26
62
|
*/
|
|
27
63
|
|
|
28
64
|
import fs from 'node:fs';
|
|
@@ -31,6 +67,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
31
67
|
import { execSync } from 'node:child_process';
|
|
32
68
|
import { TERMINAL_STATES } from './constants.mjs';
|
|
33
69
|
import { validateState } from './validate_state.mjs';
|
|
70
|
+
import { reportFilename } from './lib/report-filename.mjs';
|
|
34
71
|
|
|
35
72
|
/**
|
|
36
73
|
* Migrate a v1 state.json to v2 by injecting lane fields with defaults.
|
|
@@ -56,7 +93,9 @@ function migrateV1ToV2(state) {
|
|
|
56
93
|
}
|
|
57
94
|
|
|
58
95
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
59
|
-
const REPO_ROOT =
|
|
96
|
+
const REPO_ROOT = process.env.CLEARGATE_REPO_ROOT
|
|
97
|
+
? path.resolve(process.env.CLEARGATE_REPO_ROOT)
|
|
98
|
+
: path.resolve(__dirname, '..', '..');
|
|
60
99
|
const SCRIPTS_DIR = __dirname;
|
|
61
100
|
|
|
62
101
|
function usage() {
|
|
@@ -64,12 +103,13 @@ function usage() {
|
|
|
64
103
|
'Usage: node close_sprint.mjs <sprint-id> [--assume-ack | --report-body-stdin]\n' +
|
|
65
104
|
'\n' +
|
|
66
105
|
'Options:\n' +
|
|
67
|
-
' --assume-ack Skip user acknowledgement prompt (
|
|
68
|
-
' --report-body-stdin Read
|
|
106
|
+
' --assume-ack Skip user acknowledgement prompt (automated tests ONLY — conversational orchestrators MUST NOT pass this)\n' +
|
|
107
|
+
' --report-body-stdin Read SPRINT-<#>_REPORT.md body from stdin; implies ack (STORY-014-10)\n'
|
|
69
108
|
);
|
|
70
109
|
process.exit(2);
|
|
71
110
|
}
|
|
72
111
|
|
|
112
|
+
|
|
73
113
|
/**
|
|
74
114
|
* Atomic write using tmp+rename pattern (per M1 update_state.mjs convention).
|
|
75
115
|
* @param {string} filePath
|
|
@@ -202,11 +242,11 @@ function main() {
|
|
|
202
242
|
process.exit(1);
|
|
203
243
|
}
|
|
204
244
|
|
|
205
|
-
// Read REPORT.md
|
|
206
|
-
const reportFile2 =
|
|
245
|
+
// Read SPRINT-<#>_REPORT.md (with legacy REPORT.md fallback for pre-CR-021 sprints)
|
|
246
|
+
const reportFile2 = reportFilename(sprintDir, sprintId, { forRead: true });
|
|
207
247
|
if (!fs.existsSync(reportFile2)) {
|
|
208
248
|
process.stderr.write(
|
|
209
|
-
`close_sprint: v2.1 validation requires
|
|
249
|
+
`close_sprint: v2.1 validation requires ${path.basename(reportFile2)} at ${reportFile2}\n` +
|
|
210
250
|
' Run the Reporter agent first, then re-run close_sprint.mjs.\n'
|
|
211
251
|
);
|
|
212
252
|
process.exit(1);
|
|
@@ -247,6 +287,217 @@ function main() {
|
|
|
247
287
|
process.stdout.write('Step 2.5 passed: v2.1 validation — all required §3 metrics and §5 sections present.\n');
|
|
248
288
|
}
|
|
249
289
|
|
|
290
|
+
// ── Step 2.6: Lifecycle Reconciliation (CR-017) ──────────────────────────
|
|
291
|
+
// Block close if any artifact referenced in this sprint's commits is still
|
|
292
|
+
// non-terminal in pending-sync (excluding carry_over: true).
|
|
293
|
+
// Invokes `cleargate sprint reconcile-lifecycle <sprint-id>` CLI wrapper.
|
|
294
|
+
// Fail-open if CLI binary is unavailable (non-blocking for test environments).
|
|
295
|
+
// Test seam: CLEARGATE_SKIP_LIFECYCLE_CHECK=1 skips this step entirely (non-fatal).
|
|
296
|
+
process.stdout.write('Step 2.6: running lifecycle reconciliation...\n');
|
|
297
|
+
if (process.env.CLEARGATE_SKIP_LIFECYCLE_CHECK === '1') {
|
|
298
|
+
process.stdout.write('Step 2.6 skipped: CLEARGATE_SKIP_LIFECYCLE_CHECK=1 set (test seam).\n');
|
|
299
|
+
} else {
|
|
300
|
+
try {
|
|
301
|
+
// Resolve CLI binary: prefer local dist/
|
|
302
|
+
const cliBin = path.join(REPO_ROOT, 'cleargate-cli', 'dist', 'cli.js');
|
|
303
|
+
|
|
304
|
+
if (fs.existsSync(cliBin)) {
|
|
305
|
+
// Read sprint start_date from frontmatter for the --since arg
|
|
306
|
+
let sinceArg = '';
|
|
307
|
+
try {
|
|
308
|
+
const pendingDir = path.join(REPO_ROOT, '.cleargate', 'delivery', 'pending-sync');
|
|
309
|
+
if (fs.existsSync(pendingDir)) {
|
|
310
|
+
const entries = fs.readdirSync(pendingDir);
|
|
311
|
+
const sprintFile = entries.find(
|
|
312
|
+
(e) => (e.startsWith(`${sprintId}_`) || e === `${sprintId}.md`) && e.endsWith('.md')
|
|
313
|
+
);
|
|
314
|
+
if (sprintFile) {
|
|
315
|
+
const raw = fs.readFileSync(path.join(pendingDir, sprintFile), 'utf8');
|
|
316
|
+
const startDateMatch = /^start_date:\s*(.+)$/m.exec(raw);
|
|
317
|
+
if (startDateMatch && startDateMatch[1]) {
|
|
318
|
+
sinceArg = `--since ${startDateMatch[1].trim()}`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch { /* ignore */ }
|
|
323
|
+
|
|
324
|
+
const reconcileArgs = [
|
|
325
|
+
'node', JSON.stringify(cliBin), 'sprint', 'reconcile-lifecycle', JSON.stringify(sprintId),
|
|
326
|
+
];
|
|
327
|
+
if (sinceArg) reconcileArgs.push(sinceArg);
|
|
328
|
+
const reconcileCmd = reconcileArgs.join(' ');
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
execSync(reconcileCmd, { stdio: 'inherit', env: process.env });
|
|
332
|
+
process.stdout.write('Step 2.6 passed: lifecycle reconciliation clean.\n');
|
|
333
|
+
} catch (_reconcileErr) {
|
|
334
|
+
// Exit code 1 from reconcile-lifecycle means drift found
|
|
335
|
+
process.stderr.write(
|
|
336
|
+
'close_sprint: Step 2.6 FAILED — lifecycle drift blocks sprint close.\n' +
|
|
337
|
+
' Remediate the listed artifacts and re-run close_sprint.mjs.\n' +
|
|
338
|
+
' To carry over an artifact: set carry_over: true in its frontmatter.\n'
|
|
339
|
+
);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
process.stdout.write('Step 2.6 skipped: CLI binary not found at cleargate-cli/dist/cli.js (non-fatal).\n');
|
|
344
|
+
}
|
|
345
|
+
} catch (step26Err) {
|
|
346
|
+
// Unexpected error — fail-open (log but do not block)
|
|
347
|
+
process.stderr.write(`Step 2.6 warning: lifecycle reconciliation unavailable: ${step26Err.message}\n`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── Step 2.7: Worktree-Closed Check (CR-022 M1) ──────────────────────────
|
|
352
|
+
// Block close if any .worktrees/STORY-* path is present.
|
|
353
|
+
// v2 enforcing (exit 1); v1 advisory (warn + continue).
|
|
354
|
+
// Skip if git worktree list is unavailable (non-fatal — tests run against tmpdirs).
|
|
355
|
+
// Test seams: CLEARGATE_SKIP_WORKTREE_CHECK=1 bypasses entirely;
|
|
356
|
+
// CLEARGATE_FORCE_WORKTREE_PATHS=p1,p2 injects fake paths (no git call).
|
|
357
|
+
process.stdout.write('Step 2.7: checking for leftover worktrees...\n');
|
|
358
|
+
{
|
|
359
|
+
if (process.env.CLEARGATE_SKIP_WORKTREE_CHECK === '1') {
|
|
360
|
+
process.stdout.write('Step 2.7 skipped: CLEARGATE_SKIP_WORKTREE_CHECK=1 set (test seam).\n');
|
|
361
|
+
} else {
|
|
362
|
+
let leftoverWorktrees = [];
|
|
363
|
+
let worktreeListAvailable = true;
|
|
364
|
+
|
|
365
|
+
if (process.env.CLEARGATE_FORCE_WORKTREE_PATHS) {
|
|
366
|
+
// Test seam: inject fake worktree paths without running git
|
|
367
|
+
leftoverWorktrees = process.env.CLEARGATE_FORCE_WORKTREE_PATHS
|
|
368
|
+
.split(',')
|
|
369
|
+
.map((p) => p.trim())
|
|
370
|
+
.filter(Boolean);
|
|
371
|
+
} else {
|
|
372
|
+
try {
|
|
373
|
+
const output = execSync('git worktree list --porcelain', {
|
|
374
|
+
cwd: REPO_ROOT,
|
|
375
|
+
encoding: 'utf8',
|
|
376
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
377
|
+
});
|
|
378
|
+
for (const line of output.split('\n')) {
|
|
379
|
+
const trimmed = line.trim();
|
|
380
|
+
if (!trimmed.startsWith('worktree ')) continue;
|
|
381
|
+
const wtPath = trimmed.slice('worktree '.length);
|
|
382
|
+
if (/[/\\]\.worktrees[/\\]STORY-/.test(wtPath)) {
|
|
383
|
+
const m = /(\.(worktrees)[/\\]STORY-.+)$/.exec(wtPath);
|
|
384
|
+
leftoverWorktrees.push(m ? m[1] : wtPath);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
worktreeListAvailable = false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Step 2.7 enforcing mode: v2 execution_mode (not just schema_version, since
|
|
393
|
+
// migration bumps schema_version to 2 for all sprints before isV2 is evaluated).
|
|
394
|
+
// Using execution_mode preserves v1 advisory behaviour for sprints initialised
|
|
395
|
+
// with execution_mode: "v1" even after their schema is migrated.
|
|
396
|
+
const isEnforcingV2 = isV2 && state.execution_mode === 'v2';
|
|
397
|
+
|
|
398
|
+
if (!worktreeListAvailable) {
|
|
399
|
+
process.stdout.write('Step 2.7 skipped: git worktree list unavailable (non-fatal).\n');
|
|
400
|
+
} else if (leftoverWorktrees.length === 0) {
|
|
401
|
+
process.stdout.write('Step 2.7 passed: no leftover worktrees.\n');
|
|
402
|
+
} else if (isEnforcingV2) {
|
|
403
|
+
// v2 enforcing — block close
|
|
404
|
+
process.stderr.write(
|
|
405
|
+
`close_sprint: Step 2.7 failed: leftover worktree at ${leftoverWorktrees[0]}\n` +
|
|
406
|
+
` ${leftoverWorktrees.length === 1 ? '' : `(plus ${leftoverWorktrees.length - 1} more)\n `}` +
|
|
407
|
+
`Run \`git worktree remove ${leftoverWorktrees[0]}\` if abandoned, or merge the work in progress.\n` +
|
|
408
|
+
` All worktrees must be closed before sprint close.\n`
|
|
409
|
+
);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
} else {
|
|
412
|
+
// v1 advisory — warn + continue
|
|
413
|
+
process.stderr.write(
|
|
414
|
+
`Step 2.7 warning: leftover worktree at ${leftoverWorktrees[0]} (advisory in v1).\n`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ── Step 2.8: Sprint branch merged to main (verify-only, NO auto-merge) ──────
|
|
421
|
+
// CR-022 §1: verify-only — script asserts merge ancestry, does NOT run the merge.
|
|
422
|
+
// On miss: list unmerged commits + exit 1 (v2 enforcing); warn + continue (v1 advisory).
|
|
423
|
+
// Skip when sprintId has no numeric portion (e.g. SPRINT-TEST fixture).
|
|
424
|
+
// Test seams: CLEARGATE_SKIP_MERGE_CHECK=1 bypasses entirely;
|
|
425
|
+
// CLEARGATE_FORCE_MERGE_STATUS=merged|unmerged injects status without git call.
|
|
426
|
+
{
|
|
427
|
+
if (process.env.CLEARGATE_SKIP_MERGE_CHECK === '1') {
|
|
428
|
+
process.stdout.write('Step 2.8 skipped: CLEARGATE_SKIP_MERGE_CHECK=1 set (test seam).\n');
|
|
429
|
+
} else {
|
|
430
|
+
const sprintNumMatch = /^SPRINT-(\d{2,3})$/.exec(sprintId);
|
|
431
|
+
if (!sprintNumMatch) {
|
|
432
|
+
process.stdout.write(`Step 2.8 skipped: sprint-id "${sprintId}" has no numeric portion.\n`);
|
|
433
|
+
} else {
|
|
434
|
+
const sprintBranch = `refs/heads/sprint/S-${sprintNumMatch[1]}`;
|
|
435
|
+
const mainBranch = 'refs/heads/main';
|
|
436
|
+
process.stdout.write(`Step 2.8: verifying ${sprintBranch} merged to ${mainBranch}...\n`);
|
|
437
|
+
|
|
438
|
+
const isEnforcingV2 = isV2 && state.execution_mode === 'v2';
|
|
439
|
+
|
|
440
|
+
const forcedStatus = process.env.CLEARGATE_FORCE_MERGE_STATUS;
|
|
441
|
+
let isMerged = false;
|
|
442
|
+
let mergeCheckAvailable = true;
|
|
443
|
+
|
|
444
|
+
if (forcedStatus === 'merged') {
|
|
445
|
+
isMerged = true;
|
|
446
|
+
} else if (forcedStatus === 'unmerged') {
|
|
447
|
+
isMerged = false;
|
|
448
|
+
} else {
|
|
449
|
+
try {
|
|
450
|
+
execSync(
|
|
451
|
+
`git merge-base --is-ancestor ${sprintBranch} ${mainBranch}`,
|
|
452
|
+
{ stdio: 'pipe', cwd: REPO_ROOT, env: process.env }
|
|
453
|
+
);
|
|
454
|
+
isMerged = true;
|
|
455
|
+
} catch (mergeErr) {
|
|
456
|
+
const exitStatus = /** @type {any} */ (mergeErr).status;
|
|
457
|
+
if (exitStatus === 1) {
|
|
458
|
+
isMerged = false;
|
|
459
|
+
} else {
|
|
460
|
+
// exit 128: refs missing or other git failure — fail-open with warning
|
|
461
|
+
mergeCheckAvailable = false;
|
|
462
|
+
process.stderr.write(
|
|
463
|
+
`Step 2.8 warning: git merge-base check unavailable (${/** @type {Error} */ (mergeErr).message}). ` +
|
|
464
|
+
`Skipping merge verification.\n`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!mergeCheckAvailable) {
|
|
471
|
+
// fail-open: refs missing or git unavailable — continue to Step 3
|
|
472
|
+
} else if (isMerged) {
|
|
473
|
+
process.stdout.write(`Step 2.8 passed: ${sprintBranch} is merged to ${mainBranch}.\n`);
|
|
474
|
+
} else if (isEnforcingV2) {
|
|
475
|
+
// v2 enforcing — block close
|
|
476
|
+
let unmergedLog = '';
|
|
477
|
+
if (!forcedStatus) {
|
|
478
|
+
try {
|
|
479
|
+
unmergedLog = execSync(
|
|
480
|
+
`git log ${mainBranch}..${sprintBranch} --oneline`,
|
|
481
|
+
{ encoding: 'utf8', cwd: REPO_ROOT, env: process.env }
|
|
482
|
+
);
|
|
483
|
+
} catch { /* unmerged-log fetch failed — proceed without */ }
|
|
484
|
+
}
|
|
485
|
+
process.stderr.write(
|
|
486
|
+
`Step 2.8 failed: sprint/S-${sprintNumMatch[1]} not merged to main.\n` +
|
|
487
|
+
(unmergedLog ? ` Unmerged commits:\n${unmergedLog}` : '') +
|
|
488
|
+
` Resolve: merge sprint/S-${sprintNumMatch[1]} → main, then re-run close_sprint.mjs.\n`
|
|
489
|
+
);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
} else {
|
|
492
|
+
// v1 advisory — warn + continue
|
|
493
|
+
process.stderr.write(
|
|
494
|
+
`Step 2.8 warning: sprint/S-${sprintNumMatch[1]} not merged to main (advisory in v1).\n`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
250
501
|
// ── Step 3: Invoke prefill_report.mjs ─────────────────────────────────────
|
|
251
502
|
process.stdout.write('Step 3: running prefill_report.mjs...\n');
|
|
252
503
|
try {
|
|
@@ -259,24 +510,37 @@ function main() {
|
|
|
259
510
|
process.exit(1);
|
|
260
511
|
}
|
|
261
512
|
|
|
513
|
+
// ── Step 3.5: Build curated Reporter context bundle ───────────────────────
|
|
514
|
+
process.stdout.write('Step 3.5: building Reporter context bundle...\n');
|
|
515
|
+
try {
|
|
516
|
+
invokeScript('prep_reporter_context.mjs', [sprintId], {
|
|
517
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
518
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
519
|
+
});
|
|
520
|
+
process.stdout.write(`Step 3.5 passed: ${sprintDir}/.reporter-context.md ready.\n`);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
// Non-fatal — Reporter falls back to source files
|
|
523
|
+
process.stderr.write(`Step 3.5 warning: prep_reporter_context.mjs failed: ${/** @type {Error} */ (err).message}\n`);
|
|
524
|
+
process.stderr.write('Reporter will fall back to broad-fetch context loading.\n');
|
|
525
|
+
}
|
|
526
|
+
|
|
262
527
|
// ── Step 4: Orchestrator spawns Reporter separately ───────────────────────
|
|
263
528
|
// This script only validates preconditions; it does NOT fork the Reporter agent.
|
|
529
|
+
const reportFile = reportFilename(sprintDir, sprintId);
|
|
530
|
+
const reportBasename = path.basename(reportFile);
|
|
264
531
|
process.stdout.write(
|
|
265
532
|
'Step 4: preconditions satisfied — orchestrator should now spawn the Reporter agent.\n' +
|
|
266
|
-
|
|
267
|
-
` Expected output: ${
|
|
533
|
+
` The Reporter writes ${reportBasename} using the sprint_report.md template.\n` +
|
|
534
|
+
` Expected output: ${reportFile}\n`
|
|
268
535
|
);
|
|
269
536
|
|
|
270
|
-
// Check if REPORT.md already exists (e.g., --assume-ack path in tests)
|
|
271
|
-
const reportFile = path.join(sprintDir, 'REPORT.md');
|
|
272
|
-
|
|
273
537
|
// ── Step 4.5 (STORY-014-10): --report-body-stdin fallback ────────────────
|
|
274
538
|
// Orchestrator pipes the Reporter's markdown body here when the Reporter's
|
|
275
|
-
// Write tool is blocked. Refuses empty stdin + pre-existing
|
|
539
|
+
// Write tool is blocked. Refuses empty stdin + pre-existing report file.
|
|
276
540
|
if (reportBodyStdin) {
|
|
277
541
|
if (fs.existsSync(reportFile)) {
|
|
278
542
|
process.stderr.write(
|
|
279
|
-
`Error:
|
|
543
|
+
`Error: ${reportBasename} already exists at ${reportFile}\n` +
|
|
280
544
|
'Delete it or skip --report-body-stdin mode to use the primary Reporter-write path.\n'
|
|
281
545
|
);
|
|
282
546
|
process.exit(1);
|
|
@@ -285,7 +549,7 @@ function main() {
|
|
|
285
549
|
try {
|
|
286
550
|
body = fs.readFileSync(0, 'utf8');
|
|
287
551
|
} catch (err) {
|
|
288
|
-
process.stderr.write(`Error: failed to read stdin: ${err.message}\n`);
|
|
552
|
+
process.stderr.write(`Error: failed to read stdin: ${/** @type {Error} */ (err).message}\n`);
|
|
289
553
|
process.exit(1);
|
|
290
554
|
}
|
|
291
555
|
if (!body || body.trim().length === 0) {
|
|
@@ -294,20 +558,22 @@ function main() {
|
|
|
294
558
|
}
|
|
295
559
|
atomicWriteString(reportFile, body);
|
|
296
560
|
process.stdout.write(
|
|
297
|
-
`Step 4.5 (stdin mode):
|
|
561
|
+
`Step 4.5 (stdin mode): ${reportBasename} written (${body.length} bytes) at ${reportFile}\n`
|
|
298
562
|
);
|
|
299
|
-
// Fall through to Step 5 + 6 unconditionally — stdin mode implies ack.
|
|
563
|
+
// Fall through to Step 5 + 6 + 7 unconditionally — stdin mode implies ack.
|
|
300
564
|
} else if (!assumeAck) {
|
|
301
|
-
|
|
565
|
+
// Apply read-fallback for legacy sprints (e.g. SPRINT-15 with plain REPORT.md)
|
|
566
|
+
const reportFileForCheck = reportFilename(sprintDir, sprintId, { forRead: true });
|
|
567
|
+
if (!fs.existsSync(reportFileForCheck)) {
|
|
302
568
|
process.stdout.write(
|
|
303
|
-
|
|
569
|
+
`\nWaiting for Reporter to produce ${reportBasename}...\n` +
|
|
304
570
|
'After Reporter succeeds, re-run with --assume-ack to complete the close.\n'
|
|
305
571
|
);
|
|
306
572
|
process.exit(0);
|
|
307
573
|
}
|
|
308
|
-
// In non-assume-ack mode with existing
|
|
574
|
+
// In non-assume-ack mode with existing report, prompt user
|
|
309
575
|
process.stdout.write(
|
|
310
|
-
`\
|
|
576
|
+
`\n${reportBasename} found at ${reportFileForCheck}\n` +
|
|
311
577
|
'Review the report, then confirm close by re-running with --assume-ack\n'
|
|
312
578
|
);
|
|
313
579
|
process.exit(0);
|
|
@@ -331,13 +597,107 @@ function main() {
|
|
|
331
597
|
});
|
|
332
598
|
} catch (err) {
|
|
333
599
|
// suggest_improvements failure is non-fatal — log but do not abort
|
|
334
|
-
process.stderr.write(`Warning: suggest_improvements.mjs failed: ${err.message}\n`);
|
|
600
|
+
process.stderr.write(`Warning: suggest_improvements.mjs failed: ${/** @type {Error} */ (err).message}\n`);
|
|
335
601
|
process.stderr.write('Sprint is still marked Completed; improvement suggestions may be incomplete.\n');
|
|
336
602
|
}
|
|
337
603
|
|
|
338
|
-
|
|
339
|
-
process.
|
|
340
|
-
|
|
604
|
+
// ── Step 6.5: Run sprint_trends.mjs (stub — full impl deferred to CR-027) ──
|
|
605
|
+
if (process.env.CLEARGATE_SKIP_SPRINT_TRENDS !== '1') {
|
|
606
|
+
process.stdout.write('Step 6.5: running sprint_trends.mjs (stub)...\n');
|
|
607
|
+
try {
|
|
608
|
+
invokeScript('sprint_trends.mjs', [sprintId], {
|
|
609
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
610
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
611
|
+
});
|
|
612
|
+
} catch (err) {
|
|
613
|
+
// Non-fatal — sprint stays Completed; trends are advisory only.
|
|
614
|
+
process.stderr.write(`Step 6.5 warning: sprint_trends.mjs failed: ${/** @type {Error} */ (err).message}\n`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ── Step 6.6: Skill-candidate detection (folds into suggest_improvements.mjs) ──
|
|
619
|
+
if (process.env.CLEARGATE_SKIP_SKILL_CANDIDATES !== '1') {
|
|
620
|
+
process.stdout.write('Step 6.6: scanning for skill candidates...\n');
|
|
621
|
+
try {
|
|
622
|
+
invokeScript('suggest_improvements.mjs', [sprintId, '--skill-candidates'], {
|
|
623
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
624
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
625
|
+
});
|
|
626
|
+
} catch (err) {
|
|
627
|
+
process.stderr.write(`Step 6.6 warning: skill-candidate scan failed: ${/** @type {Error} */ (err).message}\n`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// ── Step 6.7: FLASHCARD cleanup pass (folds into suggest_improvements.mjs) ──
|
|
632
|
+
if (process.env.CLEARGATE_SKIP_FLASHCARD_CLEANUP !== '1') {
|
|
633
|
+
process.stdout.write('Step 6.7: scanning FLASHCARD.md for cleanup candidates...\n');
|
|
634
|
+
try {
|
|
635
|
+
invokeScript('suggest_improvements.mjs', [sprintId, '--flashcard-cleanup'], {
|
|
636
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
637
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
638
|
+
});
|
|
639
|
+
} catch (err) {
|
|
640
|
+
process.stderr.write(`Step 6.7 warning: FLASHCARD cleanup scan failed: ${/** @type {Error} */ (err).message}\n`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ── Step 7: Auto-push per-artifact status updates to MCP ─────────────────
|
|
645
|
+
// Runs after Gate 4 ack succeeds. Non-fatal: sprint stays Completed on failure.
|
|
646
|
+
process.stdout.write('Step 7: pushing per-artifact status updates to MCP...\n');
|
|
647
|
+
try {
|
|
648
|
+
const cliBin = path.join(REPO_ROOT, 'cleargate-cli', 'dist', 'cli.js');
|
|
649
|
+
if (fs.existsSync(cliBin)) {
|
|
650
|
+
// cleargate sync work-items takes ZERO positional args (verified cli.ts:592-598).
|
|
651
|
+
// CR-021 §3.2.3 spec shows a sprint-id arg — that is spec drift; drop it.
|
|
652
|
+
execSync(`node ${JSON.stringify(cliBin)} sync work-items`, {
|
|
653
|
+
stdio: 'inherit',
|
|
654
|
+
env: process.env,
|
|
655
|
+
timeout: 30000,
|
|
656
|
+
});
|
|
657
|
+
process.stdout.write('Step 7 passed: work-item statuses synced.\n');
|
|
658
|
+
} else {
|
|
659
|
+
process.stdout.write('Step 7 skipped: CLI binary not found (non-fatal).\n');
|
|
660
|
+
}
|
|
661
|
+
} catch (err) {
|
|
662
|
+
// Non-fatal — sprint stays Completed; sync can be retried manually
|
|
663
|
+
process.stderr.write(`Step 7 warning: sync work-items failed: ${/** @type {Error} */ (err).message}\n`);
|
|
664
|
+
process.stderr.write('Run `cleargate sync work-items` manually to retry.\n');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// ── Step 8: Verbose post-close handoff list ───────────────────────────────
|
|
668
|
+
// Prints 6 explicit next-step items to stdout (CR-022 §3 M4).
|
|
669
|
+
{
|
|
670
|
+
const sprintNumMatch = /^SPRINT-(\d{2,3})$/.exec(sprintId);
|
|
671
|
+
const nextSprintNum = sprintNumMatch
|
|
672
|
+
? String(parseInt(sprintNumMatch[1], 10) + 1).padStart(sprintNumMatch[1].length, '0')
|
|
673
|
+
: null;
|
|
674
|
+
const nextSprintId = nextSprintNum ? `SPRINT-${nextSprintNum}` : '<next-sprint-id>';
|
|
675
|
+
const reportBasename = path.basename(reportFile);
|
|
676
|
+
const suggestionsPath = path.join(sprintDir, 'improvement-suggestions.md');
|
|
677
|
+
|
|
678
|
+
process.stdout.write(`\n${sprintId} closed. Next steps:\n`);
|
|
679
|
+
process.stdout.write(` 1. Review ${reportBasename}\n`);
|
|
680
|
+
process.stdout.write(
|
|
681
|
+
` 2. Review improvement-suggestions.md (sections: Suggestions / Skill Candidates / FLASHCARD Cleanup)\n`,
|
|
682
|
+
);
|
|
683
|
+
process.stdout.write(
|
|
684
|
+
` 3. Approve or reject Skill Candidates → run /improve or cleargate skill create <name>\n`,
|
|
685
|
+
);
|
|
686
|
+
process.stdout.write(
|
|
687
|
+
` 4. Approve or reject FLASHCARD cleanup entries → run /improve or cleargate flashcard prune\n`,
|
|
688
|
+
);
|
|
689
|
+
process.stdout.write(
|
|
690
|
+
` 5. Push approved status changes to MCP if Step 7 warned (\`cleargate sync work-items\`)\n`,
|
|
691
|
+
);
|
|
692
|
+
process.stdout.write(
|
|
693
|
+
` 6. Initialize next sprint: \`cleargate sprint init ${nextSprintId} --stories <ids>\`\n`,
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
// Surface artifact paths for convenience
|
|
697
|
+
process.stdout.write(`\nArtifacts:\n`);
|
|
698
|
+
process.stdout.write(` report: ${reportFile}\n`);
|
|
699
|
+
process.stdout.write(` improvement-suggestions: ${suggestionsPath}\n`);
|
|
700
|
+
}
|
|
341
701
|
}
|
|
342
702
|
|
|
343
703
|
main();
|