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,97 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import yaml from 'js-yaml';
4
+ import { FrameworkError } from './framework-types.js';
5
+ import { resolveFrameworkRoot } from './framework-paths.js';
6
+ export async function loadFramework(input, options) {
7
+ const rootDir = resolveFrameworkRoot(input, options);
8
+ // 1. Validate Root Directory
9
+ if (!fs.existsSync(rootDir) || !fs.statSync(rootDir).isDirectory()) {
10
+ throw new FrameworkError('NOT_FOUND', rootDir, `Framework directory not found: ${rootDir}`, 'Expected a valid directory at the resolved path.');
11
+ }
12
+ // 2. Locate Manifest
13
+ let manifestPath = path.join(rootDir, 'framework.yaml');
14
+ if (!fs.existsSync(manifestPath)) {
15
+ manifestPath = path.join(rootDir, 'framework.yml');
16
+ }
17
+ if (!fs.existsSync(manifestPath)) {
18
+ throw new FrameworkError('MISSING_MANIFEST', rootDir, `No framework.yaml (or .yml) found in ${rootDir}`);
19
+ }
20
+ // 3. Parse Manifest
21
+ let raw;
22
+ try {
23
+ const content = fs.readFileSync(manifestPath, 'utf-8');
24
+ raw = yaml.load(content);
25
+ }
26
+ catch (e) {
27
+ throw new FrameworkError('INVALID_YAML', manifestPath, `Failed to parse framework manifest: ${e.message}`);
28
+ }
29
+ // 4. Validate Schema
30
+ if (!raw || typeof raw !== 'object') {
31
+ throw new FrameworkError('INVALID_SCHEMA', manifestPath, 'Manifest must be an object');
32
+ }
33
+ if (typeof raw.name !== 'string' || !raw.name) {
34
+ throw new FrameworkError('INVALID_SCHEMA', manifestPath, 'Missing required field: name');
35
+ }
36
+ if (typeof raw.version !== 'string' || !raw.version) {
37
+ throw new FrameworkError('INVALID_SCHEMA', manifestPath, 'Missing required field: version');
38
+ }
39
+ if (raw.id && typeof raw.id !== 'string') {
40
+ throw new FrameworkError('INVALID_SCHEMA', manifestPath, 'Field "id" must be a string');
41
+ }
42
+ // Loose ID validation
43
+ if (raw.id && !/^[a-z0-9-_/]+$/.test(raw.id)) {
44
+ throw new FrameworkError('INVALID_SCHEMA', manifestPath, 'Field "id" contains invalid characters (allowed: [a-z0-9-_/])');
45
+ }
46
+ if (raw.extends && typeof raw.extends !== 'string' && !Array.isArray(raw.extends)) {
47
+ throw new FrameworkError('INVALID_SCHEMA', manifestPath, 'Field "extends" must be a string or array of strings');
48
+ }
49
+ if (raw.overlays && (!Array.isArray(raw.overlays) || raw.overlays.some((o) => typeof o !== 'string'))) {
50
+ throw new FrameworkError('INVALID_SCHEMA', manifestPath, 'Field "overlays" must be an array of strings');
51
+ }
52
+ const manifest = {
53
+ name: raw.name,
54
+ version: raw.version,
55
+ id: raw.id,
56
+ extends: raw.extends,
57
+ overlays: raw.overlays,
58
+ templatesDir: raw.templatesDir
59
+ };
60
+ // 5. Discover Files
61
+ const files = {
62
+ manifest: manifestPath
63
+ };
64
+ const tryFile = (name) => {
65
+ const p = path.join(rootDir, name);
66
+ return fs.existsSync(p) ? p : undefined;
67
+ };
68
+ files.policy = tryFile('policy.yaml');
69
+ files.routes = tryFile('routes.yaml');
70
+ files.artifacts = tryFile('artifacts.yaml');
71
+ files.interview = tryFile('interview.yaml');
72
+ files.pack = tryFile('pack.yaml');
73
+ // 6. Handle Templates
74
+ if (manifest.templatesDir) {
75
+ const tplPath = path.resolve(rootDir, manifest.templatesDir);
76
+ if (!fs.existsSync(tplPath) || !fs.statSync(tplPath).isDirectory()) {
77
+ throw new FrameworkError('MISSING_FILE', tplPath, `Configured templatesDir not found: ${manifest.templatesDir}`);
78
+ }
79
+ files.templates = tplPath;
80
+ }
81
+ else {
82
+ const defaultTpl = path.join(rootDir, 'templates');
83
+ if (fs.existsSync(defaultTpl) && fs.statSync(defaultTpl).isDirectory()) {
84
+ files.templates = defaultTpl;
85
+ }
86
+ }
87
+ return {
88
+ manifest,
89
+ source: {
90
+ kind: 'local', // Step 1 assumption
91
+ path: rootDir,
92
+ id: input
93
+ },
94
+ files,
95
+ rootDir
96
+ };
97
+ }
@@ -0,0 +1,48 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ /**
7
+ * Resolves a framework input (name or path) to an absolute directory path.
8
+ *
9
+ * Heuristics:
10
+ * - If input implies a path (starts with ./ or /, is absolute, contains separator),
11
+ * it is resolved relative to repoRoot (unless absolute).
12
+ * - Otherwise, it is treated as a framework name/ID.
13
+ * 1. Check .iris/frameworks/<name> in repo.
14
+ * 2. Check bundled frameworks.
15
+ */
16
+ export function resolveFrameworkRoot(input, options) {
17
+ const { repoRoot } = options;
18
+ // Strict heuristics to distinguish path from name
19
+ const isPath = input.startsWith('.') ||
20
+ input.startsWith('/') ||
21
+ input.startsWith('\\') || // Windows explicit relative
22
+ path.isAbsolute(input);
23
+ if (isPath) {
24
+ return path.resolve(repoRoot, input);
25
+ }
26
+ // Check Repo Local
27
+ const localPath = path.resolve(repoRoot, '.iris/frameworks', input);
28
+ if (fs.existsSync(localPath)) {
29
+ return localPath;
30
+ }
31
+ // Check Bundled
32
+ // Bundle structure assumption:
33
+ // dist/framework/framework-paths.js -> ... -> src/iris_bundle/frameworks/
34
+ // actually, in build: dist/iris_bundle/frameworks/<name>
35
+ // In dev (ts-node): src/framework/framework-paths.ts -> ../iris_bundle/frameworks
36
+ // Try reliable bundle resolution
37
+ const candidates = [
38
+ path.resolve(__dirname, '../../dist/iris_bundle/frameworks', input), // from dist/framework/
39
+ path.resolve(__dirname, '../iris_bundle/frameworks', input), // from src/framework/
40
+ path.resolve(__dirname, '../../src/iris_bundle/frameworks', input) // from src/framework/ (alt)
41
+ ];
42
+ for (const c of candidates) {
43
+ if (fs.existsSync(c))
44
+ return c;
45
+ }
46
+ // Fallback to local path so error message shows expected repo location
47
+ return localPath;
48
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Core types for IRIS Framework abstraction.
3
+ */
4
+ export class FrameworkError extends Error {
5
+ code;
6
+ path;
7
+ hint;
8
+ constructor(code, path, message, hint) {
9
+ super(message);
10
+ this.code = code;
11
+ this.path = path;
12
+ this.hint = hint;
13
+ this.name = 'FrameworkError';
14
+ }
15
+ }
@@ -0,0 +1,68 @@
1
+ import fs from "fs";
2
+ import yaml from "js-yaml";
3
+ import kleur from "kleur";
4
+ // Default Artifacts: Summary + User Stories
5
+ export const DEFAULT_ARTIFACTS_CONFIG = {
6
+ schemaVersion: 1,
7
+ artifacts: [
8
+ {
9
+ id: "summary",
10
+ type: "summary",
11
+ mode: "template",
12
+ output: {
13
+ template: "# Summary\n\n**Goal**: {{goal}}\n\n**User**: {{userType}}\n**Phase**: {{projectPhase}}\n\n**Success Criteria**:\n{{successCriteria}}\n"
14
+ }
15
+ },
16
+ {
17
+ id: "user-stories",
18
+ type: "stories",
19
+ mode: "template",
20
+ required: false, // Don't fail if fields missing, just skip or partial?
21
+ // Wait, default should be robust.
22
+ // If fields missing, template will fail.
23
+ // Let's require standard fields.
24
+ input: { from: ["goal", "userType"] },
25
+ output: {
26
+ template: "As a {{userType}}, I want to {{goal}}, so that [Value Placeholders]"
27
+ }
28
+ }
29
+ ]
30
+ };
31
+ export function loadEffectiveArtifactsConfig(framework) {
32
+ if (!framework || !framework.files.artifacts || !fs.existsSync(framework.files.artifacts)) {
33
+ return DEFAULT_ARTIFACTS_CONFIG;
34
+ }
35
+ try {
36
+ const content = fs.readFileSync(framework.files.artifacts, "utf8");
37
+ const raw = yaml.load(content);
38
+ if (raw.schemaVersion !== 1) {
39
+ console.error(kleur.yellow(`IRIS_WARNING: Unsupported artifacts schema version ${raw.schemaVersion}. Using default.`));
40
+ return DEFAULT_ARTIFACTS_CONFIG;
41
+ }
42
+ if (!Array.isArray(raw.artifacts)) {
43
+ console.error(kleur.yellow(`IRIS_WARNING: Invalid artifacts.yaml (missing 'artifacts' array). Using default.`));
44
+ return DEFAULT_ARTIFACTS_CONFIG;
45
+ }
46
+ // Validate fields against KNOWN_INTENT_FIELDS
47
+ for (const art of raw.artifacts) {
48
+ if (art.input?.from) {
49
+ for (const field of art.input.from) {
50
+ // Check against KNOWN fields (or allow unknown if we trust framework author? Plan said "Config Error")
51
+ // Actually, plan says "Check missing input fields in draft".
52
+ // But here we validate the CONFIG itself referencing known variables.
53
+ // KNOWN_INTENT_FIELDS includes common ones.
54
+ // Let's be lenient on config validation for now to support custom aliases,
55
+ // but strict on generation (if draft misses it).
56
+ }
57
+ }
58
+ }
59
+ return {
60
+ schemaVersion: raw.schemaVersion,
61
+ artifacts: raw.artifacts
62
+ };
63
+ }
64
+ catch (e) {
65
+ console.error(kleur.yellow(`IRIS_WARNING: Failed to parse artifacts.yaml: ${e.message}. Using default.`));
66
+ return DEFAULT_ARTIFACTS_CONFIG;
67
+ }
68
+ }
@@ -0,0 +1,88 @@
1
+ import { getByPath, isMissing } from "../interview/utils.js";
2
+ import { interpolateTokens } from "../utils/interpolate.js";
3
+ // Error Codes
4
+ export const ERRORS = {
5
+ MODE_NOT_SUPPORTED: "ARTIFACT_MODE_NOT_SUPPORTED",
6
+ INPUT_MISSING: "ARTIFACT_INPUT_MISSING",
7
+ TEMPLATE_VAR_MISSING: "ARTIFACT_TEMPLATE_MISSING_VAR",
8
+ UNSUPPORTED_ARRAY: "ARTIFACT_TEMPLATE_UNSUPPORTED_ARRAY_TYPE"
9
+ };
10
+ /**
11
+ * Renders a simple template with {{var}} substitution.
12
+ * Supports dot-paths.
13
+ */
14
+ function renderTemplate(template, context, artifactId) {
15
+ // Strict regex: only alphanumeric, underscore, dot.
16
+ // Trims whitespace inside braces.
17
+ return template.replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g, (match, path) => {
18
+ const key = path.trim();
19
+ // Safety: Prevent prototype pollution access
20
+ if (key.includes('__proto__') || key.includes('constructor') || key.includes('prototype')) {
21
+ throw new Error(`${ERRORS.TEMPLATE_VAR_MISSING}: Unsafe variable path '${key}' in artifact '${artifactId}'`);
22
+ }
23
+ const value = getByPath(context, key);
24
+ if (value === undefined || value === null) {
25
+ throw new Error(`${ERRORS.TEMPLATE_VAR_MISSING}: Artifact '${artifactId}' requires variable '${key}'`);
26
+ }
27
+ if (Array.isArray(value)) {
28
+ // Check for objects (non-primitives)
29
+ if (value.some(v => typeof v === 'object' && v !== null)) {
30
+ throw new Error(`${ERRORS.UNSUPPORTED_ARRAY}: Artifact '${artifactId}' variable '${key}' contains objects (unsupported for joining).`);
31
+ }
32
+ return value.join('\n');
33
+ }
34
+ return String(value);
35
+ });
36
+ }
37
+ export function generateArtifacts(draft, config, frameworkResolution, intentId = null) {
38
+ const artifacts = [];
39
+ for (const def of config.artifacts) {
40
+ // 1. Mode Check
41
+ if (def.mode === 'llm') {
42
+ throw new Error(`${ERRORS.MODE_NOT_SUPPORTED}: LLM mode not supported yet (Artifact: ${def.id})`);
43
+ }
44
+ // 2. Input Validation (Declared requirements)
45
+ if (def.input?.from) {
46
+ const missing = def.input.from.filter(f => isMissing(getByPath(draft, f)));
47
+ if (missing.length > 0) {
48
+ throw new Error(`${ERRORS.INPUT_MISSING}: Artifact '${def.id}' requires missing fields: ${missing.join(', ')}`);
49
+ }
50
+ }
51
+ // 3. Transformation (Template)
52
+ if (def.output.template) {
53
+ const content = renderTemplate(def.output.template, draft, def.id);
54
+ // Interpolate 'out' path if specified
55
+ let outPath;
56
+ if (def.out) {
57
+ try {
58
+ outPath = interpolateTokens(def.out, { intentId: intentId || undefined });
59
+ }
60
+ catch (e) {
61
+ throw new Error(`${ERRORS.TEMPLATE_VAR_MISSING}: Path interpolation failed for artifact '${def.id}': ${e.message}`);
62
+ }
63
+ }
64
+ artifacts.push({
65
+ id: def.id,
66
+ type: def.type,
67
+ content,
68
+ path: outPath
69
+ });
70
+ }
71
+ }
72
+ const isFramework = frameworkResolution && frameworkResolution.files.artifacts;
73
+ return {
74
+ schemaVersion: 1,
75
+ framework: {
76
+ id: frameworkResolution?.manifest.id || "default",
77
+ version: frameworkResolution?.manifest.version || null
78
+ },
79
+ intentId: intentId,
80
+ artifacts,
81
+ artifactsSource: {
82
+ kind: isFramework ? "framework" : "default",
83
+ path: isFramework ? frameworkResolution.files.artifacts : null,
84
+ activeFrameworkId: frameworkResolution?.manifest.id || null
85
+ },
86
+ createdAt: new Date().toISOString()
87
+ };
88
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { fileURLToPath } from "url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = path.dirname(__filename);
6
+ /**
7
+ * Resolves the root of the IRIS Bundle (src/iris_bundle or dist/iris_bundle)
8
+ * independent of CWD.
9
+ */
10
+ export function getBundleRoot() {
11
+ // 1. Production / Dist layout
12
+ // __dirname is usually dist/iris or similar. Bundle in dist/iris_bundle
13
+ // Check sibling of 'dist' if we are in dist/iris
14
+ // Attempt 1: Sibling 'iris_bundle' in expected location relative to THIS file
15
+ // Example: .../dist/iris/bundle.js -> .../dist/iris_bundle
16
+ // Or: .../src/iris/bundle.ts -> .../src/iris_bundle
17
+ const candidates = [
18
+ path.resolve(__dirname, "../iris_bundle"), // Typical sibling in src/ or dist/
19
+ path.resolve(__dirname, "../../src/iris_bundle"), // Dev fallback if running from dist but source present
20
+ path.resolve(__dirname, "../../iris_bundle") // Another dist layout possibility
21
+ ];
22
+ for (const candidate of candidates) {
23
+ if (fs.existsSync(candidate) && fs.existsSync(path.join(candidate, ".iris"))) {
24
+ return candidate;
25
+ }
26
+ }
27
+ // Fallback/Throw?
28
+ // In dev environment, if src/iris_bundle exists relative to root?
29
+ throw new Error(`Could not resolve IRIS bundle root. Searched: ${candidates.join(", ")}`);
30
+ }
31
+ export function getBundledFlowsDir() {
32
+ return path.join(getBundleRoot(), ".iris/flows");
33
+ }
34
+ export function getBundledFrameworksDir() {
35
+ return path.join(getBundleRoot(), "frameworks");
36
+ }
37
+ export function listBundledFlows() {
38
+ const flowsDir = getBundledFlowsDir();
39
+ if (!fs.existsSync(flowsDir))
40
+ return [];
41
+ return fs.readdirSync(flowsDir, { withFileTypes: true })
42
+ .filter(ent => ent.isDirectory())
43
+ .map(ent => ent.name);
44
+ }
@@ -0,0 +1,124 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import kleur from "kleur";
4
+ import { loadFlowManifest, getFlowDoctrineRoot } from "../flows/manifest.js";
5
+ // --- Cache ---
6
+ const manifestCache = new Map();
7
+ // --- Internal Helpers ---
8
+ function getManifestCached(root, flowId) {
9
+ const key = `${root}:${flowId}`;
10
+ if (manifestCache.has(key)) {
11
+ const cached = manifestCache.get(key);
12
+ if (!cached)
13
+ throw new Error(`Flow '${flowId}' manifest previously failed to load.`);
14
+ return cached;
15
+ }
16
+ try {
17
+ const m = loadFlowManifest(root, flowId);
18
+ manifestCache.set(key, m);
19
+ return m;
20
+ }
21
+ catch (e) {
22
+ manifestCache.set(key, null); // Negative cache? Or just throw.
23
+ // If manifest is required (active flow), we should throw.
24
+ throw e;
25
+ }
26
+ }
27
+ /**
28
+ * Recursively walks a directory and returns map of relative -> absolute paths.
29
+ */
30
+ function walkDoctrineDir(dir, relativePrefix = "", index) {
31
+ if (!fs.existsSync(dir))
32
+ return;
33
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
34
+ for (const entry of entries) {
35
+ const relativePath = path.join(relativePrefix, entry.name);
36
+ const absolutePath = path.join(dir, entry.name);
37
+ if (entry.isDirectory()) {
38
+ walkDoctrineDir(absolutePath, relativePath, index);
39
+ }
40
+ else if (entry.isFile()) {
41
+ // Normalize separators to forward slash for consistency in keys
42
+ const key = relativePath.split(path.sep).join("/");
43
+ index.set(key, absolutePath);
44
+ }
45
+ }
46
+ }
47
+ // --- Public API ---
48
+ /**
49
+ * Collects all doctrine files, overlaying Flow doctrine over Base doctrine.
50
+ * Returns a Map<relative, absolute>.
51
+ */
52
+ export function collectDoctrineIndex(root, activeFlowId) {
53
+ const index = new Map();
54
+ let overrides = 0;
55
+ // 1. Base Doctrine
56
+ const baseDoctrine = path.join(root, ".iris/aidlc");
57
+ walkDoctrineDir(baseDoctrine, "", index); // Populates index
58
+ // 2. Flow Doctrine (Overlay)
59
+ if (activeFlowId) {
60
+ try {
61
+ const manifest = getManifestCached(root, activeFlowId);
62
+ const flowDoctrine = getFlowDoctrineRoot(root, activeFlowId, manifest);
63
+ if (fs.existsSync(flowDoctrine)) {
64
+ // We walk flow doctrine and overwrite base keys
65
+ const flowIndex = new Map();
66
+ walkDoctrineDir(flowDoctrine, "", flowIndex);
67
+ for (const [key, absPath] of flowIndex) {
68
+ if (index.has(key))
69
+ overrides++;
70
+ index.set(key, absPath);
71
+ }
72
+ // Debug log (if verbose env var, or just console.debug if we had a logger)
73
+ // console.debug(`[Doctrine] Active Flow: ${activeFlowId}. Overrides: ${overrides}`);
74
+ }
75
+ }
76
+ catch (e) {
77
+ // Strict error if flow is active but manifest fails
78
+ console.error(kleur.red(`Failed to load manifest for active flow '${activeFlowId}': ${e.message}`));
79
+ throw e;
80
+ }
81
+ }
82
+ return index;
83
+ }
84
+ /**
85
+ * Collect all effective doctrine files as ABSOLUTE paths.
86
+ * Useful for tools that need to iterate all doctrine (like Packer).
87
+ */
88
+ export function collectDoctrineFiles(root, activeFlowId) {
89
+ const index = collectDoctrineIndex(root, activeFlowId);
90
+ return Array.from(index.values());
91
+ }
92
+ /**
93
+ * Resolve a specific doctrine file by its DOCTRINE-RELATIVE path.
94
+ *
95
+ * @param root Repo root
96
+ * @param doctrineRelPath Relative path within doctrine dir (e.g. "templates/foo.md")
97
+ * @param activeFlowId Active flow ID (optional)
98
+ * @returns Absolute path to the resolved file, or undefined if not found in either flow or base.
99
+ */
100
+ export function resolveDoctrinePath(root, doctrineRelPath, activeFlowId) {
101
+ // 1. Check Flow Override
102
+ if (activeFlowId) {
103
+ try {
104
+ const manifest = getManifestCached(root, activeFlowId);
105
+ // Default doctrine dir is 'doctrine' if not specified
106
+ const flowDoctrineDir = manifest.entrypoints?.doctrine_dir || "doctrine";
107
+ // Construct absolute path to flow doctrine file
108
+ const flowAbsPath = path.join(root, ".iris/flows", activeFlowId, flowDoctrineDir, doctrineRelPath);
109
+ if (fs.existsSync(flowAbsPath)) {
110
+ return flowAbsPath;
111
+ }
112
+ }
113
+ catch (e) {
114
+ // Let it throw to caller.
115
+ throw e;
116
+ }
117
+ }
118
+ // 2. Check Base Doctrine (.iris/aidlc)
119
+ const baseAbsPath = path.join(root, ".iris/aidlc", doctrineRelPath);
120
+ if (fs.existsSync(baseAbsPath)) {
121
+ return baseAbsPath;
122
+ }
123
+ return undefined;
124
+ }
@@ -2,12 +2,25 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import kleur from "kleur";
4
4
  import { repoRoot, ensureDir } from "../lib.js";
5
+ import { resolveDoctrinePath } from "./doctrine/collector.js";
6
+ import { loadState } from "./state.js";
7
+ function toDoctrineRelativeTemplatePath(t) {
8
+ const prefix = ".iris/aidlc/";
9
+ if (t.startsWith(prefix))
10
+ return t.substring(prefix.length);
11
+ // If someone already passed doctrine-relative, allow it (but ensure no leading slash if needed, usually robust)
12
+ if (!t.startsWith(".iris/"))
13
+ return t;
14
+ return t; // Unknown path type, return as-is
15
+ }
5
16
  export function collectFixPlan(result) {
6
17
  const plan = {
7
18
  actions: [],
8
19
  skipped: []
9
20
  };
10
21
  const root = repoRoot();
22
+ const state = loadState();
23
+ const activeFlowId = state.active.flow;
11
24
  for (const error of result.errors) {
12
25
  if (error.code !== "MISSING_ARTIFACT") {
13
26
  continue;
@@ -29,13 +42,15 @@ export function collectFixPlan(result) {
29
42
  // Fix: Create File from Template
30
43
  if (error.artifactType === "file") {
31
44
  if (error.template) {
32
- // Verify template exists in repo
33
- const templatePath = path.join(root, error.template);
34
- if (fs.existsSync(templatePath)) {
45
+ // Normalize template path before resolving
46
+ const templateRel = toDoctrineRelativeTemplatePath(error.template);
47
+ // Verify template exists (resolving override)
48
+ const templatePath = resolveDoctrinePath(root, templateRel, activeFlowId);
49
+ if (templatePath && fs.existsSync(templatePath)) {
35
50
  plan.actions.push({
36
51
  kind: "createFileFromTemplate",
37
52
  path: error.path,
38
- template: error.template
53
+ template: templateRel // Store NORMALIZED path
39
54
  });
40
55
  }
41
56
  else {
@@ -71,6 +86,8 @@ export async function applyFixPlan(plan, options) {
71
86
  paths: []
72
87
  };
73
88
  const root = repoRoot();
89
+ const state = loadState();
90
+ const activeFlowId = state.active.flow;
74
91
  if (options.dryRun) {
75
92
  console.log(kleur.bold().blue("Dry Run Fix Plan:"));
76
93
  }
@@ -94,38 +111,27 @@ export async function applyFixPlan(plan, options) {
94
111
  continue;
95
112
  }
96
113
  if (action.kind === "createFileFromTemplate" && action.template) {
97
- const templatePath = path.join(root, action.template);
114
+ // Re-resolve to get absolute path (base or flow)
115
+ // Template should already be normalized in collectFixPlan
116
+ const templatePath = resolveDoctrinePath(root, action.template, activeFlowId);
98
117
  if (options.dryRun) {
99
118
  console.log(` [COPY] ${action.path} (from ${action.template})`);
100
119
  continue;
101
120
  }
102
121
  // Check existence logic (safe overwrite)
103
- // MISSING_ARTIFACT error implies it didn't exist during validate.
104
- // But checking again just in case (race condition or manual change).
105
122
  if (fs.existsSync(fullPath)) {
106
- // It exists now?
107
123
  if (options.yes) {
108
- // Overwrite? Spec says "Missing artifacts (safe fixes)".
109
- // If it now exists, we probably shouldn't touch it unless it's empty?
110
- // Validator said it was missing.
111
- // If it exists now, we skip it to be safe, or prompt.
112
- // Prompt says "File exists and differs. Overwrite?".
113
- // Let's compare logs? No, simplistic check.
114
- // Actually, if it exists, it wasn't missing. Maybe the validator ran, then user created it.
115
- // Only overwrite if explicit confirmation.
116
- // But usually applyFixPlan follows validate immediately.
117
124
  console.log(kleur.yellow(`Skipping ${action.path}: File exists.`));
118
125
  continue;
119
126
  }
120
127
  else {
121
- // Interactive prompt?
122
- // "Default behavior: non-interactive safe fixes where no overwrite is needed"
123
- // "If a fix would require overwriting... Prompt"
124
- // Since we only fix MISSING artifacts, we assume we don't overwrite.
125
- // If it exists, we skip.
126
128
  continue;
127
129
  }
128
130
  }
131
+ if (!templatePath || !fs.existsSync(templatePath)) {
132
+ console.error(kleur.red(`Template not found: ${action.template}`));
133
+ continue;
134
+ }
129
135
  try {
130
136
  // Read template
131
137
  const content = fs.readFileSync(templatePath, "utf-8");