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,102 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
function checkmark(condition) {
|
|
7
|
+
return condition ? kleur.green("✓") : kleur.red("✗");
|
|
8
|
+
}
|
|
9
|
+
function getNodeVersion() {
|
|
10
|
+
return process.version;
|
|
11
|
+
}
|
|
12
|
+
function getNpmVersion() {
|
|
13
|
+
try {
|
|
14
|
+
return execSync("npm --version", { encoding: "utf-8" }).trim();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function getNpmPrefix() {
|
|
21
|
+
try {
|
|
22
|
+
return execSync("npm prefix -g", { encoding: "utf-8" }).trim();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "unknown";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isInPath(dir) {
|
|
29
|
+
const pathEnv = process.env.PATH || "";
|
|
30
|
+
const paths = pathEnv.split(":");
|
|
31
|
+
return paths.includes(dir);
|
|
32
|
+
}
|
|
33
|
+
export const doctorCommand = new Command("doctor")
|
|
34
|
+
.description("Diagnose environment and PATH issues for navi")
|
|
35
|
+
.action(() => {
|
|
36
|
+
const root = process.cwd();
|
|
37
|
+
const cliPath = path.join(root, "dist/cli.js");
|
|
38
|
+
const cliExists = fs.existsSync(cliPath);
|
|
39
|
+
const nodeVersion = getNodeVersion();
|
|
40
|
+
const npmVersion = getNpmVersion();
|
|
41
|
+
const npmPrefix = getNpmPrefix();
|
|
42
|
+
const globalBinDir = npmPrefix !== "unknown" ? path.join(npmPrefix, "bin") : "unknown";
|
|
43
|
+
const naviLinkPath = globalBinDir !== "unknown" ? path.join(globalBinDir, "navi") : "unknown";
|
|
44
|
+
const naviLinkExists = naviLinkPath !== "unknown" && fs.existsSync(naviLinkPath);
|
|
45
|
+
const binInPath = globalBinDir !== "unknown" && isInPath(globalBinDir);
|
|
46
|
+
console.log("");
|
|
47
|
+
console.log(kleur.bold("IRIS Doctor - Environment Diagnostics"));
|
|
48
|
+
console.log("=====================================");
|
|
49
|
+
console.log("");
|
|
50
|
+
// System info
|
|
51
|
+
console.log(kleur.bold("System:"));
|
|
52
|
+
console.log(`${checkmark(true)} Node.js: ${nodeVersion}`);
|
|
53
|
+
console.log(`${checkmark(npmVersion !== "unknown")} npm: ${npmVersion}`);
|
|
54
|
+
console.log(`${checkmark(true)} Repo root: ${root}`);
|
|
55
|
+
console.log(`${checkmark(cliExists)} CLI built: ${cliExists ? "dist/cli.js exists" : "dist/cli.js MISSING"}`);
|
|
56
|
+
console.log("");
|
|
57
|
+
if (!cliExists) {
|
|
58
|
+
console.log(kleur.yellow("⚠ Action Required:"));
|
|
59
|
+
console.log(" Build the CLI first:");
|
|
60
|
+
console.log(kleur.cyan(" npm run build"));
|
|
61
|
+
console.log("");
|
|
62
|
+
}
|
|
63
|
+
// Global installation
|
|
64
|
+
console.log(kleur.bold("Global Installation:"));
|
|
65
|
+
console.log(`${checkmark(npmPrefix !== "unknown")} npm prefix: ${npmPrefix}`);
|
|
66
|
+
console.log(`${checkmark(globalBinDir !== "unknown")} bin dir: ${globalBinDir}`);
|
|
67
|
+
console.log(`${checkmark(binInPath)} In PATH: ${binInPath ? "YES" : "NO"}`);
|
|
68
|
+
console.log(`${checkmark(naviLinkExists)} navi link: ${naviLinkExists ? `${naviLinkPath} → exists` : `${naviLinkPath} → NOT FOUND`}`);
|
|
69
|
+
console.log("");
|
|
70
|
+
// Actionable guidance
|
|
71
|
+
const hasIssues = !cliExists || !binInPath || !naviLinkExists;
|
|
72
|
+
if (hasIssues) {
|
|
73
|
+
console.log(kleur.yellow("⚠ Action Required:"));
|
|
74
|
+
console.log("");
|
|
75
|
+
if (!naviLinkExists && cliExists) {
|
|
76
|
+
console.log(" Link navi globally:");
|
|
77
|
+
console.log(kleur.cyan(" npm run link:navi"));
|
|
78
|
+
console.log("");
|
|
79
|
+
}
|
|
80
|
+
if (!binInPath && globalBinDir !== "unknown") {
|
|
81
|
+
console.log(" Add global bin to PATH:");
|
|
82
|
+
console.log(kleur.cyan(` export PATH="${globalBinDir}:$PATH"`));
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log(" Then reload shell:");
|
|
85
|
+
console.log(kleur.cyan(" source ~/.zshrc && hash -r"));
|
|
86
|
+
console.log("");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log(kleur.green("✓ Everything looks good!"));
|
|
91
|
+
console.log("");
|
|
92
|
+
console.log(" You can run navi globally:");
|
|
93
|
+
console.log(kleur.cyan(" navi --version"));
|
|
94
|
+
console.log(kleur.cyan(" navi validate"));
|
|
95
|
+
console.log("");
|
|
96
|
+
}
|
|
97
|
+
// Additional tips
|
|
98
|
+
console.log(kleur.dim("Tips:"));
|
|
99
|
+
console.log(kleur.dim(" - Run 'type -a navi' to see which navi resolves"));
|
|
100
|
+
console.log(kleur.dim(" - Use 'npm run navi -- <command>' for repo-local execution"));
|
|
101
|
+
console.log("");
|
|
102
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import kleur from "kleur";
|
|
4
|
+
import { repoRoot } from "../lib.js";
|
|
5
|
+
import { detectTools, installIris, TOOLS } from "../iris/installer.js";
|
|
6
|
+
import { setSelectedIde } from "../iris/state.js";
|
|
7
|
+
export const installCommand = new Command("install")
|
|
8
|
+
.description("Install IRIS doctrine under .iris and install tool commands into this repo (interactive).")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
const root = repoRoot();
|
|
11
|
+
// Step A: Detect
|
|
12
|
+
const defaults = detectTools(root);
|
|
13
|
+
// If defaults empty, fallback to Claude + Cursor
|
|
14
|
+
const defaultSelection = defaults.length > 0 ? defaults : ["claude", "cursor"];
|
|
15
|
+
// Step B: Prompt
|
|
16
|
+
const { tools } = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: "checkbox",
|
|
19
|
+
name: "tools",
|
|
20
|
+
message: "Select the tool(s) you will use with IRIS in this repository:",
|
|
21
|
+
choices: TOOLS.map(t => ({
|
|
22
|
+
name: t.charAt(0).toUpperCase() + t.slice(1), // Capitalize
|
|
23
|
+
value: t,
|
|
24
|
+
checked: defaultSelection.includes(t)
|
|
25
|
+
})),
|
|
26
|
+
validate: (arr) => (arr.length ? true : "Select at least one tool."),
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log(kleur.bold("Installing IRIS..."));
|
|
31
|
+
// Step C-G: Core Installer
|
|
32
|
+
await installIris(root, tools);
|
|
33
|
+
// Persist IDE selection for navi develop
|
|
34
|
+
// Use first tool as default IDE, or prompt if multiple
|
|
35
|
+
let selectedIde = tools[0];
|
|
36
|
+
if (tools.length > 1) {
|
|
37
|
+
const { ide } = await inquirer.prompt([
|
|
38
|
+
{
|
|
39
|
+
type: "list",
|
|
40
|
+
name: "ide",
|
|
41
|
+
message: "Which IDE will you primarily use with 'navi develop'?",
|
|
42
|
+
choices: tools.map(t => ({
|
|
43
|
+
name: t.charAt(0).toUpperCase() + t.slice(1),
|
|
44
|
+
value: t
|
|
45
|
+
}))
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
selectedIde = ide;
|
|
49
|
+
}
|
|
50
|
+
setSelectedIde(selectedIde);
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log(kleur.bold().green("IRIS install complete."));
|
|
53
|
+
console.log(kleur.gray("Doctrine: .iris/aidlc"));
|
|
54
|
+
console.log(kleur.gray("Policy: .iris/policy.yaml"));
|
|
55
|
+
console.log(kleur.gray(`Tools: ${tools.join(", ")}`));
|
|
56
|
+
console.log(kleur.gray(`Primary IDE: ${selectedIde}`));
|
|
57
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { generatePack } from "../iris/packer.js";
|
|
3
|
+
function collect(value, previous) {
|
|
4
|
+
return previous.concat([value]);
|
|
5
|
+
}
|
|
6
|
+
export const packCommand = new Command("pack")
|
|
7
|
+
.description("Generate a deterministic context bundle (markdown) for an agent.")
|
|
8
|
+
.option("--agent <agent>", "Target agent (inception|construction|operations|master)")
|
|
9
|
+
.option("--phase <phase>", "Target phase (overrides state.phase.current)")
|
|
10
|
+
.option("--out <path>", "Output file path (default: .iris/inbox/context-pack.<agent>.<phase>.md)")
|
|
11
|
+
.option("--stdout", "Print bundle to stdout instead of writing file", false)
|
|
12
|
+
.option("--strict", "Fail on validation warnings", false)
|
|
13
|
+
.option("--max-bytes <n>", "Maximum size in bytes", (val) => parseInt(val, 10), 1_500_000)
|
|
14
|
+
.option("--include <glob>", "Glob pattern to include files (repeatable)", collect, [])
|
|
15
|
+
.option("--exclude <glob>", "Glob pattern to exclude files (repeatable)", collect, [])
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
await generatePack({
|
|
18
|
+
agent: opts.agent,
|
|
19
|
+
phase: opts.phase,
|
|
20
|
+
output: opts.out,
|
|
21
|
+
stdout: opts.stdout,
|
|
22
|
+
strict: opts.strict,
|
|
23
|
+
maxBytes: opts.maxBytes,
|
|
24
|
+
includes: opts.include,
|
|
25
|
+
excludes: opts.exclude
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import { loadState, saveState } from "../iris/state.js";
|
|
4
|
+
import { loadPolicy } from "../iris/policy.js";
|
|
5
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
6
|
+
export const phaseCommand = new Command("phase")
|
|
7
|
+
.description("Manage IRIS lifecycle phases");
|
|
8
|
+
phaseCommand
|
|
9
|
+
.command("set")
|
|
10
|
+
.description("Request a transition to a new phase")
|
|
11
|
+
.argument("<phase>", "The target phase to transition to")
|
|
12
|
+
.action((phaseName) => {
|
|
13
|
+
// 1. Validate phase exists in policy
|
|
14
|
+
const policy = loadPolicy();
|
|
15
|
+
if (!policy.phases[phaseName]) {
|
|
16
|
+
console.error(kleur.red(`Error: Phase '${phaseName}' is not defined in policy.`));
|
|
17
|
+
console.log("Available phases: " + Object.keys(policy.phases).join(", "));
|
|
18
|
+
process.exit(EXIT_CODES.POLICY_ERROR);
|
|
19
|
+
}
|
|
20
|
+
// 2. Load state
|
|
21
|
+
const state = loadState();
|
|
22
|
+
// 3. Write request
|
|
23
|
+
state.phase.requested = phaseName;
|
|
24
|
+
try {
|
|
25
|
+
saveState(state);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
console.error(kleur.red("Failed to save state."));
|
|
29
|
+
process.exit(EXIT_CODES.STATE_ERROR);
|
|
30
|
+
}
|
|
31
|
+
// 4. Print output
|
|
32
|
+
console.log("");
|
|
33
|
+
console.log(`Current Phase: ${kleur.cyan(state.phase.current)}`);
|
|
34
|
+
console.log(`Requested Phase: ${kleur.yellow(state.phase.requested || "")}`);
|
|
35
|
+
console.log("");
|
|
36
|
+
console.log(kleur.bold("Next: ") + "Run " + kleur.green("navi validate --apply") + " to commit this transition.");
|
|
37
|
+
console.log("");
|
|
38
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import { requireValidRepoOrExit } from "../iris/guard.js";
|
|
4
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
5
|
+
export const runCommand = new Command("run")
|
|
6
|
+
.description("Execute a named workflow or agent (Placeholder)")
|
|
7
|
+
.argument("<name>", "Name of the workflow/agent to run")
|
|
8
|
+
.option("--strict", "Enforce strict validation (warnings become errors)", false)
|
|
9
|
+
.action(async (name, options) => {
|
|
10
|
+
// GUARDRAIL
|
|
11
|
+
await requireValidRepoOrExit({ strict: options.strict });
|
|
12
|
+
// If we are here, repo is valid (or warnings ignored)
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log(kleur.green(`✅ Guards passed. Would run: ${name}`));
|
|
15
|
+
console.log("");
|
|
16
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
17
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import kleur from "kleur";
|
|
5
|
+
import { validate } from "../iris/validator.js";
|
|
6
|
+
import { loadState, DEFAULT_STATE } from "../iris/state.js";
|
|
7
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
8
|
+
function getIrisStatus() {
|
|
9
|
+
const root = process.cwd();
|
|
10
|
+
const policyPath = path.join(root, ".iris/policy.yaml");
|
|
11
|
+
const doctrinePath = path.join(root, ".iris/aidlc");
|
|
12
|
+
return {
|
|
13
|
+
policy: fs.existsSync(policyPath),
|
|
14
|
+
doctrine: fs.existsSync(doctrinePath)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export const statusCommand = new Command("status")
|
|
18
|
+
.description("Show IRIS validation status (informational)")
|
|
19
|
+
.option("--json", "Output result as JSON", false)
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
// 1. Check basic presence
|
|
22
|
+
const presence = getIrisStatus();
|
|
23
|
+
// 2. Run validator in read-only mode
|
|
24
|
+
// We catch errors here because status command should be safe and always exit 0
|
|
25
|
+
let result = null;
|
|
26
|
+
let error = null;
|
|
27
|
+
try {
|
|
28
|
+
result = await validate({
|
|
29
|
+
apply: false,
|
|
30
|
+
strict: false,
|
|
31
|
+
writeBack: false // Do not mutate state
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
error = e;
|
|
36
|
+
}
|
|
37
|
+
// 3. Load state manually just for reporting if validate failed completely
|
|
38
|
+
let state = DEFAULT_STATE;
|
|
39
|
+
try {
|
|
40
|
+
state = loadState();
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
// failed to load state, use defaults
|
|
44
|
+
}
|
|
45
|
+
if (options.json) {
|
|
46
|
+
const jsonOutput = {
|
|
47
|
+
policy: { ok: presence.policy },
|
|
48
|
+
doctrine: { ok: presence.doctrine },
|
|
49
|
+
phase: {
|
|
50
|
+
current: result ? result.phase.current : state.phase.current,
|
|
51
|
+
requested: result ? result.phase.requested : state.phase.requested
|
|
52
|
+
},
|
|
53
|
+
last_validation: result ? state.last_validation : null,
|
|
54
|
+
summary: result ? {
|
|
55
|
+
result: result.valid ? "valid" : "invalid",
|
|
56
|
+
errors_count: result.errors.length,
|
|
57
|
+
warnings_count: result.warnings ? 1 : 0, // simplistic
|
|
58
|
+
top_errors: result.errors
|
|
59
|
+
} : { error: "Validation failed to run" }
|
|
60
|
+
};
|
|
61
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
62
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
63
|
+
}
|
|
64
|
+
// Human Output
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log(kleur.bold("IRIS Status"));
|
|
67
|
+
console.log(`- Policy: ${presence.policy ? kleur.green("OK") : kleur.red("MISSING")} (.iris/policy.yaml)`);
|
|
68
|
+
console.log(`- Doctrine: ${presence.doctrine ? kleur.green("OK") : kleur.red("MISSING")} (.iris/aidlc)`);
|
|
69
|
+
if (result) {
|
|
70
|
+
console.log(`- Phase: ${kleur.cyan(result.phase.current)}`);
|
|
71
|
+
if (result.phase.requested) {
|
|
72
|
+
console.log(`- Requested:${kleur.yellow(result.phase.requested)}`);
|
|
73
|
+
}
|
|
74
|
+
// Show validation result
|
|
75
|
+
const validity = result.valid
|
|
76
|
+
? kleur.green("VALID")
|
|
77
|
+
: kleur.red("INVALID");
|
|
78
|
+
console.log(`- Last val.: ${validity} (dry-run just now)`);
|
|
79
|
+
if (!result.valid && result.errors.length > 0) {
|
|
80
|
+
console.log("");
|
|
81
|
+
console.log(kleur.bold(`Issues (showing top ${Math.min(5, result.errors.length)}/${result.errors.length})`));
|
|
82
|
+
result.errors.slice(0, 5).forEach((err, idx) => {
|
|
83
|
+
console.log(kleur.yellow(`${idx + 1}) [${err.code}] ${err.path || ""}`));
|
|
84
|
+
if (err.remediation)
|
|
85
|
+
console.log(` Fix: ${kleur.dim(err.remediation)}`);
|
|
86
|
+
});
|
|
87
|
+
if (result.errors.length > 5) {
|
|
88
|
+
console.log(kleur.dim(`... and ${result.errors.length - 5} more.`));
|
|
89
|
+
}
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log(kleur.dim("Hint: Run `navi validate` for full details."));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log(kleur.green("System is healthy."));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(kleur.red("Validation engine failed to run."));
|
|
100
|
+
if (error)
|
|
101
|
+
console.log(error);
|
|
102
|
+
}
|
|
103
|
+
console.log("");
|
|
104
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
105
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { uninstallIris } from "../iris/uninstaller.js";
|
|
3
|
+
export const uninstallCommand = new Command("uninstall")
|
|
4
|
+
.description("Remove .iris and tool command files installed by IRIS (manifest-driven).")
|
|
5
|
+
.option("--keep-memory", "Keep memory-bank folder if present", false)
|
|
6
|
+
.option("--force", "Skip confirmations where possible", false)
|
|
7
|
+
.action(async (opts) => {
|
|
8
|
+
await uninstallIris({
|
|
9
|
+
keepMemory: opts.keepMemory,
|
|
10
|
+
force: opts.force
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import { validate } from "../iris/validator.js";
|
|
4
|
+
import { printReport } from "../iris/report.js";
|
|
5
|
+
import { collectFixPlan, applyFixPlan } from "../iris/fixer.js";
|
|
6
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
7
|
+
export const validateCommand = new Command("validate")
|
|
8
|
+
.description("Validate the current repository state against the IRIS policy.")
|
|
9
|
+
.option("--apply", "Automatically apply requested phase changes if valid.")
|
|
10
|
+
.option("--phase <phase>", "Override the target phase to validate against.")
|
|
11
|
+
.option("--strict", "Fail on warnings as well as errors.")
|
|
12
|
+
.option("--json", "Output results as JSON.")
|
|
13
|
+
.option("--fix", "Attempt to fix missing artifacts automatically.")
|
|
14
|
+
.option("--fix-dry-run", "Show what would be fixed without making changes.")
|
|
15
|
+
.option("--yes", "Automatically confirm overwrites (if any).")
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
// 1. First Validation Pass
|
|
18
|
+
if (!options.fix && !options.fixDryRun) {
|
|
19
|
+
const result = await validate({
|
|
20
|
+
apply: options.apply || false,
|
|
21
|
+
phase: options.phase,
|
|
22
|
+
strict: options.strict || false,
|
|
23
|
+
});
|
|
24
|
+
printReport(result, options.json, undefined, options.strict);
|
|
25
|
+
let exitCode = EXIT_CODES.SUCCESS;
|
|
26
|
+
if (!result.valid) {
|
|
27
|
+
exitCode = EXIT_CODES.INVALID;
|
|
28
|
+
}
|
|
29
|
+
else if (options.strict && result.warnings) {
|
|
30
|
+
exitCode = EXIT_CODES.VALID_WITH_WARNINGS;
|
|
31
|
+
}
|
|
32
|
+
process.exit(exitCode);
|
|
33
|
+
}
|
|
34
|
+
// 2. Fix Mode
|
|
35
|
+
// Pass 1: Discovery
|
|
36
|
+
const result1 = await validate({
|
|
37
|
+
apply: false,
|
|
38
|
+
phase: options.phase,
|
|
39
|
+
strict: options.strict || false,
|
|
40
|
+
writeBack: false
|
|
41
|
+
});
|
|
42
|
+
if (result1.valid && !result1.warnings) {
|
|
43
|
+
if (options.json) {
|
|
44
|
+
printReport(result1, true);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(kleur.green("✓ Repository is valid. No fixes needed."));
|
|
48
|
+
}
|
|
49
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
50
|
+
}
|
|
51
|
+
// Collect Plan
|
|
52
|
+
const plan = collectFixPlan(result1);
|
|
53
|
+
if (plan.actions.length === 0) {
|
|
54
|
+
if (options.json) {
|
|
55
|
+
printReport(result1, true);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(kleur.yellow("No fixable missing artifacts found."));
|
|
59
|
+
printReport(result1, false, undefined, options.strict);
|
|
60
|
+
}
|
|
61
|
+
process.exit(EXIT_CODES.INVALID); // Changed from POLICY_ERROR
|
|
62
|
+
}
|
|
63
|
+
// Apply Fixes
|
|
64
|
+
const fixResult = await applyFixPlan(plan, {
|
|
65
|
+
dryRun: options.fixDryRun,
|
|
66
|
+
yes: options.yes
|
|
67
|
+
});
|
|
68
|
+
if (options.fixDryRun) {
|
|
69
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
70
|
+
}
|
|
71
|
+
// 3. Re-validate (Final Pass)
|
|
72
|
+
const result2 = await validate({
|
|
73
|
+
apply: options.apply || false,
|
|
74
|
+
phase: options.phase,
|
|
75
|
+
strict: options.strict || false,
|
|
76
|
+
writeBack: true
|
|
77
|
+
});
|
|
78
|
+
printReport(result2, options.json, fixResult, options.strict);
|
|
79
|
+
let exitCode = EXIT_CODES.SUCCESS;
|
|
80
|
+
if (!result2.valid) {
|
|
81
|
+
exitCode = EXIT_CODES.INVALID; // Changed from POLICY_ERROR
|
|
82
|
+
}
|
|
83
|
+
else if (options.strict && result2.warnings) {
|
|
84
|
+
exitCode = EXIT_CODES.VALID_WITH_WARNINGS;
|
|
85
|
+
}
|
|
86
|
+
process.exit(exitCode);
|
|
87
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes an artifact path by:
|
|
5
|
+
* - Removing CRLF (\r) characters
|
|
6
|
+
* - Trimming whitespace
|
|
7
|
+
* - Converting backslashes to forward slashes
|
|
8
|
+
* - Removing trailing slashes (except for root)
|
|
9
|
+
*/
|
|
10
|
+
export function normalizeArtifactPath(rawPath) {
|
|
11
|
+
let normalized = rawPath;
|
|
12
|
+
// Remove \r characters (CRLF normalization)
|
|
13
|
+
normalized = normalized.replace(/\r/g, "");
|
|
14
|
+
// Trim whitespace
|
|
15
|
+
normalized = normalized.trim();
|
|
16
|
+
// Convert backslashes to forward slashes
|
|
17
|
+
normalized = normalized.replace(/\\/g, "/");
|
|
18
|
+
// Remove trailing slashes (but keep single "/" for root)
|
|
19
|
+
if (normalized.length > 1) {
|
|
20
|
+
normalized = normalized.replace(/\/+$/, "");
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Infers whether a path represents a file or directory based on:
|
|
26
|
+
* - Trailing slash → directory
|
|
27
|
+
* - Has file extension → file
|
|
28
|
+
* - No extension → directory
|
|
29
|
+
*/
|
|
30
|
+
export function inferArtifactKind(normalizedPath) {
|
|
31
|
+
// If original had trailing slash (before normalization), it's a directory
|
|
32
|
+
// But we've already normalized, so check for extension
|
|
33
|
+
const basename = path.basename(normalizedPath);
|
|
34
|
+
const hasExtension = basename.includes(".") && !basename.startsWith(".");
|
|
35
|
+
return hasExtension ? "file" : "directory";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks if a raw path contains control characters
|
|
39
|
+
*/
|
|
40
|
+
export function hasControlCharacters(rawPath) {
|
|
41
|
+
// Check for common control characters: \r, \n, \t, etc.
|
|
42
|
+
return /[\r\n\t\x00-\x1F]/.test(rawPath);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Main artifact validation function.
|
|
46
|
+
* Validates an artifact path and returns detailed information.
|
|
47
|
+
*/
|
|
48
|
+
export function checkArtifact(repoRoot, rawPath, expectedType) {
|
|
49
|
+
const normalized = normalizeArtifactPath(rawPath);
|
|
50
|
+
const absolutePath = path.resolve(repoRoot, normalized);
|
|
51
|
+
const hasControlChars = hasControlCharacters(rawPath);
|
|
52
|
+
const inferredKind = expectedType || inferArtifactKind(normalized);
|
|
53
|
+
let exists = false;
|
|
54
|
+
let isDirectory = false;
|
|
55
|
+
let isFile = false;
|
|
56
|
+
try {
|
|
57
|
+
exists = fs.existsSync(absolutePath);
|
|
58
|
+
if (exists) {
|
|
59
|
+
const stats = fs.statSync(absolutePath);
|
|
60
|
+
isDirectory = stats.isDirectory();
|
|
61
|
+
isFile = stats.isFile();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
// If stat fails, treat as non-existent
|
|
66
|
+
exists = false;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
exists,
|
|
70
|
+
isDirectory,
|
|
71
|
+
isFile,
|
|
72
|
+
absolutePath,
|
|
73
|
+
normalized,
|
|
74
|
+
raw: rawPath,
|
|
75
|
+
hasControlChars,
|
|
76
|
+
inferredKind
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import kleur from "kleur";
|
|
4
|
+
import { repoRoot, ensureDir } from "../lib.js";
|
|
5
|
+
export function collectFixPlan(result) {
|
|
6
|
+
const plan = {
|
|
7
|
+
actions: [],
|
|
8
|
+
skipped: []
|
|
9
|
+
};
|
|
10
|
+
const root = repoRoot();
|
|
11
|
+
for (const error of result.errors) {
|
|
12
|
+
if (error.code !== "MISSING_ARTIFACT") {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (!error.path)
|
|
16
|
+
continue;
|
|
17
|
+
// Fix: Create Directory
|
|
18
|
+
if (error.artifactType === "dir") {
|
|
19
|
+
// Check if already in plan
|
|
20
|
+
const exists = plan.actions.find(a => a.path === error.path && a.kind === "mkdir");
|
|
21
|
+
if (!exists) {
|
|
22
|
+
plan.actions.push({
|
|
23
|
+
kind: "mkdir",
|
|
24
|
+
path: error.path
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Fix: Create File from Template
|
|
30
|
+
if (error.artifactType === "file") {
|
|
31
|
+
if (error.template) {
|
|
32
|
+
// Verify template exists in repo
|
|
33
|
+
const templatePath = path.join(root, error.template);
|
|
34
|
+
if (fs.existsSync(templatePath)) {
|
|
35
|
+
plan.actions.push({
|
|
36
|
+
kind: "createFileFromTemplate",
|
|
37
|
+
path: error.path,
|
|
38
|
+
template: error.template
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
plan.skipped.push({
|
|
43
|
+
reason: `Template missing: ${error.template}`,
|
|
44
|
+
path: error.path
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
plan.skipped.push({
|
|
50
|
+
reason: "No template defined",
|
|
51
|
+
path: error.path
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Sort actions: mkdir first, then files
|
|
58
|
+
plan.actions.sort((a, b) => {
|
|
59
|
+
if (a.kind === "mkdir" && b.kind !== "mkdir")
|
|
60
|
+
return -1;
|
|
61
|
+
if (a.kind !== "mkdir" && b.kind === "mkdir")
|
|
62
|
+
return 1;
|
|
63
|
+
return a.path.localeCompare(b.path);
|
|
64
|
+
});
|
|
65
|
+
return plan;
|
|
66
|
+
}
|
|
67
|
+
export async function applyFixPlan(plan, options) {
|
|
68
|
+
const result = {
|
|
69
|
+
createdDirs: 0,
|
|
70
|
+
createdFiles: 0,
|
|
71
|
+
paths: []
|
|
72
|
+
};
|
|
73
|
+
const root = repoRoot();
|
|
74
|
+
if (options.dryRun) {
|
|
75
|
+
console.log(kleur.bold().blue("Dry Run Fix Plan:"));
|
|
76
|
+
}
|
|
77
|
+
for (const action of plan.actions) {
|
|
78
|
+
const fullPath = path.join(root, action.path);
|
|
79
|
+
if (action.kind === "mkdir") {
|
|
80
|
+
if (options.dryRun) {
|
|
81
|
+
console.log(` [MKDIR] ${action.path}`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
if (!fs.existsSync(fullPath)) {
|
|
86
|
+
ensureDir(fullPath);
|
|
87
|
+
result.createdDirs++;
|
|
88
|
+
result.paths.push(action.path);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.error(kleur.red(`Failed to create dir ${action.path}: ${e}`));
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (action.kind === "createFileFromTemplate" && action.template) {
|
|
97
|
+
const templatePath = path.join(root, action.template);
|
|
98
|
+
if (options.dryRun) {
|
|
99
|
+
console.log(` [COPY] ${action.path} (from ${action.template})`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Check existence logic (safe overwrite)
|
|
103
|
+
// MISSING_ARTIFACT error implies it didn't exist during validate.
|
|
104
|
+
// But checking again just in case (race condition or manual change).
|
|
105
|
+
if (fs.existsSync(fullPath)) {
|
|
106
|
+
// It exists now?
|
|
107
|
+
if (options.yes) {
|
|
108
|
+
// Overwrite? Spec says "Missing artifacts (safe fixes)".
|
|
109
|
+
// If it now exists, we probably shouldn't touch it unless it's empty?
|
|
110
|
+
// Validator said it was missing.
|
|
111
|
+
// If it exists now, we skip it to be safe, or prompt.
|
|
112
|
+
// Prompt says "File exists and differs. Overwrite?".
|
|
113
|
+
// Let's compare logs? No, simplistic check.
|
|
114
|
+
// Actually, if it exists, it wasn't missing. Maybe the validator ran, then user created it.
|
|
115
|
+
// Only overwrite if explicit confirmation.
|
|
116
|
+
// But usually applyFixPlan follows validate immediately.
|
|
117
|
+
console.log(kleur.yellow(`Skipping ${action.path}: File exists.`));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Interactive prompt?
|
|
122
|
+
// "Default behavior: non-interactive safe fixes where no overwrite is needed"
|
|
123
|
+
// "If a fix would require overwriting... Prompt"
|
|
124
|
+
// Since we only fix MISSING artifacts, we assume we don't overwrite.
|
|
125
|
+
// If it exists, we skip.
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
// Read template
|
|
131
|
+
const content = fs.readFileSync(templatePath, "utf-8");
|
|
132
|
+
ensureDir(path.dirname(fullPath));
|
|
133
|
+
fs.writeFileSync(fullPath, content);
|
|
134
|
+
result.createdFiles++;
|
|
135
|
+
result.paths.push(action.path);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
console.error(kleur.red(`Failed to create file ${action.path}: ${e}`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|