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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const KNOWN_INTENT_FIELDS = new Set([
|
|
2
|
+
'successCriteria',
|
|
3
|
+
'user',
|
|
4
|
+
'problem',
|
|
5
|
+
'constraints',
|
|
6
|
+
'nonGoals',
|
|
7
|
+
'risks',
|
|
8
|
+
'acceptanceTests',
|
|
9
|
+
'context',
|
|
10
|
+
'context.user',
|
|
11
|
+
'context.phase',
|
|
12
|
+
'visuals',
|
|
13
|
+
'name', // Sometimes inferred, but valid
|
|
14
|
+
'description', // Basic
|
|
15
|
+
'features', // High level
|
|
16
|
+
]);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access a value in an object via dot-notation.
|
|
3
|
+
* Safe traversal: returns undefined if path breaks.
|
|
4
|
+
*/
|
|
5
|
+
export function getByPath(obj, path) {
|
|
6
|
+
if (!obj)
|
|
7
|
+
return undefined;
|
|
8
|
+
const parts = path.split('.');
|
|
9
|
+
let current = obj;
|
|
10
|
+
for (const part of parts) {
|
|
11
|
+
if (current === null || current === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
current = current[part];
|
|
14
|
+
}
|
|
15
|
+
return current;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Checks if a value is "missing" according to strict interview rules.
|
|
19
|
+
* Missing = null, undefined, empty string "", whitespace-only string, or empty array [].
|
|
20
|
+
*/
|
|
21
|
+
export function isMissing(value) {
|
|
22
|
+
if (value === null || value === undefined)
|
|
23
|
+
return true;
|
|
24
|
+
if (typeof value === 'string')
|
|
25
|
+
return value.trim().length === 0;
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return value.length === 0;
|
|
28
|
+
// Objects? We typically check leaf nodes, but an empty object {} usually counts as present for 'context'
|
|
29
|
+
// UNLESS the requirement is specifically context.user.
|
|
30
|
+
// So for generic objects, existence is enough.
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns a list of required fields that are missing in the draft.
|
|
35
|
+
*/
|
|
36
|
+
export function getMissingFields(draft, requiredFields) {
|
|
37
|
+
return requiredFields.filter(field => isMissing(getByPath(draft, field)));
|
|
38
|
+
}
|
package/dist/iris/packer.js
CHANGED
|
@@ -3,11 +3,11 @@ import path from "path";
|
|
|
3
3
|
import kleur from "kleur";
|
|
4
4
|
import { repoRoot, writeFile, ensureDir } from "../lib.js";
|
|
5
5
|
import { loadState } from "./state.js";
|
|
6
|
-
import {
|
|
6
|
+
import { loadEffectivePolicy } from "./policy.js";
|
|
7
7
|
import { validate } from "./validator.js";
|
|
8
8
|
import { resolveIncludes, getFileContent } from "./include.js";
|
|
9
9
|
import { checkArtifact } from "./artifact-checker.js";
|
|
10
|
-
import {
|
|
10
|
+
import { collectDoctrineFiles } from "./doctrine/collector.js";
|
|
11
11
|
export async function generatePack(options) {
|
|
12
12
|
const root = repoRoot();
|
|
13
13
|
// 1. Validate Repo (Guardrail)
|
|
@@ -15,20 +15,27 @@ export async function generatePack(options) {
|
|
|
15
15
|
const result = await validate({
|
|
16
16
|
apply: false,
|
|
17
17
|
strict: false,
|
|
18
|
-
writeBack: false
|
|
18
|
+
writeBack: false,
|
|
19
|
+
frameworkResolution: options.frameworkResolution
|
|
19
20
|
});
|
|
20
21
|
if (!result.valid) {
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const msg = !options.quiet ? kleur.red("Repo is INVALID. Fix issues before packing.") : "Repo is INVALID";
|
|
23
|
+
if (!options.quiet)
|
|
24
|
+
console.error(msg);
|
|
25
|
+
throw new Error(typeof msg === 'string' ? msg : "Repo is INVALID");
|
|
23
26
|
}
|
|
24
27
|
if (options.strict && result.warnings) {
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
const msg = !options.quiet ? kleur.red("Repo has warnings and strict mode is on.") : "Repo has warnings";
|
|
29
|
+
if (!options.quiet)
|
|
30
|
+
console.error(msg);
|
|
31
|
+
throw new Error(typeof msg === 'string' ? msg : "Repo has warnings");
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
catch (e) {
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
// If policy loading fails, we should catch it here
|
|
36
|
+
if (!options.quiet)
|
|
37
|
+
console.error("Validation failed:", e);
|
|
38
|
+
throw new Error(`Validation failed: ${e.message}`);
|
|
32
39
|
}
|
|
33
40
|
// 2. Load State & Determine Context
|
|
34
41
|
const state = loadState();
|
|
@@ -36,9 +43,9 @@ export async function generatePack(options) {
|
|
|
36
43
|
const agent = options.agent || inferAgent(phase);
|
|
37
44
|
// 3. Collect Candidates
|
|
38
45
|
// Doctrine
|
|
39
|
-
const doctrineFiles = collectDoctrine(
|
|
46
|
+
const doctrineFiles = collectDoctrine(root, state.active.flow);
|
|
40
47
|
// Required Artifacts
|
|
41
|
-
const requiredArtifacts = collectRequiredArtifacts(phase, state);
|
|
48
|
+
const requiredArtifacts = collectRequiredArtifacts(phase, state, options.frameworkResolution);
|
|
42
49
|
// Optional Artifacts
|
|
43
50
|
const optionalArtifacts = collectOptionalArtifacts(phase);
|
|
44
51
|
// Included Artifacts
|
|
@@ -58,13 +65,18 @@ export async function generatePack(options) {
|
|
|
58
65
|
// Use artifact checker for proper validation
|
|
59
66
|
const check = checkArtifact(root, p);
|
|
60
67
|
if (!check.exists) {
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
const msg = `Missing required artifact: ${check.normalized}`;
|
|
69
|
+
if (!options.quiet)
|
|
70
|
+
console.error(kleur.red(msg));
|
|
71
|
+
throw new Error(msg);
|
|
63
72
|
}
|
|
64
73
|
// For files, also check if content was readable
|
|
74
|
+
// Absolute path fix in resolver.ts implies this works now
|
|
65
75
|
if (check.isFile && res.skipped === "read_error") {
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
const msg = `Cannot read required artifact: ${check.normalized}`;
|
|
77
|
+
if (!options.quiet)
|
|
78
|
+
console.error(kleur.red(msg));
|
|
79
|
+
throw new Error(msg);
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
coreContent.push(res);
|
|
@@ -86,7 +98,7 @@ export async function generatePack(options) {
|
|
|
86
98
|
}
|
|
87
99
|
// 5. Enforce Limits
|
|
88
100
|
const maxBytes = options.maxBytes || 1_500_000;
|
|
89
|
-
const finalSelection = enforceLimits(coreContent, optionalContent, extraContent, maxBytes);
|
|
101
|
+
const finalSelection = enforceLimits(coreContent, optionalContent, extraContent, maxBytes, options.quiet);
|
|
90
102
|
// 6. Format
|
|
91
103
|
const markdown = formatBundle(finalSelection, {
|
|
92
104
|
repo: path.basename(root),
|
|
@@ -95,7 +107,7 @@ export async function generatePack(options) {
|
|
|
95
107
|
generatedAt: new Date().toISOString()
|
|
96
108
|
});
|
|
97
109
|
// 7. Output
|
|
98
|
-
if (options.stdout) {
|
|
110
|
+
if (options.stdout && !options.json) {
|
|
99
111
|
console.log(markdown);
|
|
100
112
|
if (options.output) {
|
|
101
113
|
writeFile(options.output, markdown);
|
|
@@ -103,15 +115,36 @@ export async function generatePack(options) {
|
|
|
103
115
|
}
|
|
104
116
|
else {
|
|
105
117
|
const outFile = options.output || path.join(root, `.iris/inbox/context-${phase}-${agent}.md`);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
let fileWritten = false;
|
|
119
|
+
if (!options.stdout) {
|
|
120
|
+
writeFile(outFile, markdown);
|
|
121
|
+
// Write LATEST
|
|
122
|
+
const latestFile = path.join(root, ".iris/inbox/LATEST.md");
|
|
123
|
+
ensureDir(path.dirname(latestFile));
|
|
124
|
+
const relPath = path.relative(path.dirname(latestFile), outFile);
|
|
125
|
+
writeFile(latestFile, relPath);
|
|
126
|
+
fileWritten = true;
|
|
127
|
+
}
|
|
128
|
+
const allCount = finalSelection.core.length + finalSelection.optional.length + finalSelection.extra.length;
|
|
129
|
+
const result = {
|
|
130
|
+
path: fileWritten ? outFile : "stdout",
|
|
131
|
+
size: markdown.length,
|
|
132
|
+
fileCount: allCount
|
|
133
|
+
};
|
|
134
|
+
if (options.json && !options.quiet) {
|
|
135
|
+
console.log(JSON.stringify(result, null, 2));
|
|
136
|
+
}
|
|
137
|
+
else if (!options.stdout && !options.quiet) {
|
|
138
|
+
console.log(kleur.green(`Packed context bundle to: ${outFile}`));
|
|
139
|
+
console.log(kleur.gray(`Size: ${(markdown.length / 1024).toFixed(1)} KB`));
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
114
142
|
}
|
|
143
|
+
return {
|
|
144
|
+
path: options.output || "stdout",
|
|
145
|
+
size: markdown.length,
|
|
146
|
+
fileCount: finalSelection.core.length + finalSelection.optional.length + finalSelection.extra.length
|
|
147
|
+
};
|
|
115
148
|
}
|
|
116
149
|
function inferAgent(phase) {
|
|
117
150
|
switch (phase) {
|
|
@@ -121,21 +154,11 @@ function inferAgent(phase) {
|
|
|
121
154
|
default: return "master";
|
|
122
155
|
}
|
|
123
156
|
}
|
|
124
|
-
function collectDoctrine(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
".iris/aidlc/context/load-order.md",
|
|
128
|
-
".iris/aidlc/standards/doctrine-structure.md",
|
|
129
|
-
".iris/aidlc/standards/phases-and-gates.md",
|
|
130
|
-
".iris/aidlc/validation/phase-preconditions.md",
|
|
131
|
-
".iris/aidlc/standards/artifacts-registry.md",
|
|
132
|
-
".iris/aidlc/standards/tool-wrappers.md",
|
|
133
|
-
`.iris/aidlc/agents/iris-${agent}-agent.md`,
|
|
134
|
-
`.iris/aidlc/commands/iris-${agent}-agent.md`
|
|
135
|
-
];
|
|
136
|
-
return list;
|
|
157
|
+
function collectDoctrine(root, activeFlowId) {
|
|
158
|
+
// Replaced legacy hardcoded list with flow-aware collector
|
|
159
|
+
return collectDoctrineFiles(root, activeFlowId);
|
|
137
160
|
}
|
|
138
|
-
function collectRequiredArtifacts(phase, state) {
|
|
161
|
+
function collectRequiredArtifacts(phase, state, framework) {
|
|
139
162
|
const list = [
|
|
140
163
|
".iris/state.yaml",
|
|
141
164
|
".iris/policy.yaml"
|
|
@@ -146,7 +169,7 @@ function collectRequiredArtifacts(phase, state) {
|
|
|
146
169
|
list.push(".iris/manifest.yaml");
|
|
147
170
|
// Use Policy Source of Truth
|
|
148
171
|
try {
|
|
149
|
-
const policy =
|
|
172
|
+
const policy = loadEffectivePolicy(framework, undefined, state.active.flow);
|
|
150
173
|
if (policy.phases[phase] && policy.phases[phase].requires) {
|
|
151
174
|
for (const req of policy.phases[phase].requires) {
|
|
152
175
|
list.push(req.path);
|
|
@@ -192,7 +215,7 @@ function getLatestFiles(dir, count) {
|
|
|
192
215
|
});
|
|
193
216
|
return files.slice(0, count).map(f => f.path);
|
|
194
217
|
}
|
|
195
|
-
function enforceLimits(core, optional, extra, maxBytes) {
|
|
218
|
+
function enforceLimits(core, optional, extra, maxBytes, quiet) {
|
|
196
219
|
let currentSize = sumSize(core) + sumSize(optional) + sumSize(extra);
|
|
197
220
|
const errors = [];
|
|
198
221
|
const skipped = [];
|
|
@@ -216,9 +239,11 @@ function enforceLimits(core, optional, extra, maxBytes) {
|
|
|
216
239
|
}
|
|
217
240
|
if (currentSize > maxBytes) {
|
|
218
241
|
// Hard fail if Core is too big?
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
242
|
+
if (!quiet)
|
|
243
|
+
console.error(kleur.red(`Bundle size (${currentSize}) exceeds --max-bytes limit (${maxBytes}) even after trimming optional content.`));
|
|
244
|
+
if (!quiet)
|
|
245
|
+
console.error("Required content is too large. Increase limit or clean up artifacts.");
|
|
246
|
+
throw new Error(`Bundle size exceeds limit ${maxBytes}`);
|
|
222
247
|
}
|
|
223
248
|
return {
|
|
224
249
|
core,
|
|
@@ -244,9 +269,6 @@ function formatBundle(selection, meta) {
|
|
|
244
269
|
out += `- This bundle contains doctrine + required artifacts.\n\n`;
|
|
245
270
|
// Groups
|
|
246
271
|
const all = [...core, ...optional, ...extra];
|
|
247
|
-
// Separate Doctrine from others?
|
|
248
|
-
// Core has doctrine + state + required.
|
|
249
|
-
// Let's group by type roughly using path analysis
|
|
250
272
|
const doctrine = all.filter(f => f.path.startsWith(".iris/aidlc"));
|
|
251
273
|
const stateFiles = all.filter(f => f.path.startsWith(".iris/") && !f.path.startsWith(".iris/aidlc"));
|
|
252
274
|
const memory = all.filter(f => f.path.startsWith("memory-bank/"));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function parseUnitList(content) {
|
|
2
|
+
const lines = content.split('\n');
|
|
3
|
+
const units = [];
|
|
4
|
+
let currentUnit = null;
|
|
5
|
+
for (const line of lines) {
|
|
6
|
+
// 1. Check for Unit Header: - [ ] U001: Title
|
|
7
|
+
const headerMatch = line.match(/^- \[([ x])\] (U\d+): (.+)$/);
|
|
8
|
+
if (headerMatch) {
|
|
9
|
+
if (currentUnit) {
|
|
10
|
+
units.push(currentUnit);
|
|
11
|
+
}
|
|
12
|
+
const isChecked = headerMatch[1] === 'x';
|
|
13
|
+
currentUnit = {
|
|
14
|
+
id: headerMatch[2],
|
|
15
|
+
title: headerMatch[3].trim(),
|
|
16
|
+
summary: '',
|
|
17
|
+
status: isChecked ? 'done' : 'todo'
|
|
18
|
+
};
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
// 2. Check for Summary: summary: ... (must be indented)
|
|
22
|
+
// We only accept lines starting with whitespace + "summary:"
|
|
23
|
+
if (currentUnit) {
|
|
24
|
+
const summaryMatch = line.match(/^\s+summary:\s*(.+)$/);
|
|
25
|
+
if (summaryMatch) {
|
|
26
|
+
currentUnit.summary = summaryMatch[1].trim();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Ignore other lines
|
|
30
|
+
}
|
|
31
|
+
if (currentUnit) {
|
|
32
|
+
units.push(currentUnit);
|
|
33
|
+
}
|
|
34
|
+
// Validate unique IDs
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
for (const u of units) {
|
|
37
|
+
if (ids.has(u.id)) {
|
|
38
|
+
throw new Error(`Duplicate Unit ID found: ${u.id}`);
|
|
39
|
+
}
|
|
40
|
+
ids.add(u.id);
|
|
41
|
+
}
|
|
42
|
+
return units;
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
export const IRIS_DOT_DIR = ".iris";
|
|
3
|
+
export const IRIS_INBOX_DIR = path.join(IRIS_DOT_DIR, "inbox");
|
|
4
|
+
export const INTENT_DRAFT_LATEST_FILE = "intent-draft.latest.json";
|
|
5
|
+
export const INTENT_DRAFT_LEGACY_FILE = "intent-draft.json";
|
|
6
|
+
export function getInboxPath(root) {
|
|
7
|
+
return path.join(root, IRIS_INBOX_DIR);
|
|
8
|
+
}
|
|
9
|
+
export function getLatestDraftPath(root) {
|
|
10
|
+
return path.join(getInboxPath(root), INTENT_DRAFT_LATEST_FILE);
|
|
11
|
+
}
|
|
12
|
+
export function getLegacyDraftPath(root) {
|
|
13
|
+
return path.join(getInboxPath(root), INTENT_DRAFT_LEGACY_FILE);
|
|
14
|
+
}
|
|
15
|
+
export function getHistoryDraftPath(root, timestamp) {
|
|
16
|
+
const ts = timestamp.toISOString().replace(/[-:T]/g, "").split(".")[0];
|
|
17
|
+
return path.join(getInboxPath(root), `intent-draft-${ts}.json`);
|
|
18
|
+
}
|
package/dist/iris/policy.js
CHANGED
|
@@ -1,28 +1,133 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
2
3
|
import yaml from "js-yaml";
|
|
3
|
-
import
|
|
4
|
+
import kleur from "kleur";
|
|
5
|
+
export class PolicyError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "PolicyError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class PolicyLoadError extends PolicyError {
|
|
12
|
+
cause;
|
|
13
|
+
constructor(message, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
this.name = "PolicyLoadError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
4
19
|
import { resolveArtifactPath } from "./resolver.js";
|
|
5
20
|
import { repoRoot } from "../lib.js";
|
|
6
|
-
|
|
21
|
+
// --- Loaders ---
|
|
22
|
+
/**
|
|
23
|
+
* Loads the base policy from the active framework OR fallback to legacy .iris/policy.yaml.
|
|
24
|
+
*/
|
|
25
|
+
export function loadBasePolicy(framework, root) {
|
|
7
26
|
try {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
const r = root || repoRoot();
|
|
28
|
+
// 1. Framework Priority
|
|
29
|
+
if (framework && framework.files.policy && fs.existsSync(framework.files.policy)) {
|
|
30
|
+
const content = fs.readFileSync(framework.files.policy, "utf8");
|
|
31
|
+
return parsePolicy(content);
|
|
32
|
+
}
|
|
33
|
+
// 2. Legacy Fallback
|
|
34
|
+
const legacyPath = resolveArtifactPath(r, ".iris/policy.yaml");
|
|
35
|
+
if (fs.existsSync(legacyPath)) {
|
|
36
|
+
if (framework) {
|
|
37
|
+
console.error(kleur.yellow(`IRIS_WARNING IRIS_DEPRECATED_LEGACY_POLICY: framework=${framework.manifest.id} missing policy.yaml; using .iris/policy.yaml`));
|
|
38
|
+
}
|
|
39
|
+
const content = fs.readFileSync(legacyPath, "utf8");
|
|
40
|
+
return parsePolicy(content);
|
|
14
41
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
42
|
+
// 3. Hard Failure
|
|
43
|
+
throw new PolicyLoadError(`Missing required policy file. No active framework policy and no .iris/policy.yaml found at ${legacyPath}`);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof PolicyError)
|
|
47
|
+
throw error;
|
|
48
|
+
throw new PolicyLoadError("Failed to load base policy", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function parsePolicy(content) {
|
|
52
|
+
const doc = yaml.load(content);
|
|
53
|
+
// Basic structural check
|
|
54
|
+
if (!doc || !doc.phases || !doc.transitions) {
|
|
55
|
+
throw new PolicyLoadError("Invalid base policy format: 'phases' and 'transitions' are required.");
|
|
56
|
+
}
|
|
57
|
+
return doc;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Legacy alias for backward compatibility or simple usage.
|
|
61
|
+
*/
|
|
62
|
+
export const loadPolicy = (root) => loadBasePolicy(null, root);
|
|
63
|
+
/**
|
|
64
|
+
* Loads the Effective Policy: Base + User Repo Overlay.
|
|
65
|
+
*/
|
|
66
|
+
export function loadEffectivePolicy(framework, root, activeFlowId) {
|
|
67
|
+
const r = root || repoRoot();
|
|
68
|
+
const base = loadBasePolicy(framework, r);
|
|
69
|
+
// Determine Overlay Namespace: Flow ID (if active) OR Framework ID (if loaded)
|
|
70
|
+
const overlayNamespace = activeFlowId || framework?.manifest.id;
|
|
71
|
+
if (!overlayNamespace) {
|
|
72
|
+
return base;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
// User Repo Overlay: .iris/overlays/<namespace>/policy.yaml
|
|
76
|
+
const overlayPath = path.join(r, ".iris/overlays", overlayNamespace, "policy.yaml");
|
|
77
|
+
if (fs.existsSync(overlayPath)) {
|
|
78
|
+
const content = fs.readFileSync(overlayPath, "utf8");
|
|
79
|
+
const overlay = (yaml.load(content) || {});
|
|
80
|
+
return mergePolicies(base, overlay);
|
|
21
81
|
}
|
|
22
|
-
|
|
82
|
+
// Return base if no overlay
|
|
83
|
+
return base;
|
|
23
84
|
}
|
|
24
85
|
catch (error) {
|
|
25
|
-
|
|
26
|
-
|
|
86
|
+
if (error instanceof PolicyError)
|
|
87
|
+
throw error;
|
|
88
|
+
throw new PolicyLoadError(`Failed to load effective policy for namespace '${overlayNamespace}'`, error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Merges a base policy with an overlay policy purely.
|
|
93
|
+
*/
|
|
94
|
+
export function mergePolicies(base, overlay) {
|
|
95
|
+
// Deep clone base using JSON serialization
|
|
96
|
+
const result = JSON.parse(JSON.stringify(base));
|
|
97
|
+
if (!overlay.phases)
|
|
98
|
+
return result;
|
|
99
|
+
for (const [phaseName, overlayPhase] of Object.entries(overlay.phases)) {
|
|
100
|
+
if (!overlayPhase)
|
|
101
|
+
continue;
|
|
102
|
+
// If base doesn't have this phase, initialize it
|
|
103
|
+
if (!result.phases[phaseName]) {
|
|
104
|
+
result.phases[phaseName] = { requires: [], gates: [] };
|
|
105
|
+
}
|
|
106
|
+
const basePhase = result.phases[phaseName];
|
|
107
|
+
// 1. Merge Requirements (Union by path)
|
|
108
|
+
if (overlayPhase.requires && Array.isArray(overlayPhase.requires)) {
|
|
109
|
+
const baseMap = new Map();
|
|
110
|
+
(basePhase.requires || []).forEach((r, idx) => baseMap.set(r.path, idx));
|
|
111
|
+
if (!basePhase.requires)
|
|
112
|
+
basePhase.requires = [];
|
|
113
|
+
for (const req of overlayPhase.requires) {
|
|
114
|
+
if (baseMap.has(req.path)) {
|
|
115
|
+
// OVERRIDE in place
|
|
116
|
+
const idx = baseMap.get(req.path);
|
|
117
|
+
basePhase.requires[idx] = { ...basePhase.requires[idx], ...req };
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// APPEND
|
|
121
|
+
basePhase.requires.push(req);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// 2. Merge Gates (Concatenate)
|
|
126
|
+
if (overlayPhase.gates && Array.isArray(overlayPhase.gates)) {
|
|
127
|
+
if (!basePhase.gates)
|
|
128
|
+
basePhase.gates = [];
|
|
129
|
+
basePhase.gates.push(...overlayPhase.gates);
|
|
130
|
+
}
|
|
27
131
|
}
|
|
132
|
+
return result;
|
|
28
133
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
export class ExecError extends Error {
|
|
3
|
+
command;
|
|
4
|
+
args;
|
|
5
|
+
result;
|
|
6
|
+
constructor(command, args, result) {
|
|
7
|
+
super(`Command failed with code ${result.code}: ${command} ${args.join(' ')}\nStderr: ${result.stderr}`);
|
|
8
|
+
this.command = command;
|
|
9
|
+
this.args = args;
|
|
10
|
+
this.result = result;
|
|
11
|
+
this.name = 'ExecError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function runCmd(cmd, args, opts = { cwd: process.cwd() }) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
if (opts.verbose) {
|
|
17
|
+
console.log(`> ${cmd} ${args.join(' ')} (in ${opts.cwd})`);
|
|
18
|
+
}
|
|
19
|
+
const child = spawn(cmd, args, {
|
|
20
|
+
cwd: opts.cwd,
|
|
21
|
+
env: { ...process.env, ...opts.env },
|
|
22
|
+
shell: false, // Safer to not use shell
|
|
23
|
+
stdio: 'pipe',
|
|
24
|
+
});
|
|
25
|
+
let stdout = '';
|
|
26
|
+
let stderr = '';
|
|
27
|
+
child.stdout.on('data', (data) => {
|
|
28
|
+
const chunk = data.toString();
|
|
29
|
+
stdout += chunk;
|
|
30
|
+
if (opts.verbose)
|
|
31
|
+
process.stdout.write(chunk);
|
|
32
|
+
});
|
|
33
|
+
child.stderr.on('data', (data) => {
|
|
34
|
+
const chunk = data.toString();
|
|
35
|
+
stderr += chunk;
|
|
36
|
+
if (opts.verbose)
|
|
37
|
+
process.stderr.write(chunk);
|
|
38
|
+
});
|
|
39
|
+
child.on('error', (err) => {
|
|
40
|
+
reject(err);
|
|
41
|
+
});
|
|
42
|
+
child.on('close', (code) => {
|
|
43
|
+
const result = {
|
|
44
|
+
code: code ?? 1, // Treat signal termination as error code 1
|
|
45
|
+
stdout,
|
|
46
|
+
stderr,
|
|
47
|
+
};
|
|
48
|
+
if (code === 0) {
|
|
49
|
+
resolve(result);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
reject(new ExecError(cmd, args, result));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
package/dist/iris/resolver.js
CHANGED
|
@@ -38,6 +38,9 @@ export function resolveIrisRoot(repoRoot) {
|
|
|
38
38
|
* to its absolute physical location on disk, respecting the IRIS source authority.
|
|
39
39
|
*/
|
|
40
40
|
export function resolveArtifactPath(repoRoot, relativePath) {
|
|
41
|
+
if (path.isAbsolute(relativePath)) {
|
|
42
|
+
return relativePath;
|
|
43
|
+
}
|
|
41
44
|
if (relativePath.startsWith(".iris/") || relativePath === ".iris") {
|
|
42
45
|
const root = resolveIrisRoot(repoRoot);
|
|
43
46
|
if (relativePath === ".iris")
|