project-iris 0.0.8 → 0.0.11

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 (125) hide show
  1. package/README.md +294 -264
  2. package/dist/bridge/agent-runner.js +190 -0
  3. package/dist/bridge/connector-factory.js +4 -0
  4. package/dist/bridge/connectors/in-process-connector.js +29 -0
  5. package/dist/bridge/filesystem-connector.js +5 -0
  6. package/dist/cli.js +10 -2
  7. package/dist/commands/ask.js +150 -23
  8. package/dist/commands/bridge.js +8 -0
  9. package/dist/commands/flow.js +301 -0
  10. package/dist/commands/framework.js +273 -0
  11. package/dist/commands/generate.js +59 -0
  12. package/dist/commands/install.js +72 -29
  13. package/dist/commands/pack.js +7 -1
  14. package/dist/commands/run.js +195 -13
  15. package/dist/commands/status.js +9 -0
  16. package/dist/commands/uninstall.js +3 -1
  17. package/dist/commands/use.js +20 -0
  18. package/dist/commands/validate.js +80 -65
  19. package/dist/framework/framework-loader.js +97 -0
  20. package/dist/framework/framework-paths.js +48 -0
  21. package/dist/framework/framework-types.js +15 -0
  22. package/dist/iris/artifacts/config.js +68 -0
  23. package/dist/iris/artifacts/generator.js +88 -0
  24. package/dist/iris/artifacts/types.js +1 -0
  25. package/dist/iris/bundle.js +44 -0
  26. package/dist/iris/doctrine/collector.js +124 -0
  27. package/dist/iris/fixer.js +28 -22
  28. package/dist/iris/flows/manifest.js +124 -0
  29. package/dist/iris/framework-context.js +49 -0
  30. package/dist/iris/framework-manager.js +215 -0
  31. package/dist/iris/fs/atomic.js +22 -0
  32. package/dist/iris/importers/index.js +9 -0
  33. package/dist/iris/importers/types.js +8 -0
  34. package/dist/iris/importers/writer.js +139 -0
  35. package/dist/iris/installer.js +105 -40
  36. package/dist/iris/interactive/env.js +21 -0
  37. package/dist/iris/interactive/intent-interview.js +345 -0
  38. package/dist/iris/interactive/intent-schema.js +28 -0
  39. package/dist/iris/interactive/interview-io.js +22 -0
  40. package/dist/iris/interview/config.js +71 -0
  41. package/dist/iris/interview/types.js +16 -0
  42. package/dist/iris/interview/utils.js +38 -0
  43. package/dist/iris/packer.js +69 -47
  44. package/dist/iris/parsers/unit-parser.js +43 -0
  45. package/dist/iris/paths.js +18 -0
  46. package/dist/iris/policy.js +122 -17
  47. package/dist/iris/proc.js +56 -0
  48. package/dist/iris/resolver.js +3 -0
  49. package/dist/iris/routes.js +180 -11
  50. package/dist/iris/run-state.js +3 -0
  51. package/dist/iris/state.js +37 -9
  52. package/dist/iris/templates.js +70 -0
  53. package/dist/iris/tmp.js +24 -0
  54. package/dist/iris/uninstaller.js +24 -9
  55. package/dist/iris/utils/interpolate.js +42 -0
  56. package/dist/iris/validator.js +72 -10
  57. package/dist/iris/workflow/config.js +51 -0
  58. package/dist/iris/workflow/engine.js +129 -0
  59. package/dist/iris/workflow/steps.js +448 -0
  60. package/dist/iris/workflow/types.js +1 -0
  61. package/dist/utils/logo.js +17 -0
  62. package/dist/workflows/intent-inception.js +87 -65
  63. package/package.json +8 -6
  64. package/src/iris_bundle/.iris/aidlc/README.md +0 -16
  65. package/src/iris_bundle/.iris/aidlc/agents/iris-construction-agent.md +0 -35
  66. package/src/iris_bundle/.iris/aidlc/agents/iris-inception-agent.md +0 -30
  67. package/src/iris_bundle/.iris/aidlc/agents/iris-master-agent.md +0 -35
  68. package/src/iris_bundle/.iris/aidlc/agents/iris-operations-agent.md +0 -29
  69. package/src/iris_bundle/.iris/aidlc/commands/iris-construction-agent.md +0 -18
  70. package/src/iris_bundle/.iris/aidlc/commands/iris-inception-agent.md +0 -18
  71. package/src/iris_bundle/.iris/aidlc/commands/iris-master-agent.md +0 -18
  72. package/src/iris_bundle/.iris/aidlc/commands/iris-operations-agent.md +0 -18
  73. package/src/iris_bundle/.iris/aidlc/context/context-map.md +0 -25
  74. package/src/iris_bundle/.iris/aidlc/context/exclusion-rules.md +0 -13
  75. package/src/iris_bundle/.iris/aidlc/context/load-order.md +0 -25
  76. package/src/iris_bundle/.iris/aidlc/memory/intent-rules.md +0 -9
  77. package/src/iris_bundle/.iris/aidlc/memory/log-rules.md +0 -5
  78. package/src/iris_bundle/.iris/aidlc/memory/memory-bank.yaml +0 -39
  79. package/src/iris_bundle/.iris/aidlc/memory/unit-rules.md +0 -9
  80. package/src/iris_bundle/.iris/aidlc/quick-start.md +0 -24
  81. package/src/iris_bundle/.iris/aidlc/skills/execution/implementation.md +0 -14
  82. package/src/iris_bundle/.iris/aidlc/skills/execution/refactoring.md +0 -13
  83. package/src/iris_bundle/.iris/aidlc/skills/execution/scaffold-generation.md +0 -15
  84. package/src/iris_bundle/.iris/aidlc/skills/governance/escalation.md +0 -13
  85. package/src/iris_bundle/.iris/aidlc/skills/governance/quality-gates.md +0 -14
  86. package/src/iris_bundle/.iris/aidlc/skills/governance/stop-conditions.md +0 -11
  87. package/src/iris_bundle/.iris/aidlc/skills/reasoning/decomposition.md +0 -23
  88. package/src/iris_bundle/.iris/aidlc/skills/reasoning/risk-analysis.md +0 -14
  89. package/src/iris_bundle/.iris/aidlc/skills/reasoning/verification.md +0 -21
  90. package/src/iris_bundle/.iris/aidlc/standards/artifacts-registry.md +0 -38
  91. package/src/iris_bundle/.iris/aidlc/standards/decision-logging.md +0 -16
  92. package/src/iris_bundle/.iris/aidlc/standards/doctrine-structure.md +0 -31
  93. package/src/iris_bundle/.iris/aidlc/standards/documentation-rules.md +0 -15
  94. package/src/iris_bundle/.iris/aidlc/standards/file-structure.md +0 -21
  95. package/src/iris_bundle/.iris/aidlc/standards/naming-conventions.md +0 -18
  96. package/src/iris_bundle/.iris/aidlc/standards/phases-and-gates.md +0 -25
  97. package/src/iris_bundle/.iris/aidlc/standards/routes-and-routing.md +0 -35
  98. package/src/iris_bundle/.iris/aidlc/standards/tool-wrappers.md +0 -32
  99. package/src/iris_bundle/.iris/aidlc/templates/bolt.md +0 -23
  100. package/src/iris_bundle/.iris/aidlc/templates/doctrine-doc-template.md +0 -33
  101. package/src/iris_bundle/.iris/aidlc/templates/intent.md +0 -23
  102. package/src/iris_bundle/.iris/aidlc/templates/log.md +0 -24
  103. package/src/iris_bundle/.iris/aidlc/templates/review.md +0 -21
  104. package/src/iris_bundle/.iris/aidlc/templates/unit.md +0 -31
  105. package/src/iris_bundle/.iris/aidlc/validation/failure-modes.md +0 -16
  106. package/src/iris_bundle/.iris/aidlc/validation/phase-preconditions.md +0 -21
  107. package/src/iris_bundle/.iris/aidlc/validation/quality-checklist.md +0 -20
  108. package/src/iris_bundle/.iris/policy.yaml +0 -27
  109. package/src/iris_bundle/.iris/routes.yaml +0 -98
  110. package/src/iris_bundle/.iris/state.yaml +0 -7
  111. package/src/iris_bundle/.iris/tools/claude/.claude/claude.md +0 -9
  112. package/src/iris_bundle/.iris/tools/claude/.claude/commands/compare-specs.md +0 -203
  113. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-construction-agent.md +0 -25
  114. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-inception-agent.md +0 -25
  115. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-master-agent.md +0 -25
  116. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-operations-agent.md +0 -25
  117. package/src/iris_bundle/.iris/tools/codex/AGENTS.md +0 -15
  118. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-construction-agent.md +0 -25
  119. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-inception-agent.md +0 -25
  120. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-master-agent.md +0 -25
  121. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-operations-agent.md +0 -25
  122. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-construction-agent.toml +0 -29
  123. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-inception-agent.toml +0 -29
  124. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-master-agent.toml +0 -29
  125. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-operations-agent.toml +0 -29
