cleargate 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/MANIFEST.json +58 -16
  3. package/dist/admin-api/index.cjs +88 -1
  4. package/dist/admin-api/index.cjs.map +1 -1
  5. package/dist/admin-api/index.d.cts +105 -1
  6. package/dist/admin-api/index.d.ts +105 -1
  7. package/dist/admin-api/index.js +77 -1
  8. package/dist/admin-api/index.js.map +1 -1
  9. package/dist/bootstrap-root-FGWDICDT.js +130 -0
  10. package/dist/bootstrap-root-FGWDICDT.js.map +1 -0
  11. package/dist/chunk-OM4FAEA7.js +184 -0
  12. package/dist/chunk-OM4FAEA7.js.map +1 -0
  13. package/dist/cli.cjs +8530 -3957
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +5282 -1220
  16. package/dist/cli.js.map +1 -1
  17. package/dist/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  18. package/dist/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  19. package/dist/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  20. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  21. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  22. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  23. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  24. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  25. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  26. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  27. package/dist/templates/cleargate-planning/.claude/settings.json +11 -0
  28. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  29. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  30. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  31. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  32. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  33. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  34. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  35. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  36. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  37. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  38. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  39. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  40. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  41. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  42. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  43. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  44. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  45. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  46. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  47. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  48. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  49. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  50. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  51. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  52. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  53. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  54. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  55. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  56. package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  57. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  58. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  59. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  60. package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
  61. package/dist/templates/cleargate-planning/MANIFEST.json +58 -16
  62. package/dist/whoami-CX7CXJD5.js +76 -0
  63. package/dist/whoami-CX7CXJD5.js.map +1 -0
  64. package/package.json +6 -2
  65. package/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  66. package/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  67. package/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  68. package/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  69. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  70. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  71. package/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  72. package/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  73. package/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  74. package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  75. package/templates/cleargate-planning/.claude/settings.json +11 -0
  76. package/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  77. package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  78. package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  79. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  80. package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  81. package/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  82. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  83. package/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  84. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  85. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  86. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  87. package/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  88. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  89. package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  90. package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  91. package/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  92. package/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  93. package/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  94. package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  95. package/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  96. package/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  97. package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  98. package/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  99. package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  100. package/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  101. package/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  102. package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  103. package/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  104. package/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  105. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  106. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  107. package/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  108. package/templates/cleargate-planning/CLAUDE.md +2 -0
  109. package/templates/cleargate-planning/MANIFEST.json +58 -16
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * update_state.mjs — Atomic state/counter update for a story in state.json
4
+ *
5
+ * Usage:
6
+ * node update_state.mjs <STORY-ID> <new-state> — transition to a new state
7
+ * node update_state.mjs <STORY-ID> --qa-bounce — increment qa_bounces counter
8
+ * node update_state.mjs <STORY-ID> --arch-bounce — increment arch_bounces counter
9
+ *
10
+ * Atomic write: write to .tmp.<pid> file, then rename to final path.
11
+ * Idempotent: if new state equals current (for state transitions) and
12
+ * no counter change, exit 0 without rewriting the file.
13
+ *
14
+ * Auto-escalation: when qa_bounces or arch_bounces reaches BOUNCE_CAP (3),
15
+ * state is automatically set to "Escalated".
16
+ */
17
+
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { SCHEMA_VERSION, VALID_STATES, TERMINAL_STATES, BOUNCE_CAP } from './constants.mjs';
22
+ import { validateState } from './validate_state.mjs';
23
+
24
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
26
+
27
+ function usage() {
28
+ process.stderr.write(
29
+ 'Usage:\n' +
30
+ ' node update_state.mjs <STORY-ID> <new-state>\n' +
31
+ ' node update_state.mjs <STORY-ID> --qa-bounce\n' +
32
+ ' node update_state.mjs <STORY-ID> --arch-bounce\n'
33
+ );
34
+ process.exit(2);
35
+ }
36
+
37
+ function resolveStateFile() {
38
+ const envFile = process.env.CLEARGATE_STATE_FILE;
39
+ if (envFile) return path.resolve(envFile);
40
+ throw new Error(
41
+ 'CLEARGATE_STATE_FILE env var not set; cannot resolve state.json'
42
+ );
43
+ }
44
+
45
+ function atomicWrite(stateFile, state) {
46
+ const tmpFile = `${stateFile}.tmp.${process.pid}`;
47
+ fs.writeFileSync(tmpFile, JSON.stringify(state, null, 2) + '\n', 'utf8');
48
+ fs.renameSync(tmpFile, stateFile);
49
+ }
50
+
51
+ function main() {
52
+ const args = process.argv.slice(2);
53
+
54
+ if (args.length < 2) usage();
55
+
56
+ const storyId = args[0];
57
+ const action = args[1];
58
+
59
+ const stateFile = resolveStateFile();
60
+
61
+ if (!fs.existsSync(stateFile)) {
62
+ process.stderr.write(`Error: state.json not found at ${stateFile}\n`);
63
+ process.exit(1);
64
+ }
65
+
66
+ let state;
67
+ try {
68
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
69
+ } catch (err) {
70
+ process.stderr.write(`Error: failed to parse state.json: ${err.message}\n`);
71
+ process.exit(1);
72
+ }
73
+
74
+ // Validate existing state before modifications
75
+ const { valid, errors } = validateState(state);
76
+ if (!valid) {
77
+ process.stderr.write(`Error: state.json is invalid:\n`);
78
+ for (const e of errors) process.stderr.write(` - ${e}\n`);
79
+ process.exit(1);
80
+ }
81
+
82
+ if (!state.stories[storyId]) {
83
+ process.stderr.write(`Error: story ${storyId} not found in state.json\n`);
84
+ process.exit(1);
85
+ }
86
+
87
+ const story = state.stories[storyId];
88
+
89
+ if (action === '--qa-bounce') {
90
+ if (story.state === 'Escalated') {
91
+ process.stderr.write(`Error: story ${storyId} is already Escalated\n`);
92
+ process.exit(1);
93
+ }
94
+ story.qa_bounces += 1;
95
+ if (story.qa_bounces >= BOUNCE_CAP) {
96
+ story.state = 'Escalated';
97
+ }
98
+ story.updated_at = new Date().toISOString();
99
+ state.last_action = `qa-bounce ${storyId}: qa_bounces=${story.qa_bounces}`;
100
+ state.updated_at = story.updated_at;
101
+ atomicWrite(stateFile, state);
102
+ process.stdout.write(
103
+ `Updated ${storyId}: qa_bounces=${story.qa_bounces}, state=${story.state}\n`
104
+ );
105
+
106
+ } else if (action === '--arch-bounce') {
107
+ if (story.state === 'Escalated') {
108
+ process.stderr.write(`Error: story ${storyId} is already Escalated\n`);
109
+ process.exit(1);
110
+ }
111
+ story.arch_bounces += 1;
112
+ if (story.arch_bounces >= BOUNCE_CAP) {
113
+ story.state = 'Escalated';
114
+ }
115
+ story.updated_at = new Date().toISOString();
116
+ state.last_action = `arch-bounce ${storyId}: arch_bounces=${story.arch_bounces}`;
117
+ state.updated_at = story.updated_at;
118
+ atomicWrite(stateFile, state);
119
+ process.stdout.write(
120
+ `Updated ${storyId}: arch_bounces=${story.arch_bounces}, state=${story.state}\n`
121
+ );
122
+
123
+ } else {
124
+ // State transition
125
+ const newState = action;
126
+
127
+ if (!VALID_STATES.includes(newState)) {
128
+ process.stderr.write(
129
+ `Error: invalid state "${newState}"; valid states: ${VALID_STATES.join(', ')}\n`
130
+ );
131
+ process.exit(1);
132
+ }
133
+
134
+ // Idempotency: if state is already the target, no-op
135
+ if (story.state === newState) {
136
+ process.stdout.write(`No-op: ${storyId} is already in state "${newState}"\n`);
137
+ process.exit(0);
138
+ }
139
+
140
+ // Reset worktree to null on Done
141
+ if (newState === 'Done') {
142
+ story.worktree = null;
143
+ }
144
+
145
+ story.state = newState;
146
+ story.updated_at = new Date().toISOString();
147
+ state.last_action = `transition ${storyId} → ${newState}`;
148
+ state.updated_at = story.updated_at;
149
+ atomicWrite(stateFile, state);
150
+ process.stdout.write(`Updated ${storyId}: state="${newState}"\n`);
151
+ }
152
+ }
153
+
154
+ main();
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * validate_bounce_readiness.mjs — Pre-bounce gate check for a story
4
+ *
5
+ * Usage: node validate_bounce_readiness.mjs <STORY-ID>
6
+ *
7
+ * Checks:
8
+ * (a) state.json exists and is valid
9
+ * (b) story is present in state.json
10
+ * (c) story state is "Ready to Bounce"
11
+ * (d) git working tree is clean (git status --porcelain returns empty)
12
+ *
13
+ * Exits non-zero on any failure, with detail on stderr.
14
+ * Exits 0 if all checks pass.
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { execSync } from 'node:child_process';
21
+ import { validateState } from './validate_state.mjs';
22
+
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
25
+
26
+ function usage() {
27
+ process.stderr.write('Usage: node validate_bounce_readiness.mjs <STORY-ID>\n');
28
+ process.exit(2);
29
+ }
30
+
31
+ function main() {
32
+ const args = process.argv.slice(2);
33
+ if (args.length < 1 || args[0].startsWith('--')) usage();
34
+
35
+ const storyId = args[0];
36
+
37
+ const stateFile = process.env.CLEARGATE_STATE_FILE
38
+ ? path.resolve(process.env.CLEARGATE_STATE_FILE)
39
+ : null;
40
+
41
+ if (!stateFile) {
42
+ process.stderr.write(
43
+ 'Error: CLEARGATE_STATE_FILE env var not set; cannot resolve state.json\n'
44
+ );
45
+ process.exit(1);
46
+ }
47
+
48
+ // (a) state.json exists
49
+ if (!fs.existsSync(stateFile)) {
50
+ process.stderr.write(`Error: state.json not found at ${stateFile}\n`);
51
+ process.exit(1);
52
+ }
53
+
54
+ let state;
55
+ try {
56
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
57
+ } catch (err) {
58
+ process.stderr.write(`Error: failed to parse state.json: ${err.message}\n`);
59
+ process.exit(1);
60
+ }
61
+
62
+ // (a) schema valid
63
+ const { valid, errors } = validateState(state);
64
+ if (!valid) {
65
+ process.stderr.write('Error: state.json is invalid:\n');
66
+ for (const e of errors) process.stderr.write(` - ${e}\n`);
67
+ process.exit(1);
68
+ }
69
+
70
+ // (b) story present
71
+ if (!state.stories[storyId]) {
72
+ process.stderr.write(`Error: story ${storyId} not found in state.json\n`);
73
+ process.exit(1);
74
+ }
75
+
76
+ const story = state.stories[storyId];
77
+
78
+ // (c) state is "Ready to Bounce"
79
+ if (story.state !== 'Ready to Bounce') {
80
+ process.stderr.write(
81
+ `Error: story ${storyId} state is "${story.state}", expected "Ready to Bounce"\n`
82
+ );
83
+ process.exit(1);
84
+ }
85
+
86
+ // (d) git working tree is clean
87
+ let gitOutput;
88
+ try {
89
+ gitOutput = execSync('git status --porcelain', {
90
+ cwd: REPO_ROOT,
91
+ encoding: 'utf8',
92
+ });
93
+ } catch (err) {
94
+ process.stderr.write(`Error: failed to run git status: ${err.message}\n`);
95
+ process.exit(1);
96
+ }
97
+
98
+ if (gitOutput.trim().length > 0) {
99
+ process.stderr.write(
100
+ `Error: git working tree is dirty. Uncommitted changes:\n${gitOutput}`
101
+ );
102
+ process.exit(1);
103
+ }
104
+
105
+ process.stdout.write(
106
+ `Bounce readiness check passed for ${storyId} (state="Ready to Bounce", clean tree)\n`
107
+ );
108
+ process.exit(0);
109
+ }
110
+
111
+ main();
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * validate_state.mjs — Validate state.json schema and invariants
4
+ *
5
+ * Usage: node validate_state.mjs [--state-file <path>]
6
+ *
7
+ * Reads .cleargate/sprint-runs/<sprint-id>/state.json (or a specified path),
8
+ * confirms schema version, and reports invariant violations.
9
+ *
10
+ * Exports validateState(state) for use by other scripts.
11
+ */
12
+
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { SCHEMA_VERSION, VALID_STATES, BOUNCE_CAP } from './constants.mjs';
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
20
+
21
+ /**
22
+ * Validate a parsed state object.
23
+ * @param {object} state - Parsed state.json content
24
+ * @returns {{ valid: boolean, errors: string[] }}
25
+ */
26
+ export function validateState(state) {
27
+ const errors = [];
28
+
29
+ if (typeof state !== 'object' || state === null) {
30
+ errors.push('state is not an object');
31
+ return { valid: false, errors };
32
+ }
33
+
34
+ if (state.schema_version !== SCHEMA_VERSION) {
35
+ errors.push(
36
+ `schema_version mismatch: expected ${SCHEMA_VERSION}, got ${state.schema_version}`
37
+ );
38
+ }
39
+
40
+ if (!state.sprint_id) {
41
+ errors.push('missing required field: sprint_id');
42
+ }
43
+
44
+ if (!state.execution_mode) {
45
+ errors.push('missing required field: execution_mode');
46
+ }
47
+
48
+ if (!state.sprint_status) {
49
+ errors.push('missing required field: sprint_status');
50
+ }
51
+
52
+ if (typeof state.stories !== 'object' || state.stories === null) {
53
+ errors.push('stories field must be an object');
54
+ return { valid: false, errors };
55
+ }
56
+
57
+ for (const [storyId, story] of Object.entries(state.stories)) {
58
+ if (typeof story !== 'object' || story === null) {
59
+ errors.push(`story ${storyId}: not an object`);
60
+ continue;
61
+ }
62
+
63
+ if (!VALID_STATES.includes(story.state)) {
64
+ errors.push(
65
+ `story ${storyId}: invalid state "${story.state}"; expected one of: ${VALID_STATES.join(', ')}`
66
+ );
67
+ }
68
+
69
+ if (typeof story.qa_bounces !== 'number') {
70
+ errors.push(`story ${storyId}: qa_bounces must be a number`);
71
+ } else if (story.qa_bounces > BOUNCE_CAP) {
72
+ errors.push(
73
+ `invariant violation: story ${storyId} qa_bounces=${story.qa_bounces} exceeds BOUNCE_CAP (${BOUNCE_CAP})`
74
+ );
75
+ } else if (story.qa_bounces < 0) {
76
+ errors.push(`story ${storyId}: qa_bounces must be >= 0`);
77
+ }
78
+
79
+ if (typeof story.arch_bounces !== 'number') {
80
+ errors.push(`story ${storyId}: arch_bounces must be a number`);
81
+ } else if (story.arch_bounces > BOUNCE_CAP) {
82
+ errors.push(
83
+ `invariant violation: story ${storyId} arch_bounces=${story.arch_bounces} exceeds BOUNCE_CAP (${BOUNCE_CAP})`
84
+ );
85
+ } else if (story.arch_bounces < 0) {
86
+ errors.push(`story ${storyId}: arch_bounces must be >= 0`);
87
+ }
88
+
89
+ if (!story.updated_at) {
90
+ errors.push(`story ${storyId}: missing required field: updated_at`);
91
+ }
92
+ }
93
+
94
+ if (!state.updated_at) {
95
+ errors.push('missing required top-level field: updated_at');
96
+ }
97
+
98
+ return { valid: errors.length === 0, errors };
99
+ }
100
+
101
+ // CLI mode
102
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
103
+ const args = process.argv.slice(2);
104
+ const fileIdx = args.indexOf('--state-file');
105
+ let stateFile;
106
+
107
+ if (fileIdx !== -1 && args[fileIdx + 1]) {
108
+ stateFile = path.resolve(args[fileIdx + 1]);
109
+ } else {
110
+ // Attempt to discover via environment or fallback
111
+ const envStateFile = process.env.CLEARGATE_STATE_FILE;
112
+ if (envStateFile) {
113
+ stateFile = path.resolve(envStateFile);
114
+ } else {
115
+ // Look for state.json in sprint-runs/
116
+ const sprintRunsDir = path.join(REPO_ROOT, '.cleargate', 'sprint-runs');
117
+ if (!fs.existsSync(sprintRunsDir)) {
118
+ process.stderr.write(`Error: sprint-runs directory not found at ${sprintRunsDir}\n`);
119
+ process.exit(1);
120
+ }
121
+ const entries = fs.readdirSync(sprintRunsDir);
122
+ const found = entries
123
+ .map((e) => path.join(sprintRunsDir, e, 'state.json'))
124
+ .filter((p) => fs.existsSync(p));
125
+ if (found.length === 0) {
126
+ process.stderr.write('Error: no state.json found in sprint-runs/\n');
127
+ process.exit(1);
128
+ }
129
+ if (found.length > 1) {
130
+ process.stderr.write(
131
+ `Multiple state.json files found; specify --state-file:\n${found.join('\n')}\n`
132
+ );
133
+ process.exit(1);
134
+ }
135
+ stateFile = found[0];
136
+ }
137
+ }
138
+
139
+ if (!fs.existsSync(stateFile)) {
140
+ process.stderr.write(`Error: state.json not found at ${stateFile}\n`);
141
+ process.exit(1);
142
+ }
143
+
144
+ let state;
145
+ try {
146
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
147
+ } catch (err) {
148
+ process.stderr.write(`Error: failed to parse state.json: ${err.message}\n`);
149
+ process.exit(1);
150
+ }
151
+
152
+ const { valid, errors } = validateState(state);
153
+
154
+ if (valid) {
155
+ process.stdout.write(`state.json at ${stateFile} is valid (schema_version=${state.schema_version})\n`);
156
+ process.exit(0);
157
+ } else {
158
+ process.stderr.write(`Validation failed for ${stateFile}:\n`);
159
+ for (const err of errors) {
160
+ process.stderr.write(` - ${err}\n`);
161
+ }
162
+ process.exit(1);
163
+ }
164
+ }
@@ -35,6 +35,15 @@ cached_gate_result:
35
35
  pass: null
