project-iris 0.0.8 → 0.0.12

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 (160) 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 +12 -2
  7. package/dist/commands/ask.js +150 -23
  8. package/dist/commands/bridge.js +8 -0
  9. package/dist/commands/create.js +25 -0
  10. package/dist/commands/flow.js +301 -0
  11. package/dist/commands/framework.js +273 -0
  12. package/dist/commands/generate.js +59 -0
  13. package/dist/commands/install.js +72 -29
  14. package/dist/commands/pack.js +7 -1
  15. package/dist/commands/run.js +195 -13
  16. package/dist/commands/status.js +9 -0
  17. package/dist/commands/uninstall.js +3 -1
  18. package/dist/commands/use.js +20 -0
  19. package/dist/commands/validate.js +80 -65
  20. package/dist/framework/framework-loader.js +97 -0
  21. package/dist/framework/framework-paths.js +48 -0
  22. package/dist/framework/framework-types.js +15 -0
  23. package/dist/iris/artifacts/config.js +68 -0
  24. package/dist/iris/artifacts/generator.js +88 -0
  25. package/dist/iris/artifacts/types.js +1 -0
  26. package/dist/iris/bundle.js +44 -0
  27. package/dist/iris/doctrine/collector.js +124 -0
  28. package/dist/iris/fixer.js +28 -22
  29. package/dist/iris/flows/manifest.js +124 -0
  30. package/dist/iris/framework-context.js +49 -0
  31. package/dist/iris/framework-manager.js +215 -0
  32. package/dist/iris/fs/atomic.js +22 -0
  33. package/dist/iris/importers/index.js +9 -0
  34. package/dist/iris/importers/types.js +8 -0
  35. package/dist/iris/importers/writer.js +139 -0
  36. package/dist/iris/installer.js +105 -40
  37. package/dist/iris/interactive/env.js +21 -0
  38. package/dist/iris/interactive/intent-interview.js +345 -0
  39. package/dist/iris/interactive/intent-schema.js +28 -0
  40. package/dist/iris/interactive/interview-io.js +22 -0
  41. package/dist/iris/interview/config.js +71 -0
  42. package/dist/iris/interview/types.js +16 -0
  43. package/dist/iris/interview/utils.js +38 -0
  44. package/dist/iris/packer.js +69 -47
  45. package/dist/iris/parsers/unit-parser.js +43 -0
  46. package/dist/iris/paths.js +18 -0
  47. package/dist/iris/policy.js +122 -17
  48. package/dist/iris/proc.js +56 -0
  49. package/dist/iris/resolver.js +3 -0
  50. package/dist/iris/routes.js +180 -11
  51. package/dist/iris/run-state.js +3 -0
  52. package/dist/iris/state.js +37 -9
  53. package/dist/iris/templates.js +70 -0
  54. package/dist/iris/tmp.js +24 -0
  55. package/dist/iris/uninstaller.js +24 -9
  56. package/dist/iris/utils/interpolate.js +42 -0
  57. package/dist/iris/validator.js +72 -10
  58. package/dist/iris/workflow/config.js +51 -0
  59. package/dist/iris/workflow/engine.js +129 -0
  60. package/dist/iris/workflow/steps.js +448 -0
  61. package/dist/iris/workflow/types.js +1 -0
  62. package/dist/iris_bundle/frameworks/iris-core/framework.yaml +9 -0
  63. package/dist/iris_bundle/frameworks/iris-core/memory/memory-bank.yaml +1 -0
  64. package/dist/iris_bundle/frameworks/iris-core/policy.yaml +7 -0
  65. package/dist/iris_bundle/frameworks/iris-core/templates/config/memory-bank.yaml +1 -0
  66. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-template.md +226 -0
  67. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +49 -0
  68. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +55 -0
  69. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +67 -0
  70. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +62 -0
  71. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/ddd-construction-bolt.md +528 -0
  72. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/simple-construction-bolt.md +347 -0
  73. package/dist/iris_bundle/frameworks/iris-core/templates/construction/bolt-types/spike-bolt.md +240 -0
  74. package/dist/iris_bundle/frameworks/iris-core/templates/inception/requirements-template.md +144 -0
  75. package/dist/iris_bundle/frameworks/iris-core/templates/inception/stories-template.md +38 -0
  76. package/dist/iris_bundle/frameworks/iris-core/templates/inception/story-template.md +147 -0
  77. package/dist/iris_bundle/frameworks/iris-core/templates/inception/system-context-template.md +29 -0
  78. package/dist/iris_bundle/frameworks/iris-core/templates/inception/unit-brief-template.md +177 -0
  79. package/dist/iris_bundle/frameworks/iris-core/templates/inception/units-template.md +52 -0
  80. package/dist/templates/construction/bolt-template.md +226 -0
  81. package/dist/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +49 -0
  82. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +55 -0
  83. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +67 -0
  84. package/dist/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +62 -0
  85. package/dist/templates/construction/bolt-types/ddd-construction-bolt.md +528 -0
  86. package/dist/templates/construction/bolt-types/simple-construction-bolt.md +347 -0
  87. package/dist/templates/construction/bolt-types/spike-bolt.md +240 -0
  88. package/dist/templates/inception/requirements-template.md +144 -0
  89. package/dist/templates/inception/stories-template.md +38 -0
  90. package/dist/templates/inception/story-template.md +147 -0
  91. package/dist/templates/inception/system-context-template.md +29 -0
  92. package/dist/templates/inception/unit-brief-template.md +177 -0
  93. package/dist/templates/inception/units-template.md +52 -0
  94. package/dist/utils/logo.js +17 -0
  95. package/dist/workflows/bolt-plan.js +57 -28
  96. package/dist/workflows/intent-inception.js +169 -72
  97. package/dist/workflows/memory-bank-generator.js +180 -0
  98. package/package.json +10 -7
  99. package/src/iris_bundle/.iris/aidlc/README.md +0 -16
  100. package/src/iris_bundle/.iris/aidlc/agents/iris-construction-agent.md +0 -35
  101. package/src/iris_bundle/.iris/aidlc/agents/iris-inception-agent.md +0 -30
  102. package/src/iris_bundle/.iris/aidlc/agents/iris-master-agent.md +0 -35
  103. package/src/iris_bundle/.iris/aidlc/agents/iris-operations-agent.md +0 -29
  104. package/src/iris_bundle/.iris/aidlc/commands/iris-construction-agent.md +0 -18
  105. package/src/iris_bundle/.iris/aidlc/commands/iris-inception-agent.md +0 -18
  106. package/src/iris_bundle/.iris/aidlc/commands/iris-master-agent.md +0 -18
  107. package/src/iris_bundle/.iris/aidlc/commands/iris-operations-agent.md +0 -18
  108. package/src/iris_bundle/.iris/aidlc/context/context-map.md +0 -25
  109. package/src/iris_bundle/.iris/aidlc/context/exclusion-rules.md +0 -13
  110. package/src/iris_bundle/.iris/aidlc/context/load-order.md +0 -25
  111. package/src/iris_bundle/.iris/aidlc/memory/intent-rules.md +0 -9
  112. package/src/iris_bundle/.iris/aidlc/memory/log-rules.md +0 -5
  113. package/src/iris_bundle/.iris/aidlc/memory/memory-bank.yaml +0 -39
  114. package/src/iris_bundle/.iris/aidlc/memory/unit-rules.md +0 -9
  115. package/src/iris_bundle/.iris/aidlc/quick-start.md +0 -24
  116. package/src/iris_bundle/.iris/aidlc/skills/execution/implementation.md +0 -14
  117. package/src/iris_bundle/.iris/aidlc/skills/execution/refactoring.md +0 -13
  118. package/src/iris_bundle/.iris/aidlc/skills/execution/scaffold-generation.md +0 -15
  119. package/src/iris_bundle/.iris/aidlc/skills/governance/escalation.md +0 -13
  120. package/src/iris_bundle/.iris/aidlc/skills/governance/quality-gates.md +0 -14
  121. package/src/iris_bundle/.iris/aidlc/skills/governance/stop-conditions.md +0 -11
  122. package/src/iris_bundle/.iris/aidlc/skills/reasoning/decomposition.md +0 -23
  123. package/src/iris_bundle/.iris/aidlc/skills/reasoning/risk-analysis.md +0 -14
  124. package/src/iris_bundle/.iris/aidlc/skills/reasoning/verification.md +0 -21
  125. package/src/iris_bundle/.iris/aidlc/standards/artifacts-registry.md +0 -38
  126. package/src/iris_bundle/.iris/aidlc/standards/decision-logging.md +0 -16
  127. package/src/iris_bundle/.iris/aidlc/standards/doctrine-structure.md +0 -31
  128. package/src/iris_bundle/.iris/aidlc/standards/documentation-rules.md +0 -15
  129. package/src/iris_bundle/.iris/aidlc/standards/file-structure.md +0 -21
  130. package/src/iris_bundle/.iris/aidlc/standards/naming-conventions.md +0 -18
  131. package/src/iris_bundle/.iris/aidlc/standards/phases-and-gates.md +0 -25
  132. package/src/iris_bundle/.iris/aidlc/standards/routes-and-routing.md +0 -35
  133. package/src/iris_bundle/.iris/aidlc/standards/tool-wrappers.md +0 -32
  134. package/src/iris_bundle/.iris/aidlc/templates/bolt.md +0 -23
  135. package/src/iris_bundle/.iris/aidlc/templates/doctrine-doc-template.md +0 -33
  136. package/src/iris_bundle/.iris/aidlc/templates/intent.md +0 -23
  137. package/src/iris_bundle/.iris/aidlc/templates/log.md +0 -24
  138. package/src/iris_bundle/.iris/aidlc/templates/review.md +0 -21
  139. package/src/iris_bundle/.iris/aidlc/templates/unit.md +0 -31
  140. package/src/iris_bundle/.iris/aidlc/validation/failure-modes.md +0 -16
  141. package/src/iris_bundle/.iris/aidlc/validation/phase-preconditions.md +0 -21
  142. package/src/iris_bundle/.iris/aidlc/validation/quality-checklist.md +0 -20
  143. package/src/iris_bundle/.iris/policy.yaml +0 -27
  144. package/src/iris_bundle/.iris/routes.yaml +0 -98
  145. package/src/iris_bundle/.iris/state.yaml +0 -7
  146. package/src/iris_bundle/.iris/tools/claude/.claude/claude.md +0 -9
  147. package/src/iris_bundle/.iris/tools/claude/.claude/commands/compare-specs.md +0 -203
  148. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-construction-agent.md +0 -25
  149. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-inception-agent.md +0 -25
  150. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-master-agent.md +0 -25
  151. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-operations-agent.md +0 -25
  152. package/src/iris_bundle/.iris/tools/codex/AGENTS.md +0 -15
  153. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-construction-agent.md +0 -25
  154. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-inception-agent.md +0 -25
  155. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-master-agent.md +0 -25
  156. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-operations-agent.md +0 -25
  157. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-construction-agent.toml +0 -29
  158. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-inception-agent.toml +0 -29
  159. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-master-agent.toml +0 -29
  160. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-operations-agent.toml +0 -29
