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
@@ -0,0 +1,124 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import yaml from "js-yaml";
4
+ // --- Errors ---
5
+ export class FlowNotFoundError extends Error {
6
+ flowId;
7
+ path;
8
+ constructor(flowId, path) {
9
+ super(`Flow '${flowId}' not found at ${path}`);
10
+ this.flowId = flowId;
11
+ this.path = path;
12
+ this.name = "FlowNotFoundError";
13
+ }
14
+ }
15
+ export class FlowManifestValidationError extends Error {
16
+ flowId;
17
+ constructor(flowId, message) {
18
+ super(`Invalid manifest for flow '${flowId}': ${message}`);
19
+ this.flowId = flowId;
20
+ this.name = "FlowManifestValidationError";
21
+ }
22
+ }
23
+ // --- Constants & Defaults ---
24
+ const DEFAULT_ENTRYPOINTS = {
25
+ doctrine_dir: "doctrine",
26
+ policy_overlay: "policy.overlay.yaml",
27
+ routes_overlay: "routes.overlay.yaml"
28
+ };
29
+ // --- Helpers ---
30
+ export function getFlowsDir(root) {
31
+ return path.join(root, ".iris/flows");
32
+ }
33
+ export function getFlowDir(root, flowId) {
34
+ return path.join(getFlowsDir(root), flowId);
35
+ }
36
+ export function getFlowDoctrineRoot(root, flowId, manifest) {
37
+ return path.join(getFlowDir(root, flowId), manifest.entrypoints.doctrine_dir);
38
+ }
39
+ export function getFlowPolicyOverlayPath(root, flowId, manifest) {
40
+ return path.join(getFlowDir(root, flowId), manifest.entrypoints.policy_overlay);
41
+ }
42
+ export function getFlowRoutesOverlayPath(root, flowId, manifest) {
43
+ return path.join(getFlowDir(root, flowId), manifest.entrypoints.routes_overlay);
44
+ }
45
+ // --- Loader ---
46
+ export function loadFlowManifest(root, flowId) {
47
+ const flowDir = getFlowDir(root, flowId);
48
+ const manifestPath = path.join(flowDir, "flow.yaml");
49
+ if (!fs.existsSync(manifestPath)) {
50
+ throw new FlowNotFoundError(flowId, manifestPath);
51
+ }
52
+ let raw;
53
+ try {
54
+ raw = yaml.load(fs.readFileSync(manifestPath, "utf8"));
55
+ }
56
+ catch (e) {
57
+ throw new FlowManifestValidationError(flowId, `Failed to parse YAML: ${e.message}`);
58
+ }
59
+ if (!raw || typeof raw !== "object") {
60
+ throw new FlowManifestValidationError(flowId, "Manifest is not a valid object");
61
+ }
62
+ // Validation: Required Fields
63
+ if (!raw.id || typeof raw.id !== "string" || !raw.id.trim()) {
64
+ throw new FlowManifestValidationError(flowId, "Missing or empty 'id'");
65
+ }
66
+ if (!raw.name || typeof raw.name !== "string" || !raw.name.trim()) {
67
+ throw new FlowManifestValidationError(flowId, "Missing or empty 'name'");
68
+ }
69
+ if (!raw.version || typeof raw.version !== "string" || !raw.version.trim()) {
70
+ throw new FlowManifestValidationError(flowId, "Missing or empty 'version'");
71
+ }
72
+ // Validation: ID Match
73
+ if (raw.id !== flowId) {
74
+ throw new FlowManifestValidationError(flowId, `Manifest ID '${raw.id}' does not match flow ID '${flowId}'`);
75
+ }
76
+ // Defaults & Entrypoint Validation
77
+ const entrypoints = { ...DEFAULT_ENTRYPOINTS, ...(raw.entrypoints || {}) };
78
+ // Validate Paths
79
+ validateRelativePath(flowId, "doctrine_dir", entrypoints.doctrine_dir);
80
+ validateRelativePath(flowId, "policy_overlay", entrypoints.policy_overlay);
81
+ validateRelativePath(flowId, "routes_overlay", entrypoints.routes_overlay);
82
+ return {
83
+ id: raw.id,
84
+ name: raw.name,
85
+ version: raw.version,
86
+ description: raw.description,
87
+ entrypoints
88
+ };
89
+ }
90
+ function validateRelativePath(flowId, field, p) {
91
+ if (!p || typeof p !== "string" || !p.trim()) {
92
+ throw new FlowManifestValidationError(flowId, `Entrypoint '${field}' must be a non-empty string`);
93
+ }
94
+ if (path.isAbsolute(p)) {
95
+ throw new FlowManifestValidationError(flowId, `Entrypoint '${field}' must be relative`);
96
+ }
97
+ const normalized = path.normalize(p);
98
+ if (normalized.startsWith("..") || normalized === ".." || normalized.includes(path.sep + "..")) {
99
+ throw new FlowManifestValidationError(flowId, `Entrypoint '${field}' cannot traverse upwards`);
100
+ }
101
+ }
102
+ export function listInstalledFlows(root) {
103
+ const flowsDir = getFlowsDir(root);
104
+ if (!fs.existsSync(flowsDir))
105
+ return [];
106
+ const results = [];
107
+ const entries = fs.readdirSync(flowsDir, { withFileTypes: true });
108
+ for (const entry of entries) {
109
+ if (!entry.isDirectory())
110
+ continue;
111
+ const flowId = entry.name;
112
+ try {
113
+ // This will validate schema and ID match
114
+ const manifest = loadFlowManifest(root, flowId);
115
+ results.push(manifest);
116
+ }
117
+ catch (e) {
118
+ // Ignore invalid/partial flows during listing unless specific debug needed?
119
+ // User requested: "don't crash on partial folders"
120
+ // We sip it.
121
+ }
122
+ }
123
+ return results;
124
+ }
@@ -0,0 +1,49 @@
1
+ import kleur from "kleur";
2
+ import { loadState, getActiveFramework } from "./state.js";
3
+ import { loadFramework } from "../framework/framework-loader.js";
4
+ import { FrameworkError } from "../framework/framework-types.js";
5
+ import { repoRoot } from "../lib.js";
6
+ /**
7
+ * Resolves the currently active framework from state.
8
+ * Centralizes logic for commands to obtain the framework context.
9
+ *
10
+ * Rules:
11
+ * 1. Reads .iris/state.yaml for active ID.
12
+ * 2. Attempts to load framework.
13
+ * 3. Returns structured context (success or failure).
14
+ * 4. Prints warning to stderr if load fails (but returns context so caller can decide fallback).
15
+ */
16
+ export async function resolveActiveFramework(root) {
17
+ const r = root || repoRoot();
18
+ const state = loadState(); // loadState() takes no args currently
19
+ const activeFw = getActiveFramework(state);
20
+ try {
21
+ const resolution = await loadFramework(activeFw.current, { repoRoot: r });
22
+ return {
23
+ activeId: activeFw.current,
24
+ activeVersion: activeFw.version,
25
+ resolution,
26
+ error: null
27
+ };
28
+ }
29
+ catch (err) {
30
+ // If it's a known FrameworkError, return it.
31
+ // If generic error, wrap it? loadFramework usually throws FrameworkError or Error.
32
+ let fwError;
33
+ if (err instanceof FrameworkError) {
34
+ fwError = err;
35
+ }
36
+ else {
37
+ // Generic error wrapper
38
+ fwError = new FrameworkError("INVALID_YAML", activeFw.current, `Failed to load framework '${activeFw.current}': ${err instanceof Error ? err.message : String(err)}`);
39
+ }
40
+ // Emit Warning as requested
41
+ console.error(kleur.yellow(`IRIS_WARNING IRIS_FRAMEWORK_LOAD_FAILED: Could not load active framework '${activeFw.current}'. Reason: ${fwError.message}`));
42
+ return {
43
+ activeId: activeFw.current,
44
+ activeVersion: activeFw.version,
45
+ resolution: null,
46
+ error: fwError
47
+ };
48
+ }
49
+ }
@@ -0,0 +1,215 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { FrameworkError } from "../framework/framework-types.js";
4
+ import { loadFramework } from "../framework/framework-loader.js";
5
+ import { getBundledFrameworksDir } from "./bundle.js";
6
+ import { copyDir, ensureDir } from "../lib.js";
7
+ import { loadState, getActiveFramework } from "./state.js";
8
+ // --- Validation ---
9
+ /**
10
+ * Validates a framework ID.
11
+ * Defaults to strict mode: ^[a-z0-9][a-z0-9-_]*$
12
+ * Future: allowScoped option.
13
+ */
14
+ export function validateFrameworkId(id, options = {}) {
15
+ const { allowScoped = false } = options;
16
+ if (!id || typeof id !== 'string')
17
+ return false;
18
+ // Strict mode (current default)
19
+ // Starts with alphanumeric, followed by alphanumeric, -, _
20
+ // TODO: Support scoped IDs (e.g. @scope/pkg) in future version.
21
+ // Suggestion: Store scoped IDs under .iris/frameworks/@scope__pkg (encoded) to keep flat structure.
22
+ const strictRegex = /^[a-z0-9][a-z0-9-_]*$/;
23
+ return strictRegex.test(id);
24
+ }
25
+ // --- Listing ---
26
+ export async function listInstalledFrameworks(root) {
27
+ const frameworksDir = path.join(root, ".iris/frameworks");
28
+ const state = loadState();
29
+ const activeFw = getActiveFramework(state);
30
+ if (!fs.existsSync(frameworksDir)) {
31
+ return [];
32
+ }
33
+ const entries = fs.readdirSync(frameworksDir, { withFileTypes: true });
34
+ const results = [];
35
+ for (const entry of entries) {
36
+ if (!entry.isDirectory())
37
+ continue;
38
+ // Ignore temp directories from failed atomic installs
39
+ if (entry.name.startsWith('.tmp-'))
40
+ continue;
41
+ const fwPath = path.join(frameworksDir, entry.name);
42
+ // Ensure active checking uses the folder name derived ID if logical ID is missing?
43
+ // Actually framework state uses the ID.
44
+ const isActive = activeFw.current === entry.name;
45
+ try {
46
+ const resolution = await loadFramework(entry.name, { repoRoot: root });
47
+ results.push({
48
+ id: resolution.manifest.id || entry.name, // Source of truth fallback
49
+ version: resolution.manifest.version,
50
+ path: fwPath,
51
+ valid: true,
52
+ active: isActive,
53
+ manifest: resolution.manifest
54
+ });
55
+ }
56
+ catch (error) {
57
+ results.push({
58
+ id: entry.name, // Fallback to folder name
59
+ version: null,
60
+ path: fwPath,
61
+ valid: false,
62
+ active: isActive,
63
+ error: {
64
+ code: error.code || 'UNKNOWN',
65
+ message: error.message || String(error)
66
+ }
67
+ });
68
+ }
69
+ }
70
+ return results;
71
+ }
72
+ export async function listBundledFrameworks() {
73
+ const bundleDir = getBundledFrameworksDir();
74
+ if (!fs.existsSync(bundleDir)) {
75
+ return [];
76
+ }
77
+ const entries = fs.readdirSync(bundleDir, { withFileTypes: true });
78
+ const results = [];
79
+ for (const entry of entries) {
80
+ if (!entry.isDirectory())
81
+ continue;
82
+ const fwPath = path.join(bundleDir, entry.name);
83
+ try {
84
+ // Load explicitly from path
85
+ const resolution = await loadFramework(fwPath, { repoRoot: "/" });
86
+ results.push({
87
+ id: resolution.manifest.id || entry.name,
88
+ path: fwPath,
89
+ version: resolution.manifest.version,
90
+ valid: true
91
+ });
92
+ }
93
+ catch (e) {
94
+ results.push({
95
+ id: entry.name,
96
+ path: fwPath,
97
+ valid: false,
98
+ error: e.message
99
+ });
100
+ }
101
+ }
102
+ return results;
103
+ }
104
+ // --- Installation ---
105
+ export async function installFrameworkFromPath(root, sourcePath, options = {}) {
106
+ const frameworksDir = path.join(root, ".iris/frameworks");
107
+ ensureDir(frameworksDir);
108
+ // 1. Validate Source & Get ID
109
+ // We load it to ensure it's a valid framework AND to get the canonical ID
110
+ let resolution;
111
+ try {
112
+ resolution = await loadFramework(sourcePath, { repoRoot: root });
113
+ }
114
+ catch (e) {
115
+ throw new FrameworkError('INSTALL_FAILED', sourcePath, `Failed to load source framework: ${e.message}`, e.hint);
116
+ }
117
+ // Ensure ID is present
118
+ const id = resolution.manifest.id;
119
+ if (!id) {
120
+ throw new FrameworkError('INVALID_SCHEMA', sourcePath, `Framework manifest missing required 'id' field.`);
121
+ }
122
+ // 2. Validate ID (Strict)
123
+ if (!validateFrameworkId(id)) {
124
+ throw new FrameworkError('INVALID_ID', id, `Framework ID '${id}' is invalid.`, "IDs must match ^[a-z0-9][a-z0-9-_]*$");
125
+ }
126
+ // 3. Resolve Target Path & Safety Guard
127
+ const targetPath = path.resolve(frameworksDir, id);
128
+ // Guard: Path Traversal
129
+ if (!targetPath.startsWith(path.resolve(frameworksDir) + path.sep)) {
130
+ throw new FrameworkError('SECURITY_VIOLATION', id, `Invalid framework ID '${id}': Resulting path traverses outside frameworks directory.`);
131
+ }
132
+ // 4. Check Existence
133
+ if (fs.existsSync(targetPath)) {
134
+ if (!options.force) {
135
+ throw new FrameworkError('ALREADY_EXISTS', targetPath, `Framework '${id}' is already installed.`, "Use --force to overwrite.");
136
+ }
137
+ }
138
+ // 5. Atomic Install
139
+ // Copy to .tmp-<id>-<random>
140
+ const tempName = `.tmp-${id}-${Math.random().toString(36).substring(7)}`;
141
+ const tempPath = path.join(frameworksDir, tempName);
142
+ try {
143
+ // Copy recursive
144
+ copyDir(resolution.rootDir, tempPath);
145
+ // 6. Rename / Swap
146
+ if (fs.existsSync(targetPath)) {
147
+ // Remove old (Safe because verified inside frameworksDir)
148
+ fs.rmSync(targetPath, { recursive: true, force: true });
149
+ }
150
+ fs.renameSync(tempPath, targetPath);
151
+ return {
152
+ id,
153
+ version: resolution.manifest.version,
154
+ path: targetPath,
155
+ valid: true,
156
+ active: false,
157
+ manifest: resolution.manifest
158
+ };
159
+ }
160
+ catch (e) {
161
+ // Cleanup temp
162
+ if (fs.existsSync(tempPath)) {
163
+ fs.rmSync(tempPath, { recursive: true, force: true });
164
+ }
165
+ throw new FrameworkError('INSTALL_FAILED', targetPath, `Failed to install framework: ${e.message}`);
166
+ }
167
+ }
168
+ export async function installBundledFramework(root, id, options = {}) {
169
+ const bundleDir = getBundledFrameworksDir();
170
+ // Fallback logic to find bundled framework
171
+ let sourcePath = path.join(bundleDir, id);
172
+ if (!fs.existsSync(sourcePath)) {
173
+ const allBundled = await listBundledFrameworks();
174
+ const found = allBundled.find(b => b.id === id && b.valid);
175
+ if (found) {
176
+ sourcePath = found.path;
177
+ }
178
+ else {
179
+ throw new FrameworkError('NOT_FOUND', id, `Bundled framework '${id}' not found.`);
180
+ }
181
+ }
182
+ return installFrameworkFromPath(root, sourcePath, options);
183
+ }
184
+ // --- Removal ---
185
+ export function removeFramework(root, id) {
186
+ const frameworksDir = path.join(root, ".iris/frameworks");
187
+ // 1. Guard: Frameworks dir exists?
188
+ if (!fs.existsSync(frameworksDir)) {
189
+ throw new FrameworkError('NOT_FOUND', frameworksDir, "No frameworks installed.");
190
+ }
191
+ // 2. Resolve Target & Safety
192
+ // Use regex first
193
+ if (!validateFrameworkId(id)) {
194
+ throw new FrameworkError('INVALID_ID', id, `Invalid framework ID format.`);
195
+ }
196
+ const targetPath = path.resolve(frameworksDir, id);
197
+ if (!targetPath.startsWith(path.resolve(frameworksDir) + path.sep)) {
198
+ throw new FrameworkError('SECURITY_VIOLATION', id, `Path traversal detected.`);
199
+ }
200
+ if (!fs.existsSync(targetPath)) {
201
+ throw new FrameworkError('NOT_FOUND', targetPath, `Framework '${id}' is not installed.`);
202
+ }
203
+ // 3. Guard: Active
204
+ const state = loadState();
205
+ if (state.framework.current === id) {
206
+ throw new FrameworkError('ACTIVE_FRAMEWORK', id, `Cannot remove active framework '${id}'.`, "Switch to another framework first (e.g. 'iris framework use iris-core').");
207
+ }
208
+ // 4. Delete
209
+ fs.rmSync(targetPath, { recursive: true, force: true });
210
+ }
211
+ // --- Validation Wrapper ---
212
+ export async function validateFrameworkParams(input, root) {
213
+ // Just a wrapper for loadFramework but enables consistent error handling for CLI
214
+ return loadFramework(input, { repoRoot: root });
215
+ }
@@ -0,0 +1,22 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export function writeJsonAtomic(filePath, data) {
4
+ const tmpPath = `${filePath}.tmp.${Date.now()}`;
5
+ const dir = path.dirname(filePath);
6
+ if (!fs.existsSync(dir)) {
7
+ fs.mkdirSync(dir, { recursive: true });
8
+ }
9
+ try {
10
+ fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
11
+ fs.renameSync(tmpPath, filePath);
12
+ }
13
+ catch (error) {
14
+ // Attempt cleanup
15
+ try {
16
+ if (fs.existsSync(tmpPath))
17
+ fs.unlinkSync(tmpPath);
18
+ }
19
+ catch { }
20
+ throw error;
21
+ }
22
+ }
@@ -0,0 +1,9 @@
1
+ const REGISTRY = {};
2
+ export function registerImporter(importer) {
3
+ REGISTRY[importer.id] = importer;
4
+ }
5
+ // No importers registered - IRIS ships only iris-core
6
+ export function getImporter(id) {
7
+ return REGISTRY[id] || null;
8
+ }
9
+ export const IMPORTERS = REGISTRY;
@@ -0,0 +1,8 @@
1
+ export class ImporterError extends Error {
2
+ exitCode;
3
+ constructor(message, exitCode = 1) {
4
+ super(message);
5
+ this.exitCode = exitCode;
6
+ this.name = 'ImporterError';
7
+ }
8
+ }
@@ -0,0 +1,139 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ // Safe extensions to import
4
+ const SAFE_EXTENSIONS = ['.md', '.txt', '.yml', '.yaml', '.json'];
5
+ const IGNORED_DIRS = ['node_modules', '.git', 'dist', 'build', 'coverage'];
6
+ export function ensureDoctrineOnlyFlowInstalled(opts) {
7
+ const result = {
8
+ flowId: opts.flowId,
9
+ installedDir: '', // set below
10
+ copiedFiles: 0,
11
+ skippedFiles: 0,
12
+ overwrittenFiles: 0,
13
+ };
14
+ const flowDir = path.join(opts.repoRoot, '.iris', 'flows', opts.flowId);
15
+ result.installedDir = flowDir;
16
+ if (opts.verbose) {
17
+ console.log(`Preparing to install flow '${opts.flowId}' into ${flowDir}`);
18
+ }
19
+ // 1. Check if flow already exists
20
+ if (fs.existsSync(flowDir) && !opts.force) {
21
+ // If not creating/updating, we might still want to report what happened,
22
+ // but typically we'd expect the caller to handle "already exists" checks if they wanted to abort early.
23
+ // However, the requirement is "overwrite if force", which implies "skip/merge if not force".
24
+ // For simplicity, if it exists and !force, we might just return early if we consider the flow "installed".
25
+ // But let's proceed to copy templates with "skip existing" semantics.
26
+ }
27
+ if (opts.dryRun) {
28
+ console.log(`[Dry Run] Would create/update flow directory: ${flowDir}`);
29
+ }
30
+ else {
31
+ fs.mkdirSync(flowDir, { recursive: true });
32
+ }
33
+ // 2. Ensure flow.yaml exists
34
+ const flowYamlPath = path.join(flowDir, 'flow.yaml');
35
+ if (opts.dryRun) {
36
+ if (!fs.existsSync(flowYamlPath) || opts.force) {
37
+ console.log(`[Dry Run] Would write flow.yaml for ${opts.flowId}`);
38
+ }
39
+ }
40
+ else {
41
+ if (!fs.existsSync(flowYamlPath) || opts.force) {
42
+ const flowYamlContent = `# Generated by iris flow import
43
+ id: ${opts.flowId}
44
+ name: ${opts.flowId}
45
+ description: Imported from ${opts.displayName}
46
+ version: imported
47
+ `;
48
+ fs.writeFileSync(flowYamlPath, flowYamlContent);
49
+ }
50
+ }
51
+ // 3. Ensure overlays exist (empty)
52
+ const overlays = ['policy.overlay.yaml', 'routes.overlay.yaml'];
53
+ for (const file of overlays) {
54
+ const filePath = path.join(flowDir, file);
55
+ if (opts.dryRun) {
56
+ if (!fs.existsSync(filePath) || opts.force) {
57
+ console.log(`[Dry Run] Would ensure empty ${file}`);
58
+ }
59
+ }
60
+ else {
61
+ if (!fs.existsSync(filePath) || opts.force) {
62
+ fs.writeFileSync(filePath, '{}\n');
63
+ }
64
+ }
65
+ }
66
+ // 4. Copy templates
67
+ // Destination: .iris/flows/<id>/doctrine/templates/<id>/...
68
+ // We nest under <id> again inside templates/ to avoid collision if multiple flows share generic names,
69
+ // and to keep it namespaced.
70
+ const destTemplateRoot = path.join(flowDir, 'doctrine', 'templates', opts.flowId);
71
+ if (opts.dryRun) {
72
+ console.log(`[Dry Run] Would copy templates to ${destTemplateRoot}`);
73
+ }
74
+ else {
75
+ fs.mkdirSync(destTemplateRoot, { recursive: true });
76
+ }
77
+ for (const srcDir of opts.sourceTemplateDirs) {
78
+ copyRecursive(srcDir, destTemplateRoot, opts, result);
79
+ }
80
+ return result;
81
+ }
82
+ function copyRecursive(src, dest, opts, result, relPath = '') {
83
+ if (!fs.existsSync(src))
84
+ return;
85
+ const stats = fs.statSync(src);
86
+ if (stats.isDirectory()) {
87
+ // Check ignore list
88
+ const dirName = path.basename(src);
89
+ if (IGNORED_DIRS.includes(dirName))
90
+ return;
91
+ if (!opts.dryRun) {
92
+ fs.mkdirSync(dest, { recursive: true });
93
+ }
94
+ const children = fs.readdirSync(src);
95
+ for (const child of children) {
96
+ copyRecursive(path.join(src, child), path.join(dest, child), opts, result, path.join(relPath, child));
97
+ }
98
+ }
99
+ else if (stats.isFile()) {
100
+ // Filter extensions
101
+ const ext = path.extname(src).toLowerCase();
102
+ if (!SAFE_EXTENSIONS.includes(ext)) {
103
+ if (opts.verbose)
104
+ console.log(`Skipping unsafe/irrelevant file type: ${src}`);
105
+ return;
106
+ }
107
+ if (opts.dryRun) {
108
+ // Just log what would happen
109
+ if (fs.existsSync(dest)) {
110
+ if (opts.force) {
111
+ console.log(`[Dry Run] Would overwrite ${relPath}`);
112
+ result.overwrittenFiles++;
113
+ }
114
+ else {
115
+ console.log(`[Dry Run] Would skip existing ${relPath}`);
116
+ result.skippedFiles++;
117
+ }
118
+ }
119
+ else {
120
+ console.log(`[Dry Run] Would copy ${relPath}`);
121
+ result.copiedFiles++;
122
+ }
123
+ return;
124
+ }
125
+ if (fs.existsSync(dest)) {
126
+ if (opts.force) {
127
+ fs.copyFileSync(src, dest);
128
+ result.overwrittenFiles++;
129
+ }
130
+ else {
131
+ result.skippedFiles++;
132
+ }
133
+ }
134
+ else {
135
+ fs.copyFileSync(src, dest);
136
+ result.copiedFiles++;
137
+ }
138
+ }
139
+ }