36
36
  failing_criteria: []
37
37
  last_gate_check: null
38
+ # Sync attribution (EPIC-010). Optional; stamped by `cleargate push` / `cleargate pull`.
39
+ pushed_by: null # STORY-010-07 writer / STORY-010-04 reader
40
+ pushed_at: null # STORY-010-07 writer / STORY-010-04 reader
41
+ last_pulled_by: null # STORY-010-04 writer / STORY-010-03 reader
42
+ last_pulled_at: null # STORY-010-04 writer / STORY-010-03 reader
43
+ last_remote_update: null # STORY-010-02 writer (from MCP) / STORY-010-03 reader
44
+ source: "local-authored" # STORY-010-05 flips to "remote-authored" on intake
45
+ last_synced_status: null # STORY-010-04 writer; required for conflict-detector rule 6
46
+ last_synced_body_sha: null # STORY-010-04 writer; sha256 of body at last sync
38
47
  ---
39
48
 
40
49
  # BUG-{ID}: {Bug Name}
@@ -33,6 +33,15 @@ cached_gate_result:
33
33
  pass: null
34
34
  failing_criteria: []
35
35
  last_gate_check: null
36
+ # Sync attribution (EPIC-010). Optional; stamped by `cleargate push` / `cleargate pull`.
37
+ pushed_by: null # STORY-010-07 writer / STORY-010-04 reader
38
+ pushed_at: null # STORY-010-07 writer / STORY-010-04 reader
39
+ last_pulled_by: null # STORY-010-04 writer / STORY-010-03 reader
40
+ last_pulled_at: null # STORY-010-04 writer / STORY-010-03 reader
41
+ last_remote_update: null # STORY-010-02 writer (from MCP) / STORY-010-03 reader
42
+ source: "local-authored" # STORY-010-05 flips to "remote-authored" on intake
43
+ last_synced_status: null # STORY-010-04 writer; required for conflict-detector rule 6
44
+ last_synced_body_sha: null # STORY-010-04 writer; sha256 of body at last sync
36
45
  ---
