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.
- package/README.md +294 -264
- package/dist/bridge/agent-runner.js +190 -0
- package/dist/bridge/connector-factory.js +4 -0
- package/dist/bridge/connectors/in-process-connector.js +29 -0
- package/dist/bridge/filesystem-connector.js +5 -0
- package/dist/cli.js +10 -2
- package/dist/commands/ask.js +150 -23
- package/dist/commands/bridge.js +8 -0
- package/dist/commands/flow.js +301 -0
- package/dist/commands/framework.js +273 -0
- package/dist/commands/generate.js +59 -0
- package/dist/commands/install.js +72 -29
- package/dist/commands/pack.js +7 -1
- package/dist/commands/run.js +195 -13
- package/dist/commands/status.js +9 -0
- package/dist/commands/uninstall.js +3 -1
- package/dist/commands/use.js +20 -0
- package/dist/commands/validate.js +80 -65
- package/dist/framework/framework-loader.js +97 -0
- package/dist/framework/framework-paths.js +48 -0
- package/dist/framework/framework-types.js +15 -0
- package/dist/iris/artifacts/config.js +68 -0
- package/dist/iris/artifacts/generator.js +88 -0
- package/dist/iris/artifacts/types.js +1 -0
- package/dist/iris/bundle.js +44 -0
- package/dist/iris/doctrine/collector.js +124 -0
- package/dist/iris/fixer.js +28 -22
- package/dist/iris/flows/manifest.js +124 -0
- package/dist/iris/framework-context.js +49 -0
- package/dist/iris/framework-manager.js +215 -0
- package/dist/iris/fs/atomic.js +22 -0
- package/dist/iris/importers/index.js +9 -0
- package/dist/iris/importers/types.js +8 -0
- package/dist/iris/importers/writer.js +139 -0
- package/dist/iris/installer.js +105 -40
- package/dist/iris/interactive/env.js +21 -0
- package/dist/iris/interactive/intent-interview.js +345 -0
- package/dist/iris/interactive/intent-schema.js +28 -0
- package/dist/iris/interactive/interview-io.js +22 -0
- package/dist/iris/interview/config.js +71 -0
- package/dist/iris/interview/types.js +16 -0
- package/dist/iris/interview/utils.js +38 -0
- package/dist/iris/packer.js +69 -47
- package/dist/iris/parsers/unit-parser.js +43 -0
- package/dist/iris/paths.js +18 -0
- package/dist/iris/policy.js +122 -17
- package/dist/iris/proc.js +56 -0
- package/dist/iris/resolver.js +3 -0
- package/dist/iris/routes.js +180 -11
- package/dist/iris/run-state.js +3 -0
- package/dist/iris/state.js +37 -9
- package/dist/iris/templates.js +70 -0
- package/dist/iris/tmp.js +24 -0
- package/dist/iris/uninstaller.js +24 -9
- package/dist/iris/utils/interpolate.js +42 -0
- package/dist/iris/validator.js +72 -10
- package/dist/iris/workflow/config.js +51 -0
- package/dist/iris/workflow/engine.js +129 -0
- package/dist/iris/workflow/steps.js +448 -0
- package/dist/iris/workflow/types.js +1 -0
- package/dist/utils/logo.js +17 -0
- package/dist/workflows/intent-inception.js +87 -65
- package/package.json +8 -6
- package/src/iris_bundle/.iris/aidlc/README.md +0 -16
- package/src/iris_bundle/.iris/aidlc/agents/iris-construction-agent.md +0 -35
- package/src/iris_bundle/.iris/aidlc/agents/iris-inception-agent.md +0 -30
- package/src/iris_bundle/.iris/aidlc/agents/iris-master-agent.md +0 -35
- package/src/iris_bundle/.iris/aidlc/agents/iris-operations-agent.md +0 -29
- package/src/iris_bundle/.iris/aidlc/commands/iris-construction-agent.md +0 -18
- package/src/iris_bundle/.iris/aidlc/commands/iris-inception-agent.md +0 -18
- package/src/iris_bundle/.iris/aidlc/commands/iris-master-agent.md +0 -18
- package/src/iris_bundle/.iris/aidlc/commands/iris-operations-agent.md +0 -18
- package/src/iris_bundle/.iris/aidlc/context/context-map.md +0 -25
- package/src/iris_bundle/.iris/aidlc/context/exclusion-rules.md +0 -13
- package/src/iris_bundle/.iris/aidlc/context/load-order.md +0 -25
- package/src/iris_bundle/.iris/aidlc/memory/intent-rules.md +0 -9
- package/src/iris_bundle/.iris/aidlc/memory/log-rules.md +0 -5
- package/src/iris_bundle/.iris/aidlc/memory/memory-bank.yaml +0 -39
- package/src/iris_bundle/.iris/aidlc/memory/unit-rules.md +0 -9
- package/src/iris_bundle/.iris/aidlc/quick-start.md +0 -24
- package/src/iris_bundle/.iris/aidlc/skills/execution/implementation.md +0 -14
- package/src/iris_bundle/.iris/aidlc/skills/execution/refactoring.md +0 -13
- package/src/iris_bundle/.iris/aidlc/skills/execution/scaffold-generation.md +0 -15
- package/src/iris_bundle/.iris/aidlc/skills/governance/escalation.md +0 -13
- package/src/iris_bundle/.iris/aidlc/skills/governance/quality-gates.md +0 -14
- package/src/iris_bundle/.iris/aidlc/skills/governance/stop-conditions.md +0 -11
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/decomposition.md +0 -23
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/risk-analysis.md +0 -14
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/verification.md +0 -21
- package/src/iris_bundle/.iris/aidlc/standards/artifacts-registry.md +0 -38
- package/src/iris_bundle/.iris/aidlc/standards/decision-logging.md +0 -16
- package/src/iris_bundle/.iris/aidlc/standards/doctrine-structure.md +0 -31
- package/src/iris_bundle/.iris/aidlc/standards/documentation-rules.md +0 -15
- package/src/iris_bundle/.iris/aidlc/standards/file-structure.md +0 -21
- package/src/iris_bundle/.iris/aidlc/standards/naming-conventions.md +0 -18
- package/src/iris_bundle/.iris/aidlc/standards/phases-and-gates.md +0 -25
- package/src/iris_bundle/.iris/aidlc/standards/routes-and-routing.md +0 -35
- package/src/iris_bundle/.iris/aidlc/standards/tool-wrappers.md +0 -32
- package/src/iris_bundle/.iris/aidlc/templates/bolt.md +0 -23
- package/src/iris_bundle/.iris/aidlc/templates/doctrine-doc-template.md +0 -33
- package/src/iris_bundle/.iris/aidlc/templates/intent.md +0 -23
- package/src/iris_bundle/.iris/aidlc/templates/log.md +0 -24
- package/src/iris_bundle/.iris/aidlc/templates/review.md +0 -21
- package/src/iris_bundle/.iris/aidlc/templates/unit.md +0 -31
- package/src/iris_bundle/.iris/aidlc/validation/failure-modes.md +0 -16
- package/src/iris_bundle/.iris/aidlc/validation/phase-preconditions.md +0 -21
- package/src/iris_bundle/.iris/aidlc/validation/quality-checklist.md +0 -20
- package/src/iris_bundle/.iris/policy.yaml +0 -27
- package/src/iris_bundle/.iris/routes.yaml +0 -98
- package/src/iris_bundle/.iris/state.yaml +0 -7
- package/src/iris_bundle/.iris/tools/claude/.claude/claude.md +0 -9
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/compare-specs.md +0 -203
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-construction-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-inception-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-master-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-operations-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/codex/AGENTS.md +0 -15
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-construction-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-inception-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-master-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-operations-agent.md +0 -25
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-construction-agent.toml +0 -29
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-inception-agent.toml +0 -29
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-master-agent.toml +0 -29
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-operations-agent.toml +0 -29
package/dist/iris/routes.js
CHANGED
|
@@ -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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
185
|
+
// Normalize Phase
|
|
186
|
+
if (r.phase) {
|
|
187
|
+
r.phase = r.phase.toLowerCase();
|
|
19
188
|
}
|
|
20
189
|
}
|
package/dist/iris/run-state.js
CHANGED
|
@@ -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,
|
package/dist/iris/state.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
44
|
-
if (
|
|
45
|
-
merged.
|
|
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
|
+
`;
|
package/dist/iris/tmp.js
ADDED
|
@@ -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
|
+
}
|
package/dist/iris/uninstaller.js
CHANGED
|
@@ -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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}
|
package/dist/iris/validator.js
CHANGED
|
@@ -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 {
|
|
4
|
+
import { loadEffectivePolicy } from "./policy.js";
|
|
5
5
|
import { loadState, saveState } from "./state.js";
|
|
6
6
|
import { checkArtifact } from "./artifact-checker.js";
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
+
}
|