project-iris 0.0.12 → 0.0.14

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 (189) hide show
  1. package/README.md +214 -323
  2. package/bin/cli.js +21 -0
  3. package/flows/aidlc/README.md +372 -0
  4. package/flows/aidlc/agents/construction-agent.md +79 -0
  5. package/flows/aidlc/agents/inception-agent.md +97 -0
  6. package/flows/aidlc/agents/master-agent.md +61 -0
  7. package/flows/aidlc/agents/operations-agent.md +89 -0
  8. package/flows/aidlc/commands/construction-agent.md +63 -0
  9. package/flows/aidlc/commands/inception-agent.md +55 -0
  10. package/flows/aidlc/commands/master-agent.md +47 -0
  11. package/flows/aidlc/commands/operations-agent.md +77 -0
  12. package/flows/aidlc/context-config.yaml +67 -0
  13. package/flows/aidlc/memory-bank.yaml +104 -0
  14. package/flows/aidlc/quick-start.md +322 -0
  15. package/flows/aidlc/skills/construction/bolt-list.md +163 -0
  16. package/flows/aidlc/skills/construction/bolt-replan.md +345 -0
  17. package/flows/aidlc/skills/construction/bolt-start.md +442 -0
  18. package/flows/aidlc/skills/construction/bolt-status.md +185 -0
  19. package/flows/aidlc/skills/construction/navigator.md +196 -0
  20. package/flows/aidlc/skills/inception/bolt-plan.md +372 -0
  21. package/flows/aidlc/skills/inception/context.md +171 -0
  22. package/flows/aidlc/skills/inception/intent-create.md +211 -0
  23. package/flows/aidlc/skills/inception/intent-list.md +124 -0
  24. package/flows/aidlc/skills/inception/navigator.md +207 -0
  25. package/flows/aidlc/skills/inception/requirements.md +227 -0
  26. package/flows/aidlc/skills/inception/review.md +248 -0
  27. package/flows/aidlc/skills/inception/story-create.md +304 -0
  28. package/flows/aidlc/skills/inception/units.md +278 -0
  29. package/flows/aidlc/skills/master/analyze-context.md +239 -0
  30. package/flows/aidlc/skills/master/answer-question.md +141 -0
  31. package/flows/aidlc/skills/master/explain-flow.md +158 -0
  32. package/flows/aidlc/skills/master/project-init.md +281 -0
  33. package/flows/aidlc/skills/master/route-request.md +126 -0
  34. package/flows/aidlc/skills/operations/build.md +237 -0
  35. package/flows/aidlc/skills/operations/deploy.md +259 -0
  36. package/flows/aidlc/skills/operations/monitor.md +265 -0
  37. package/flows/aidlc/skills/operations/navigator.md +209 -0
  38. package/flows/aidlc/skills/operations/verify.md +224 -0
  39. package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt.md +3 -3
  40. package/{dist → flows/aidlc}/templates/construction/bolt-types/spike-bolt.md +2 -2
  41. package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
  42. package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
  43. package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
  44. package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
  45. package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
  46. package/flows/aidlc/templates/inception/project/README.md +55 -0
  47. package/flows/aidlc/templates/standards/catalog.yaml +345 -0
  48. package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
  49. package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
  50. package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
  51. package/lib/InstallerFactory.js +36 -0
  52. package/lib/analytics/env-detector.js +92 -0
  53. package/lib/analytics/index.js +22 -0
  54. package/lib/analytics/machine-id.js +33 -0
  55. package/lib/analytics/tracker.js +232 -0
  56. package/lib/cli-utils.js +342 -0
  57. package/lib/constants.js +32 -0
  58. package/lib/installer.js +402 -0
  59. package/lib/installers/AntigravityInstaller.js +22 -0
  60. package/lib/installers/ClaudeInstaller.js +85 -0
  61. package/lib/installers/ClineInstaller.js +21 -0
  62. package/lib/installers/CodexInstaller.js +21 -0
  63. package/lib/installers/CopilotInstaller.js +113 -0
  64. package/lib/installers/CursorInstaller.js +63 -0
  65. package/lib/installers/GeminiInstaller.js +75 -0
  66. package/lib/installers/KiroInstaller.js +22 -0
  67. package/lib/installers/OpenCodeInstaller.js +22 -0
  68. package/lib/installers/RooInstaller.js +22 -0
  69. package/lib/installers/ToolInstaller.js +73 -0
  70. package/lib/installers/WindsurfInstaller.js +22 -0
  71. package/lib/markdown-validator.ts +175 -0
  72. package/lib/yaml-validator.ts +99 -0
  73. package/package.json +105 -32
  74. package/scripts/artifact-validator.js +594 -0
  75. package/scripts/bolt-complete.js +606 -0
  76. package/scripts/status-integrity.js +598 -0
  77. package/dist/bridge/agent-runner.js +0 -190
  78. package/dist/bridge/connector-factory.js +0 -31
  79. package/dist/bridge/connectors/antigravity-connector.js +0 -18
  80. package/dist/bridge/connectors/cursor-connector.js +0 -31
  81. package/dist/bridge/connectors/in-process-connector.js +0 -29
  82. package/dist/bridge/connectors/vscode-connector.js +0 -31
  83. package/dist/bridge/connectors/windsurf-connector.js +0 -23
  84. package/dist/bridge/filesystem-connector.js +0 -110
  85. package/dist/bridge/helper.js +0 -203
  86. package/dist/bridge/types.js +0 -10
  87. package/dist/cli.js +0 -40
  88. package/dist/commands/ask.js +0 -259
  89. package/dist/commands/bridge.js +0 -88
  90. package/dist/commands/create.js +0 -25
  91. package/dist/commands/develop.js +0 -141
  92. package/dist/commands/doctor.js +0 -102
  93. package/dist/commands/flow.js +0 -301
  94. package/dist/commands/framework.js +0 -273
  95. package/dist/commands/generate.js +0 -59
  96. package/dist/commands/install.js +0 -100
  97. package/dist/commands/pack.js +0 -33
  98. package/dist/commands/phase.js +0 -38
  99. package/dist/commands/run.js +0 -199
  100. package/dist/commands/status.js +0 -114
  101. package/dist/commands/uninstall.js +0 -14
  102. package/dist/commands/use.js +0 -20
  103. package/dist/commands/validate.js +0 -102
  104. package/dist/framework/framework-loader.js +0 -97
  105. package/dist/framework/framework-paths.js +0 -48
  106. package/dist/framework/framework-types.js +0 -15
  107. package/dist/iris/artifact-checker.js +0 -78
  108. package/dist/iris/artifacts/config.js +0 -68
  109. package/dist/iris/artifacts/generator.js +0 -88
  110. package/dist/iris/artifacts/types.js +0 -1
  111. package/dist/iris/bundle.js +0 -44
  112. package/dist/iris/doctrine/collector.js +0 -124
  113. package/dist/iris/fixer.js +0 -149
  114. package/dist/iris/flows/manifest.js +0 -124
  115. package/dist/iris/framework-context.js +0 -49
  116. package/dist/iris/framework-manager.js +0 -215
  117. package/dist/iris/fs/atomic.js +0 -22
  118. package/dist/iris/guard.js +0 -38
  119. package/dist/iris/importers/index.js +0 -9
  120. package/dist/iris/importers/types.js +0 -8
  121. package/dist/iris/importers/writer.js +0 -139
  122. package/dist/iris/include.js +0 -49
  123. package/dist/iris/installer.js +0 -334
  124. package/dist/iris/interactive/env.js +0 -21
  125. package/dist/iris/interactive/intent-interview.js +0 -345
  126. package/dist/iris/interactive/intent-schema.js +0 -28
  127. package/dist/iris/interactive/interview-io.js +0 -22
  128. package/dist/iris/interview/config.js +0 -71
  129. package/dist/iris/interview/types.js +0 -16
  130. package/dist/iris/interview/utils.js +0 -38
  131. package/dist/iris/manifest.js +0 -54
  132. package/dist/iris/packer.js +0 -325
  133. package/dist/iris/parsers/unit-parser.js +0 -43
  134. package/dist/iris/paths.js +0 -18
  135. package/dist/iris/policy.js +0 -133
  136. package/dist/iris/proc.js +0 -56
  137. package/dist/iris/report.js +0 -53
  138. package/dist/iris/resolver.js +0 -66
  139. package/dist/iris/router.js +0 -114
  140. package/dist/iris/routes.js +0 -189
  141. package/dist/iris/run-state.js +0 -146
  142. package/dist/iris/state.js +0 -113
  143. package/dist/iris/templates.js +0 -70
  144. package/dist/iris/tmp.js +0 -24
  145. package/dist/iris/uninstaller.js +0 -181
  146. package/dist/iris/utils/interpolate.js +0 -42
  147. package/dist/iris/validator.js +0 -391
  148. package/dist/iris/workflow/config.js +0 -51
  149. package/dist/iris/workflow/engine.js +0 -129
  150. package/dist/iris/workflow/steps.js +0 -448
  151. package/dist/iris/workflow/types.js +0 -1
  152. package/dist/iris_bundle/frameworks/iris-core/framework.yaml +0 -9
  153. package/dist/iris_bundle/frameworks/iris-core/memory/memory-bank.yaml +0 -1
  154. package/dist/iris_bundle/frameworks/iris-core/policy.yaml +0 -7
  155. package/dist/iris_bundle/frameworks/iris-core/templates/config/memory-bank.yaml +0 -1
  156. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/spike-bolt.md +0 -240
  157. package/dist/lib.js +0 -96
  158. package/dist/templates/construction/bolt-template.md +0 -226
  159. package/dist/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -49
  160. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -55
  161. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -67
  162. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -62
  163. package/dist/templates/construction/bolt-types/ddd-construction-bolt.md +0 -528
  164. package/dist/templates/construction/bolt-types/simple-construction-bolt.md +0 -347
  165. package/dist/templates/inception/requirements-template.md +0 -144
  166. package/dist/templates/inception/stories-template.md +0 -38
  167. package/dist/templates/inception/story-template.md +0 -147
  168. package/dist/templates/inception/system-context-template.md +0 -29
  169. package/dist/templates/inception/unit-brief-template.md +0 -177
  170. package/dist/templates/inception/units-template.md +0 -52
  171. package/dist/utils/exit-codes.js +0 -7
  172. package/dist/utils/logo.js +0 -17
  173. package/dist/workflows/bolt-execution.js +0 -238
  174. package/dist/workflows/bolt-plan.js +0 -221
  175. package/dist/workflows/intent-inception.js +0 -285
  176. package/dist/workflows/memory-bank-generator.js +0 -180
  177. package/dist/workflows/reporting.js +0 -74
  178. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-template.md +0 -0
  179. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -0
  180. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -0
  181. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -0
  182. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -0
  183. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/simple-construction-bolt.md +0 -0
  184. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/requirements-template.md +0 -0
  185. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/stories-template.md +0 -0
  186. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/story-template.md +0 -0
  187. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/system-context-template.md +0 -0
  188. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/unit-brief-template.md +0 -0
  189. /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/units-template.md +0 -0
