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.
- package/README.md +214 -323
- package/bin/cli.js +21 -0
- package/flows/aidlc/README.md +372 -0
- package/flows/aidlc/agents/construction-agent.md +79 -0
- package/flows/aidlc/agents/inception-agent.md +97 -0
- package/flows/aidlc/agents/master-agent.md +61 -0
- package/flows/aidlc/agents/operations-agent.md +89 -0
- package/flows/aidlc/commands/construction-agent.md +63 -0
- package/flows/aidlc/commands/inception-agent.md +55 -0
- package/flows/aidlc/commands/master-agent.md +47 -0
- package/flows/aidlc/commands/operations-agent.md +77 -0
- package/flows/aidlc/context-config.yaml +67 -0
- package/flows/aidlc/memory-bank.yaml +104 -0
- package/flows/aidlc/quick-start.md +322 -0
- package/flows/aidlc/skills/construction/bolt-list.md +163 -0
- package/flows/aidlc/skills/construction/bolt-replan.md +345 -0
- package/flows/aidlc/skills/construction/bolt-start.md +442 -0
- package/flows/aidlc/skills/construction/bolt-status.md +185 -0
- package/flows/aidlc/skills/construction/navigator.md +196 -0
- package/flows/aidlc/skills/inception/bolt-plan.md +372 -0
- package/flows/aidlc/skills/inception/context.md +171 -0
- package/flows/aidlc/skills/inception/intent-create.md +211 -0
- package/flows/aidlc/skills/inception/intent-list.md +124 -0
- package/flows/aidlc/skills/inception/navigator.md +207 -0
- package/flows/aidlc/skills/inception/requirements.md +227 -0
- package/flows/aidlc/skills/inception/review.md +248 -0
- package/flows/aidlc/skills/inception/story-create.md +304 -0
- package/flows/aidlc/skills/inception/units.md +278 -0
- package/flows/aidlc/skills/master/analyze-context.md +239 -0
- package/flows/aidlc/skills/master/answer-question.md +141 -0
- package/flows/aidlc/skills/master/explain-flow.md +158 -0
- package/flows/aidlc/skills/master/project-init.md +281 -0
- package/flows/aidlc/skills/master/route-request.md +126 -0
- package/flows/aidlc/skills/operations/build.md +237 -0
- package/flows/aidlc/skills/operations/deploy.md +259 -0
- package/flows/aidlc/skills/operations/monitor.md +265 -0
- package/flows/aidlc/skills/operations/navigator.md +209 -0
- package/flows/aidlc/skills/operations/verify.md +224 -0
- package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt.md +3 -3
- package/{dist → flows/aidlc}/templates/construction/bolt-types/spike-bolt.md +2 -2
- package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
- package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
- package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
- package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
- package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
- package/flows/aidlc/templates/inception/project/README.md +55 -0
- package/flows/aidlc/templates/standards/catalog.yaml +345 -0
- package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
- package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
- package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
- package/lib/InstallerFactory.js +36 -0
- package/lib/analytics/env-detector.js +92 -0
- package/lib/analytics/index.js +22 -0
- package/lib/analytics/machine-id.js +33 -0
- package/lib/analytics/tracker.js +232 -0
- package/lib/cli-utils.js +342 -0
- package/lib/constants.js +32 -0
- package/lib/installer.js +402 -0
- package/lib/installers/AntigravityInstaller.js +22 -0
- package/lib/installers/ClaudeInstaller.js +85 -0
- package/lib/installers/ClineInstaller.js +21 -0
- package/lib/installers/CodexInstaller.js +21 -0
- package/lib/installers/CopilotInstaller.js +113 -0
- package/lib/installers/CursorInstaller.js +63 -0
- package/lib/installers/GeminiInstaller.js +75 -0
- package/lib/installers/KiroInstaller.js +22 -0
- package/lib/installers/OpenCodeInstaller.js +22 -0
- package/lib/installers/RooInstaller.js +22 -0
- package/lib/installers/ToolInstaller.js +73 -0
- package/lib/installers/WindsurfInstaller.js +22 -0
- package/lib/markdown-validator.ts +175 -0
- package/lib/yaml-validator.ts +99 -0
- package/package.json +105 -32
- package/scripts/artifact-validator.js +594 -0
- package/scripts/bolt-complete.js +606 -0
- package/scripts/status-integrity.js +598 -0
- package/dist/bridge/agent-runner.js +0 -190
- package/dist/bridge/connector-factory.js +0 -31
- package/dist/bridge/connectors/antigravity-connector.js +0 -18
- package/dist/bridge/connectors/cursor-connector.js +0 -31
- package/dist/bridge/connectors/in-process-connector.js +0 -29
- package/dist/bridge/connectors/vscode-connector.js +0 -31
- package/dist/bridge/connectors/windsurf-connector.js +0 -23
- package/dist/bridge/filesystem-connector.js +0 -110
- package/dist/bridge/helper.js +0 -203
- package/dist/bridge/types.js +0 -10
- package/dist/cli.js +0 -40
- package/dist/commands/ask.js +0 -259
- package/dist/commands/bridge.js +0 -88
- package/dist/commands/create.js +0 -25
- package/dist/commands/develop.js +0 -141
- package/dist/commands/doctor.js +0 -102
- package/dist/commands/flow.js +0 -301
- package/dist/commands/framework.js +0 -273
- package/dist/commands/generate.js +0 -59
- package/dist/commands/install.js +0 -100
- package/dist/commands/pack.js +0 -33
- package/dist/commands/phase.js +0 -38
- package/dist/commands/run.js +0 -199
- package/dist/commands/status.js +0 -114
- package/dist/commands/uninstall.js +0 -14
- package/dist/commands/use.js +0 -20
- package/dist/commands/validate.js +0 -102
- package/dist/framework/framework-loader.js +0 -97
- package/dist/framework/framework-paths.js +0 -48
- package/dist/framework/framework-types.js +0 -15
- package/dist/iris/artifact-checker.js +0 -78
- package/dist/iris/artifacts/config.js +0 -68
- package/dist/iris/artifacts/generator.js +0 -88
- package/dist/iris/artifacts/types.js +0 -1
- package/dist/iris/bundle.js +0 -44
- package/dist/iris/doctrine/collector.js +0 -124
- package/dist/iris/fixer.js +0 -149
- package/dist/iris/flows/manifest.js +0 -124
- package/dist/iris/framework-context.js +0 -49
- package/dist/iris/framework-manager.js +0 -215
- package/dist/iris/fs/atomic.js +0 -22
- package/dist/iris/guard.js +0 -38
- package/dist/iris/importers/index.js +0 -9
- package/dist/iris/importers/types.js +0 -8
- package/dist/iris/importers/writer.js +0 -139
- package/dist/iris/include.js +0 -49
- package/dist/iris/installer.js +0 -334
- package/dist/iris/interactive/env.js +0 -21
- package/dist/iris/interactive/intent-interview.js +0 -345
- package/dist/iris/interactive/intent-schema.js +0 -28
- package/dist/iris/interactive/interview-io.js +0 -22
- package/dist/iris/interview/config.js +0 -71
- package/dist/iris/interview/types.js +0 -16
- package/dist/iris/interview/utils.js +0 -38
- package/dist/iris/manifest.js +0 -54
- package/dist/iris/packer.js +0 -325
- package/dist/iris/parsers/unit-parser.js +0 -43
- package/dist/iris/paths.js +0 -18
- package/dist/iris/policy.js +0 -133
- package/dist/iris/proc.js +0 -56
- package/dist/iris/report.js +0 -53
- package/dist/iris/resolver.js +0 -66
- package/dist/iris/router.js +0 -114
- package/dist/iris/routes.js +0 -189
- package/dist/iris/run-state.js +0 -146
- package/dist/iris/state.js +0 -113
- package/dist/iris/templates.js +0 -70
- package/dist/iris/tmp.js +0 -24
- package/dist/iris/uninstaller.js +0 -181
- package/dist/iris/utils/interpolate.js +0 -42
- package/dist/iris/validator.js +0 -391
- package/dist/iris/workflow/config.js +0 -51
- package/dist/iris/workflow/engine.js +0 -129
- package/dist/iris/workflow/steps.js +0 -448
- package/dist/iris/workflow/types.js +0 -1
- package/dist/iris_bundle/frameworks/iris-core/framework.yaml +0 -9
- package/dist/iris_bundle/frameworks/iris-core/memory/memory-bank.yaml +0 -1
- package/dist/iris_bundle/frameworks/iris-core/policy.yaml +0 -7
- package/dist/iris_bundle/frameworks/iris-core/templates/config/memory-bank.yaml +0 -1
- package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/spike-bolt.md +0 -240
- package/dist/lib.js +0 -96
- package/dist/templates/construction/bolt-template.md +0 -226
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -49
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -55
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -67
- package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -62
- package/dist/templates/construction/bolt-types/ddd-construction-bolt.md +0 -528
- package/dist/templates/construction/bolt-types/simple-construction-bolt.md +0 -347
- package/dist/templates/inception/requirements-template.md +0 -144
- package/dist/templates/inception/stories-template.md +0 -38
- package/dist/templates/inception/story-template.md +0 -147
- package/dist/templates/inception/system-context-template.md +0 -29
- package/dist/templates/inception/unit-brief-template.md +0 -177
- package/dist/templates/inception/units-template.md +0 -52
- package/dist/utils/exit-codes.js +0 -7
- package/dist/utils/logo.js +0 -17
- package/dist/workflows/bolt-execution.js +0 -238
- package/dist/workflows/bolt-plan.js +0 -221
- package/dist/workflows/intent-inception.js +0 -285
- package/dist/workflows/memory-bank-generator.js +0 -180
- package/dist/workflows/reporting.js +0 -74
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/construction/bolt-types/simple-construction-bolt.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/requirements-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/stories-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/story-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/system-context-template.md +0 -0
- /package/{dist/iris_bundle/frameworks/iris-core → flows/aidlc}/templates/inception/unit-brief-template.md +0 -0
- /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
|
+
});
|