37
46
 
38
47
  # CR-{ID}: {Change Request Name}
@@ -11,6 +11,7 @@ sprint_id: "SPRINT-{ID}"
11
11
  remote_id: "{PM_TOOL_SPRINT_ID}"
12
12
  source_tool: "linear | jira"
13
13
  status: "Draft | Active | Completed"
14
+ execution_mode: "v1" # Enum: "v1" | "v2". Default "v1". Under "v2", §§15–18 of cleargate-protocol.md are enforcing (worktree isolation, pre-gate scanning, bounce counters, flashcard gate, sprint-close pipeline). Under "v1", those sections are advisory only and all new CLI commands (sprint init|close, story start|complete, gate qa|arch, state update|validate) print an inert-mode message. Set to "v2" only after all EPIC-013 M2 stories have shipped and the Architect has completed a Sprint Design Review (see §19 of the protocol).
14
15
  start_date: "{YYYY-MM-DD}"
15
16
  end_date: "{YYYY-MM-DD}"
16
17
  synced_at: "{ISO-8601 timestamp}"
@@ -36,11 +37,36 @@ cached_gate_result:
36
37
  ## Sprint Goal
37
38
  {One clear sentence describing the primary objective of this sprint, as defined in the PM tool.}
38
39
 
39
- ## Consolidated Deliverables
40
+ ## 1. Consolidated Deliverables
40
41
  *(Pulled from PM tool. IDs are the remote PM entity IDs.)*