@@ -0,0 +1,129 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { writeJsonAtomic } from "../fs/atomic.js";
4
+ import { ensureDir } from "../../lib.js";
5
+ import { generateIntentId } from "../utils/interpolate.js";
6
+ // Constants
7
+ export const RUNS_DIR = ".iris/runs";
8
+ export const RUN_FILE_NAME = "run.json";
9
+ export const EVENTS_FILE_NAME = "events.ndjson";
10
+ export const LATEST_POINTER_FILE = "latest";
11
+ export class WorkflowEngine {
12
+ repoRoot;
13
+ runDir;
14
+ state;
15
+ eventsPath;
16
+ constructor(repoRoot, runId, initialState) {
17
+ this.repoRoot = repoRoot;
18
+ this.runDir = path.join(repoRoot, RUNS_DIR, runId);
19
+ this.eventsPath = path.join(this.runDir, EVENTS_FILE_NAME);
20
+ if (initialState) {
21
+ this.state = initialState;
22
+ }
23
+ else {
24
+ // Check if exists
25
+ try {
26
+ const content = fs.readFileSync(path.join(this.runDir, RUN_FILE_NAME), "utf-8");
27
+ this.state = JSON.parse(content);
28
+ }
29
+ catch (e) {
30
+ throw new Error(`Run '${runId}' not found and no initial state provided.`);
31
+ }
32
+ }
33
+ }
34
+ getRunId() {
35
+ return this.state.runId;
36
+ }
37
+ getRunDir() {
38
+ return this.runDir;
39
+ }
40
+ getState() {
41
+ return { ...this.state }; // Clone
42
+ }
43
+ static create(repoRoot, frameworkId, frameworkVersion, workflow) {
44
+ const runId = generateRunId();
45
+ const runDir = path.join(repoRoot, RUNS_DIR, runId);
46
+ ensureDir(runDir);
47
+ // Generate intentId (no draft available at creation, uses fallback slug)
48
+ const intentId = generateIntentId();
49
+ const state = {
50
+ schemaVersion: 1,
51
+ runId,
52
+ intentId,
53
+ framework: { id: frameworkId, version: frameworkVersion },
54
+ workflow,
55
+ cursor: { nextStepId: workflow.steps[0]?.id || null },
56
+ steps: {},
57
+ createdAt: new Date().toISOString(),
58
+ updatedAt: new Date().toISOString()
59
+ };
60
+ const engine = new WorkflowEngine(repoRoot, runId, state);
61
+ engine.persist();
62
+ engine.appendEvent("RUN_STARTED", { msg: "Run created" });
63
+ engine.updateLatestPointer();
64
+ return engine;
65
+ }
66
+ static load(repoRoot, runId) {
67
+ return new WorkflowEngine(repoRoot, runId);
68
+ }
69
+ static getLatestRunId(repoRoot) {
70
+ try {
71
+ const pointer = path.join(repoRoot, RUNS_DIR, LATEST_POINTER_FILE);
72
+ if (fs.existsSync(pointer)) {
73
+ return fs.readFileSync(pointer, "utf-8").trim();
74
+ }
75
+ }
76
+ catch (e) {
77
+ return null;
78
+ }
79
+ return null;
80
+ }
81
+ persist() {
82
+ this.state.updatedAt = new Date().toISOString();
83
+ writeJsonAtomic(path.join(this.runDir, RUN_FILE_NAME), this.state);
84
+ }
85
+ updateLatestPointer() {
86
+ const pointer = path.join(this.repoRoot, RUNS_DIR, LATEST_POINTER_FILE);
87
+ ensureDir(path.dirname(pointer));
88
+ fs.writeFileSync(pointer, this.state.runId);
89
+ }
90
+ appendEvent(type, data = {}) {
91
+ const event = {
92
+ ts: new Date().toISOString(),
93
+ type,
94
+ ...data
95
+ };
96
+ fs.appendFileSync(this.eventsPath, JSON.stringify(event) + "\n");
97
+ }
98
+ getStepStatus(stepId) {
99
+ return this.state.steps[stepId];
100
+ }
101
+ updateStepstatus(stepId, update) {
102
+ if (!this.state.steps[stepId]) {
103
+ this.state.steps[stepId] = {
104
+ status: 'pending',
105
+ updatedAt: new Date().toISOString(),
106
+ ...update
107
+ };
108
+ }
109
+ else {
110
+ this.state.steps[stepId] = {
111
+ ...this.state.steps[stepId],
112
+ updatedAt: new Date().toISOString(),
113
+ ...update
114
+ };
115
+ }
116
+ this.persist();
117
+ }
118
+ advanceCursor(nextStepId) {
119
+ this.state.cursor.nextStepId = nextStepId;
120
+ this.persist();
121
+ }
122
+ }
123
+ function generateRunId() {
124
+ const now = new Date();
125
+ // YYYYMMDD-HHMMSS-rand
126
+ const ts = now.toISOString().replace(/[:.]/g, "-").split("T").join("-").substring(0, 19);
127
+ const rand = Math.random().toString(36).substring(2, 6);
128
+ return `${ts}-${rand}`;
129
+ }
@@ -0,0 +1,448 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { runIntentInterview } from "../interactive/intent-interview.js";
4
+ import { validateDraft } from "../interactive/intent-schema.js";
5
+ import { loadEffectiveInterviewConfig } from "../interview/config.js";
6
+ import { loadEffectiveArtifactsConfig } from "../artifacts/config.js";
7
+ import { generateArtifacts } from "../artifacts/generator.js";
8
+ import { validate } from "../validator.js";
9
+ import { generatePack } from "../packer.js";
10
+ import { loadFramework } from "../../framework/framework-loader.js";
11
+ import { writeJsonAtomic } from "../fs/atomic.js";
12
+ import { ensureDir } from "../../lib.js";
13
+ // --- Interview Step ---
14
+ export const InterviewStep = {
15
+ async execute(state, config, root, runDir, options) {
16
+ const draftPath = path.join(runDir, "intent-draft.json");
17
+ // 1. Seed handling
18
+ if (options.seed) {
19
+ const seedPath = path.isAbsolute(options.seed) ? options.seed : path.resolve(root, options.seed);
20
+ if (!fs.existsSync(seedPath)) {
21
+ return { status: "failed", error: `Seed file not found: ${options.seed}` };
22
+ }
23
+ try {
24
+ const content = JSON.parse(fs.readFileSync(seedPath, "utf-8"));
25
+ // Validation
26
+ const val = validateDraft(content); // Basic check
27
+ if (!val.valid) {
28
+ return { status: "failed", error: `Invalid seed draft: ${val.errors.join(", ")}` };
29
+ }
30
+ writeJsonAtomic(draftPath, content);
31
+ return { status: "done", outputs: ["intent-draft.json"] };
32
+ }
33
+ catch (e) {
34
+ return { status: "failed", error: `Failed to process seed: ${e.message}` };
35
+ }
36
+ }
37
+ // 2. Interactive
38
+ if (options.interactive) {
39
+ // Load framework resolution for config
40
+ const resolution = await resolveFramework(state, root);
41
+ const interviewConfig = loadEffectiveInterviewConfig(resolution);
42
+ // Run interactive
43
+ const draft = await runIntentInterview("", interviewConfig); // TODO: Resume logic?
44
+ if (!draft) {
45
+ return { status: "failed", error: "Interview cancelled" };
46
+ }
47
+ writeJsonAtomic(draftPath, draft);
48
+ return { status: "done", outputs: ["intent-draft.json"] };
49
+ }
50
+ // 3. Non-interactive (Blocked)
51
+ return {
52
+ status: "blocked",
53
+ reason: "Interview requires interactive mode or valid seed.",
54
+ error: "Cannot run interview in non-interactive mode without seed."
55
+ };
56
+ }
57
+ };
58
+ // --- Artifacts Step ---
59
+ export const ArtifactsStep = {
60
+ async execute(state, config, root, runDir, options) {
61
+ const draftPath = path.join(runDir, "intent-draft.json");
62
+ if (!fs.existsSync(draftPath)) {
63
+ return { status: "failed", error: "Missing input: intent-draft.json" };
64
+ }
65
+ const draft = JSON.parse(fs.readFileSync(draftPath, "utf-8"));
66
+ const resolution = await resolveFramework(state, root);
67
+ const artifactsConfig = loadEffectiveArtifactsConfig(resolution);
68
+ // 4.7D SaaS Bootstrap injection
69
+ // If package.json missing, inject U000 into unit-list template
70
+ const packageJsonPath = path.join(root, "package.json");
71
+ if (!fs.existsSync(packageJsonPath)) {
72
+ const unitListDef = artifactsConfig.artifacts.find(a => a.id === "unit-list");
73
+ if (unitListDef && unitListDef.output.template) {
74
+ // Check if we already have U000
75
+ if (!unitListDef.output.template.includes("U000:")) {
76
+ const injection = ` - [ ] U000: Repo Bootstrap
77
+ summary: Initialize project repository with basic structure and tooling.`;
78
+ // Regex replace the first list item or insert after ## Units
79
+ // The default template has "## Units"
80
+ unitListDef.output.template = unitListDef.output.template.replace(/## Units\s*/, `## Units\n${injection}\n`);
81
+ }
82
+ }
83
+ }
84
+ try {
85
+ const result = generateArtifacts(draft, artifactsConfig, resolution, state.intentId);
86
+ // Write artifacts to runDir? Or to artifacts.json?
87
+ // Spec says "artifacts.json". But usually generateArtifacts returns contents step by step.
88
+ // We should save the `GeneratedArtifactsOutput` to `artifacts.json`.
89
+ // AND potentially write them to disk?
90
+ // "artifacts: template-only" -> usually writes to disk.
91
+ // `iris generate` writes to disk. `iris run` should probably do the same?
92
+ // Or just produce the JSON?
93
+ // "Save .iris/runs/<runId>/artifacts.json" implies we enable tooling to read it.
94
+ // Let's write artifacts.json containing the generated content.
95
+ // If the user wants files, they might need to be written to the project?
96
+ // Wait, "Step 7" `iris generate` writes to output path.
97
+ // "OS loop" -> should it modify the project?
98
+ // "Artifacts: call generateArtifacts... Input: draft... Save artifacts.json"
99
+ // It seems `iris run` is about accumulating state. Project mutation happens when?
100
+ // Ideally `iris run` prepares everything.
101
+ // `validate` runs on REPO state. So artifacts MUST be written to repo for validate to pass?
102
+ // "Validation Plan" says "iris run ... verify run directory created".
103
+ // If artifacts are NOT written to repo, validate will fail if it checks for them?
104
+ // But `validate` checks *policy constraints* which might require artifacts.
105
+ // IF artifacts step doesn't write to repo, `validate` sees old repo.
106
+ // DECISION: Write artifacts to `artifacts.json` AND apply them to repo?
107
+ // Or does `pack` take them from `artifacts.json`?
108
+ // `pack` collects files from repo.
109
+ // SO: Artifacts MUST be written to repo to be useful for `validate` and `pack`.
110
+ // BUT: `iris run` should be safe?
111
+ // Start by writing to `artifacts.json`.
112
+ // Writing to real files updates the project state.
113
+ // Let's assume for now we WRITE to repo, because otherwise validate/pack sees nothing.
114
+ const artifactsPath = path.join(runDir, "artifacts.json");
115
+ writeJsonAtomic(artifactsPath, result);
116
+ // Write to project (Side Effect)
117
+ for (const art of result.artifacts) {
118
+ // If it has a path? `GeneratedArtifact` has `id, content`.
119
+ // Artifact definition in `artifacts.yaml` has `out`.
120
+ // Transformation needed?
121
+ // `generateArtifacts` returns `GeneratedArtifact` which logic (in command) mapped to files.
122
+ // `src/iris/artifacts/generator.ts` does NOT write files. `src/commands/generate.ts` does.
123
+ // We need to map `result.artifacts` to files using `artifactsConfig.artifacts`.
124
+ const def = artifactsConfig.artifacts.find(d => d.id === art.id);
125
+ // Use interpolated path from generator if available
126
+ const relativePath = art.path || (def && def.out);
127
+ if (relativePath) {
128
+ const outPath = path.join(root, relativePath);
129
+ ensureDir(path.dirname(outPath));
130
+ fs.writeFileSync(outPath, art.content);
131
+ }
132
+ }
133
+ return { status: "done", outputs: ["artifacts.json"] };
134
+ }
135
+ catch (e) {
136
+ return { status: "failed", error: e.message };
137
+ }
138
+ }
139
+ };
140
+ // --- Validate Step ---
141
+ export const ValidateStep = {
142
+ async execute(state, config, root, runDir, options) {
143
+ const resolution = await resolveFramework(state, root);
144
+ try {
145
+ const result = await validate({
146
+ apply: false,
147
+ strict: true,
148
+ frameworkResolution: resolution
149
+ });
150
+ const validatePath = path.join(runDir, "validate.json");
151
+ writeJsonAtomic(validatePath, result);
152
+ if (!result.valid) {
153
+ return {
154
+ status: "failed",
155
+ error: `Validation failed with ${result.errors.length} errors.`,
156
+ outputs: ["validate.json"]
157
+ };
158
+ }
159
+ return { status: "done", outputs: ["validate.json"] };
160
+ }
161
+ catch (e) {
162
+ return { status: "failed", error: `Validation exception: ${e.message}` };
163
+ }
164
+ }
165
+ };
166
+ // --- Pack Step ---
167
+ export const PackStep = {
168
+ async execute(state, config, root, runDir, options) {
169
+ const resolution = await resolveFramework(state, root);
170
+ const packPath = path.join(runDir, "pack.md"); // Artifact
171
+ const packMetaPath = path.join(runDir, "pack.json"); // Metadata (files list etc)
172
+ try {
173
+ const result = await generatePack({
174
+ output: packPath,
175
+ json: true, // We want the metadata object returned
176
+ frameworkResolution: resolution,
177
+ stdout: false,
178
+ quiet: options.quiet // Suppress console output
179
+ });
180
+ // generatePack returns { path, size, fileCount } if not stdout
181
+ // It writes the file to `output`.
182
+ writeJsonAtomic(packMetaPath, result);
183
+ return { status: "done", outputs: ["pack.md", "pack.json"] };
184
+ }
185
+ catch (e) {
186
+ return { status: "failed", error: `Pack failed: ${e.message}` };
187
+ }
188
+ }
189
+ };
190
+ // --- Handoff Step ---
191
+ export const HandoffStep = {
192
+ async execute(state, config, root, runDir, options) {
193
+ const handoffJsonPath = path.join(runDir, "handoff.json");
194
+ const handoffMdPath = path.join(runDir, "handoff.md");
195
+ const handoffData = {
196
+ schemaVersion: 1,
197
+ runId: state.runId,
198
+ framework: state.framework,
199
+ files: {
200
+ intentDraft: path.join(runDir, "intent-draft.json"),
201
+ artifacts: path.join(runDir, "artifacts.json"),
202
+ validate: path.join(runDir, "validate.json"),
203
+ pack: path.join(runDir, "pack.md")
204
+ },
205
+ nextActions: ["Review plan", "Execute bolts"], // Placeholder or derived?
206
+ timestamp: new Date().toISOString()
207
+ };
208
+ writeJsonAtomic(handoffJsonPath, handoffData);
209
+ const mdContent = `# IRIS Handoff
210
+ Run ID: ${state.runId}
211
+ Framework: ${state.framework.id}
212
+
213
+ ## Files
214
+ - [Intent Draft](file://${handoffData.files.intentDraft})
215
+ - [Artifacts](file://${handoffData.files.artifacts})
216
+ - [Validation Report](file://${handoffData.files.validate})
217
+ - [Context Pack](file://${handoffData.files.pack})
218
+
219
+ ## Next Actions
220
+ ${handoffData.nextActions.map(a => `- ${a}`).join("\n")}
221
+ `;
222
+ fs.writeFileSync(handoffMdPath, mdContent);
223
+ return { status: "done", outputs: ["handoff.json", "handoff.md"] };
224
+ }
225
+ };
226
+ // Helper
227
+ async function resolveFramework(state, root) {
228
+ if (state.framework.id) {
229
+ return await loadFramework(state.framework.id, { repoRoot: root });
230
+ }
231
+ return null;
232
+ }
233
+ // --- Planning Step ---
234
+ export const PlanningStep = {
235
+ async execute(state, config, root, runDir, options) {
236
+ if (!state.intentId) {
237
+ return { status: "failed", error: "Missing intentId in state" };
238
+ }
239
+ const unitListPath = path.join(root, "memory-bank", "intents", state.intentId, "unit-list.md");
240
+ if (!fs.existsSync(unitListPath)) {
241
+ return { status: "failed", error: `Missing unit-list artifact at ${unitListPath}` };
242
+ }
243
+ try {
244
+ const { parseUnitList } = await import("../parsers/unit-parser.js");
245
+ const { UNIT_BRIEF_TEMPLATE } = await import("../templates.js");
246
+ const { interpolateTokens } = await import("../utils/interpolate.js"); // If needed for other paths
247
+ const content = fs.readFileSync(unitListPath, "utf-8");
248
+ const units = parseUnitList(content);
249
+ const outputs = [];
250
+ for (const unit of units) {
251
+ const unitDir = path.join(root, "memory-bank", "intents", state.intentId, "units", unit.id);
252
+ ensureDir(unitDir);
253
+ const briefPath = path.join(unitDir, "unit-brief.md");
254
+ // Skip if exists and not forced
255
+ if (fs.existsSync(briefPath) && !options.force) {
256
+ outputs.push(briefPath);
257
+ continue;
258
+ }
259
+ // Render Template
260
+ let briefContent = UNIT_BRIEF_TEMPLATE
261
+ .replace("{{unitId}}", unit.id)
262
+ .replace("{{title}}", unit.title)
263
+ .replace("{{summary}}", unit.summary);
264
+ fs.writeFileSync(briefPath, briefContent);
265
+ outputs.push(briefPath);
266
+ }
267
+ return { status: "done", outputs };
268
+ }
269
+ catch (e) {
270
+ return { status: "failed", error: `Planning step failed: ${e.message}` };
271
+ }
272
+ }
273
+ };
274
+ // --- Construction Step ---
275
+ export const ConstructionStep = {
276
+ async execute(state, config, root, runDir, options) {
277
+ if (!state.intentId)
278
+ return { status: "failed", error: "Missing intentId" };
279
+ const unitsDir = path.join(root, "memory-bank", "intents", state.intentId, "units");
280
+ if (!fs.existsSync(unitsDir)) {
281
+ // If no units, we can't construct anything. Should validation fail first?
282
+ // Or is this valid "nothing to do"?
283
+ // If planning succeeded but produced no units (unlikely), we do nothing.
284
+ return { status: "done", outputs: [] };
285
+ }
286
+ try {
287
+ const { BOLT_TEMPLATE } = await import("../templates.js");
288
+ const glob = (await import("fast-glob")).default;
289
+ // Find all APPROVED unit briefs
290
+ const briefPattern = path.join(unitsDir, "*", "unit-brief.md");
291
+ // Fast-glob expects forward slashes, handle windows? internal logic usually handles it.
292
+ // Using absolute path might be tricky with glob. Relative logic is safer.
293
+ const relUnitsDir = path.relative(process.cwd(), unitsDir);
294
+ const briefs = await glob(path.join(relUnitsDir, "*", "unit-brief.md").replace(/\\/g, "/"), { cwd: process.cwd() });
295
+ const outputs = [];
296
+ // TODO: In future, track which bolts are already generated for which unit.
297
+ // For MVP: 1 Bolt per Unit.
298
+ // Check if bolt exists for unit.
299
+ // Or just read frontmatter? We need matter.
300
+ const matter = (await import("gray-matter")).default;
301
+ for (const briefRel of briefs) {
302
+ const briefPath = path.resolve(process.cwd(), briefRel);
303
+ const content = fs.readFileSync(briefPath, 'utf-8');
304
+ const parsed = matter(content);
305
+ if (parsed.data.status !== 'approved')
306
+ continue;
307
+ const unitId = parsed.data.id;
308
+ const unitTitle = (content.match(/^# Unit Brief: (.+)$/m) || [])[1] || "Untitled"; // Fallback title parsing
309
+ // Generate Bolt ID: B001 (How to map U001 -> B001?)
310
+ // If we assume 1-to-1, maybe B<UnitNumber>? Or global counter?
311
+ // Let's use B + UnitNumber for simplicity if U001 -> B001.
312
+ // Or just B001 for first bolt of unit.
313
+ const boltId = `B${unitId.replace(/^U/, '')}.1`; // U001 -> B001.1? U001 -> B001 check
314
+ const boltsDir = path.join(path.dirname(briefPath), "bolts");
315
+ ensureDir(boltsDir);
316
+ // Slugify title
317
+ const slug = unitTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
318
+ const boltDirName = `${boltId}-${slug}`;
319
+ const boltDir = path.join(boltsDir, boltDirName);
320
+ ensureDir(boltDir);
321
+ const boltPath = path.join(boltDir, "bolt.md");
322
+ if (fs.existsSync(boltPath) && !options.force) {
323
+ outputs.push(boltPath);
324
+ continue;
325
+ }
326
+ let boltContent = BOLT_TEMPLATE
327
+ .replace("{{boltId}}", boltId)
328
+ .replace("{{unitId}}", unitId)
329
+ .replace("{{title}}", unitTitle);
330
+ fs.writeFileSync(boltPath, boltContent);
331
+ outputs.push(boltPath);
332
+ }
333
+ return { status: "done", outputs };
334
+ }
335
+ catch (e) {
336
+ return { status: "failed", error: `Construction step failed: ${e.message}` };
337
+ }
338
+ }
339
+ };
340
+ // --- Execution Step ---
341
+ export const ExecutionStep = {
342
+ async execute(state, config, root, runDir, options) {
343
+ if (!state.intentId)
344
+ return { status: "failed", error: "Missing intentId" };
345
+ const unitsDir = path.join(root, "memory-bank", "intents", state.intentId, "units");
346
+ if (!fs.existsSync(unitsDir)) {
347
+ return { status: "done", outputs: [] };
348
+ }
349
+ try {
350
+ const { TEST_REPORT_TEMPLATE } = await import("../templates.js");
351
+ const glob = (await import("fast-glob")).default;
352
+ const matter = (await import("gray-matter")).default;
353
+ const { spawnAsync } = await import("../../lib.js"); // Assume lib.ts is in ../../lib.ts relative to steps.ts
354
+ // Find all bolts under units
355
+ const pattern = path.join(path.relative(process.cwd(), unitsDir), "*", "bolts", "*", "bolt.md").replace(/\\/g, "/");
356
+ const boltFiles = await glob(pattern, { cwd: process.cwd() });
357
+ let executedCount = 0;
358
+ const outputs = [];
359
+ let hasFailures = false;
360
+ for (const boltRel of boltFiles) {
361
+ const boltPath = path.resolve(process.cwd(), boltRel);
362
+ const content = fs.readFileSync(boltPath, 'utf-8');
363
+ const parsed = matter(content);
364
+ // Run ONLY if status is 'approved'
365
+ // If 'done' or 'failed', we skip unless force?
366
+ // Wait, if failed, user fixes code and resets status or we re-run?
367
+ // If status is 'failed' and user updated code (fs mtime?), can we detect?
368
+ // Simplest MVP: User must manually set status to 'approved' to re-run.
369
+ if (parsed.data.status !== 'approved')
370
+ continue;
371
+ // Mark in_progress
372
+ /*
373
+ // Updating bolt file status complicates things if we fail mid-write.
374
+ // But we should to indicate activity.
375
+ const inProgressContent = content.replace(/^status: .*$/m, "status: in_progress");
376
+ fs.writeFileSync(boltPath, inProgressContent);
377
+ */
378
+ // Actually, if we mark in_progress, and crash, user sees in_progress.
379
+ // Let's run test.
380
+ const boltCwd = parsed.data.cwd || ".";
381
+ const testCmd = parsed.data.commands?.test;
382
+ if (!testCmd) {
383
+ outputs.push(`${boltRel} (skipped: no test command)`);
384
+ continue;
385
+ }
386
+ if (!options.quiet)
387
+ console.log(`Running tests for ${parsed.data.id}...`);
388
+ // Execute
389
+ const absCwd = path.resolve(path.dirname(boltPath), boltCwd);
390
+ // Ensure cwd exists
391
+ if (!fs.existsSync(absCwd)) {
392
+ // Fail bolt?
393
+ console.error(`CWD not found: ${absCwd}`);
394
+ continue;
395
+ }
396
+ // If testCmd has "npm ", make sure npm is valid? spawnAsync handles shell: true.
397
+ const { code, stdout, stderr } = await spawnAsync(testCmd, [], absCwd);
398
+ const status = code === 0 ? "done" : "failed";
399
+ if (status === "failed")
400
+ hasFailures = true;
401
+ // Update Bolt Status
402
+ // We use replace to update status field.
403
+ // Re-read content in case changed? No, we are lock-step.
404
+ const newContent = content.replace(/^status: .*$/m, `status: ${status}`);
405
+ fs.writeFileSync(boltPath, newContent);
406
+ // Write Test Report
407
+ const reportPath = path.join(path.dirname(boltPath), "test-report.md");
408
+ const report = TEST_REPORT_TEMPLATE
409
+ .replace(/{{boltId}}/g, parsed.data.id)
410
+ .replace(/{{status}}/g, status)
411
+ .replace(/{{title}}/g, parsed.data.id || "Untitled")
412
+ .replace(/{{timestamp}}/g, new Date().toISOString())
413
+ .replace("{{output}}", stdout + "\n" + stderr);
414
+ fs.writeFileSync(reportPath, report);
415
+ outputs.push(`${parsed.data.id}: ${status}`);
416
+ executedCount++;
417
+ }
418
+ if (executedCount === 0) {
419
+ // No approved bolts found.
420
+ // Should we return 'done'?
421
+ return { status: "done", outputs: ["No approved bolts to execute"] };
422
+ }
423
+ // If we ran bolts, and some failed, should execute step fail?
424
+ // "Run loop" implies we want to see failure in CLI?
425
+ // But if we return 'failed', workflow stops.
426
+ // If we return 'done', workflow continues to 'validate'.
427
+ // If bolts failed, validation might fail if policy checks test-reports.
428
+ // Generally, execution step is "done" (it finished executing).
429
+ // The *Result* of execution is persisted in artifacts.
430
+ // If strict is on, maybe fail?
431
+ // Let's return 'done' but with outputs indicating status.
432
+ return { status: "done", outputs };
433
+ }
434
+ catch (e) {
435
+ return { status: "failed", error: `Execution step failed: ${e.message}` };
436
+ }
437
+ }
438
+ };
439
+ export const EXEC_REGISTRY = {
440
+ interview: InterviewStep,
441
+ artifacts: ArtifactsStep,
442
+ planning: PlanningStep,
443
+ construction: ConstructionStep,
444
+ execution: ExecutionStep,
445
+ validate: ValidateStep,
446
+ pack: PackStep,
447
+ handoff: HandoffStep
448
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import kleur from "kleur";
2
+ /**
3
+ * Display the OSiris Operating System IRIS logo banner
4
+ */
5
+ export function displayLogo(version = "0.0.11") {
6
+ const logo = `
7
+ ██████╗ ███████╗██╗██████╗ ██╗███████╗
8
+ ██╔═══██╗██╔════╝██║██╔══██╗██║██╔════╝
9
+ ██║ ██║███████╗██║██████╔╝██║███████╗
10
+ ██║ ██║╚════██║██║██╔══██╗██║╚════██║
11
+ ╚██████╔╝███████║██║██║ ██║██║███████║
12
+ ╚═════╝ ╚══════╝╚═╝╚═╝ ╚═╝╚═╝╚══════╝`;
13
+ console.log(kleur.yellow(logo));
14
+ console.log("");
15
+ console.log(kleur.yellow().bold("OSiris Operating System") + kleur.yellow(" Intelligent Repository for Intent-driven Systems") + kleur.gray(` v${version}`));
16
+ console.log("");
17
+ }