project-iris 0.0.6

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/README.md +384 -0
  2. package/dist/bridge/connector-factory.js +27 -0
  3. package/dist/bridge/connectors/antigravity-connector.js +18 -0
  4. package/dist/bridge/connectors/cursor-connector.js +31 -0
  5. package/dist/bridge/connectors/vscode-connector.js +31 -0
  6. package/dist/bridge/connectors/windsurf-connector.js +23 -0
  7. package/dist/bridge/filesystem-connector.js +100 -0
  8. package/dist/bridge/types.js +10 -0
  9. package/dist/cli.js +30 -0
  10. package/dist/commands/ask.js +232 -0
  11. package/dist/commands/bridge.js +259 -0
  12. package/dist/commands/develop.js +108 -0
  13. package/dist/commands/doctor.js +102 -0
  14. package/dist/commands/install.js +57 -0
  15. package/dist/commands/pack.js +27 -0
  16. package/dist/commands/phase.js +38 -0
  17. package/dist/commands/run.js +17 -0
  18. package/dist/commands/status.js +105 -0
  19. package/dist/commands/uninstall.js +12 -0
  20. package/dist/commands/validate.js +87 -0
  21. package/dist/iris/artifact-checker.js +78 -0
  22. package/dist/iris/fixer.js +143 -0
  23. package/dist/iris/guard.js +38 -0
  24. package/dist/iris/include.js +49 -0
  25. package/dist/iris/installer.js +269 -0
  26. package/dist/iris/manifest.js +54 -0
  27. package/dist/iris/packer.js +303 -0
  28. package/dist/iris/policy.js +28 -0
  29. package/dist/iris/report.js +53 -0
  30. package/dist/iris/resolver.js +63 -0
  31. package/dist/iris/router.js +114 -0
  32. package/dist/iris/routes.js +20 -0
  33. package/dist/iris/run-state.js +143 -0
  34. package/dist/iris/state.js +85 -0
  35. package/dist/iris/uninstaller.js +166 -0
  36. package/dist/iris/validator.js +329 -0
  37. package/dist/lib.js +96 -0
  38. package/dist/utils/exit-codes.js +7 -0
  39. package/dist/workflows/bolt-execution.js +238 -0
  40. package/dist/workflows/bolt-plan.js +192 -0
  41. package/dist/workflows/intent-inception.js +188 -0
  42. package/package.json +41 -0
  43. package/src/iris_bundle/.iris/aidlc/README.md +16 -0
  44. package/src/iris_bundle/.iris/aidlc/agents/iris-construction-agent.md +35 -0
  45. package/src/iris_bundle/.iris/aidlc/agents/iris-inception-agent.md +30 -0
  46. package/src/iris_bundle/.iris/aidlc/agents/iris-master-agent.md +35 -0
  47. package/src/iris_bundle/.iris/aidlc/agents/iris-operations-agent.md +29 -0
  48. package/src/iris_bundle/.iris/aidlc/commands/iris-construction-agent.md +18 -0
  49. package/src/iris_bundle/.iris/aidlc/commands/iris-inception-agent.md +18 -0
  50. package/src/iris_bundle/.iris/aidlc/commands/iris-master-agent.md +18 -0
  51. package/src/iris_bundle/.iris/aidlc/commands/iris-operations-agent.md +18 -0
  52. package/src/iris_bundle/.iris/aidlc/context/context-map.md +25 -0
  53. package/src/iris_bundle/.iris/aidlc/context/exclusion-rules.md +13 -0
  54. package/src/iris_bundle/.iris/aidlc/context/load-order.md +25 -0
  55. package/src/iris_bundle/.iris/aidlc/memory/intent-rules.md +9 -0
  56. package/src/iris_bundle/.iris/aidlc/memory/log-rules.md +5 -0
  57. package/src/iris_bundle/.iris/aidlc/memory/memory-bank.yaml +39 -0
  58. package/src/iris_bundle/.iris/aidlc/memory/unit-rules.md +9 -0
  59. package/src/iris_bundle/.iris/aidlc/quick-start.md +24 -0
  60. package/src/iris_bundle/.iris/aidlc/skills/execution/implementation.md +14 -0
  61. package/src/iris_bundle/.iris/aidlc/skills/execution/refactoring.md +13 -0
  62. package/src/iris_bundle/.iris/aidlc/skills/execution/scaffold-generation.md +15 -0
  63. package/src/iris_bundle/.iris/aidlc/skills/governance/escalation.md +13 -0
  64. package/src/iris_bundle/.iris/aidlc/skills/governance/quality-gates.md +14 -0
  65. package/src/iris_bundle/.iris/aidlc/skills/governance/stop-conditions.md +11 -0
  66. package/src/iris_bundle/.iris/aidlc/skills/reasoning/decomposition.md +23 -0
  67. package/src/iris_bundle/.iris/aidlc/skills/reasoning/risk-analysis.md +14 -0
  68. package/src/iris_bundle/.iris/aidlc/skills/reasoning/verification.md +21 -0
  69. package/src/iris_bundle/.iris/aidlc/standards/artifacts-registry.md +38 -0
  70. package/src/iris_bundle/.iris/aidlc/standards/decision-logging.md +16 -0
  71. package/src/iris_bundle/.iris/aidlc/standards/doctrine-structure.md +31 -0
  72. package/src/iris_bundle/.iris/aidlc/standards/documentation-rules.md +15 -0
  73. package/src/iris_bundle/.iris/aidlc/standards/file-structure.md +21 -0
  74. package/src/iris_bundle/.iris/aidlc/standards/naming-conventions.md +18 -0
  75. package/src/iris_bundle/.iris/aidlc/standards/phases-and-gates.md +25 -0
  76. package/src/iris_bundle/.iris/aidlc/standards/routes-and-routing.md +35 -0
  77. package/src/iris_bundle/.iris/aidlc/standards/tool-wrappers.md +32 -0
  78. package/src/iris_bundle/.iris/aidlc/templates/bolt.md +23 -0
  79. package/src/iris_bundle/.iris/aidlc/templates/doctrine-doc-template.md +33 -0
  80. package/src/iris_bundle/.iris/aidlc/templates/intent.md +23 -0
  81. package/src/iris_bundle/.iris/aidlc/templates/log.md +24 -0
  82. package/src/iris_bundle/.iris/aidlc/templates/review.md +21 -0
  83. package/src/iris_bundle/.iris/aidlc/templates/unit.md +31 -0
  84. package/src/iris_bundle/.iris/aidlc/validation/failure-modes.md +16 -0
  85. package/src/iris_bundle/.iris/aidlc/validation/phase-preconditions.md +21 -0
  86. package/src/iris_bundle/.iris/aidlc/validation/quality-checklist.md +20 -0
  87. package/src/iris_bundle/.iris/policy.yaml +27 -0
  88. package/src/iris_bundle/.iris/routes.yaml +98 -0
  89. package/src/iris_bundle/.iris/state.yaml +7 -0
  90. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/knowledge/IRIS.md +6 -0
  91. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-construction-agent.md +25 -0
  92. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-inception-agent.md +25 -0
  93. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-master-agent.md +25 -0
  94. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-operations-agent.md +25 -0
  95. package/src/iris_bundle/.iris/tools/claude/.claude/claude.md +9 -0
  96. package/src/iris_bundle/.iris/tools/claude/.claude/commands/compare-specs.md +203 -0
  97. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-construction-agent.md +25 -0
  98. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-inception-agent.md +25 -0
  99. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-master-agent.md +25 -0
  100. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-operations-agent.md +25 -0
  101. package/src/iris_bundle/.iris/tools/codex/AGENTS.md +15 -0
  102. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-construction-agent.md +25 -0
  103. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-inception-agent.md +25 -0
  104. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-master-agent.md +25 -0
  105. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-operations-agent.md +25 -0
  106. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-construction-agent.toml +29 -0
  107. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-inception-agent.toml +29 -0
  108. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-master-agent.toml +29 -0
  109. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-operations-agent.toml +29 -0