@@ -0,0 +1,606 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * iris Bolt Completion Script
5
+ *
6
+ * Called from bolt-start.md skill when a bolt completes.
7
+ * Updates bolt status, stories, and cascades to unit/intent status.
8
+ *
9
+ * ═══════════════════════════════════════════════════════════════════════════════
10
+ * EXECUTION FLOW
11
+ * ═══════════════════════════════════════════════════════════════════════════════
12
+ *
13
+ * This script performs a deterministic cascade of status updates when a bolt
14
+ * completes. It replaces manual multi-step LLM operations that were prone
15
+ * to being skipped due to "Lost in the Middle" effect.
16
+ *
17
+ * ┌──────────────────────────────────────────────────────────────────────────┐
18
+ * │ STATUS CASCADE │
19
+ * │ │
20
+ * │ Bolt Complete ──→ Stories Complete ──→ Unit Complete ──→ Intent Complete │
21
+ * │ │ │ │ │ │
22
+ * │ ▼ ▼ ▼ ▼ │
23
+ * │ bolt.md story/*.md unit-brief.md requirements.md │
24
+ * │ status: status: status: status: │
25
+ * │ complete complete complete complete │
26
+ * └──────────────────────────────────────────────────────────────────────────┘
27
+ *
28
+ * ┌──────────────────────────────────────────────────────────────────────────┐
29
+ * │ STEP-BY-STEP │
30
+ * └──────────────────────────────────────────────────────────────────────────┘
31
+ *
32
+ * Step 1: READ BOLT FILE
33
+ * ┌─────────────────────────────────────────────────────────────────────┐
34
+ * │ Input: bolt-id (e.g., "016-analytics-tracker" or "016") │
35
+ * │ Output: Bolt object with id, unit, intent, stories array │
36
+ * │ │
37
+ * │ - Scans memory-bank/bolts/ for matching directory │
38
+ * │ - Reads bolt.md frontmatter │
39
+ * │ - Extracts: intent, unit, stories, current_status │
40
+ * └─────────────────────────────────────────────────────────────────────┘
41
+ *
42
+ * Step 2: UPDATE BOLT STATUS
43
+ * ┌─────────────────────────────────────────────────────────────────────┐
44
+ * │ Action: Update bolt.md frontmatter │
45
+ * │ │
46
+ * │ status: in-progress → complete │
47
+ * │ completed: {ISO-8601-timestamp} │
48
+ * │ current_stage: null │
49
+ * │ stages_completed: [adds final stage if not present] │
50
+ * └─────────────────────────────────────────────────────────────────────┘
51
+ *
52
+ * Step 3: UPDATE ALL STORIES
53
+ * ┌─────────────────────────────────────────────────────────────────────┐
54
+ * │ For each story in bolt.stories array: │
55
+ * │ │
56
+ * │ 1. Find story file (handles numeric prefixes, fuzzy matching) │
57
+ * │ 2. Read story frontmatter │
58
+ * │ 3. If not complete: │
59
+ * │ status: {current} → complete │
60
+ * │ implemented: false → true │
61
+ * │ │
62
+ * │ Story search strategy: │
63
+ * │ - Direct path: {intent}/units/{unit}/stories/{story}.md │
64
+ * │ - With prefix: {intent}/units/*-{unit}/stories/{story}.md │
65
+ * │ - Fuzzy match: filename starts with {story}- │
66
+ * └─────────────────────────────────────────────────────────────────────┘
67
+ *
68
+ * Step 4: UPDATE UNIT STATUS
69
+ * ┌─────────────────────────────────────────────────────────────────────┐
70
+ * │ Action: Check ALL bolts for this unit, update if all complete │
71
+ * │ │
72
+ * │ 1. Scan memory-bank/bolts/ for bolts with matching unit │
73
+ * │ 2. Check if ALL bolts have status: complete │
74
+ * │ 3. If yes, update unit-brief.md: │
75
+ * │ status: {current} → complete │
76
+ * │ │
77
+ * │ Note: Unit path = {intent}/units/{unit}/unit-brief.md │
78
+ * │ Handles numeric prefixes (e.g., 001-analytics-tracker) │
79
+ * └─────────────────────────────────────────────────────────────────────┘
80
+ *
81
+ * Step 5: UPDATE INTENT STATUS
82
+ * ┌─────────────────────────────────────────────────────────────────────┐
83
+ * │ Action: Check ALL units for this intent, update if all complete │
84
+ * │ │
85
+ * │ 1. Scan {intent}/units/ for all unit directories │
86
+ * │ 2. Read each unit-brief.md status │
87
+ * │ 3. If ALL units have status: complete: │
88
+ * │ Update requirements.md: │
89
+ * │ status: {current} → complete │
90
+ * │ │
91
+ * │ Note: Intent path = {intent}/requirements.md │
92
+ * └─────────────────────────────────────────────────────────────────────┘
93
+ *
94
+ * ┌──────────────────────────────────────────────────────────────────────────┐
95
+ * │ FILE STRUCTURE │
96
+ * └──────────────────────────────────────────────────────────────────────────┘
97
+ *
98
+ * memory-bank/
99
+ * ├── bolts/
100
+ * │ └── {BBB}-{unit}/
101
+ * │ └── bolt.md ← Updated: status, completed
102
+ * ├── intents/
103
+ * │ └── {intent}/
104
+ * │ ├── requirements.md ← Updated: status (if all units complete)
105
+ * │ └── units/
106
+ * │ └── {unit}/
107
+ * │ ├── unit-brief.md ← Updated: status (if all bolts complete)
108
+ * │ └── stories/
109
+ * │ ├── 001-{story}.md ← Updated: status, implemented
110
+ * │ └── ...
111
+ *
112
+ * ┌──────────────────────────────────────────────────────────────────────────┐
113
+ * │ USAGE │
114
+ * └──────────────────────────────────────────────────────────────────────────┘
115
+ *
116
+ * From agent skill (bolt-start.md Step 10):
117
+ *
118
+ * node .iris/scripts/bolt-complete.js 016-analytics-tracker
119
+ *
120
+ * With optional stage name:
121
+ *
122
+ * node .iris/scripts/bolt-complete.js 016-analytics-tracker --last-stage test
123
+ *
124
+ * ═══════════════════════════════════════════════════════════════════════════════
125
+ */
126
+
127
+ const fs = require('fs-extra');
128
+ const path = require('path');
129
+ const yaml = require('js-yaml');
130
+
131
+ // Theme colors for output
132
+ const colors = {
133
+ reset: '\x1b[0m',
134
+ green: '\x1b[32m',
135
+ red: '\x1b[31m',
136
+ yellow: '\x1b[33m',
137
+ blue: '\x1b[34m',
138
+ dim: '\x1b[90m',
139
+ bright: '\x1b[1m'
140
+ };
141
+
142
+ // Memory bank paths (relative to project root)
143
+ const MEMORY_BANK_DIR = 'memory-bank';
144
+ const BOLTS_DIR = path.join(MEMORY_BANK_DIR, 'bolts');
145
+ const INTENTS_DIR = path.join(MEMORY_BANK_DIR, 'intents');
146
+
147
+ /**
148
+ * Extract frontmatter from a markdown file
149
+ */
150
+ function extractFrontmatter(content) {
151
+ const match = content.match(/^---\n([\s\S]+?)\n---/);
152
+ if (!match) return null;
153
+
154
+ try {
155
+ return yaml.load(match[1]);
156
+ } catch (error) {
157
+ console.error(`${colors.red}Error parsing YAML frontmatter:${colors.reset}`, error.message);
158
+ return null;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Update frontmatter in a markdown file
164
+ */
165
+ function updateFrontmatter(content, newFrontmatter) {
166
+ const match = content.match(/^---\n([\s\S]+?)\n---/);
167
+ if (!match) return null;
168
+
169
+ const newYaml = yaml.dump(newFrontmatter, {
170
+ lineWidth: -1,
171
+ noRefs: true,
172
+ quotingType: '"',
173
+ forceQuotes: false,
174
+ sortKeys: false
175
+ }).trim();
176
+
177
+ return `---\n${newYaml}\n---${content.slice(match[0].length)}`;
178
+ }
179
+
180
+ /**
181
+ * Format timestamp as ISO 8601
182
+ * Format: YYYY-MM-DDTHH:MM:SSZ (no milliseconds, per memory-bank.yaml convention)
183
+ */
184
+ function getTimestamp() {
185
+ const date = new Date();
186
+ // Format to ISO 8601 without milliseconds
187
+ return date.toISOString().replace(/\.\d+Z$/, 'Z');
188
+ }
189
+
190
+ /**
191
+ * Read a bolt file and extract metadata
192
+ */
193
+ async function readBolt(boltId) {
194
+ // Support both formats: "016-analytics-tracker" or "016"
195
+ const boltDirs = await fs.readdir(BOLTS_DIR).catch(() => []);
196
+ const boltDir = boltId.includes('-')
197
+ ? boltId
198
+ : boltDirs.find(d => d.startsWith(boltId + '-'));
199
+
200
+ if (!boltDir) {
201
+ throw new Error(`Bolt not found: ${boltId}`);
202
+ }
203
+
204
+ const boltPath = path.join(BOLTS_DIR, boltDir, 'bolt.md');
205
+
206
+ if (!await fs.pathExists(boltPath)) {
207
+ throw new Error(`Bolt file not found: ${boltPath}`);
208
+ }
209
+
210
+ const content = await fs.readFile(boltPath, 'utf8');
211
+ const frontmatter = extractFrontmatter(content);
212
+
213
+ if (!frontmatter) {
214
+ throw new Error(`Invalid bolt file (no frontmatter): ${boltPath}`);
215
+ }
216
+
217
+ return {
218
+ id: frontmatter.id || boltDir.replace(/\/$/, ''),
219
+ path: boltPath,
220
+ dir: boltDir,
221
+ content,
222
+ frontmatter
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Find a story file by ID
228
+ * Tries multiple approaches to find the story file:
229
+ * 1. Direct path with unit name
230
+ * 2. Search all unit directories with prefix matching
231
+ * 3. Fuzzy search by story ID pattern
232
+ */
233
+ async function findStoryFile(intent, unit, storyId) {
234
+ // Try with .md extension (direct path)
235
+ let storyPath = path.join(INTENTS_DIR, intent, 'units', unit, 'stories', `${storyId}.md`);
236
+
237
+ if (await fs.pathExists(storyPath)) {
238
+ return storyPath;
239
+ }
240
+
241
+ // Try without extension if storyId already has it
242
+ storyPath = path.join(INTENTS_DIR, intent, 'units', unit, 'stories', storyId);
243
+ if (await fs.pathExists(storyPath)) {
244
+ return storyPath;
245
+ }
246
+
247
+ // Try to find unit directory with prefix (e.g., unit=analytics-tracker could be 001-analytics-tracker)
248
+ const intentUnitsDir = path.join(INTENTS_DIR, intent, 'units');
249
+ if (await fs.pathExists(intentUnitsDir)) {
250
+ const unitDirs = await fs.readdir(intentUnitsDir).catch(() => []);
251
+
252
+ for (const unitDir of unitDirs) {
253
+ // Check if directory name ends with the unit name (handles numeric prefixes)
254
+ if (unitDir === unit || unitDir.endsWith(`-${unit}`)) {
255
+ storyPath = path.join(intentUnitsDir, unitDir, 'stories', `${storyId}.md`);
256
+ if (await fs.pathExists(storyPath)) {
257
+ return storyPath;
258
+ }
259
+
260
+ // Also try fuzzy match - story ID might be embedded in filename
261
+ const storiesDir = path.join(intentUnitsDir, unitDir, 'stories');
262
+ if (await fs.pathExists(storiesDir)) {
263
+ const storyFiles = await fs.readdir(storiesDir).catch(() => []);
264
+
265
+ // Look for files that start with the story ID or contain it
266
+ for (const storyFile of storyFiles) {
267
+ const baseName = storyFile.replace('.md', '');
268
+ if (baseName === storyId || baseName.startsWith(`${storyId}-`)) {
269
+ return path.join(storiesDir, storyFile);
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ return null;
278
+ }
279
+
280
+ /**
281
+ * Update bolt file to complete status
282
+ */
283
+ async function updateBoltStatus(bolt, lastStage) {
284
+ const newFrontmatter = {
285
+ ...bolt.frontmatter,
286
+ status: 'complete',
287
+ completed: getTimestamp(),
288
+ current_stage: null
289
+ };
290
+
291
+ // Ensure stages_completed includes the final stage
292
+ const stagesCompleted = bolt.frontmatter.stages_completed || [];
293
+ if (lastStage && !stagesCompleted.find(s => s.name === lastStage)) {
294
+ stagesCompleted.push({
295
+ name: lastStage,
296
+ completed: getTimestamp(),
297
+ artifact: `completion-stage-${lastStage}.md`
298
+ });
299
+ }
300
+ newFrontmatter.stages_completed = stagesCompleted;
301
+
302
+ const newContent = updateFrontmatter(bolt.content, newFrontmatter);
303
+ if (!newContent) {
304
+ throw new Error('Failed to update bolt frontmatter');
305
+ }
306
+
307
+ await fs.writeFile(bolt.path, newContent, 'utf8');
308
+ return newFrontmatter;
309
+ }
310
+
311
+ /**
312
+ * Update all stories in the bolt to complete status
313
+ */
314
+ async function updateStories(bolt) {
315
+ const stories = bolt.frontmatter.stories || [];
316
+ const intent = bolt.frontmatter.intent;
317
+ const unit = bolt.frontmatter.unit;
318
+
319
+ if (stories.length === 0) {
320
+ return { updated: 0, skipped: 0, errors: 0 };
321
+ }
322
+
323
+ const results = { updated: 0, skipped: 0, errors: 0, details: [] };
324
+
325
+ for (const storyId of stories) {
326
+ const storyPath = await findStoryFile(intent, unit, storyId);
327
+
328
+ if (!storyPath) {
329
+ console.log(` ${colors.red}✗${colors.reset} ${storyId} - ${colors.dim}File not found${colors.reset}`);
330
+ results.errors++;
331
+ results.details.push({ storyId, status: 'error', reason: 'File not found' });
332
+ continue;
333
+ }
334
+
335
+ const content = await fs.readFile(storyPath, 'utf8');
336
+ const frontmatter = extractFrontmatter(content);
337
+
338
+ if (!frontmatter) {
339
+ console.log(` ${colors.red}✗${colors.reset} ${storyId} - ${colors.dim}Invalid frontmatter${colors.reset}`);
340
+ results.errors++;
341
+ results.details.push({ storyId, status: 'error', reason: 'Invalid frontmatter' });
342
+ continue;
343
+ }
344
+
345
+ // Check if already complete
346
+ if (frontmatter.status === 'complete' && frontmatter.implemented === true) {
347
+ console.log(` ${colors.dim}−${colors.reset} ${storyId} - ${colors.dim}Already complete${colors.reset}`);
348
+ results.skipped++;
349
+ results.details.push({ storyId, status: 'skipped', reason: 'Already complete' });
350
+ continue;
351
+ }
352
+
353
+ // Update frontmatter
354
+ const newFrontmatter = {
355
+ ...frontmatter,
356
+ status: 'complete',
357
+ implemented: true
358
+ };
359
+
360
+ const newContent = updateFrontmatter(content, newFrontmatter);
361
+ if (newContent) {
362
+ await fs.writeFile(storyPath, newContent, 'utf8');
363
+ const oldStatus = frontmatter.status || 'draft';
364
+ console.log(` ${colors.green}✓${colors.reset} ${storyId} - ${colors.dim}${oldStatus} → complete${colors.reset}`);
365
+ results.updated++;
366
+ results.details.push({ storyId, status: 'updated', from: oldStatus, to: 'complete' });
367
+ } else {
368
+ console.log(` ${colors.red}✗${colors.reset} ${storyId} - ${colors.dim}Failed to update${colors.reset}`);
369
+ results.errors++;
370
+ results.details.push({ storyId, status: 'error', reason: 'Failed to update' });
371
+ }
372
+ }
373
+
374
+ return results;
375
+ }
376
+
377
+ /**
378
+ * Update unit status if all bolts are complete
379
+ */
380
+ async function updateUnitStatus(bolt) {
381
+ const unit = bolt.frontmatter.unit;
382
+ const intent = bolt.frontmatter.intent;
383
+
384
+ // Find all bolts for this unit
385
+ const boltDirs = await fs.readdir(BOLTS_DIR).catch(() => []);
386
+ const unitBolts = [];
387
+
388
+ for (const boltDir of boltDirs) {
389
+ const boltPath = path.join(BOLTS_DIR, boltDir, 'bolt.md');
390
+ if (await fs.pathExists(boltPath)) {
391
+ const content = await fs.readFile(boltPath, 'utf8');
392
+ const frontmatter = extractFrontmatter(content);
393
+ if (frontmatter && frontmatter.unit === unit) {
394
+ unitBolts.push({
395
+ id: frontmatter.id || boltDir,
396
+ status: frontmatter.status
397
+ });
398
+ }
399
+ }
400
+ }
401
+
402
+ const allComplete = unitBolts.every(b => b.status === 'complete');
403
+
404
+ // Find unit brief
405
+ const unitBriefPath = path.join(INTENTS_DIR, intent, 'units', unit, 'unit-brief.md');
406
+
407
+ if (!await fs.pathExists(unitBriefPath)) {
408
+ console.log(`\n${colors.dim}Unit brief not found, skipping unit status update${colors.reset}`);
409
+ return { updated: false };
410
+ }
411
+
412
+ const unitBriefContent = await fs.readFile(unitBriefPath, 'utf8');
413
+ const unitBriefFrontmatter = extractFrontmatter(unitBriefContent);
414
+
415
+ if (!unitBriefFrontmatter) {
416
+ console.log(`\n${colors.dim}Invalid unit brief frontmatter, skipping${colors.reset}`);
417
+ return { updated: false };
418
+ }
419
+
420
+ const currentStatus = unitBriefFrontmatter.status || 'unknown';
421
+
422
+ if (allComplete && currentStatus !== 'complete') {
423
+ const newFrontmatter = {
424
+ ...unitBriefFrontmatter,
425
+ status: 'complete'
426
+ };
427
+ const newContent = updateFrontmatter(unitBriefContent, newFrontmatter);
428
+ if (newContent) {
429
+ await fs.writeFile(unitBriefPath, newContent, 'utf8');
430
+ console.log(`\n${colors.green}✓${colors.reset} Unit status: ${currentStatus} → complete`);
431
+ return { updated: true, from: currentStatus, to: 'complete' };
432
+ }
433
+ } else if (allComplete) {
434
+ console.log(`\n${colors.dim}Unit status already complete${colors.reset}`);
435
+ } else {
436
+ const incompleteCount = unitBolts.filter(b => b.status !== 'complete').length;
437
+ console.log(`\n${colors.dim}Unit has ${incompleteCount} incomplete bolt(s), status unchanged${colors.reset}`);
438
+ }
439
+
440
+ return { updated: false };
441
+ }
442
+
443
+ /**
444
+ * Update intent status if all units are complete
445
+ */
446
+ async function updateIntentStatus(bolt) {
447
+ const intent = bolt.frontmatter.intent;
448
+
449
+ // Find all units for this intent
450
+ const intentPath = path.join(INTENTS_DIR, intent);
451
+ const unitsDir = path.join(intentPath, 'units');
452
+
453
+ if (!await fs.pathExists(unitsDir)) {
454
+ console.log(`${colors.dim}No units directory found for intent${colors.reset}`);
455
+ return { updated: false };
456
+ }
457
+
458
+ const unitDirs = await fs.readdir(unitsDir).catch(() => []);
459
+ const allComplete = [];
460
+
461
+ for (const unitDir of unitDirs) {
462
+ const unitBriefPath = path.join(unitsDir, unitDir, 'unit-brief.md');
463
+ if (await fs.pathExists(unitBriefPath)) {
464
+ const content = await fs.readFile(unitBriefPath, 'utf8');
465
+ const frontmatter = extractFrontmatter(content);
466
+ if (frontmatter) {
467
+ allComplete.push(frontmatter.status === 'complete');
468
+ }
469
+ }
470
+ }
471
+
472
+ const unitsAllComplete = allComplete.length > 0 && allComplete.every(Boolean);
473
+
474
+ // Find requirements file
475
+ const requirementsPath = path.join(intentPath, 'requirements.md');
476
+
477
+ if (!await fs.pathExists(requirementsPath)) {
478
+ console.log(`${colors.dim}Requirements file not found, skipping intent status update${colors.reset}`);
479
+ return { updated: false };
480
+ }
481
+
482
+ const requirementsContent = await fs.readFile(requirementsPath, 'utf8');
483
+ const requirementsFrontmatter = extractFrontmatter(requirementsContent);
484
+
485
+ if (!requirementsFrontmatter) {
486
+ console.log(`${colors.dim}Invalid requirements frontmatter, skipping${colors.reset}`);
487
+ return { updated: false };
488
+ }
489
+
490
+ const currentStatus = requirementsFrontmatter.status || 'unknown';
491
+
492
+ if (unitsAllComplete && currentStatus !== 'complete') {
493
+ const newFrontmatter = {
494
+ ...requirementsFrontmatter,
495
+ status: 'complete'
496
+ };
497
+ const newContent = updateFrontmatter(requirementsContent, newFrontmatter);
498
+ if (newContent) {
499
+ await fs.writeFile(requirementsPath, newContent, 'utf8');
500
+ console.log(`${colors.green}✓${colors.reset} Intent status: ${currentStatus} → complete`);
501
+ return { updated: true, from: currentStatus, to: 'complete' };
502
+ }
503
+ } else if (unitsAllComplete) {
504
+ console.log(`${colors.dim}Intent status already complete${colors.reset}`);
505
+ } else {
506
+ const incompleteCount = allComplete.filter(c => !c).length;
507
+ console.log(`${colors.dim}Intent has ${incompleteCount} incomplete unit(s), status unchanged${colors.reset}`);
508
+ }
509
+
510
+ return { updated: false };
511
+ }
512
+
513
+ /**
514
+ * Validate bolt status before allowing completion
515
+ *
516
+ * Pre-flight checks to ensure:
517
+ * - Bolt is in "in-progress" status (can't complete already-complete or not-started bolts)
518
+ * - Bolt has not already been completed
519
+ */
520
+ function validateBoltStatus(bolt) {
521
+ const status = bolt.frontmatter.status || 'unknown';
522
+
523
+ // Cannot complete a bolt that's already complete
524
+ if (status === 'complete') {
525
+ return { valid: false, reason: 'Bolt is already complete' };
526
+ }
527
+
528
+ // Bolt should be in-progress before completing
529
+ if (status !== 'in-progress') {
530
+ return { valid: false, reason: `Bolt status is "${status}", expected "in-progress"` };
531
+ }
532
+
533
+ return { valid: true };
534
+ }
535
+
536
+ /**
537
+ * Main: Mark bolt as complete with all dependent updates
538
+ */
539
+ async function boltMarkComplete(boltId, lastStage) {
540
+ console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}`);
541
+ console.log(`${colors.bright}${colors.blue}Bolt Completion: ${boltId}${colors.reset}`);
542
+ console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}\n`);
543
+
544
+ try {
545
+ // Step 1: Read bolt file
546
+ const bolt = await readBolt(boltId);
547
+
548
+ // Step 1.5: Validate bolt status before proceeding
549
+ const validation = validateBoltStatus(bolt);
550
+ if (!validation.valid) {
551
+ console.error(`\n${colors.red}Error:${colors.reset} ${validation.reason}`);
552
+ console.error(`${colors.dim}Use bolt-status command to check current state.${colors.reset}`);
553
+ return 1;
554
+ }
555
+
556
+ console.log(`${colors.dim}Bolt: ${bolt.id}${colors.reset}`);
557
+ console.log(`${colors.dim}Intent: ${bolt.frontmatter.intent}${colors.reset}`);
558
+ console.log(`${colors.dim}Unit: ${bolt.frontmatter.unit}${colors.reset}`);
559
+ console.log(`${colors.dim}Stories: ${(bolt.frontmatter.stories || []).length}${colors.reset}\n`);
560
+
561
+ // Step 2: Update bolt file to complete status
562
+ await updateBoltStatus(bolt, lastStage);
563
+ console.log(`${colors.green}✓${colors.reset} Bolt status: ${bolt.frontmatter.status || 'in-progress'} → complete\n`);
564
+
565
+ // Step 3: Update stories
566
+ console.log(`${colors.bright}Updating stories:${colors.reset}`);
567
+ const storyResults = await updateStories(bolt);
568
+ console.log(`\n${colors.dim}Stories: ${colors.green}${storyResults.updated} updated${colors.reset}, ${colors.dim}${storyResults.skipped} skipped${colors.reset}${storyResults.errors > 0 ? `, ${colors.red}${storyResults.errors} errors${colors.reset}` : ''}\n`);
569
+
570
+ // Step 4: Update unit status
571
+ await updateUnitStatus(bolt);
572
+ console.log();
573
+
574
+ // Step 5: Update intent status
575
+ await updateIntentStatus(bolt);
576
+ console.log();
577
+
578
+ // Final summary
579
+ console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}`);
580
+ console.log(`${colors.bright}${colors.green}✓ Bolt Complete: ${boltId}${colors.reset}`);
581
+ console.log(`${colors.bright}${colors.blue}════════════════════════════════════════${colors.reset}`);
582
+
583
+ return storyResults.errors > 0 ? 1 : 0;
584
+
585
+ } catch (error) {
586
+ console.error(`\n${colors.red}Error:${colors.reset}`, error.message);
587
+ return 1;
588
+ }
589
+ }
590
+
591
+ // CLI entry point
592
+ const boltId = process.argv[2];
593
+ const lastStage = process.argv['--last-stage'] || null;
594
+
595
+ if (!boltId) {
596
+ console.error(`${colors.red}Error:${colors.reset} Bolt ID required`);
597
+ console.error(`${colors.dim}Usage: node bolt-complete.js <bolt-id> [--last-stage <stage-name>]${colors.reset}`);
598
+ process.exit(1);
599
+ }
600
+
601
+ boltMarkComplete(boltId, lastStage)
602
+ .then(exitCode => process.exit(exitCode))
603
+ .catch(error => {
604
+ console.error(`${colors.red}Error:${colors.reset}`, error.message);
605
+ process.exit(1);
606
+ });