cleargate 0.8.2 → 0.11.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 +210 -0
- package/README.md +22 -1
- package/dist/MANIFEST.json +276 -31
- package/dist/chunk-HZPJ5QX4.js +459 -0
- package/dist/chunk-HZPJ5QX4.js.map +1 -0
- package/dist/{chunk-OM4FAEA7.js → chunk-Q3BTSXCK.js} +69 -3
- package/dist/chunk-Q3BTSXCK.js.map +1 -0
- package/dist/cli.cjs +2888 -598
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2481 -619
- 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/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 +65 -10
- 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 +51 -2
- package/dist/templates/cleargate-planning/.claude/agents/devops.md +249 -0
- package/dist/templates/cleargate-planning/.claude/agents/qa.md +91 -1
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +72 -14
- package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +21 -0
- 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/stamp-and-gate.sh +12 -1
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +334 -96
- package/dist/templates/cleargate-planning/.claude/settings.json +4 -0
- package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +644 -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/mid-sprint-triage-rubric.md +160 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +72 -9
- 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 +471 -29
- package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +219 -0
- 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/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/run_script.sh +173 -87
- package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +71 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +483 -13
- package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +482 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +32 -8
- package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +136 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +27 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +35 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +48 -14
- package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +40 -3
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +53 -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_context.md +8 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +23 -4
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +58 -3
- package/dist/templates/cleargate-planning/CLAUDE.md +30 -10
- package/dist/templates/cleargate-planning/MANIFEST.json +276 -31
- package/dist/{whoami-CX7CXJD5.js → whoami-W4U6DPVG.js} +17 -17
- package/dist/whoami-W4U6DPVG.js.map +1 -0
- package/package.json +20 -6
- package/templates/cleargate-planning/.claude/agents/architect.md +65 -10
- 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 +51 -2
- package/templates/cleargate-planning/.claude/agents/devops.md +249 -0
- package/templates/cleargate-planning/.claude/agents/qa.md +91 -1
- package/templates/cleargate-planning/.claude/agents/reporter.md +72 -14
- package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +21 -0
- 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/stamp-and-gate.sh +12 -1
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +334 -96
- package/templates/cleargate-planning/.claude/settings.json +4 -0
- package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +644 -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/mid-sprint-triage-rubric.md +160 -0
- package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +72 -9
- 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 +471 -29
- package/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +219 -0
- 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/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/run_script.sh +173 -87
- package/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +71 -0
- package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +483 -13
- package/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +482 -0
- package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +32 -8
- package/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +136 -0
- package/templates/cleargate-planning/.cleargate/templates/Bug.md +27 -1
- package/templates/cleargate-planning/.cleargate/templates/CR.md +35 -1
- package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +48 -14
- package/templates/cleargate-planning/.cleargate/templates/epic.md +40 -3
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +53 -0
- package/templates/cleargate-planning/.cleargate/templates/initiative.md +98 -29
- package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +8 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +23 -4
- package/templates/cleargate-planning/.cleargate/templates/story.md +58 -3
- package/templates/cleargate-planning/CLAUDE.md +30 -10
- package/templates/cleargate-planning/MANIFEST.json +276 -31
- 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,60 @@
|
|
|
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 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).
|
|
41
|
+
* CLEARGATE_SKIP_WORKTREE_CHECK=1 — skip Step 2.7 entirely (test environments that cannot
|
|
42
|
+
* run git worktree list from a real git root).
|
|
43
|
+
* CLEARGATE_FORCE_WORKTREE_PATHS=p1,p2 — comma-separated fake worktree paths injected into
|
|
44
|
+
* Step 2.7 instead of running git worktree list.
|
|
45
|
+
* Used to exercise the v2 block / v1 advisory paths
|
|
46
|
+
* without a real .worktrees/STORY-* directory.
|
|
47
|
+
* CLEARGATE_SKIP_MERGE_CHECK=1 — skip Step 2.8 entirely (test environments where git
|
|
48
|
+
* refs are absent or merge state is irrelevant).
|
|
49
|
+
* CLEARGATE_FORCE_MERGE_STATUS=merged|unmerged — inject merge status for Step 2.8 without
|
|
50
|
+
* running git merge-base. Used to exercise
|
|
51
|
+
* the v2 block / v1 advisory paths.
|
|
52
|
+
* CLEARGATE_REPO_ROOT=<path> — override REPO_ROOT for Step 2.8 git commands
|
|
53
|
+
* (used in tests that need a controlled git repo).
|
|
54
|
+
* CLEARGATE_SKIP_SPRINT_TRENDS=1 — skip Step 6.5 entirely (test environments).
|
|
55
|
+
* CLEARGATE_SKIP_SKILL_CANDIDATES=1 — skip Step 6.6 entirely (test environments).
|
|
56
|
+
* CLEARGATE_SKIP_FLASHCARD_CLEANUP=1 — skip Step 6.7 entirely (test environments).
|
|
57
|
+
* CLEARGATE_SPRINT_RUNS_DIR=<path> — override .cleargate/sprint-runs/ root for
|
|
58
|
+
* sibling-sprint counting in sprint_trends.mjs.
|
|
59
|
+
* CLEARGATE_FLASHCARD_PATH=<path> — override .cleargate/FLASHCARD.md path for
|
|
60
|
+
* --flashcard-cleanup scan in suggest_improvements.mjs.
|
|
61
|
+
* CLEARGATE_FLASHCARD_LOOKBACK=<N> — override 3-sprint default lookback for
|
|
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.
|
|
26
66
|
*/
|
|
27
67
|
|
|
28
68
|
import fs from 'node:fs';
|
|
@@ -31,6 +71,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
31
71
|
import { execSync } from 'node:child_process';
|
|
32
72
|
import { TERMINAL_STATES } from './constants.mjs';
|
|
33
73
|
import { validateState } from './validate_state.mjs';
|
|
74
|
+
import { reportFilename } from './lib/report-filename.mjs';
|
|
34
75
|
|
|
35
76
|
/**
|
|
36
77
|
* Migrate a v1 state.json to v2 by injecting lane fields with defaults.
|
|
@@ -56,7 +97,9 @@ function migrateV1ToV2(state) {
|
|
|
56
97
|
}
|
|
57
98
|
|
|
58
99
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
59
|
-
const REPO_ROOT =
|
|
100
|
+
const REPO_ROOT = process.env.CLEARGATE_REPO_ROOT
|
|
101
|
+
? path.resolve(process.env.CLEARGATE_REPO_ROOT)
|
|
102
|
+
: path.resolve(__dirname, '..', '..');
|
|
60
103
|
const SCRIPTS_DIR = __dirname;
|
|
61
104
|
|
|
62
105
|
function usage() {
|
|
@@ -64,12 +107,13 @@ function usage() {
|
|
|
64
107
|
'Usage: node close_sprint.mjs <sprint-id> [--assume-ack | --report-body-stdin]\n' +
|
|
65
108
|
'\n' +
|
|
66
109
|
'Options:\n' +
|
|
67
|
-
' --assume-ack Skip user acknowledgement prompt (
|
|
68
|
-
' --report-body-stdin Read
|
|
110
|
+
' --assume-ack Skip user acknowledgement prompt (automated tests ONLY — conversational orchestrators MUST NOT pass this)\n' +
|
|
111
|
+
' --report-body-stdin Read SPRINT-<#>_REPORT.md body from stdin; implies ack (STORY-014-10)\n'
|
|
69
112
|
);
|
|
70
113
|
process.exit(2);
|
|
71
114
|
}
|
|
72
115
|
|
|
116
|
+
|
|
73
117
|
/**
|
|
74
118
|
* Atomic write using tmp+rename pattern (per M1 update_state.mjs convention).
|
|
75
119
|
* @param {string} filePath
|
|
@@ -113,7 +157,7 @@ function invokeScript(scriptName, scriptArgs, env) {
|
|
|
113
157
|
});
|
|
114
158
|
}
|
|
115
159
|
|
|
116
|
-
function main() {
|
|
160
|
+
async function main() {
|
|
117
161
|
const args = process.argv.slice(2);
|
|
118
162
|
|
|
119
163
|
if (args.length < 1) usage();
|
|
@@ -202,11 +246,11 @@ function main() {
|
|
|
202
246
|
process.exit(1);
|
|
203
247
|
}
|
|
204
248
|
|
|
205
|
-
// Read REPORT.md
|
|
206
|
-
const reportFile2 =
|
|
249
|
+
// Read SPRINT-<#>_REPORT.md (with legacy REPORT.md fallback for pre-CR-021 sprints)
|
|
250
|
+
const reportFile2 = reportFilename(sprintDir, sprintId, { forRead: true });
|
|
207
251
|
if (!fs.existsSync(reportFile2)) {
|
|
208
252
|
process.stderr.write(
|
|
209
|
-
`close_sprint: v2.1 validation requires
|
|
253
|
+
`close_sprint: v2.1 validation requires ${path.basename(reportFile2)} at ${reportFile2}\n` +
|
|
210
254
|
' Run the Reporter agent first, then re-run close_sprint.mjs.\n'
|
|
211
255
|
);
|
|
212
256
|
process.exit(1);
|
|
@@ -247,6 +291,272 @@ function main() {
|
|
|
247
291
|
process.stdout.write('Step 2.5 passed: v2.1 validation — all required §3 metrics and §5 sections present.\n');
|
|
248
292
|
}
|
|
249
293
|
|
|
294
|
+
// ── Step 2.6: Lifecycle Reconciliation (CR-017) ──────────────────────────
|
|
295
|
+
// Block close if any artifact referenced in this sprint's commits is still
|
|
296
|
+
// non-terminal in pending-sync (excluding carry_over: true).
|
|
297
|
+
// Invokes `cleargate sprint reconcile-lifecycle <sprint-id>` CLI wrapper.
|
|
298
|
+
// Fail-open if CLI binary is unavailable (non-blocking for test environments).
|
|
299
|
+
// Test seam: CLEARGATE_SKIP_LIFECYCLE_CHECK=1 skips this step entirely (non-fatal).
|
|
300
|
+
process.stdout.write('Step 2.6: running lifecycle reconciliation...\n');
|
|
301
|
+
if (process.env.CLEARGATE_SKIP_LIFECYCLE_CHECK === '1') {
|
|
302
|
+
process.stdout.write('Step 2.6 skipped: CLEARGATE_SKIP_LIFECYCLE_CHECK=1 set (test seam).\n');
|
|
303
|
+
} else {
|
|
304
|
+
try {
|
|
305
|
+
// Resolve CLI binary: prefer local dist/
|
|
306
|
+
const cliBin = path.join(REPO_ROOT, 'cleargate-cli', 'dist', 'cli.js');
|
|
307
|
+
|
|
308
|
+
if (fs.existsSync(cliBin)) {
|
|
309
|
+
// Read sprint start_date from frontmatter for the --since arg
|
|
310
|
+
let sinceArg = '';
|
|
311
|
+
try {
|
|
312
|
+
const pendingDir = path.join(REPO_ROOT, '.cleargate', 'delivery', 'pending-sync');
|
|
313
|
+
if (fs.existsSync(pendingDir)) {
|
|
314
|
+
const entries = fs.readdirSync(pendingDir);
|
|
315
|
+
const sprintFile = entries.find(
|
|
316
|
+
(e) => (e.startsWith(`${sprintId}_`) || e === `${sprintId}.md`) && e.endsWith('.md')
|
|
317
|
+
);
|
|
318
|
+
if (sprintFile) {
|
|
319
|
+
const raw = fs.readFileSync(path.join(pendingDir, sprintFile), 'utf8');
|
|
320
|
+
const startDateMatch = /^start_date:\s*(.+)$/m.exec(raw);
|
|
321
|
+
if (startDateMatch && startDateMatch[1]) {
|
|
322
|
+
sinceArg = `--since ${startDateMatch[1].trim()}`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch { /* ignore */ }
|
|
327
|
+
|
|
328
|
+
const reconcileArgs = [
|
|
329
|
+
'node', JSON.stringify(cliBin), 'sprint', 'reconcile-lifecycle', JSON.stringify(sprintId),
|
|
330
|
+
];
|
|
331
|
+
if (sinceArg) reconcileArgs.push(sinceArg);
|
|
332
|
+
const reconcileCmd = reconcileArgs.join(' ');
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
execSync(reconcileCmd, { stdio: 'inherit', env: process.env });
|
|
336
|
+
process.stdout.write('Step 2.6 passed: lifecycle reconciliation clean.\n');
|
|
337
|
+
} catch (_reconcileErr) {
|
|
338
|
+
// Exit code 1 from reconcile-lifecycle means drift found
|
|
339
|
+
process.stderr.write(
|
|
340
|
+
'close_sprint: Step 2.6 FAILED — lifecycle drift blocks sprint close.\n' +
|
|
341
|
+
' Remediate the listed artifacts and re-run close_sprint.mjs.\n' +
|
|
342
|
+
' To carry over an artifact: set carry_over: true in its frontmatter.\n'
|
|
343
|
+
);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
process.stdout.write('Step 2.6 skipped: CLI binary not found at cleargate-cli/dist/cli.js (non-fatal).\n');
|
|
348
|
+
}
|
|
349
|
+
} catch (step26Err) {
|
|
350
|
+
// Unexpected error — fail-open (log but do not block)
|
|
351
|
+
process.stderr.write(`Step 2.6 warning: lifecycle reconciliation unavailable: ${step26Err.message}\n`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
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
|
+
|
|
410
|
+
// ── Step 2.7: Worktree-Closed Check (CR-022 M1) ──────────────────────────
|
|
411
|
+
// Block close if any .worktrees/STORY-* path is present.
|
|
412
|
+
// v2 enforcing (exit 1); v1 advisory (warn + continue).
|
|
413
|
+
// Skip if git worktree list is unavailable (non-fatal — tests run against tmpdirs).
|
|
414
|
+
// Test seams: CLEARGATE_SKIP_WORKTREE_CHECK=1 bypasses entirely;
|
|
415
|
+
// CLEARGATE_FORCE_WORKTREE_PATHS=p1,p2 injects fake paths (no git call).
|
|
416
|
+
process.stdout.write('Step 2.7: checking for leftover worktrees...\n');
|
|
417
|
+
{
|
|
418
|
+
if (process.env.CLEARGATE_SKIP_WORKTREE_CHECK === '1') {
|
|
419
|
+
process.stdout.write('Step 2.7 skipped: CLEARGATE_SKIP_WORKTREE_CHECK=1 set (test seam).\n');
|
|
420
|
+
} else {
|
|
421
|
+
let leftoverWorktrees = [];
|
|
422
|
+
let worktreeListAvailable = true;
|
|
423
|
+
|
|
424
|
+
if (process.env.CLEARGATE_FORCE_WORKTREE_PATHS) {
|
|
425
|
+
// Test seam: inject fake worktree paths without running git
|
|
426
|
+
leftoverWorktrees = process.env.CLEARGATE_FORCE_WORKTREE_PATHS
|
|
427
|
+
.split(',')
|
|
428
|
+
.map((p) => p.trim())
|
|
429
|
+
.filter(Boolean);
|
|
430
|
+
} else {
|
|
431
|
+
try {
|
|
432
|
+
const output = execSync('git worktree list --porcelain', {
|
|
433
|
+
cwd: REPO_ROOT,
|
|
434
|
+
encoding: 'utf8',
|
|
435
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
436
|
+
});
|
|
437
|
+
for (const line of output.split('\n')) {
|
|
438
|
+
const trimmed = line.trim();
|
|
439
|
+
if (!trimmed.startsWith('worktree ')) continue;
|
|
440
|
+
const wtPath = trimmed.slice('worktree '.length);
|
|
441
|
+
if (/[/\\]\.worktrees[/\\]STORY-/.test(wtPath)) {
|
|
442
|
+
const m = /(\.(worktrees)[/\\]STORY-.+)$/.exec(wtPath);
|
|
443
|
+
leftoverWorktrees.push(m ? m[1] : wtPath);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
worktreeListAvailable = false;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Step 2.7 enforcing mode: v2 execution_mode (not just schema_version, since
|
|
452
|
+
// migration bumps schema_version to 2 for all sprints before isV2 is evaluated).
|
|
453
|
+
// Using execution_mode preserves v1 advisory behaviour for sprints initialised
|
|
454
|
+
// with execution_mode: "v1" even after their schema is migrated.
|
|
455
|
+
const isEnforcingV2 = isV2 && state.execution_mode === 'v2';
|
|
456
|
+
|
|
457
|
+
if (!worktreeListAvailable) {
|
|
458
|
+
process.stdout.write('Step 2.7 skipped: git worktree list unavailable (non-fatal).\n');
|
|
459
|
+
} else if (leftoverWorktrees.length === 0) {
|
|
460
|
+
process.stdout.write('Step 2.7 passed: no leftover worktrees.\n');
|
|
461
|
+
} else if (isEnforcingV2) {
|
|
462
|
+
// v2 enforcing — block close
|
|
463
|
+
process.stderr.write(
|
|
464
|
+
`close_sprint: Step 2.7 failed: leftover worktree at ${leftoverWorktrees[0]}\n` +
|
|
465
|
+
` ${leftoverWorktrees.length === 1 ? '' : `(plus ${leftoverWorktrees.length - 1} more)\n `}` +
|
|
466
|
+
`Run \`git worktree remove ${leftoverWorktrees[0]}\` if abandoned, or merge the work in progress.\n` +
|
|
467
|
+
` All worktrees must be closed before sprint close.\n`
|
|
468
|
+
);
|
|
469
|
+
process.exit(1);
|
|
470
|
+
} else {
|
|
471
|
+
// v1 advisory — warn + continue
|
|
472
|
+
process.stderr.write(
|
|
473
|
+
`Step 2.7 warning: leftover worktree at ${leftoverWorktrees[0]} (advisory in v1).\n`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ── Step 2.8: Sprint branch merged to main (verify-only, NO auto-merge) ──────
|
|
480
|
+
// CR-022 §1: verify-only — script asserts merge ancestry, does NOT run the merge.
|
|
481
|
+
// On miss: list unmerged commits + exit 1 (v2 enforcing); warn + continue (v1 advisory).
|
|
482
|
+
// Skip when sprintId has no numeric portion (e.g. SPRINT-TEST fixture).
|
|
483
|
+
// Test seams: CLEARGATE_SKIP_MERGE_CHECK=1 bypasses entirely;
|
|
484
|
+
// CLEARGATE_FORCE_MERGE_STATUS=merged|unmerged injects status without git call.
|
|
485
|
+
{
|
|
486
|
+
if (process.env.CLEARGATE_SKIP_MERGE_CHECK === '1') {
|
|
487
|
+
process.stdout.write('Step 2.8 skipped: CLEARGATE_SKIP_MERGE_CHECK=1 set (test seam).\n');
|
|
488
|
+
} else {
|
|
489
|
+
const sprintNumMatch = /^SPRINT-(\d{2,3})$/.exec(sprintId);
|
|
490
|
+
if (!sprintNumMatch) {
|
|
491
|
+
process.stdout.write(`Step 2.8 skipped: sprint-id "${sprintId}" has no numeric portion.\n`);
|
|
492
|
+
} else {
|
|
493
|
+
const sprintBranch = `refs/heads/sprint/S-${sprintNumMatch[1]}`;
|
|
494
|
+
const mainBranch = 'refs/heads/main';
|
|
495
|
+
process.stdout.write(`Step 2.8: verifying ${sprintBranch} merged to ${mainBranch}...\n`);
|
|
496
|
+
|
|
497
|
+
const isEnforcingV2 = isV2 && state.execution_mode === 'v2';
|
|
498
|
+
|
|
499
|
+
const forcedStatus = process.env.CLEARGATE_FORCE_MERGE_STATUS;
|
|
500
|
+
let isMerged = false;
|
|
501
|
+
let mergeCheckAvailable = true;
|
|
502
|
+
|
|
503
|
+
if (forcedStatus === 'merged') {
|
|
504
|
+
isMerged = true;
|
|
505
|
+
} else if (forcedStatus === 'unmerged') {
|
|
506
|
+
isMerged = false;
|
|
507
|
+
} else {
|
|
508
|
+
try {
|
|
509
|
+
execSync(
|
|
510
|
+
`git merge-base --is-ancestor ${sprintBranch} ${mainBranch}`,
|
|
511
|
+
{ stdio: 'pipe', cwd: REPO_ROOT, env: process.env }
|
|
512
|
+
);
|
|
513
|
+
isMerged = true;
|
|
514
|
+
} catch (mergeErr) {
|
|
515
|
+
const exitStatus = /** @type {any} */ (mergeErr).status;
|
|
516
|
+
if (exitStatus === 1) {
|
|
517
|
+
isMerged = false;
|
|
518
|
+
} else {
|
|
519
|
+
// exit 128: refs missing or other git failure — fail-open with warning
|
|
520
|
+
mergeCheckAvailable = false;
|
|
521
|
+
process.stderr.write(
|
|
522
|
+
`Step 2.8 warning: git merge-base check unavailable (${/** @type {Error} */ (mergeErr).message}). ` +
|
|
523
|
+
`Skipping merge verification.\n`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (!mergeCheckAvailable) {
|
|
530
|
+
// fail-open: refs missing or git unavailable — continue to Step 3
|
|
531
|
+
} else if (isMerged) {
|
|
532
|
+
process.stdout.write(`Step 2.8 passed: ${sprintBranch} is merged to ${mainBranch}.\n`);
|
|
533
|
+
} else if (isEnforcingV2) {
|
|
534
|
+
// v2 enforcing — block close
|
|
535
|
+
let unmergedLog = '';
|
|
536
|
+
if (!forcedStatus) {
|
|
537
|
+
try {
|
|
538
|
+
unmergedLog = execSync(
|
|
539
|
+
`git log ${mainBranch}..${sprintBranch} --oneline`,
|
|
540
|
+
{ encoding: 'utf8', cwd: REPO_ROOT, env: process.env }
|
|
541
|
+
);
|
|
542
|
+
} catch { /* unmerged-log fetch failed — proceed without */ }
|
|
543
|
+
}
|
|
544
|
+
process.stderr.write(
|
|
545
|
+
`Step 2.8 failed: sprint/S-${sprintNumMatch[1]} not merged to main.\n` +
|
|
546
|
+
(unmergedLog ? ` Unmerged commits:\n${unmergedLog}` : '') +
|
|
547
|
+
` Resolve: merge sprint/S-${sprintNumMatch[1]} → main, then re-run close_sprint.mjs.\n`
|
|
548
|
+
);
|
|
549
|
+
process.exit(1);
|
|
550
|
+
} else {
|
|
551
|
+
// v1 advisory — warn + continue
|
|
552
|
+
process.stderr.write(
|
|
553
|
+
`Step 2.8 warning: sprint/S-${sprintNumMatch[1]} not merged to main (advisory in v1).\n`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
250
560
|
// ── Step 3: Invoke prefill_report.mjs ─────────────────────────────────────
|
|
251
561
|
process.stdout.write('Step 3: running prefill_report.mjs...\n');
|
|
252
562
|
try {
|
|
@@ -259,24 +569,60 @@ function main() {
|
|
|
259
569
|
process.exit(1);
|
|
260
570
|
}
|
|
261
571
|
|
|
572
|
+
// ── Step 3.5: Build curated Reporter context bundle ───────────────────────
|
|
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
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
262
609
|
// ── Step 4: Orchestrator spawns Reporter separately ───────────────────────
|
|
263
610
|
// This script only validates preconditions; it does NOT fork the Reporter agent.
|
|
611
|
+
const reportFile = reportFilename(sprintDir, sprintId);
|
|
612
|
+
const reportBasename = path.basename(reportFile);
|
|
264
613
|
process.stdout.write(
|
|
265
614
|
'Step 4: preconditions satisfied — orchestrator should now spawn the Reporter agent.\n' +
|
|
266
|
-
|
|
267
|
-
` Expected output: ${
|
|
615
|
+
` The Reporter writes ${reportBasename} using the sprint_report.md template.\n` +
|
|
616
|
+
` Expected output: ${reportFile}\n`
|
|
268
617
|
);
|
|
269
618
|
|
|
270
|
-
// Check if REPORT.md already exists (e.g., --assume-ack path in tests)
|
|
271
|
-
const reportFile = path.join(sprintDir, 'REPORT.md');
|
|
272
|
-
|
|
273
619
|
// ── Step 4.5 (STORY-014-10): --report-body-stdin fallback ────────────────
|
|
274
620
|
// Orchestrator pipes the Reporter's markdown body here when the Reporter's
|
|
275
|
-
// Write tool is blocked. Refuses empty stdin + pre-existing
|
|
621
|
+
// Write tool is blocked. Refuses empty stdin + pre-existing report file.
|
|
276
622
|
if (reportBodyStdin) {
|
|
277
623
|
if (fs.existsSync(reportFile)) {
|
|
278
624
|
process.stderr.write(
|
|
279
|
-
`Error:
|
|
625
|
+
`Error: ${reportBasename} already exists at ${reportFile}\n` +
|
|
280
626
|
'Delete it or skip --report-body-stdin mode to use the primary Reporter-write path.\n'
|
|
281
627
|
);
|
|
282
628
|
process.exit(1);
|
|
@@ -285,7 +631,7 @@ function main() {
|
|
|
285
631
|
try {
|
|
286
632
|
body = fs.readFileSync(0, 'utf8');
|
|
287
633
|
} catch (err) {
|
|
288
|
-
process.stderr.write(`Error: failed to read stdin: ${err.message}\n`);
|
|
634
|
+
process.stderr.write(`Error: failed to read stdin: ${/** @type {Error} */ (err).message}\n`);
|
|
289
635
|
process.exit(1);
|
|
290
636
|
}
|
|
291
637
|
if (!body || body.trim().length === 0) {
|
|
@@ -294,20 +640,22 @@ function main() {
|
|
|
294
640
|
}
|
|
295
641
|
atomicWriteString(reportFile, body);
|
|
296
642
|
process.stdout.write(
|
|
297
|
-
`Step 4.5 (stdin mode):
|
|
643
|
+
`Step 4.5 (stdin mode): ${reportBasename} written (${body.length} bytes) at ${reportFile}\n`
|
|
298
644
|
);
|
|
299
|
-
// Fall through to Step 5 + 6 unconditionally — stdin mode implies ack.
|
|
645
|
+
// Fall through to Step 5 + 6 + 7 unconditionally — stdin mode implies ack.
|
|
300
646
|
} else if (!assumeAck) {
|
|
301
|
-
|
|
647
|
+
// Apply read-fallback for legacy sprints (e.g. SPRINT-15 with plain REPORT.md)
|
|
648
|
+
const reportFileForCheck = reportFilename(sprintDir, sprintId, { forRead: true });
|
|
649
|
+
if (!fs.existsSync(reportFileForCheck)) {
|
|
302
650
|
process.stdout.write(
|
|
303
|
-
|
|
651
|
+
`\nWaiting for Reporter to produce ${reportBasename}...\n` +
|
|
304
652
|
'After Reporter succeeds, re-run with --assume-ack to complete the close.\n'
|
|
305
653
|
);
|
|
306
654
|
process.exit(0);
|
|
307
655
|
}
|
|
308
|
-
// In non-assume-ack mode with existing
|
|
656
|
+
// In non-assume-ack mode with existing report, prompt user
|
|
309
657
|
process.stdout.write(
|
|
310
|
-
`\
|
|
658
|
+
`\n${reportBasename} found at ${reportFileForCheck}\n` +
|
|
311
659
|
'Review the report, then confirm close by re-running with --assume-ack\n'
|
|
312
660
|
);
|
|
313
661
|
process.exit(0);
|
|
@@ -331,13 +679,107 @@ function main() {
|
|
|
331
679
|
});
|
|
332
680
|
} catch (err) {
|
|
333
681
|
// suggest_improvements failure is non-fatal — log but do not abort
|
|
334
|
-
process.stderr.write(`Warning: suggest_improvements.mjs failed: ${err.message}\n`);
|
|
682
|
+
process.stderr.write(`Warning: suggest_improvements.mjs failed: ${/** @type {Error} */ (err).message}\n`);
|
|
335
683
|
process.stderr.write('Sprint is still marked Completed; improvement suggestions may be incomplete.\n');
|
|
336
684
|
}
|
|
337
685
|
|
|
338
|
-
|
|
339
|
-
process.
|
|
340
|
-
|
|
686
|
+
// ── Step 6.5: Run sprint_trends.mjs (stub — full impl deferred to CR-027) ──
|
|
687
|
+
if (process.env.CLEARGATE_SKIP_SPRINT_TRENDS !== '1') {
|
|
688
|
+
process.stdout.write('Step 6.5: running sprint_trends.mjs (stub)...\n');
|
|
689
|
+
try {
|
|
690
|
+
invokeScript('sprint_trends.mjs', [sprintId], {
|
|
691
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
692
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
693
|
+
});
|
|
694
|
+
} catch (err) {
|
|
695
|
+
// Non-fatal — sprint stays Completed; trends are advisory only.
|
|
696
|
+
process.stderr.write(`Step 6.5 warning: sprint_trends.mjs failed: ${/** @type {Error} */ (err).message}\n`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ── Step 6.6: Skill-candidate detection (folds into suggest_improvements.mjs) ──
|
|
701
|
+
if (process.env.CLEARGATE_SKIP_SKILL_CANDIDATES !== '1') {
|
|
702
|
+
process.stdout.write('Step 6.6: scanning for skill candidates...\n');
|
|
703
|
+
try {
|
|
704
|
+
invokeScript('suggest_improvements.mjs', [sprintId, '--skill-candidates'], {
|
|
705
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
706
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
707
|
+
});
|
|
708
|
+
} catch (err) {
|
|
709
|
+
process.stderr.write(`Step 6.6 warning: skill-candidate scan failed: ${/** @type {Error} */ (err).message}\n`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// ── Step 6.7: FLASHCARD cleanup pass (folds into suggest_improvements.mjs) ──
|
|
714
|
+
if (process.env.CLEARGATE_SKIP_FLASHCARD_CLEANUP !== '1') {
|
|
715
|
+
process.stdout.write('Step 6.7: scanning FLASHCARD.md for cleanup candidates...\n');
|
|
716
|
+
try {
|
|
717
|
+
invokeScript('suggest_improvements.mjs', [sprintId, '--flashcard-cleanup'], {
|
|
718
|
+
CLEARGATE_STATE_FILE: stateFile,
|
|
719
|
+
CLEARGATE_SPRINT_DIR: sprintDir,
|
|
720
|
+
});
|
|
721
|
+
} catch (err) {
|
|
722
|
+
process.stderr.write(`Step 6.7 warning: FLASHCARD cleanup scan failed: ${/** @type {Error} */ (err).message}\n`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ── Step 7: Auto-push per-artifact status updates to MCP ─────────────────
|
|
727
|
+
// Runs after Gate 4 ack succeeds. Non-fatal: sprint stays Completed on failure.
|
|
728
|
+
process.stdout.write('Step 7: pushing per-artifact status updates to MCP...\n');
|
|
729
|
+
try {
|
|
730
|
+
const cliBin = path.join(REPO_ROOT, 'cleargate-cli', 'dist', 'cli.js');
|
|
731
|
+
if (fs.existsSync(cliBin)) {
|
|
732
|
+
// cleargate sync work-items takes ZERO positional args (verified cli.ts:592-598).
|
|
733
|
+
// CR-021 §3.2.3 spec shows a sprint-id arg — that is spec drift; drop it.
|
|
734
|
+
execSync(`node ${JSON.stringify(cliBin)} sync work-items`, {
|
|
735
|
+
stdio: 'inherit',
|
|
736
|
+
env: process.env,
|
|
737
|
+
timeout: 30000,
|
|
738
|
+
});
|
|
739
|
+
process.stdout.write('Step 7 passed: work-item statuses synced.\n');
|
|
740
|
+
} else {
|
|
741
|
+
process.stdout.write('Step 7 skipped: CLI binary not found (non-fatal).\n');
|
|
742
|
+
}
|
|
743
|
+
} catch (err) {
|
|
744
|
+
// Non-fatal — sprint stays Completed; sync can be retried manually
|
|
745
|
+
process.stderr.write(`Step 7 warning: sync work-items failed: ${/** @type {Error} */ (err).message}\n`);
|
|
746
|
+
process.stderr.write('Run `cleargate sync work-items` manually to retry.\n');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ── Step 8: Verbose post-close handoff list ───────────────────────────────
|
|
750
|
+
// Prints 6 explicit next-step items to stdout (CR-022 §3 M4).
|
|
751
|
+
{
|
|
752
|
+
const sprintNumMatch = /^SPRINT-(\d{2,3})$/.exec(sprintId);
|
|
753
|
+
const nextSprintNum = sprintNumMatch
|
|
754
|
+
? String(parseInt(sprintNumMatch[1], 10) + 1).padStart(sprintNumMatch[1].length, '0')
|
|
755
|
+
: null;
|
|
756
|
+
const nextSprintId = nextSprintNum ? `SPRINT-${nextSprintNum}` : '<next-sprint-id>';
|
|
757
|
+
const reportBasename = path.basename(reportFile);
|
|
758
|
+
const suggestionsPath = path.join(sprintDir, 'improvement-suggestions.md');
|
|
759
|
+
|
|
760
|
+
process.stdout.write(`\n${sprintId} closed. Next steps:\n`);
|
|
761
|
+
process.stdout.write(` 1. Review ${reportBasename}\n`);
|
|
762
|
+
process.stdout.write(
|
|
763
|
+
` 2. Review improvement-suggestions.md (sections: Suggestions / Skill Candidates / FLASHCARD Cleanup)\n`,
|
|
764
|
+
);
|
|
765
|
+
process.stdout.write(
|
|
766
|
+
` 3. Approve or reject Skill Candidates → run /improve or cleargate skill create <name>\n`,
|
|
767
|
+
);
|
|
768
|
+
process.stdout.write(
|
|
769
|
+
` 4. Approve or reject FLASHCARD cleanup entries → run /improve or cleargate flashcard prune\n`,
|
|
770
|
+
);
|
|
771
|
+
process.stdout.write(
|
|
772
|
+
` 5. Push approved status changes to MCP if Step 7 warned (\`cleargate sync work-items\`)\n`,
|
|
773
|
+
);
|
|
774
|
+
process.stdout.write(
|
|
775
|
+
` 6. Initialize next sprint: \`cleargate sprint init ${nextSprintId} --stories <ids>\`\n`,
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
// Surface artifact paths for convenience
|
|
779
|
+
process.stdout.write(`\nArtifacts:\n`);
|
|
780
|
+
process.stdout.write(` report: ${reportFile}\n`);
|
|
781
|
+
process.stdout.write(` improvement-suggestions: ${suggestionsPath}\n`);
|
|
782
|
+
}
|
|
341
783
|
}
|
|
342
784
|
|
|
343
|
-
main();
|
|
785
|
+
await main();
|