@@ -1,20 +1,189 @@
1
1
  import fs from "fs";
2
+ import path from "path";
2
3
  import yaml from "js-yaml";
3
4
  import { repoRoot } from "../lib.js";
4
5
  import { resolveArtifactPath } from "./resolver.js";
5
- import { EXIT_CODES } from "../utils/exit-codes.js";
6
- export function loadRoutes() {
7
- const root = repoRoot();
8
- const routesPath = resolveArtifactPath(root, ".iris/routes.yaml");
9
- if (!fs.existsSync(routesPath)) {
10
- return null;
6
+ import kleur from "kleur";
7
+ // --- Errors ---
8
+ export class RoutesError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "RoutesError";
11
12
  }
13
+ }
14
+ export class RoutesLoadError extends RoutesError {
15
+ cause;
16
+ constructor(message, cause) {
17
+ super(message);
18
+ this.cause = cause;
19
+ this.name = "RoutesLoadError";
20
+ }
21
+ }
22
+ export class RoutesOverlayMissingError extends RoutesError {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "RoutesOverlayMissingError";
26
+ }
27
+ }
28
+ // --- Loaders ---
29
+ export function loadBaseRoutes(framework, root) {
12
30
  try {
13
- const content = fs.readFileSync(routesPath, "utf8");
14
- return yaml.load(content);
31
+ const r = root || repoRoot();
32
+ // 1. Framework Priority
33
+ if (framework && framework.files.routes && fs.existsSync(framework.files.routes)) {
34
+ const content = fs.readFileSync(framework.files.routes, "utf8");
35
+ const doc = yaml.load(content);
36
+ validateRoutes(doc, "framework-base");
37
+ validateRoutes(doc, "framework-base");
38
+ doc.routes.forEach(r => r._source = "base");
39
+ Object.defineProperty(doc, '_meta', {
40
+ value: { sourceKind: "framework", sourcePath: framework.files.routes },
41
+ enumerable: false,
42
+ writable: true
43
+ });
44
+ return doc;
45
+ }
46
+ // 2. Legacy Fallback
47
+ const legacyPath = resolveArtifactPath(r, ".iris/routes.yaml");
48
+ if (fs.existsSync(legacyPath)) {
49
+ if (framework) {
50
+ console.error(kleur.yellow(`IRIS_WARNING IRIS_DEPRECATED_LEGACY_ROUTES: framework=${framework.manifest.id} missing routes.yaml; using .iris/routes.yaml`));
51
+ }
52
+ const content = fs.readFileSync(legacyPath, "utf8");
53
+ const doc = yaml.load(content);
54
+ validateRoutes(doc, "legacy-base");
55
+ doc.routes.forEach(r => r._source = "base");
56
+ Object.defineProperty(doc, '_meta', {
57
+ value: { sourceKind: "legacy", sourcePath: legacyPath },
58
+ enumerable: false,
59
+ writable: true
60
+ });
61
+ return doc;
62
+ }
63
+ throw new RoutesLoadError(`Missing required routes.yaml (Checked framework '${framework?.manifest.id || 'none'}' and legacy .iris/routes.yaml)`);
64
+ }
65
+ catch (error) {
66
+ if (error instanceof RoutesError)
67
+ throw error;
68
+ throw new RoutesLoadError("Failed to load base routes", error);
69
+ }
70
+ }
71
+ export const loadRoutes = (root) => loadBaseRoutes(null, root);
72
+ /**
73
+ * Loads Effective Routes: Base + User Repo Overlay.
74
+ */
75
+ export function loadEffectiveRoutes(framework, root, activeFlowId) {
76
+ // 1. Load Base (Framework or Legacy)
77
+ // Note: base is required. loadBaseRoutes throws if missing.
78
+ const r = root || repoRoot();
79
+ const base = loadBaseRoutes(framework, r);
80
+ // Determine Overlay Namespace: Flow ID (if active) OR Framework ID (if loaded)
81
+ const overlayNamespace = activeFlowId || framework?.manifest.id;
82
+ if (!overlayNamespace)
83
+ return base;
84
+ try {
85
+ // User Repo Overlay: .iris/overlays/<namespace>/routes.yaml
86
+ // This allows the user to override routing via flow OR framework namespace.
87
+ const overlayPath = path.join(r, ".iris/overlays", overlayNamespace, "routes.yaml");
88
+ if (fs.existsSync(overlayPath)) {
89
+ const content = fs.readFileSync(overlayPath, "utf8");
90
+ const overlay = (yaml.load(content) || {});
91
+ const merged = mergeRoutes(base, overlay);
92
+ Object.defineProperty(merged, '_meta', {
93
+ value: {
94
+ sourceKind: "mixed",
95
+ sourcePath: overlayPath
96
+ },
97
+ enumerable: false,
98
+ writable: true
99
+ });
100
+ return merged;
101
+ }
102
+ // Return base if no overlay found (Optional)
103
+ // Ensure we throw if explicit namespace was requested but not found?
104
+ // The test expects RoutesOverlayMissingError if "missing" flow is passed.
105
+ // Assuming if overlayNamespace is provided, we EXPECT an overlay.
106
+ if (overlayNamespace) {
107
+ // Only throw if this was an EXPLICIT flow request (activeFlowId is set)
108
+ // If it fell back to framework ID, treat overlay as optional.
109
+ if (activeFlowId) {
110
+ throw new RoutesOverlayMissingError(`Routes overlay not found for namespace '${overlayNamespace}' at ${overlayPath}`);
111
+ }
112
+ // else: explicit overlay not found for base framework -> return base (safe)
113
+ }
114
+ return base;
115
+ }
116
+ catch (error) {
117
+ if (error instanceof RoutesError)
118
+ throw error;
119
+ throw new RoutesLoadError(`Failed to load effective routes for namespace '${overlayNamespace}'`, error);
120
+ }
121
+ }
122
+ /**
123
+ * Merges routes purely.
124
+ */
125
+ export function mergeRoutes(base, overlay) {
126
+ // Deep clone base
127
+ const result = JSON.parse(JSON.stringify(base));
128
+ // 1. Merge Default
129
+ if (overlay.default) {
130
+ result.default = { ...result.default, ...overlay.default };
131
+ }
132
+ if (!overlay.routes || !Array.isArray(overlay.routes)) {
133
+ return result;
134
+ }
135
+ // Validate Overlay Routes first
136
+ overlay.routes.forEach(r => validateRoute(r, "overlay"));
137
+ // 2. Merge Routes
138
+ const baseMap = new Map();
139
+ result.routes.forEach((r, idx) => baseMap.set(r.route_id, idx));
140
+ const newRoutes = [];
141
+ for (const oRoute of overlay.routes) {
142
+ if (baseMap.has(oRoute.route_id)) {
143
+ // Override
144
+ const idx = baseMap.get(oRoute.route_id);
145
+ result.routes[idx] = {
146
+ ...result.routes[idx],
147
+ ...oRoute,
148
+ _source: "merged"
149
+ };
150
+ }
151
+ else {
152
+ // New
153
+ newRoutes.push({
154
+ ...oRoute,
155
+ _source: "overlay"
156
+ });
157
+ }
158
+ }
159
+ // Prepend new routes
160
+ if (newRoutes.length > 0) {
161
+ result.routes.unshift(...newRoutes);
162
+ }
163
+ // Validate Final Result (Uniqueness check)
164
+ validateRoutes(result, "effective");
165
+ return result;
166
+ }
167
+ // --- Validation Helpers ---
168
+ function validateRoutes(routes, context) {
169
+ if (!routes || !routes.routes) {
170
+ throw new RoutesLoadError(`Invalid routes format (${context}): 'routes' array missing.`);
171
+ }
172
+ const seenIds = new Set();
173
+ routes.routes.forEach(r => {
174
+ validateRoute(r, context);
175
+ if (seenIds.has(r.route_id)) {
176
+ throw new RoutesLoadError(`Duplicate route_id found (${context}): '${r.route_id}'`);
177
+ }
178
+ seenIds.add(r.route_id);
179
+ });
180
+ }
181
+ function validateRoute(r, context) {
182
+ if (!r.route_id || typeof r.route_id !== "string" || !r.route_id.trim()) {
183
+ throw new RoutesLoadError(`Invalid route in ${context}: route_id must be a non-empty string.`);
15
184
  }
16
- catch (e) {
17
- console.error("Failed to parse .iris/routes.yaml:", e);
18
- process.exit(EXIT_CODES.POLICY_ERROR);
185
+ // Normalize Phase
186
+ if (r.phase) {
187
+ r.phase = r.phase.toLowerCase();
19
188
  }
20
189
  }
@@ -3,15 +3,18 @@ import path from "path";
3
3
  import { randomUUID } from "crypto";
4
4
  import { WorkflowStage } from "../bridge/types.js";
5
5
  const RUNS_DIR = path.join(process.cwd(), ".iris/runs");
6
+ import { generateIntentId } from "./utils/interpolate.js";
6
7
  /**
7
8
  * Create a new workflow run
8
9
  */
9
10
  export function createRun(intent, ideId, gateMode = "manual") {
10
11
  const runId = randomUUID();
11
12
  const now = new Date().toISOString();
13
+ const intentId = generateIntentId({ goal: intent }); // Fallback to intent processing if draft is missing
12
14
  const runState = {
13
15
  runId,
14
16
  intent,
17
+ intentId,
15
18
  ideId,
16
19
  stage: WorkflowStage.INTENT_INCEPTION,
17
20
  gateMode,
@@ -5,6 +5,10 @@ import { EXIT_CODES } from "../utils/exit-codes.js";
5
5
  const STATE_FILE_PATH = path.join(process.cwd(), ".iris/state.yaml");
6
6
  export const DEFAULT_STATE = {
7
7
  version: 1,
8
+ framework: {
9
+ current: "iris-core",
10
+ version: null,
11
+ },
8
12
  phase: {
9
13
  current: "inception",
10
14
  requested: null,
@@ -13,6 +17,7 @@ export const DEFAULT_STATE = {
13
17
  intent_id: null,
14
18
  unit_id: null,
15
19
  bolt_id: null,
20
+ flow: null,
16
21
  },
17
22
  last_validation: {
18
23
  at: null,
@@ -32,7 +37,19 @@ export function loadState() {
32
37
  }
33
38
  const content = fs.readFileSync(STATE_FILE_PATH, "utf8");
34
39
  const doc = yaml.load(content);
35
- const merged = { ...DEFAULT_STATE, ...doc };
40
+ // Start with defaults to ensure all fields exist (deep merge strategy)
41
+ // We manually merge top-level objects to avoid losing nested defaults
42
+ const merged = {
43
+ ...DEFAULT_STATE,
44
+ ...doc,
45
+ framework: { ...DEFAULT_STATE.framework, ...doc.framework },
46
+ phase: { ...DEFAULT_STATE.phase, ...doc.phase },
47
+ active: { ...DEFAULT_STATE.active, ...doc.active },
48
+ last_validation: { ...DEFAULT_STATE.last_validation, ...doc.last_validation },
49
+ // Optional fields
50
+ ide: doc.ide ? { ...doc.ide } : undefined,
51
+ bridge: doc.bridge ? { ...doc.bridge } : undefined,
52
+ };
36
53
  // Migration/Fixup for legacy/bundled schema
37
54
  if (doc.current_phase && !doc.phase) {
38
55
  merged.phase = {
@@ -40,14 +57,9 @@ export function loadState() {
40
57
  requested: null
41
58
  };
42
59
  }
43
- // Ensure nested objects exist
44
- if (!merged.phase)
45
- merged.phase = { ...DEFAULT_STATE.phase };
46
- if (!merged.active)
47
- merged.active = { ...DEFAULT_STATE.active };
48
- if (!merged.last_validation || typeof merged.last_validation !== 'object') {
49
- merged.last_validation = { ...DEFAULT_STATE.last_validation };
50
- }
60
+ // Ensure flow exists (backward compatibility)
61
+ if (merged.active.flow === undefined)
62
+ merged.active.flow = null;
51
63
  return merged;
52
64
  }
53
65
  catch (error) {
@@ -83,3 +95,19 @@ export function setSelectedIde(ideId) {
83
95
  };
84
96
  saveState(state);
85
97
  }
98
+ /**
99
+ * Get the active framework info
100
+ */
101
+ export function getActiveFramework(state = loadState()) {
102
+ return state.framework;
103
+ }
104
+ /**
105
+ * Set the active framework
106
+ */
107
+ export function setActiveFramework(state, id, version = null) {
108
+ state.framework = {
109
+ current: id,
110
+ version: version
111
+ };
112
+ saveState(state);
113
+ }
@@ -0,0 +1,70 @@
1
+ export const UNIT_BRIEF_TEMPLATE = `---
2
+ id: {{unitId}}
3
+ status: draft
4
+ ---
5
+
6
+ # Unit Brief: {{title}}
7
+
8
+ ## Objective
9
+ {{summary}}
10
+
11
+ ## Scope
12
+ ### In Scope
13
+ - [ ] Implementation item 1
14
+ - [ ] Implementation item 2
15
+
16
+ ### Out of Scope
17
+ - Non-goals for this unit
18
+
19
+ ## Acceptance Criteria
20
+ - [ ] Criteria 1
21
+ - [ ] Criteria 2
22
+
23
+ ## Risks
24
+ - Risk 1
25
+ `;
26
+ export const BOLT_TEMPLATE = `---
27
+ id: {{boltId}}
28
+ unit: {{unitId}}
29
+ status: draft
30
+ type: feature
31
+ files:
32
+ - src/index.ts
33
+ cwd: .
34
+ commands:
35
+ test: npm test
36
+ ---
37
+
38
+ # Bolt: {{title}}
39
+
40
+ ## Implementation Plan
41
+ - [ ] Step 1
42
+ - [ ] Step 2
43
+
44
+ ## Files to Touch
45
+ - src/index.ts
46
+
47
+ ## Verification Plan
48
+ ### Automated Tests
49
+ - \`npm test\`
50
+
51
+ ### Manual Verification
52
+ - Check output
53
+ `;
54
+ export const TEST_REPORT_TEMPLATE = `---
55
+ bolt: {{boltId}}
56
+ status: {{status}}
57
+ timestamp: {{timestamp}}
58
+ ---
59
+
60
+ # Test Report: {{title}}
61
+
62
+ ## Summary
63
+ Status: {{status}}
64
+ Time: {{timestamp}}
65
+
66
+ ## Output
67
+ \`\`\`
68
+ {{output}}
69
+ \`\`\`
70
+ `;
@@ -0,0 +1,24 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ export function makeTempDir(prefix) {
5
+ // Ensure the prefix is safe and doesn't contain path separators
6
+ const safePrefix = prefix.replace(/[^a-zA-Z0-9_-]/g, '_');
7
+ return fs.mkdtempSync(path.join(os.tmpdir(), `iris-${safePrefix}-`));
8
+ }
9
+ export function removeTempDir(dir) {
10
+ try {
11
+ if (fs.existsSync(dir)) {
12
+ // Use rmSync with force and recursive options for robust deletion
13
+ // This is available in Node.js 14.14.0+
14
+ fs.rmSync(dir, { recursive: true, force: true });
15
+ }
16
+ }
17
+ catch (err) {
18
+ // Log but don't throw, cleanup failure shouldn't crash the main process
19
+ // unless strictly required. For CLI tools, silence is usually gold for cleanup.
20
+ // However, if verbose logging was available here we'd use it.
21
+ // For now, we swallow the error to prevent crashing.
22
+ // console.warn(`Failed to remove temp dir ${dir}:`, err);
23
+ }
24
+ }
@@ -70,9 +70,15 @@ export async function uninstallIris(options) {
70
70
  // Actually if we remove .iris entire folder, we don't need to do C.
71
71
  // Step D: Remove doctrine prompt
72
72
  let removeDoctrine = false;
73
+ const isNonInteractive = options.force || options.yes || !!process.env.CI;
73
74
  if (options.force) {
74
75
  removeDoctrine = true;
75
76
  }
77
+ else if (isNonInteractive) {
78
+ // Safe default: Preserve doctrine in non-interactive/CI unless --force is used
79
+ removeDoctrine = false;
80
+ console.log(kleur.gray("Non-interactive mode (CI/--yes): Preserving .iris/ directory by default."));
81
+ }
76
82
  else {
77
83
  const { confirm } = await inquirer.prompt([
78
84
  {
@@ -92,15 +98,24 @@ export async function uninstallIris(options) {
92
98
  console.log(kleur.gray("Kept .iris/ directory."));
93
99
  }
94
100
  // Step E: Memory Bank
95
- if (!options.keepMemory) {
96
- const { confirmMemory } = await inquirer.prompt([
97
- {
98
- type: "confirm",
99
- name: "confirmMemory",
100
- message: "Keep memory-bank/ folder? (default: Yes, keep)",
101
- default: true
102
- }
103
- ]);
101
+ if (!options.keepMemory && !options.force) {
102
+ let confirmMemory = true; // Default Keep
103
+ if (isNonInteractive) {
104
+ // Safe default: Preserve memory bank
105
+ confirmMemory = true;
106
+ console.log(kleur.gray("Non-interactive mode: Preserving memory-bank/ by default."));
107
+ }
108
+ else {
109
+ const { confirm } = await inquirer.prompt([
110
+ {
111
+ type: "confirm",
112
+ name: "confirm",
113
+ message: "Keep memory-bank/ folder? (default: Yes, keep)",
114
+ default: true
115
+ }
116
+ ]);
117
+ confirmMemory = confirm;
118
+ }
104
119
  if (!confirmMemory) { // User said "No" to keeping -> Delete
105
120
  // Check if empty or created by us
106
121
  // Created dirs = manifest.created_dirs
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Interpolates tokens in a string.
3
+ * Currently supports: {intentId}
4
+ * Throws on unknown tokens.
5
+ */
6
+ export function interpolateTokens(str, tokens) {
7
+ return str.replace(/\{(\w+)\}/g, (match, key) => {
8
+ if (key === 'intentId') {
9
+ if (!tokens.intentId) {
10
+ throw new Error(`Token {intentId} requires intentId value but none provided`);
11
+ }
12
+ return tokens.intentId;
13
+ }
14
+ throw new Error(`Unknown token {${key}} in path: ${str}`);
15
+ });
16
+ }
17
+ /**
18
+ * Generates a unique intentId from draft fields.
19
+ * Format: YYYYMMDD-HHMMSS-<slug>
20
+ * Slug priority: goal > problem > valueProp > fallback
21
+ */
22
+ export function generateIntentId(draft) {
23
+ const now = new Date();
24
+ const date = [
25
+ now.getFullYear(),
26
+ String(now.getMonth() + 1).padStart(2, '0'),
27
+ String(now.getDate()).padStart(2, '0')
28
+ ].join('');
29
+ const time = [
30
+ String(now.getHours()).padStart(2, '0'),
31
+ String(now.getMinutes()).padStart(2, '0'),
32
+ String(now.getSeconds()).padStart(2, '0')
33
+ ].join('');
34
+ const source = draft?.goal || draft?.problem || draft?.valueProp || 'intent';
35
+ const slug = source
36
+ .toLowerCase()
37
+ .replace(/[^a-z0-9]+/g, '-')
38
+ .replace(/-+/g, '-')
39
+ .replace(/^-|-$/g, '')
40
+ .slice(0, 40);
41
+ return `${date}-${time}-${slug || 'intent'}`;
42
+ }
@@ -1,16 +1,17 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import matter from "gray-matter";
4
- import { loadPolicy } from "./policy.js";
4
+ import { loadEffectivePolicy } from "./policy.js";
5
5
  import { loadState, saveState } from "./state.js";
6
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.
7
+ import glob from "fast-glob";
8
+ import { interpolateTokens } from "./utils/interpolate.js";
9
9
  export async function validate(options) {
10
10
  const shouldWrite = options.writeBack !== false;
11
11
  // Step A: Load & validate config
12
- const policy = loadPolicy();
12
+ // May throw PolicyLoadError or PolicyOverlayMissingError - caller must handle
13
13
  const state = loadState();
14
+ const policy = loadEffectivePolicy(options.frameworkResolution, undefined, state.active.flow);
14
15
  const errors = [];
15
16
  // Step B: Resolve target phase
16
17
  let targetPhase = state.phase.current;
@@ -30,7 +31,45 @@ export async function validate(options) {
30
31
  // Gather requirements for the phase
31
32
  const requirements = policy.phases[targetPhase].requires || [];
32
33
  for (const req of requirements) {
33
- const check = checkArtifact(process.cwd(), req.path, req.type);
34
+ let reqPath = req.path;
35
+ try {
36
+ // Interpolate path with intentId if available
37
+ reqPath = interpolateTokens(req.path, { intentId: state.active.intent_id || undefined });
38
+ }
39
+ catch (e) {
40
+ // If interpolation fails (e.g. missing intentId), we treat it as an error
41
+ errors.push({
42
+ code: "CONFIG_ERROR",
43
+ message: `Policy requirement path interpolation failed: ${e.message}`,
44
+ severity: "error"
45
+ });
46
+ continue;
47
+ }
48
+ if (glob.isDynamicPattern(reqPath)) {
49
+ const matches = glob.sync(reqPath, { cwd: process.cwd(), onlyFiles: req.type === 'file', onlyDirectories: req.type === 'directory' });
50
+ if (matches.length === 0) {
51
+ errors.push({
52
+ code: "MISSING_ARTIFACT",
53
+ path: reqPath,
54
+ message: `No artifacts found matching required pattern: ${reqPath}`,
55
+ remediation: req.template
56
+ ? `Create artifact matching pattern`
57
+ : `Create ${req.type} matching ${reqPath}`,
58
+ severity: "error"
59
+ });
60
+ }
61
+ else {
62
+ // Check quality for all matches?
63
+ for (const match of matches) {
64
+ const check = checkArtifact(process.cwd(), match, req.type);
65
+ if (req.type === "file") {
66
+ errors.push(...checkContentQuality(check.absolutePath));
67
+ }
68
+ }
69
+ }
70
+ continue;
71
+ }
72
+ const check = checkArtifact(process.cwd(), reqPath, req.type);
34
73
  if (!check.exists) {
35
74
  errors.push({
36
75
  code: "MISSING_ARTIFACT",
@@ -89,9 +128,21 @@ export async function validate(options) {
89
128
  const gates = policy.phases[targetPhase].gates || [];
90
129
  for (const gate of gates) {
91
130
  if (gate.kind === "frontmatter") {
92
- const gateError = checkFrontmatterGate(gate);
93
- if (gateError) {
94
- errors.push(gateError);
131
+ try {
132
+ // Create a copy with interpolated file path
133
+ const interpolatedGate = { ...gate };
134
+ interpolatedGate.file = interpolateTokens(gate.file, { intentId: state.active.intent_id || undefined });
135
+ const gateError = checkFrontmatterGate(interpolatedGate);
136
+ if (gateError) {
137
+ errors.push(gateError);
138
+ }
139
+ }
140
+ catch (e) {
141
+ errors.push({
142
+ code: "CONFIG_ERROR",
143
+ message: `Gate file path interpolation failed: ${e.message}`,
144
+ severity: "error"
145
+ });
95
146
  }
96
147
  }
97
148
  else {
@@ -207,7 +258,18 @@ function checkFrontmatterGate(gate) {
207
258
  };
208
259
  }
209
260
  if (gate.field) {
210
- const val = parsed.data[gate.field];
261
+ // Support dot-notation for nested fields
262
+ const fields = gate.field.split('.');
263
+ let val = parsed.data;
264
+ for (const f of fields) {
265
+ if (val && typeof val === 'object') {
266
+ val = val[f];
267
+ }
268
+ else {
269
+ val = undefined;
270
+ break;
271
+ }
272
+ }
211
273
  if (val === undefined) {
212
274
  return {
213
275
  code: "GATE_FIELD_MISSING",
@@ -217,7 +279,7 @@ function checkFrontmatterGate(gate) {
217
279
  severity: "error"
218
280
  };
219
281
  }
220
- if (gate.equals !== undefined && val !== gate.equals) {
282
+ if (gate.equals !== undefined && String(val) !== String(gate.equals)) {
221
283
  return {
222
284
  code: "GATE_VALUE_MISMATCH",
223
285
  path: gate.file,
@@ -0,0 +1,51 @@
1
+ import yaml from "js-yaml";
2
+ import fs from "fs";
3
+ import { FrameworkError } from "../../framework/framework-types.js";
4
+ // Default workflow for frameworks that don't specify one
5
+ export const DEFAULT_WORKFLOW_CONFIG = {
6
+ schemaVersion: 1,
7
+ steps: [
8
+ { id: "interview", kind: "interview" },
9
+ { id: "artifacts", kind: "artifacts" },
10
+ { id: "plan", kind: "planning" },
11
+ { id: "build", kind: "construction" },
12
+ { id: "execute", kind: "execution" },
13
+ { id: "validate", kind: "validate" },
14
+ { id: "pack", kind: "pack" },
15
+ { id: "handoff", kind: "handoff" }
16
+ ]
17
+ };
18
+ export function loadEffectiveWorkflowConfig(framework) {
19
+ if (!framework || !framework.files.workflow) {
20
+ return DEFAULT_WORKFLOW_CONFIG;
21
+ }
22
+ try {
23
+ const content = fs.readFileSync(framework.files.workflow, "utf-8");
24
+ const doc = yaml.load(content);
25
+ if (!doc)
26
+ throw new Error("Empty workflow file");
27
+ // Basic schema validation
28
+ if (doc.schemaVersion !== 1) {
29
+ throw new FrameworkError("INVALID_SCHEMA", framework.files.workflow, `Unsupported workflow schema version: ${doc.schemaVersion}. Only version 1 is supported.`);
30
+ }
31
+ if (!Array.isArray(doc.steps)) {
32
+ throw new FrameworkError("INVALID_SCHEMA", framework.files.workflow, "Workflow must have a 'steps' array.");
33
+ }
34
+ // Validate steps
35
+ for (const step of doc.steps) {
36
+ if (!step.id || !step.kind) {
37
+ throw new FrameworkError("INVALID_SCHEMA", framework.files.workflow, "Workflow steps must have 'id' and 'kind'.");
38
+ }
39
+ if (!['interview', 'artifacts', 'planning', 'construction', 'validate', 'pack', 'handoff', 'execution'].includes(step.kind)) {
40
+ throw new FrameworkError("INVALID_SCHEMA", framework.files.workflow, `Unknown step kind: '${step.kind}'.`);
41
+ }
42
+ }
43
+ return doc;
44
+ }
45
+ catch (e) {
46
+ // Wrap generic errors
47
+ if (e instanceof FrameworkError)
48
+ throw e;
49
+ throw new FrameworkError("INVALID_YAML", framework.files.workflow, `Failed to parse workflow configuration: ${e.message}`);
50
+ }
51
+ }