41
42
 
42
- - `{TASK-ID}`: {Title} {Brief Description}
43
- - `{TASK-ID}`: {Title} — {Brief Description}
43
+ | Story ID | Title | Milestone | Parallel? | Bounce Exposure |
44
+ |---|---|---|---|---|
45
+ | `{STORY-NNN-NN}` | {Title} | M{N} | y / n | low / med / high |
46
+
47
+ ## 2. Execution Strategy
48
+ *(Written by Architect during Sprint Design Review. Required before `execution_mode: v2` sprint start. Under v1, this section may be omitted or left as a stub.)*
49
+
50
+ ### 2.1 Phase Plan
51
+ {Parallel vs sequential story groups. List which stories run concurrently in each wave and which must be serialized.}
52
+ Example:
53
+ - Wave 1 (sequential): STORY-NNN-01 → STORY-NNN-02 (02 depends on 01's schema)
54
+ - Wave 2 (parallel): STORY-NNN-03 ‖ STORY-NNN-04
55
+
56
+ ### 2.2 Merge Ordering (Shared-File Surface Analysis)
57
+ {List files touched by more than one story. For each shared file, specify which story lands first and why.}
58
+
59
+ | Shared File | Stories Touching It | Merge Order | Rationale |
60
+ |---|---|---|---|
61
+ | `.cleargate/knowledge/cleargate-protocol.md` | STORY-NNN-01, STORY-NNN-02 | 01 → 02 | 01 adds §16; 02 amends §16 |
62
+
63
+ ### 2.3 Shared-Surface Warnings
64
+ {Explicit conflict risks. One bullet per risk. Cite file + story pair.}
65
+ - None identified. (Replace with actual warnings if applicable.)
66
+
67
+ ### 2.4 ADR-Conflict Flags
68
+ {Any story whose implementation conflicts with an Architectural Decision Record in `.cleargate/knowledge/` or prior sprint decisions. One bullet per flag.}
69
+ - None identified. (Replace with actual flags if applicable.)
44
70
 
45
71
  ## Risks & Dependencies
46
72
  *(As defined in the PM tool.)*
@@ -38,6 +38,15 @@ cached_gate_result:
38
38
  pass: null
39
39
  failing_criteria: []
40
40
  last_gate_check: null
41
+ # Sync attribution (EPIC-010). Optional; stamped by `cleargate push` / `cleargate pull`.
42
+ pushed_by: null # STORY-010-07 writer / STORY-010-04 reader
43
+ pushed_at: null # STORY-010-07 writer / STORY-010-04 reader
44
+ last_pulled_by: null # STORY-010-04 writer / STORY-010-03 reader
45
+ last_pulled_at: null # STORY-010-04 writer / STORY-010-03 reader
46
+ last_remote_update: null # STORY-010-02 writer (from MCP) / STORY-010-03 reader
47
+ source: "local-authored" # STORY-010-05 flips to "remote-authored" on intake
48
+ last_synced_status: null # STORY-010-04 writer; required for conflict-detector rule 6
49
+ last_synced_body_sha: null # STORY-010-04 writer; sha256 of body at last sync
41
50
  ---
42
51
 
43
52
  # EPIC-{ID}: {Epic Name}
@@ -28,6 +28,15 @@ cached_gate_result:
28
28
  pass: null
29
29
  failing_criteria: []
30
30
  last_gate_check: null
31
+ # Sync attribution (EPIC-010). Optional; stamped by `cleargate push` / `cleargate pull`.
32
+ pushed_by: null # STORY-010-07 writer / STORY-010-04 reader
33
+ pushed_at: null # STORY-010-07 writer / STORY-010-04 reader
34
+ last_pulled_by: null # STORY-010-04 writer / STORY-010-03 reader
35
+ last_pulled_at: null # STORY-010-04 writer / STORY-010-03 reader
36
+ last_remote_update: null # STORY-010-02 writer (from MCP) / STORY-010-03 reader
37
+ source: "local-authored" # STORY-010-05 flips to "remote-authored" on intake
38
+ last_synced_status: null # STORY-010-04 writer; required for conflict-detector rule 6
39
+ last_synced_body_sha: null # STORY-010-04 writer; sha256 of body at last sync
31
40
  PROPOSAL-{ID}: {Initiative Name}
32
41
  1. Initiative & Context
33
42
  1.1 Objective
@@ -0,0 +1,42 @@
1
+ ---
2
+ sprint_id: "S-NN"
3
+ created_at: "YYYY-MM-DDTHH:MM:SSZ"
4
+ last_updated: "YYYY-MM-DDTHH:MM:SSZ"
5
+ ---
6
+
7
+ # Sprint Context
8
+
9
+ Per-sprint audit artefact. Populated at sprint init (M1 planning) and re-touched after each story merges. Referenced from every Developer/QA/Architect task brief so all agents start from the same baseline.
10
+
11
+ ## Locked Versions
12
+
13
+ Frozen dependency versions for this sprint. Orchestrator populates from `package.json` snapshots at sprint init; Developers must not upgrade these mid-sprint without an explicit CR.
14
+
15
+ | Package | Version |
16
+ |---------|---------|
17
+ | Node | `>=24.0.0` |
18
+ | TypeScript | `^5.8.0` |
19
+ | (add rows per workspace below) | |
20
+
21
+ ## Cross-Cutting Rules
22
+
23
+ Sprint-wide architecture rules and UI/API tokens that every story must honour. Populated from the parent Epic's `<architecture_rules>` block.
24
+
25
+ 1. (rule 1)
26
+ 2. (rule 2)
27
+ 3. (rule 3)
28
+
29
+ ## Active FLASHCARD Tags
30
+
31
+ FLASHCARD tags that appear in any story's `<agent_context>` for this sprint. Auto-populated by grepping `.cleargate/FLASHCARD.md` at sprint init. Agents: grep the flashcard file for each tag listed here before starting work.
32
+
33
+ - `#tag1` — one-line summary of the most recent card
34
+ - `#tag2` — one-line summary
35
+
36
+ ## Adjacent Implementations (Reuse First)
37
+
38
+ Exported helpers and modules from already-merged stories in this sprint. The Architect updates this section after each story merges. Developers check here before writing new helpers — if the module already exists, import it; duplication is a kick-back criterion.
39
+
40
+ | Story | Module / Export | Path |
41
+ |-------|----------------|------|
42
+ | (populated as stories merge) | | |