project-iris 0.0.6
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 +384 -0
- package/dist/bridge/connector-factory.js +27 -0
- package/dist/bridge/connectors/antigravity-connector.js +18 -0
- package/dist/bridge/connectors/cursor-connector.js +31 -0
- package/dist/bridge/connectors/vscode-connector.js +31 -0
- package/dist/bridge/connectors/windsurf-connector.js +23 -0
- package/dist/bridge/filesystem-connector.js +100 -0
- package/dist/bridge/types.js +10 -0
- package/dist/cli.js +30 -0
- package/dist/commands/ask.js +232 -0
- package/dist/commands/bridge.js +259 -0
- package/dist/commands/develop.js +108 -0
- package/dist/commands/doctor.js +102 -0
- package/dist/commands/install.js +57 -0
- package/dist/commands/pack.js +27 -0
- package/dist/commands/phase.js +38 -0
- package/dist/commands/run.js +17 -0
- package/dist/commands/status.js +105 -0
- package/dist/commands/uninstall.js +12 -0
- package/dist/commands/validate.js +87 -0
- package/dist/iris/artifact-checker.js +78 -0
- package/dist/iris/fixer.js +143 -0
- package/dist/iris/guard.js +38 -0
- package/dist/iris/include.js +49 -0
- package/dist/iris/installer.js +269 -0
- package/dist/iris/manifest.js +54 -0
- package/dist/iris/packer.js +303 -0
- package/dist/iris/policy.js +28 -0
- package/dist/iris/report.js +53 -0
- package/dist/iris/resolver.js +63 -0
- package/dist/iris/router.js +114 -0
- package/dist/iris/routes.js +20 -0
- package/dist/iris/run-state.js +143 -0
- package/dist/iris/state.js +85 -0
- package/dist/iris/uninstaller.js +166 -0
- package/dist/iris/validator.js +329 -0
- package/dist/lib.js +96 -0
- package/dist/utils/exit-codes.js +7 -0
- package/dist/workflows/bolt-execution.js +238 -0
- package/dist/workflows/bolt-plan.js +192 -0
- package/dist/workflows/intent-inception.js +188 -0
- package/package.json +41 -0
- package/src/iris_bundle/.iris/aidlc/README.md +16 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-construction-agent.md +35 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-inception-agent.md +30 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-master-agent.md +35 -0
- package/src/iris_bundle/.iris/aidlc/agents/iris-operations-agent.md +29 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-construction-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-inception-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-master-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/commands/iris-operations-agent.md +18 -0
- package/src/iris_bundle/.iris/aidlc/context/context-map.md +25 -0
- package/src/iris_bundle/.iris/aidlc/context/exclusion-rules.md +13 -0
- package/src/iris_bundle/.iris/aidlc/context/load-order.md +25 -0
- package/src/iris_bundle/.iris/aidlc/memory/intent-rules.md +9 -0
- package/src/iris_bundle/.iris/aidlc/memory/log-rules.md +5 -0
- package/src/iris_bundle/.iris/aidlc/memory/memory-bank.yaml +39 -0
- package/src/iris_bundle/.iris/aidlc/memory/unit-rules.md +9 -0
- package/src/iris_bundle/.iris/aidlc/quick-start.md +24 -0
- package/src/iris_bundle/.iris/aidlc/skills/execution/implementation.md +14 -0
- package/src/iris_bundle/.iris/aidlc/skills/execution/refactoring.md +13 -0
- package/src/iris_bundle/.iris/aidlc/skills/execution/scaffold-generation.md +15 -0
- package/src/iris_bundle/.iris/aidlc/skills/governance/escalation.md +13 -0
- package/src/iris_bundle/.iris/aidlc/skills/governance/quality-gates.md +14 -0
- package/src/iris_bundle/.iris/aidlc/skills/governance/stop-conditions.md +11 -0
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/decomposition.md +23 -0
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/risk-analysis.md +14 -0
- package/src/iris_bundle/.iris/aidlc/skills/reasoning/verification.md +21 -0
- package/src/iris_bundle/.iris/aidlc/standards/artifacts-registry.md +38 -0
- package/src/iris_bundle/.iris/aidlc/standards/decision-logging.md +16 -0
- package/src/iris_bundle/.iris/aidlc/standards/doctrine-structure.md +31 -0
- package/src/iris_bundle/.iris/aidlc/standards/documentation-rules.md +15 -0
- package/src/iris_bundle/.iris/aidlc/standards/file-structure.md +21 -0
- package/src/iris_bundle/.iris/aidlc/standards/naming-conventions.md +18 -0
- package/src/iris_bundle/.iris/aidlc/standards/phases-and-gates.md +25 -0
- package/src/iris_bundle/.iris/aidlc/standards/routes-and-routing.md +35 -0
- package/src/iris_bundle/.iris/aidlc/standards/tool-wrappers.md +32 -0
- package/src/iris_bundle/.iris/aidlc/templates/bolt.md +23 -0
- package/src/iris_bundle/.iris/aidlc/templates/doctrine-doc-template.md +33 -0
- package/src/iris_bundle/.iris/aidlc/templates/intent.md +23 -0
- package/src/iris_bundle/.iris/aidlc/templates/log.md +24 -0
- package/src/iris_bundle/.iris/aidlc/templates/review.md +21 -0
- package/src/iris_bundle/.iris/aidlc/templates/unit.md +31 -0
- package/src/iris_bundle/.iris/aidlc/validation/failure-modes.md +16 -0
- package/src/iris_bundle/.iris/aidlc/validation/phase-preconditions.md +21 -0
- package/src/iris_bundle/.iris/aidlc/validation/quality-checklist.md +20 -0
- package/src/iris_bundle/.iris/policy.yaml +27 -0
- package/src/iris_bundle/.iris/routes.yaml +98 -0
- package/src/iris_bundle/.iris/state.yaml +7 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/knowledge/IRIS.md +6 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-construction-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-inception-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-master-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-operations-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/claude.md +9 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/compare-specs.md +203 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-construction-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-inception-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-master-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-operations-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/codex/AGENTS.md +15 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-construction-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-inception-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-master-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-operations-agent.md +25 -0
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-construction-agent.toml +29 -0
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-inception-agent.toml +29 -0
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-master-agent.toml +29 -0
- package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-operations-agent.toml +29 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import kleur from "kleur";
|
|
4
|
+
import { repoRoot, writeFile, ensureDir } from "../lib.js";
|
|
5
|
+
import { loadState } from "./state.js";
|
|
6
|
+
import { loadPolicy } from "./policy.js";
|
|
7
|
+
import { validate } from "./validator.js";
|
|
8
|
+
import { resolveIncludes, getFileContent } from "./include.js";
|
|
9
|
+
import { checkArtifact } from "./artifact-checker.js";
|
|
10
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
11
|
+
export async function generatePack(options) {
|
|
12
|
+
const root = repoRoot();
|
|
13
|
+
// 1. Validate Repo (Guardrail)
|
|
14
|
+
try {
|
|
15
|
+
const result = await validate({
|
|
16
|
+
apply: false,
|
|
17
|
+
strict: false,
|
|
18
|
+
writeBack: false
|
|
19
|
+
});
|
|
20
|
+
if (!result.valid) {
|
|
21
|
+
console.error(kleur.red("Repo is INVALID. Fix issues before packing."));
|
|
22
|
+
process.exit(EXIT_CODES.INVALID); // Exit 2
|
|
23
|
+
}
|
|
24
|
+
if (options.strict && result.warnings) {
|
|
25
|
+
console.error(kleur.red("Repo has warnings and strict mode is on."));
|
|
26
|
+
process.exit(EXIT_CODES.INVALID);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
console.error("Validation failed:", e);
|
|
31
|
+
process.exit(EXIT_CODES.INVALID);
|
|
32
|
+
}
|
|
33
|
+
// 2. Load State & Determine Context
|
|
34
|
+
const state = loadState();
|
|
35
|
+
const phase = options.phase || state.phase.current;
|
|
36
|
+
const agent = options.agent || inferAgent(phase);
|
|
37
|
+
// 3. Collect Candidates
|
|
38
|
+
// Doctrine
|
|
39
|
+
const doctrineFiles = collectDoctrine(agent);
|
|
40
|
+
// Required Artifacts
|
|
41
|
+
const requiredArtifacts = collectRequiredArtifacts(phase, state);
|
|
42
|
+
// Optional Artifacts
|
|
43
|
+
const optionalArtifacts = collectOptionalArtifacts(phase);
|
|
44
|
+
// Included Artifacts
|
|
45
|
+
const includedFiles = options.includes && options.includes.length > 0
|
|
46
|
+
? await resolveIncludes(options.includes, options.excludes)
|
|
47
|
+
: [];
|
|
48
|
+
// 4. Read & Assemble
|
|
49
|
+
// Core (Doctrine + Required) - Must be included
|
|
50
|
+
const coreContent = [];
|
|
51
|
+
for (const p of [...doctrineFiles, ...requiredArtifacts]) {
|
|
52
|
+
// Required files generally SHOULD exist, but if missing, we record them
|
|
53
|
+
// Doctrine: "If a file is missing, skip it but record in the bundle '(missing)' list."
|
|
54
|
+
// Required Artifacts: "If required file missing -> pack must fail (exit 2)"
|
|
55
|
+
const res = getFileContent(p);
|
|
56
|
+
// Check requirement for artifacts (not doctrine)
|
|
57
|
+
if (requiredArtifacts.includes(p)) {
|
|
58
|
+
// Use artifact checker for proper validation
|
|
59
|
+
const check = checkArtifact(root, p);
|
|
60
|
+
if (!check.exists) {
|
|
61
|
+
console.error(kleur.red(`Missing required artifact: ${check.normalized}`));
|
|
62
|
+
process.exit(EXIT_CODES.INVALID);
|
|
63
|
+
}
|
|
64
|
+
// For files, also check if content was readable
|
|
65
|
+
if (check.isFile && res.skipped === "read_error") {
|
|
66
|
+
console.error(kleur.red(`Cannot read required artifact: ${check.normalized}`));
|
|
67
|
+
process.exit(EXIT_CODES.INVALID);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
coreContent.push(res);
|
|
71
|
+
}
|
|
72
|
+
// Optional - Can be dropped
|
|
73
|
+
const optionalContent = [];
|
|
74
|
+
for (const p of optionalArtifacts) {
|
|
75
|
+
// Optional artifacts usually are logs/bolts which are repo-local.
|
|
76
|
+
// getFileContent resolves them. If they don't exist, we skip.
|
|
77
|
+
const res = getFileContent(p);
|
|
78
|
+
if (res.skipped === "read_error" || res.size === 0)
|
|
79
|
+
continue;
|
|
80
|
+
optionalContent.push(res);
|
|
81
|
+
}
|
|
82
|
+
// Includes - Can be dropped
|
|
83
|
+
const extraContent = [];
|
|
84
|
+
for (const p of includedFiles) {
|
|
85
|
+
extraContent.push(getFileContent(p));
|
|
86
|
+
}
|
|
87
|
+
// 5. Enforce Limits
|
|
88
|
+
const maxBytes = options.maxBytes || 1_500_000;
|
|
89
|
+
const finalSelection = enforceLimits(coreContent, optionalContent, extraContent, maxBytes);
|
|
90
|
+
// 6. Format
|
|
91
|
+
const markdown = formatBundle(finalSelection, {
|
|
92
|
+
repo: path.basename(root),
|
|
93
|
+
phase,
|
|
94
|
+
agent,
|
|
95
|
+
generatedAt: new Date().toISOString()
|
|
96
|
+
});
|
|
97
|
+
// 7. Output
|
|
98
|
+
if (options.stdout) {
|
|
99
|
+
console.log(markdown);
|
|
100
|
+
if (options.output) {
|
|
101
|
+
writeFile(options.output, markdown);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
const outFile = options.output || path.join(root, `.iris/inbox/context-pack.${agent}.${phase}.md`);
|
|
106
|
+
writeFile(outFile, markdown);
|
|
107
|
+
// Write LATEST
|
|
108
|
+
const latestFile = path.join(root, ".iris/inbox/LATEST.md");
|
|
109
|
+
ensureDir(path.dirname(latestFile)); // Ensure dir exists for LATEST too
|
|
110
|
+
const relPath = path.relative(path.dirname(latestFile), outFile);
|
|
111
|
+
writeFile(latestFile, relPath);
|
|
112
|
+
console.log(kleur.green(`Packed context bundle to: ${outFile}`));
|
|
113
|
+
console.log(kleur.gray(`Size: ${(markdown.length / 1024).toFixed(1)} KB`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function inferAgent(phase) {
|
|
117
|
+
switch (phase) {
|
|
118
|
+
case "inception": return "inception";
|
|
119
|
+
case "construction": return "construction";
|
|
120
|
+
case "operations": return "operations";
|
|
121
|
+
default: return "master";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function collectDoctrine(agent) {
|
|
125
|
+
const list = [
|
|
126
|
+
".iris/aidlc/quick-start.md",
|
|
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;
|
|
137
|
+
}
|
|
138
|
+
function collectRequiredArtifacts(phase, state) {
|
|
139
|
+
const list = [
|
|
140
|
+
".iris/state.yaml",
|
|
141
|
+
".iris/policy.yaml"
|
|
142
|
+
];
|
|
143
|
+
if (fs.existsSync(path.join(repoRoot(), ".iris/routes.yaml")))
|
|
144
|
+
list.push(".iris/routes.yaml");
|
|
145
|
+
if (fs.existsSync(path.join(repoRoot(), ".iris/manifest.yaml")))
|
|
146
|
+
list.push(".iris/manifest.yaml");
|
|
147
|
+
// Use Policy Source of Truth
|
|
148
|
+
try {
|
|
149
|
+
const policy = loadPolicy();
|
|
150
|
+
if (policy.phases[phase] && policy.phases[phase].requires) {
|
|
151
|
+
for (const req of policy.phases[phase].requires) {
|
|
152
|
+
list.push(req.path);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
// Fallback or ignore if policy broken (validator should have caught it)
|
|
158
|
+
}
|
|
159
|
+
return list;
|
|
160
|
+
}
|
|
161
|
+
function collectOptionalArtifacts(phase) {
|
|
162
|
+
const list = [];
|
|
163
|
+
const root = repoRoot();
|
|
164
|
+
if (phase === "inception") {
|
|
165
|
+
if (fs.existsSync(path.join(root, "memory-bank/intents/intent.md"))) {
|
|
166
|
+
list.push("memory-bank/intents/intent.md");
|
|
167
|
+
}
|
|
168
|
+
list.push(...getLatestFiles(path.join(root, "memory-bank/logs"), 5));
|
|
169
|
+
}
|
|
170
|
+
else if (phase === "construction") {
|
|
171
|
+
list.push(...getLatestFiles(path.join(root, "memory-bank/bolts"), 5));
|
|
172
|
+
list.push(...getLatestFiles(path.join(root, "memory-bank/logs"), 5));
|
|
173
|
+
}
|
|
174
|
+
else if (phase === "operations") {
|
|
175
|
+
list.push(...getLatestFiles(path.join(root, "memory-bank/logs"), 10));
|
|
176
|
+
list.push(...getLatestFiles(path.join(root, "memory-bank/standards"), 5));
|
|
177
|
+
}
|
|
178
|
+
return list;
|
|
179
|
+
}
|
|
180
|
+
function getLatestFiles(dir, count) {
|
|
181
|
+
const root = repoRoot();
|
|
182
|
+
if (!fs.existsSync(dir))
|
|
183
|
+
return [];
|
|
184
|
+
const files = fs.readdirSync(dir)
|
|
185
|
+
.map(f => path.join(dir, f))
|
|
186
|
+
.filter(f => fs.statSync(f).isFile())
|
|
187
|
+
.map(f => ({ path: path.relative(root, f), mtime: fs.statSync(f).mtime.getTime() }))
|
|
188
|
+
.sort((a, b) => {
|
|
189
|
+
if (b.mtime !== a.mtime)
|
|
190
|
+
return b.mtime - a.mtime; // Descending time
|
|
191
|
+
return a.path.localeCompare(b.path); // Tie-break name
|
|
192
|
+
});
|
|
193
|
+
return files.slice(0, count).map(f => f.path);
|
|
194
|
+
}
|
|
195
|
+
function enforceLimits(core, optional, extra, maxBytes) {
|
|
196
|
+
let currentSize = sumSize(core) + sumSize(optional) + sumSize(extra);
|
|
197
|
+
const errors = [];
|
|
198
|
+
const skipped = [];
|
|
199
|
+
// 1. Reduce Optional
|
|
200
|
+
while (currentSize > maxBytes && optional.length > 0) {
|
|
201
|
+
const removed = optional.pop(); // Remove from end (which are older logs/bolts usually!)
|
|
202
|
+
// Wait, collectOptionalArtifacts sorted by latest first.
|
|
203
|
+
// So pop() removes the OLDEST (least relevant). Correct.
|
|
204
|
+
if (removed) {
|
|
205
|
+
currentSize -= (removed.size || 0);
|
|
206
|
+
skipped.push(removed.path);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// 2. Reduce Extra
|
|
210
|
+
while (currentSize > maxBytes && extra.length > 0) {
|
|
211
|
+
const removed = extra.pop(); // Remove from end of glob list (arbitrary?)
|
|
212
|
+
if (removed) {
|
|
213
|
+
currentSize -= (removed.size || 0);
|
|
214
|
+
skipped.push(removed.path);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (currentSize > maxBytes) {
|
|
218
|
+
// Hard fail if Core is too big?
|
|
219
|
+
console.error(kleur.red(`Bundle size (${currentSize}) exceeds --max-bytes limit (${maxBytes}) even after trimming optional content.`));
|
|
220
|
+
console.error("Required content is too large. Increase limit or clean up artifacts.");
|
|
221
|
+
process.exit(EXIT_CODES.INVALID);
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
core,
|
|
225
|
+
optional,
|
|
226
|
+
extra,
|
|
227
|
+
skipped
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function sumSize(files) {
|
|
231
|
+
return files.reduce((acc, f) => acc + (f.content ? f.content.length : 0), 0);
|
|
232
|
+
}
|
|
233
|
+
function formatBundle(selection, meta) {
|
|
234
|
+
const { core, optional, extra, skipped } = selection;
|
|
235
|
+
let out = `# IRIS Context Pack\n\n`;
|
|
236
|
+
out += `## Metadata\n`;
|
|
237
|
+
out += `- Generated: ${meta.generatedAt}\n`;
|
|
238
|
+
out += `- Repo: ${meta.repo}\n`;
|
|
239
|
+
out += `- Phase: ${meta.phase}\n`;
|
|
240
|
+
out += `- Agent: ${meta.agent}\n`;
|
|
241
|
+
out += `- Navi: 0.0.07\n\n`; // Hardcoded version for now or read pkg
|
|
242
|
+
out += `## How to use\n`;
|
|
243
|
+
out += `- Run: /iris-${meta.agent}-agent\n`;
|
|
244
|
+
out += `- This bundle contains doctrine + required artifacts.\n\n`;
|
|
245
|
+
// Groups
|
|
246
|
+
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
|
+
const doctrine = all.filter(f => f.path.startsWith(".iris/aidlc"));
|
|
251
|
+
const stateFiles = all.filter(f => f.path.startsWith(".iris/") && !f.path.startsWith(".iris/aidlc"));
|
|
252
|
+
const memory = all.filter(f => f.path.startsWith("memory-bank/"));
|
|
253
|
+
const others = all.filter(f => !f.path.startsWith(".iris/") && !f.path.startsWith("memory-bank/"));
|
|
254
|
+
const printer = (title, files) => {
|
|
255
|
+
if (files.length === 0)
|
|
256
|
+
return;
|
|
257
|
+
out += `## ${title}\n\n`;
|
|
258
|
+
files.forEach((f, idx) => {
|
|
259
|
+
if (f.skipped) {
|
|
260
|
+
// Should list in skipped section?
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!f.content && !f.skipped) {
|
|
264
|
+
// Missing file (e.g. Doctrine missing)
|
|
265
|
+
out += `### [${idx + 1}] ${f.path} (MISSING)\n\n`;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
out += `### [${idx + 1}] ${f.path}\n`;
|
|
269
|
+
out += "```" + detectLang(f.path) + "\n";
|
|
270
|
+
out += f.content + "\n";
|
|
271
|
+
out += "```\n\n";
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
printer("Doctrine (Load Order)", doctrine);
|
|
275
|
+
printer("Repo State", stateFiles);
|
|
276
|
+
printer("Memory Bank", memory);
|
|
277
|
+
printer("Included Context", others);
|
|
278
|
+
// Missing / Skipped
|
|
279
|
+
const missing = all.filter(f => !f.content && !f.skipped).map(f => f.path);
|
|
280
|
+
const skippedReasons = all.filter(f => f.skipped).map(f => `${f.path} (${f.skipped})`);
|
|
281
|
+
const droppedLimits = skipped;
|
|
282
|
+
if (missing.length > 0 || skippedReasons.length > 0 || droppedLimits.length > 0) {
|
|
283
|
+
out += `## Missing / Skipped Files\n\n`;
|
|
284
|
+
missing.forEach(p => out += `* ${p} (missing)\n`);
|
|
285
|
+
skippedReasons.forEach(p => out += `* ${p}\n`);
|
|
286
|
+
droppedLimits.forEach(p => out += `* ${p} (dropped for size)\n`);
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
}
|
|
290
|
+
function detectLang(p) {
|
|
291
|
+
const ext = path.extname(p);
|
|
292
|
+
if (ext === ".md")
|
|
293
|
+
return "markdown";
|
|
294
|
+
if (ext === ".yaml")
|
|
295
|
+
return "yaml";
|
|
296
|
+
if (ext === ".json")
|
|
297
|
+
return "json";
|
|
298
|
+
if (ext === ".ts")
|
|
299
|
+
return "typescript";
|
|
300
|
+
if (ext === ".js")
|
|
301
|
+
return "javascript";
|
|
302
|
+
return "";
|
|
303
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
4
|
+
import { resolveArtifactPath } from "./resolver.js";
|
|
5
|
+
import { repoRoot } from "../lib.js";
|
|
6
|
+
export function loadPolicy() {
|
|
7
|
+
try {
|
|
8
|
+
const root = repoRoot();
|
|
9
|
+
const policyPath = resolveArtifactPath(root, ".iris/policy.yaml");
|
|
10
|
+
if (!fs.existsSync(policyPath)) {
|
|
11
|
+
// Policy is REQUIRED.
|
|
12
|
+
console.error(`Missing required policy file: ${policyPath}`);
|
|
13
|
+
process.exit(EXIT_CODES.POLICY_ERROR);
|
|
14
|
+
}
|
|
15
|
+
const content = fs.readFileSync(policyPath, "utf8");
|
|
16
|
+
const doc = yaml.load(content);
|
|
17
|
+
// Basic structural check
|
|
18
|
+
if (!doc || !doc.phases || !doc.transitions) {
|
|
19
|
+
console.error("Invalid policy file format: 'phases' and 'transitions' are required.");
|
|
20
|
+
process.exit(EXIT_CODES.POLICY_ERROR);
|
|
21
|
+
}
|
|
22
|
+
return doc;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error("Failed to load .iris/policy.yaml:", error);
|
|
26
|
+
process.exit(EXIT_CODES.POLICY_ERROR);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import kleur from "kleur";
|
|
2
|
+
export function printReport(result, json = false, fixResult, strict = false) {
|
|
3
|
+
if (json) {
|
|
4
|
+
console.log(JSON.stringify({
|
|
5
|
+
valid: result.valid,
|
|
6
|
+
warnings: result.warnings,
|
|
7
|
+
errors: result.errors,
|
|
8
|
+
fix_result: fixResult // Optional
|
|
9
|
+
}, null, 2));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (fixResult) {
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log(kleur.bold().green("Applied fixes:"));
|
|
15
|
+
if (fixResult.paths.length === 0) {
|
|
16
|
+
console.log(kleur.gray(" (None needed)"));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
for (const p of fixResult.paths) {
|
|
20
|
+
console.log(kleur.green(` + Created: ${p}`));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
console.log("");
|
|
24
|
+
console.log(kleur.bold(`Remaining issues (${result.errors.length}):`));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
if (result.valid && !result.warnings) {
|
|
28
|
+
console.log(kleur.green("✓ Repository is valid."));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!result.valid) {
|
|
32
|
+
console.log(kleur.red(`✗ Validation failed with ${result.errors.length} error(s):`));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (strict) {
|
|
36
|
+
console.log(kleur.red("✗ Validation FAILED (strict) due to warnings:"));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(kleur.yellow("! Validation PASSED with warnings:"));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Print errors
|
|
44
|
+
for (const err of result.errors) {
|
|
45
|
+
const icon = err.severity === "warning" ? kleur.yellow("!") : kleur.red("x");
|
|
46
|
+
const code = kleur.dim(`[${err.code}]`);
|
|
47
|
+
// If fixed, skip? No, this function prints current result.
|
|
48
|
+
// If called after fix, these are remaining errors.
|
|
49
|
+
console.log(` ${icon} ${code} ${err.message}`);
|
|
50
|
+
if (err.path)
|
|
51
|
+
console.log(kleur.dim(` Path: ${err.path}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
// Get __dirname equivalent in ESM
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the root directory of the active IRIS configuration (.iris folder).
|
|
9
|
+
* Strategy:
|
|
10
|
+
* 1. Check <repoRoot>/.iris
|
|
11
|
+
* 2. Fallback to bundled <package>/.iris (e.g. ../iris_bundle/.iris)
|
|
12
|
+
*/
|
|
13
|
+
export function resolveIrisRoot(repoRoot) {
|
|
14
|
+
const repoIris = path.join(repoRoot, ".iris");
|
|
15
|
+
// Check for policy.yaml to confirm this is a valid IRIS configuration authority
|
|
16
|
+
if (fs.existsSync(path.join(repoIris, "policy.yaml"))) {
|
|
17
|
+
return { path: repoIris, source: "repo" };
|
|
18
|
+
}
|
|
19
|
+
// Fallback to bundled
|
|
20
|
+
// We assume this file is in .../iris/resolver.js|ts
|
|
21
|
+
// And bundle is in .../iris_bundle/.iris
|
|
22
|
+
let bundledIris = path.resolve(__dirname, "../iris_bundle/.iris");
|
|
23
|
+
// Safety check (bundled should always exist, but in dev vs prod checks...)
|
|
24
|
+
if (!fs.existsSync(bundledIris)) {
|
|
25
|
+
// Try finding it in src if we are in dist (dev environment fallback)
|
|
26
|
+
const srcBundle = path.resolve(__dirname, "../../src/iris_bundle/.iris");
|
|
27
|
+
if (fs.existsSync(srcBundle)) {
|
|
28
|
+
bundledIris = srcBundle;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.warn(`[IRIS] Warning: Bundled IRIS not found at ${bundledIris}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { path: bundledIris, source: "bundled" };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a relative path (like ".iris/policy.yaml" or "memory-bank/foo.md")
|
|
38
|
+
* to its absolute physical location on disk, respecting the IRIS source authority.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveArtifactPath(repoRoot, relativePath) {
|
|
41
|
+
if (relativePath.startsWith(".iris/") || relativePath === ".iris") {
|
|
42
|
+
const root = resolveIrisRoot(repoRoot);
|
|
43
|
+
if (relativePath === ".iris")
|
|
44
|
+
return root.path;
|
|
45
|
+
// internal path inside .iris
|
|
46
|
+
const subPath = relativePath.substring(6); // remove ".iris/"
|
|
47
|
+
return path.join(root.path, subPath);
|
|
48
|
+
}
|
|
49
|
+
// Standard repo file
|
|
50
|
+
return path.join(repoRoot, relativePath);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Debug helper to show resolution status
|
|
54
|
+
*/
|
|
55
|
+
export function getIrisDebugInfo(repoRoot) {
|
|
56
|
+
const root = resolveIrisRoot(repoRoot);
|
|
57
|
+
return {
|
|
58
|
+
repoRoot,
|
|
59
|
+
irisRoot: root.path,
|
|
60
|
+
source: root.source,
|
|
61
|
+
bundledPath: path.resolve(__dirname, "../iris_bundle/.iris")
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export function routeIntent(intent, routesConfig) {
|
|
2
|
+
const candidates = [];
|
|
3
|
+
for (const route of routesConfig.routes) {
|
|
4
|
+
// 1. Check exclusions
|
|
5
|
+
if (checkExclusion(intent, route.exclude)) {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
// 2. Score Match
|
|
9
|
+
const { score, matches } = scoreRoute(intent, route);
|
|
10
|
+
// Qualification:
|
|
11
|
+
if (score > 0) {
|
|
12
|
+
candidates.push({ route, score, matches });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// Sort: 1. Score desc, 2. Longest match count desc, 3. Route order (stable sort)
|
|
16
|
+
candidates.sort((a, b) => {
|
|
17
|
+
if (b.score !== a.score)
|
|
18
|
+
return b.score - a.score;
|
|
19
|
+
if (b.matches.length !== a.matches.length)
|
|
20
|
+
return b.matches.length - a.matches.length;
|
|
21
|
+
return 0;
|
|
22
|
+
});
|
|
23
|
+
if (candidates.length > 0) {
|
|
24
|
+
return {
|
|
25
|
+
route: candidates[0].route,
|
|
26
|
+
score: candidates[0].score,
|
|
27
|
+
matches: candidates[0].matches
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Default
|
|
31
|
+
return {
|
|
32
|
+
route: "default",
|
|
33
|
+
score: 0,
|
|
34
|
+
matches: []
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function checkExclusion(intent, exclude) {
|
|
38
|
+
if (!exclude)
|
|
39
|
+
return false;
|
|
40
|
+
const lower = intent.toLowerCase();
|
|
41
|
+
// Any / Any Keywords
|
|
42
|
+
const anyList = [...(exclude.any || []), ...(exclude.any_keywords || [])];
|
|
43
|
+
if (anyList.length > 0) {
|
|
44
|
+
for (const kw of anyList) {
|
|
45
|
+
if (lower.includes(kw.toLowerCase()))
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Regex
|
|
50
|
+
if (exclude.regex) {
|
|
51
|
+
for (const pattern of exclude.regex) {
|
|
52
|
+
try {
|
|
53
|
+
if (new RegExp(pattern, "i").test(intent))
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
console.warn(`Invalid exclude regex: ${pattern}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function scoreRoute(intent, route) {
|
|
64
|
+
let score = 0;
|
|
65
|
+
const matches = [];
|
|
66
|
+
const lower = intent.toLowerCase();
|
|
67
|
+
const rules = route.match;
|
|
68
|
+
// 1. Check ALL
|
|
69
|
+
const allList = [...(rules.all || []), ...(rules.all_keywords || [])];
|
|
70
|
+
if (allList.length > 0) {
|
|
71
|
+
for (const kw of allList) {
|
|
72
|
+
if (lower.includes(kw.toLowerCase())) {
|
|
73
|
+
score += 1;
|
|
74
|
+
matches.push(`all:${kw}`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
return { score: 0, matches: [] }; // Failed ALL condition
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 2. Check ANY + Phrases
|
|
82
|
+
const anyList = [
|
|
83
|
+
...(rules.any || []),
|
|
84
|
+
...(rules.any_keywords || []),
|
|
85
|
+
...(rules.phrases || [])
|
|
86
|
+
];
|
|
87
|
+
if (anyList.length > 0) {
|
|
88
|
+
for (const kw of anyList) {
|
|
89
|
+
if (lower.includes(kw.toLowerCase())) {
|
|
90
|
+
score += 1;
|
|
91
|
+
matches.push(`any:${kw}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// 3. Check Regex
|
|
96
|
+
if (rules.regex) {
|
|
97
|
+
for (const pattern of rules.regex) {
|
|
98
|
+
try {
|
|
99
|
+
if (new RegExp(pattern, "i").test(intent)) {
|
|
100
|
+
score += 2;
|
|
101
|
+
matches.push(`regex:${pattern}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
console.warn(`Invalid match regex in route ${route.route_id}: ${pattern}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Boost
|
|
110
|
+
if (score > 0 && route.boost) {
|
|
111
|
+
score += route.boost;
|
|
112
|
+
}
|
|
113
|
+
return { score, matches };
|
|
114
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import { repoRoot } from "../lib.js";
|
|
4
|
+
import { resolveArtifactPath } from "./resolver.js";
|
|
5
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
6
|
+
export function loadRoutes() {
|
|
7
|
+
const root = repoRoot();
|
|
8
|
+
const routesPath = resolveArtifactPath(root, ".iris/routes.yaml");
|
|
9
|
+
if (!fs.existsSync(routesPath)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const content = fs.readFileSync(routesPath, "utf8");
|
|
14
|
+
return yaml.load(content);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
console.error("Failed to parse .iris/routes.yaml:", e);
|
|
18
|
+
process.exit(EXIT_CODES.POLICY_ERROR);
|
|
19
|
+
}
|
|
20
|
+
}
|