@@ -0,0 +1,329 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import matter from "gray-matter";
4
+ import { loadPolicy } from "./policy.js";
5
+ import { loadState, saveState } from "./state.js";
6
+ import { checkArtifact } from "./artifact-checker.js";
7
+ // Force specific phases to be valid per context? Or just trust policy? context says "Valid Phases: inception, construction, operations"
8
+ // We will rely on policy.yaml for configuration but code logic handles general cases.
9
+ export async function validate(options) {
10
+ const shouldWrite = options.writeBack !== false;
11
+ // Step A: Load & validate config
12
+ const policy = loadPolicy();
13
+ const state = loadState();
14
+ const errors = [];
15
+ // Step B: Resolve target phase
16
+ let targetPhase = state.phase.current;
17
+ if (options.phase) {
18
+ targetPhase = options.phase;
19
+ }
20
+ // Check if phase exists in policy
21
+ if (!policy.phases[targetPhase]) {
22
+ errors.push({
23
+ code: "UNKNOWN_PHASE",
24
+ message: `Target phase '${targetPhase}' does not exist in policy.`,
25
+ severity: "error"
26
+ });
27
+ return finalizeResult(errors, state, false, shouldWrite);
28
+ }
29
+ // Step C: Required artifacts (FAIL FAST)
30
+ // Gather requirements for the phase
31
+ const requirements = policy.phases[targetPhase].requires || [];
32
+ for (const req of requirements) {
33
+ const check = checkArtifact(process.cwd(), req.path, req.type);
34
+ if (!check.exists) {
35
+ errors.push({
36
+ code: "MISSING_ARTIFACT",
37
+ path: check.normalized,
38
+ message: `Required ${req.type} missing: ${check.normalized}`,
39
+ remediation: req.template
40
+ ? `Create from template: ${req.template}`
41
+ : `Create ${req.type} at ${check.normalized}`,
42
+ template: req.template,
43
+ artifactType: req.type,
44
+ severity: "error"
45
+ });
46
+ }
47
+ else {
48
+ // Validate type matches
49
+ if (req.type === "directory" && !check.isDirectory) {
50
+ errors.push({
51
+ code: "INVALID_TYPE",
52
+ path: check.normalized,
53
+ message: `Expected directory but found file: ${check.normalized}`,
54
+ remediation: "Remove file and create directory",
55
+ severity: "error"
56
+ });
57
+ }
58
+ else if (req.type === "file" && !check.isFile) {
59
+ errors.push({
60
+ code: "INVALID_TYPE",
61
+ path: check.normalized,
62
+ message: `Expected file but found directory: ${check.normalized}`,
63
+ remediation: "Remove directory and create file",
64
+ severity: "error"
65
+ });
66
+ }
67
+ else if (req.type === "file") {
68
+ // Content Quality Check (Warnings)
69
+ const qualityWarnings = checkContentQuality(check.absolutePath);
70
+ errors.push(...qualityWarnings);
71
+ }
72
+ }
73
+ }
74
+ // Fail fast if strict artifact check fails?
75
+ // Specs say "Missing artifacts -> fail fast errors".
76
+ // Usually means return immediately or don't proceed to gates?
77
+ // Let's collect all missing artifacts first, but if ANY are missing, we probably shouldn't check gates that rely on them.
78
+ if (errors.length > 0) {
79
+ return finalizeResult(errors, state, false, shouldWrite);
80
+ }
81
+ // Optional Structure Check: Units
82
+ // IF units/ exists under any intent, IT MUST contain unit-brief.md
83
+ checkOptionalUnits(errors);
84
+ if (errors.length > 0) {
85
+ return finalizeResult(errors, state, false, shouldWrite);
86
+ }
87
+ // Step D: Gate checks (HARD ERRORS)
88
+ // "Support frontmatter gates"
89
+ const gates = policy.phases[targetPhase].gates || [];
90
+ for (const gate of gates) {
91
+ if (gate.kind === "frontmatter") {
92
+ const gateError = checkFrontmatterGate(gate);
93
+ if (gateError) {
94
+ errors.push(gateError);
95
+ }
96
+ }
97
+ else {
98
+ console.warn(`Unknown gate kind: ${gate.kind}`);
99
+ }
100
+ }
101
+ if (errors.length > 0) {
102
+ return finalizeResult(errors, state, false, shouldWrite);
103
+ }
104
+ // Step E: Phase transition enforcement
105
+ let applying = false;
106
+ // Only check transition if we have a requested phase and we are NOT just probing a specific phase with --phase
107
+ if (state.phase.requested && !options.phase) {
108
+ // Validate transition
109
+ const from = state.phase.current;
110
+ const to = state.phase.requested;
111
+ const transition = policy.transitions.find(t => t.from === from && t.to === to);
112
+ if (!transition) {
113
+ errors.push({
114
+ code: "INVALID_TRANSITION",
115
+ message: `Transition from '${from}' to '${to}' is not defined in policy.`,
116
+ remediation: "Reset requested phase or update policy.",
117
+ severity: "error"
118
+ });
119
+ }
120
+ else {
121
+ // Validate requires_gates[] for transition
122
+ // These reference gates defined in the phase? OR are they ids?
123
+ // "Validate requires_gates[] for transition" implies the transition object has a list of gate IDs that must pass.
124
+ // But where are these gates defined? Usually gates are on the phase.
125
+ // Interpretation: Check if the gates identified by ID in the CURRENT phase (or destination?) are passed?
126
+ // "If transition not allowed -> ERROR"
127
+ // "Validate requires_gates[] for transition" -> "If any gate fails -> ERROR"
128
+ // Let's assume gates are identified by 'id' in policy.phases[...].gates
129
+ // AND the transition rules apply to the CURRENT state config or the TARGET?
130
+ // Usually transition gates check if you are ready to LEAVE 'from' or ENTER 'to'.
131
+ // Given "inception.intent.approved", it likely checks the state of the *current* phase artifacts to allow moving forward.
132
+ if (transition.requires_gates) {
133
+ // Find these gates. They could be in the 'from' phase or 'to' phase.
134
+ // A safe bet is to search all defined gates in 'from' phase, or global?
135
+ // The snippet shows: id: "inception.intent.approved" -> implies inception phase.
136
+ // We need to look up these gates.
137
+ // NOTE: We only ran checks for 'targetPhase' above.
138
+ // If targetPhase == current, we checked current.
139
+ // If targetPhase == requested (via --phase), we checked requested.
140
+ // Transition logic is: checking if we can move FROM current TO requested.
141
+ // So we should validate gates required by the transition.
142
+ for (const gateId of transition.requires_gates) {
143
+ // Find the gate definition.
144
+ // We search in 'from' phase first, then 'to' phase?
145
+ // Or just iterate all phases?
146
+ let foundGate;
147
+ // Optimization: check 'from' first
148
+ if (policy.phases[from]?.gates) {
149
+ foundGate = policy.phases[from].gates?.find(g => g.id === gateId);
150
+ }
151
+ // check 'to' next
152
+ if (!foundGate && policy.phases[to]?.gates) {
153
+ foundGate = policy.phases[to].gates?.find(g => g.id === gateId);
154
+ }
155
+ if (!foundGate) {
156
+ errors.push({
157
+ code: "MISSING_GATE_DEF",
158
+ message: `Transition requires gate '${gateId}' but it is not found in '${from}' or '${to}' phases.`,
159
+ severity: "error"
160
+ });
161
+ continue;
162
+ }
163
+ // Run the gate check
164
+ if (foundGate.kind === "frontmatter") {
165
+ const gateError = checkFrontmatterGate(foundGate);
166
+ if (gateError) {
167
+ errors.push(gateError);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ // Step F: Apply transition (--apply)
175
+ // Only apply if we are allowed to write back
176
+ if (shouldWrite && errors.length === 0 && options.apply && state.phase.requested && !options.phase) {
177
+ // Valid and apply requested
178
+ // Safety: ensure transition valid again? (already done in Step E if we had errors, it would fail)
179
+ // If we are here, Step E passed.
180
+ state.phase.current = state.phase.requested;
181
+ state.phase.requested = null; // Clear request
182
+ applying = true;
183
+ }
184
+ return finalizeResult(errors, state, applying, shouldWrite);
185
+ }
186
+ function checkFrontmatterGate(gate) {
187
+ const filePath = path.join(process.cwd(), gate.file);
188
+ if (!fs.existsSync(filePath)) {
189
+ return {
190
+ code: "GATE_FILE_MISSING",
191
+ path: gate.file,
192
+ message: `Gate '${gate.id}' failed: File missing`,
193
+ remediation: `Create ${gate.file}`,
194
+ severity: "error"
195
+ };
196
+ }
197
+ try {
198
+ const content = fs.readFileSync(filePath, 'utf8');
199
+ const parsed = matter(content);
200
+ if (!parsed.data) {
201
+ return {
202
+ code: "GATE_NO_FRONTMATTER",
203
+ path: gate.file,
204
+ message: `Gate '${gate.id}' failed: No frontmatter found`,
205
+ remediation: "Add frontmatter variables",
206
+ severity: "error"
207
+ };
208
+ }
209
+ if (gate.field) {
210
+ const val = parsed.data[gate.field];
211
+ if (val === undefined) {
212
+ return {
213
+ code: "GATE_FIELD_MISSING",
214
+ path: gate.file,
215
+ message: `Gate '${gate.id}' failed: Field '${gate.field}' missing`,
216
+ remediation: `Add '${gate.field}' to frontmatter`,
217
+ severity: "error"
218
+ };
219
+ }
220
+ if (gate.equals !== undefined && val !== gate.equals) {
221
+ return {
222
+ code: "GATE_VALUE_MISMATCH",
223
+ path: gate.file,
224
+ message: `Gate '${gate.id}' failed: Field '${gate.field}' is '${val}', expected '${gate.equals}'`,
225
+ remediation: `Set '${gate.field}: ${gate.equals}' in ${gate.file}`,
226
+ severity: "error"
227
+ };
228
+ }
229
+ }
230
+ return null;
231
+ }
232
+ catch (e) {
233
+ return {
234
+ code: "GATE_PARSE_ERROR",
235
+ path: gate.file,
236
+ message: `Gate '${gate.id}' failed: Error parsing file: ${e}`,
237
+ severity: "error"
238
+ };
239
+ }
240
+ }
241
+ function checkOptionalUnits(errors) {
242
+ const intentsDir = path.join(process.cwd(), "memory-bank", "intents");
243
+ if (!fs.existsSync(intentsDir))
244
+ return;
245
+ try {
246
+ const intents = fs.readdirSync(intentsDir).filter(f => fs.statSync(path.join(intentsDir, f)).isDirectory());
247
+ for (const intent of intents) {
248
+ const unitsDir = path.join(intentsDir, intent, "units");
249
+ if (fs.existsSync(unitsDir) && fs.statSync(unitsDir).isDirectory()) {
250
+ // Units folder exists, check contents
251
+ const units = fs.readdirSync(unitsDir).filter(f => fs.statSync(path.join(unitsDir, f)).isDirectory());
252
+ for (const unit of units) {
253
+ const briefPath = path.join(unitsDir, unit, "unit-brief.md");
254
+ if (!fs.existsSync(briefPath)) {
255
+ const relPath = path.relative(process.cwd(), briefPath);
256
+ errors.push({
257
+ code: "MISSING_ARTIFACT",
258
+ path: relPath,
259
+ message: `Unit '${unit}' requires unit-brief.md`,
260
+ remediation: "Create from template",
261
+ template: ".iris/aidlc/templates/unit-brief.md",
262
+ artifactType: "file",
263
+ severity: "error"
264
+ });
265
+ }
266
+ else {
267
+ // Check quality of unit brief too
268
+ errors.push(...checkContentQuality(briefPath));
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+ catch (e) {
275
+ // Ignore read errors
276
+ }
277
+ }
278
+ function checkContentQuality(filePath) {
279
+ const warnings = [];
280
+ try {
281
+ const content = fs.readFileSync(filePath, "utf8");
282
+ const relativePath = path.relative(process.cwd(), filePath);
283
+ // 1. Effective emptiness
284
+ if (content.trim().length < 20) {
285
+ warnings.push({
286
+ code: "QUALITY_EMPTY",
287
+ path: relativePath,
288
+ message: `File is effectively empty (<20 chars): ${relativePath}`,
289
+ severity: "warning",
290
+ remediation: "Add meaningful content"
291
+ });
292
+ }
293
+ // 2. Placeholders
294
+ const placeholderRegex = /\b(TODO|FIXME|TBD)\b|<[^>]+>/;
295
+ if (placeholderRegex.test(content)) {
296
+ warnings.push({
297
+ code: "QUALITY_PLACEHOLDER",
298
+ path: relativePath,
299
+ message: `File contains placeholders (TODO/FIXME/TBD/<...>): ${relativePath}`,
300
+ severity: "warning",
301
+ remediation: "Replace placeholders with actual content"
302
+ });
303
+ }
304
+ }
305
+ catch (e) {
306
+ // Ignore read errors
307
+ }
308
+ return warnings;
309
+ }
310
+ function finalizeResult(errors, state, applied, writeBack) {
311
+ const hasErrors = errors.some(e => e.severity === "error");
312
+ const hasWarnings = errors.some(e => e.severity === "warning");
313
+ const isValid = !hasErrors;
314
+ if (writeBack) {
315
+ state.last_validation.at = new Date().toISOString();
316
+ state.last_validation.result = isValid ? "valid" : "invalid";
317
+ saveState(state);
318
+ }
319
+ return {
320
+ valid: isValid,
321
+ warnings: hasWarnings,
322
+ phase: {
323
+ current: state.phase.current,
324
+ requested: state.phase.requested,
325
+ applied: applied
326
+ },
327
+ errors: errors
328
+ };
329
+ }
package/dist/lib.js ADDED
@@ -0,0 +1,96 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ export function repoRoot() {
5
+ return process.cwd();
6
+ }
7
+ export function ensureDir(p) {
8
+ fs.mkdirSync(p, { recursive: true });
9
+ }
10
+ export function copyDir(src, dst) {
11
+ ensureDir(dst);
12
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
13
+ const s = path.join(src, entry.name);
14
+ const d = path.join(dst, entry.name);
15
+ if (entry.isDirectory())
16
+ copyDir(s, d);
17
+ else if (entry.isSymbolicLink()) {
18
+ const link = fs.readlinkSync(s);
19
+ try {
20
+ fs.symlinkSync(link, d);
21
+ }
22
+ catch { /* ignore */ }
23
+ }
24
+ else {
25
+ ensureDir(path.dirname(d));
26
+ fs.copyFileSync(s, d);
27
+ }
28
+ }
29
+ }
30
+ export function removeDir(p) {
31
+ if (!fs.existsSync(p))
32
+ return;
33
+ fs.rmSync(p, { recursive: true, force: true });
34
+ }
35
+ export function writeFile(p, content) {
36
+ ensureDir(path.dirname(p));
37
+ fs.writeFileSync(p, content, "utf8");
38
+ }
39
+ /**
40
+ * Sanitize a folder name to be a valid npm package name
41
+ */
42
+ export function sanitizeFolderName(name) {
43
+ return name
44
+ .toLowerCase()
45
+ .replace(/[^a-z0-9-_]/g, "-")
46
+ .replace(/^-+|-+$/g, "")
47
+ .replace(/-+/g, "-")
48
+ || "my-project";
49
+ }
50
+ /**
51
+ * Check if npm is available in PATH
52
+ */
53
+ export function hasNpm() {
54
+ try {
55
+ const result = spawn("npm", ["--version"], { stdio: "ignore" });
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ /**
63
+ * Spawn a command asynchronously and return the result
64
+ */
65
+ export function spawnAsync(command, args, cwd) {
66
+ return new Promise((resolve) => {
67
+ const proc = spawn(command, args, {
68
+ cwd,
69
+ stdio: ["inherit", "pipe", "pipe"],
70
+ shell: true
71
+ });
72
+ let stdout = "";
73
+ let stderr = "";
74
+ if (proc.stdout) {
75
+ proc.stdout.on("data", (data) => {
76
+ const text = data.toString();
77
+ stdout += text;
78
+ process.stdout.write(text);
79
+ });
80
+ }
81
+ if (proc.stderr) {
82
+ proc.stderr.on("data", (data) => {
83
+ const text = data.toString();
84
+ stderr += text;
85
+ process.stderr.write(text);
86
+ });
87
+ }
88
+ proc.on("close", (code) => {
89
+ resolve({ code: code ?? 0, stdout, stderr });
90
+ });
91
+ proc.on("error", (err) => {
92
+ stderr += err.message;
93
+ resolve({ code: 1, stdout, stderr });
94
+ });
95
+ });
96
+ }
@@ -0,0 +1,7 @@
1
+ export const EXIT_CODES = {
2
+ SUCCESS: 0,
3
+ INVALID: 2, // Missing artifacts, failed gates, invalid transition
4
+ VALID_WITH_WARNINGS: 3,
5
+ POLICY_ERROR: 4, // Policy invalid
6
+ STATE_ERROR: 5, // State invalid/unwritable
7
+ };
@@ -0,0 +1,238 @@
1
+ import { randomUUID } from "crypto";
2
+ import inquirer from "inquirer";
3
+ import kleur from "kleur";
4
+ import { WorkflowStage } from "../bridge/types.js";
5
+ import { appendEvent, saveRun } from "../iris/run-state.js";
6
+ /**
7
+ * Execute Bolt Execution stage
8
+ * For each bolt:
9
+ * 1. Design notes (optional gate)
10
+ * 2. Implementation
11
+ * 3. Testing
12
+ * 4. Gate after implementation
13
+ */
14
+ export async function executeBoltExecution(runState, connector, options) {
15
+ console.log(kleur.bold("Bolt Execution Stage"));
16
+ console.log(kleur.dim(`Executing ${runState.bolts.length} bolt(s)\n`));
17
+ // Execute each bolt in order
18
+ while (runState.currentBoltIndex < runState.bolts.length) {
19
+ const bolt = runState.bolts[runState.currentBoltIndex];
20
+ console.log(kleur.bold(kleur.cyan(`\n=== Bolt ${runState.currentBoltIndex + 1}/${runState.bolts.length}: ${bolt.id} ===\n`)));
21
+ bolt.status = "in_progress";
22
+ saveRun(runState);
23
+ try {
24
+ await executeSingleBolt(runState, bolt, connector, options);
25
+ bolt.status = "completed";
26
+ runState.currentBoltIndex++;
27
+ saveRun(runState);
28
+ console.log(kleur.green(`✓ Bolt "${bolt.id}" completed\n`));
29
+ }
30
+ catch (error) {
31
+ console.error(kleur.red(`✗ Bolt "${bolt.id}" failed: ${error.message}\n`));
32
+ bolt.status = "failed";
33
+ saveRun(runState);
34
+ throw error;
35
+ }
36
+ }
37
+ // All bolts complete
38
+ console.log(kleur.bold(kleur.green("\n✓ All bolts executed successfully!")));
39
+ runState.stage = WorkflowStage.DONE;
40
+ saveRun(runState);
41
+ }
42
+ async function executeSingleBolt(runState, bolt, connector, options) {
43
+ // Step 1: Design (optional)
44
+ console.log(kleur.cyan("→ Design phase..."));
45
+ const designTaskId = randomUUID();
46
+ const designPacket = {
47
+ taskId: designTaskId,
48
+ intent: runState.intent,
49
+ stage: WorkflowStage.BOLT_EXECUTION,
50
+ agent: "iris-construction-agent",
51
+ instructions: `Design phase for bolt: ${bolt.id}
52
+
53
+ Review the bolt file: memory-bank/operations/bolts/${bolt.id}.md
54
+
55
+ Create design notes covering:
56
+ - Domain model (if applicable)
57
+ - Technical approach
58
+ - Key decisions (ADRs if needed)
59
+ - File structure
60
+
61
+ You may create design notes inline in the bolt file or in separate files.
62
+ This is optional but recommended for complex bolts.`,
63
+ inputs: [
64
+ `memory-bank/operations/bolts/${bolt.id}.md`,
65
+ ...runState.artifacts
66
+ ],
67
+ expectedOutputs: [],
68
+ metadata: { step: "design", boltId: bolt.id }
69
+ };
70
+ appendEvent(runState.runId, {
71
+ stage: WorkflowStage.BOLT_EXECUTION,
72
+ boltId: bolt.id,
73
+ action: "Design phase started",
74
+ taskId: designTaskId
75
+ });
76
+ if (!options.noBridge) {
77
+ await connector.sendTask(designPacket);
78
+ console.log(kleur.dim(` Task sent: ${designTaskId}`));
79
+ console.log(kleur.dim(" Waiting for design notes...\n"));
80
+ const designResult = await connector.waitResult(designTaskId, {
81
+ timeoutMs: 600000 // 10 minutes
82
+ });
83
+ if (designResult.status === "ok") {
84
+ console.log(kleur.green("✓ Design complete"));
85
+ if (designResult.filesChanged) {
86
+ designResult.filesChanged.forEach(file => console.log(kleur.dim(` - ${file}`)));
87
+ }
88
+ console.log("");
89
+ bolt.designCompleted = true;
90
+ appendEvent(runState.runId, {
91
+ stage: WorkflowStage.BOLT_EXECUTION,
92
+ boltId: bolt.id,
93
+ action: "Design completed",
94
+ outputs: designResult.filesChanged,
95
+ resultStatus: "ok"
96
+ });
97
+ }
98
+ }
99
+ else {
100
+ console.log(kleur.yellow(" Bridge disabled. Skipping design phase.\n"));
101
+ }
102
+ // Step 2: Implementation
103
+ console.log(kleur.cyan("→ Implementation phase..."));
104
+ const implTaskId = randomUUID();
105
+ const implPacket = {
106
+ taskId: implTaskId,
107
+ intent: runState.intent,
108
+ stage: WorkflowStage.BOLT_EXECUTION,
109
+ agent: "iris-construction-agent",
110
+ instructions: `Implementation phase for bolt: ${bolt.id}
111
+
112
+ Review:
113
+ - Bolt file: memory-bank/operations/bolts/${bolt.id}.md
114
+ - Intent artifacts in memory-bank/intents/
115
+ - Any design notes created
116
+
117
+ Implement all tasks listed in the bolt file.
118
+ Create/modify code files as needed.
119
+ Follow the project's coding standards.
120
+
121
+ Mark tasks as complete in the bolt file as you finish them.`,
122
+ inputs: [
123
+ `memory-bank/operations/bolts/${bolt.id}.md`,
124
+ ...runState.artifacts
125
+ ],
126
+ expectedOutputs: [],
127
+ gates: ["Implementation complete - ready for testing"],
128
+ metadata: { step: "implementation", boltId: bolt.id }
129
+ };
130
+ appendEvent(runState.runId, {
131
+ stage: WorkflowStage.BOLT_EXECUTION,
132
+ boltId: bolt.id,
133
+ action: "Implementation started",
134
+ taskId: implTaskId
135
+ });
136
+ if (!options.noBridge) {
137
+ await connector.sendTask(implPacket);
138
+ console.log(kleur.dim(` Task sent: ${implTaskId}`));
139
+ console.log(kleur.dim(" Waiting for implementation...\n"));
140
+ const implResult = await connector.waitResult(implTaskId, {
141
+ timeoutMs: 1200000 // 20 minutes
142
+ });
143
+ if (implResult.status === "error") {
144
+ throw new Error(implResult.error || "Implementation failed");
145
+ }
146
+ console.log(kleur.green("✓ Implementation complete"));
147
+ if (implResult.filesChanged) {
148
+ implResult.filesChanged.forEach(file => console.log(kleur.dim(` - ${file}`)));
149
+ }
150
+ console.log("");
151
+ bolt.implementationCompleted = true;
152
+ appendEvent(runState.runId, {
153
+ stage: WorkflowStage.BOLT_EXECUTION,
154
+ boltId: bolt.id,
155
+ action: "Implementation completed",
156
+ outputs: implResult.filesChanged,
157
+ resultStatus: "ok"
158
+ });
159
+ }
160
+ else {
161
+ console.log(kleur.yellow(" Bridge disabled. Skipping implementation.\n"));
162
+ }
163
+ // Step 3: Testing
164
+ console.log(kleur.cyan("→ Testing phase..."));
165
+ const testTaskId = randomUUID();
166
+ const testPacket = {
167
+ taskId: testTaskId,
168
+ intent: runState.intent,
169
+ stage: WorkflowStage.BOLT_EXECUTION,
170
+ agent: "iris-construction-agent",
171
+ instructions: `Testing phase for bolt: ${bolt.id}
172
+
173
+ Run tests or propose test commands for the implemented changes.
174
+
175
+ If tests exist, run them and report results.
176
+ If no tests exist, propose what should be tested and expected outcomes.
177
+
178
+ Include:
179
+ - Test commands run
180
+ - Test results
181
+ - Any issues found`,
182
+ inputs: [
183
+ `memory-bank/operations/bolts/${bolt.id}.md`
184
+ ],
185
+ expectedOutputs: [],
186
+ metadata: { step: "testing", boltId: bolt.id }
187
+ };
188
+ appendEvent(runState.runId, {
189
+ stage: WorkflowStage.BOLT_EXECUTION,
190
+ boltId: bolt.id,
191
+ action: "Testing started",
192
+ taskId: testTaskId
193
+ });
194
+ if (!options.noBridge) {
195
+ await connector.sendTask(testPacket);
196
+ console.log(kleur.dim(` Task sent: ${testTaskId}`));
197
+ console.log(kleur.dim(" Waiting for test results...\n"));
198
+ const testResult = await connector.waitResult(testTaskId, {
199
+ timeoutMs: 600000 // 10 minutes
200
+ });
201
+ console.log(kleur.green("✓ Testing complete"));
202
+ console.log(kleur.dim(` ${testResult.message}`));
203
+ console.log("");
204
+ bolt.testingCompleted = true;
205
+ appendEvent(runState.runId, {
206
+ stage: WorkflowStage.BOLT_EXECUTION,
207
+ boltId: bolt.id,
208
+ action: "Testing completed",
209
+ message: testResult.message,
210
+ resultStatus: testResult.status
211
+ });
212
+ }
213
+ else {
214
+ console.log(kleur.yellow(" Bridge disabled. Skipping testing.\n"));
215
+ }
216
+ // Step 4: Gate - Approve bolt?
217
+ if (options.gate === "manual") {
218
+ console.log(kleur.bold(kleur.yellow("Gate: Bolt Completion")));
219
+ const { approved } = await inquirer.prompt([
220
+ {
221
+ type: "confirm",
222
+ name: "approved",
223
+ message: `Approve bolt "${bolt.id}" and continue?`,
224
+ default: true
225
+ }
226
+ ]);
227
+ appendEvent(runState.runId, {
228
+ stage: WorkflowStage.BOLT_EXECUTION,
229
+ boltId: bolt.id,
230
+ action: "Gate decision",
231
+ userGateDecision: approved ? "approved" : "rejected"
232
+ });
233
+ if (!approved) {
234
+ throw new Error("Bolt rejected by user");
235
+ }
236
+ console.log(kleur.green("✓ Bolt approved\n"));
237
+ }
238
+ }