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.
- package/README.md +384 -0
- package/dist/bridge/connector-factory.js +27 -0
- package/dist/bridge/connectors/antigravity-connector.js +18 -0
- package/dist/bridge/connectors/cursor-connector.js +31 -0
- package/dist/bridge/connectors/vscode-connector.js +31 -0
- package/dist/bridge/connectors/windsurf-connector.js +23 -0
- package/dist/bridge/filesystem-connector.js +100 -0
- package/dist/bridge/types.js +10 -0
- package/dist/cli.js +30 -0
- package/dist/commands/ask.js +232 -0
- package/dist/commands/bridge.js +259 -0
- package/dist/commands/develop.js +108 -0
- package/dist/commands/doctor.js +102 -0
- package/dist/commands/install.js +57 -0
- package/dist/commands/pack.js +27 -0
- package/dist/commands/phase.js +38 -0
- package/dist/commands/run.js +17 -0
- package/dist/commands/status.js +105 -0
- package/dist/commands/uninstall.js +12 -0
- package/dist/commands/validate.js +87 -0
- package/dist/iris/artifact-checker.js +78 -0
- package/dist/iris/fixer.js +143 -0
- package/dist/iris/guard.js +38 -0
- package/dist/iris/include.js +49 -0
- package/dist/iris/installer.js +269 -0
- package/dist/iris/manifest.js +54 -0
- package/dist/iris/packer.js +303 -0
- package/dist/iris/policy.js +28 -0
- package/dist/iris/report.js +53 -0
- package/dist/iris/resolver.js +63 -0
- package/dist/iris/router.js +114 -0
- package/dist/iris/routes.js +20 -0
- package/dist/iris/run-state.js +143 -0
- package/dist/iris/state.js +85 -0
- package/dist/iris/uninstaller.js +166 -0
- package/dist/iris/validator.js +329 -0
- package/dist/lib.js +96 -0
- package/dist/utils/exit-codes.js +7 -0
- package/dist/workflows/bolt-execution.js +238 -0
- package/dist/workflows/bolt-plan.js +192 -0
- package/dist/workflows/intent-inception.js +188 -0
- package/package.json +41 -0
- package/src/iris_bundle/.iris/aidlc/README.md +16 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-construction-agent.md +35 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-inception-agent.md +30 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-master-agent.md +35 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-operations-agent.md +29 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-construction-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-inception-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-master-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-operations-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/context/context-map.md +25 -0
- package/src/iris_bundle/.iris/aidlc/context/exclusion-rules.md +13 -0
- package/src/iris_bundle/.iris/aidlc/context/load-order.md +25 -0
- package/src/iris_bundle/.iris/aidlc/memory/intent-rules.md +9 -0
- package/src/iris_bundle/.iris/aidlc/memory/log-rules.md +5 -0
- package/src/iris_bundle/.iris/aidlc/memory/memory-bank.yaml +39 -0
- package/src/iris_bundle/.iris/aidlc/memory/unit-rules.md +9 -0
- package/src/iris_bundle/.iris/aidlc/quick-start.md +24 -0
- package/src/iris_bundle/.iris/aidlc/skills/execution/implementation.md +14 -0
- package/src/iris_bundle/.iris/aidlc/skills/execution/refactoring.md +13 -0
- package/src/iris_bundle/.iris/aidlc/skills/execution/scaffold-generation.md +15 -0
- package/src/iris_bundle/.iris/aidlc/skills/governance/escalation.md +13 -0
- package/src/iris_bundle/.iris/aidlc/skills/governance/quality-gates.md +14 -0
- package/src/iris_bundle/.iris/aidlc/skills/governance/stop-conditions.md +11 -0
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/decomposition.md +23 -0
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/risk-analysis.md +14 -0
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/verification.md +21 -0
- package/src/iris_bundle/.iris/aidlc/standards/artifacts-registry.md +38 -0
- package/src/iris_bundle/.iris/aidlc/standards/decision-logging.md +16 -0
- package/src/iris_bundle/.iris/aidlc/standards/doctrine-structure.md +31 -0
- package/src/iris_bundle/.iris/aidlc/standards/documentation-rules.md +15 -0
- package/src/iris_bundle/.iris/aidlc/standards/file-structure.md +21 -0
- package/src/iris_bundle/.iris/aidlc/standards/naming-conventions.md +18 -0
- package/src/iris_bundle/.iris/aidlc/standards/phases-and-gates.md +25 -0
- package/src/iris_bundle/.iris/aidlc/standards/routes-and-routing.md +35 -0
- package/src/iris_bundle/.iris/aidlc/standards/tool-wrappers.md +32 -0
- package/src/iris_bundle/.iris/aidlc/templates/bolt.md +23 -0
- package/src/iris_bundle/.iris/aidlc/templates/doctrine-doc-template.md +33 -0
- package/src/iris_bundle/.iris/aidlc/templates/intent.md +23 -0
- package/src/iris_bundle/.iris/aidlc/templates/log.md +24 -0
- package/src/iris_bundle/.iris/aidlc/templates/review.md +21 -0
- package/src/iris_bundle/.iris/aidlc/templates/unit.md +31 -0
- package/src/iris_bundle/.iris/aidlc/validation/failure-modes.md +16 -0
- package/src/iris_bundle/.iris/aidlc/validation/phase-preconditions.md +21 -0
- package/src/iris_bundle/.iris/aidlc/validation/quality-checklist.md +20 -0
- package/src/iris_bundle/.iris/policy.yaml +27 -0
- package/src/iris_bundle/.iris/routes.yaml +98 -0
- package/src/iris_bundle/.iris/state.yaml +7 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/knowledge/IRIS.md +6 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-construction-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-inception-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-master-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-operations-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/claude.md +9 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/compare-specs.md +203 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-construction-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-inception-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-master-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-operations-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/codex/AGENTS.md +15 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-construction-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-inception-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-master-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-operations-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-construction-agent.toml +29 -0
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-inception-agent.toml +29 -0
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-master-agent.toml +29 -0
- 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,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
|
+
}
|