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.
Files changed (109) hide show
  1. package/README.md +384 -0
  2. package/dist/bridge/connector-factory.js +27 -0
  3. package/dist/bridge/connectors/antigravity-connector.js +18 -0
  4. package/dist/bridge/connectors/cursor-connector.js +31 -0
  5. package/dist/bridge/connectors/vscode-connector.js +31 -0
  6. package/dist/bridge/connectors/windsurf-connector.js +23 -0
  7. package/dist/bridge/filesystem-connector.js +100 -0
  8. package/dist/bridge/types.js +10 -0
  9. package/dist/cli.js +30 -0
  10. package/dist/commands/ask.js +232 -0
  11. package/dist/commands/bridge.js +259 -0
  12. package/dist/commands/develop.js +108 -0
  13. package/dist/commands/doctor.js +102 -0
  14. package/dist/commands/install.js +57 -0
  15. package/dist/commands/pack.js +27 -0
  16. package/dist/commands/phase.js +38 -0
  17. package/dist/commands/run.js +17 -0
  18. package/dist/commands/status.js +105 -0
  19. package/dist/commands/uninstall.js +12 -0
  20. package/dist/commands/validate.js +87 -0
  21. package/dist/iris/artifact-checker.js +78 -0
  22. package/dist/iris/fixer.js +143 -0
  23. package/dist/iris/guard.js +38 -0
  24. package/dist/iris/include.js +49 -0
  25. package/dist/iris/installer.js +269 -0
  26. package/dist/iris/manifest.js +54 -0
  27. package/dist/iris/packer.js +303 -0
  28. package/dist/iris/policy.js +28 -0
  29. package/dist/iris/report.js +53 -0
  30. package/dist/iris/resolver.js +63 -0
  31. package/dist/iris/router.js +114 -0
  32. package/dist/iris/routes.js +20 -0
  33. package/dist/iris/run-state.js +143 -0
  34. package/dist/iris/state.js +85 -0
  35. package/dist/iris/uninstaller.js +166 -0
  36. package/dist/iris/validator.js +329 -0
  37. package/dist/lib.js +96 -0
  38. package/dist/utils/exit-codes.js +7 -0
  39. package/dist/workflows/bolt-execution.js +238 -0
  40. package/dist/workflows/bolt-plan.js +192 -0
  41. package/dist/workflows/intent-inception.js +188 -0
  42. package/package.json +41 -0
  43. package/src/iris_bundle/.iris/aidlc/README.md +16 -0
  44. package/src/iris_bundle/.iris/aidlc/agents/iris-construction-agent.md +35 -0
  45. package/src/iris_bundle/.iris/aidlc/agents/iris-inception-agent.md +30 -0
  46. package/src/iris_bundle/.iris/aidlc/agents/iris-master-agent.md +35 -0
  47. package/src/iris_bundle/.iris/aidlc/agents/iris-operations-agent.md +29 -0
  48. package/src/iris_bundle/.iris/aidlc/commands/iris-construction-agent.md +18 -0
  49. package/src/iris_bundle/.iris/aidlc/commands/iris-inception-agent.md +18 -0
  50. package/src/iris_bundle/.iris/aidlc/commands/iris-master-agent.md +18 -0
  51. package/src/iris_bundle/.iris/aidlc/commands/iris-operations-agent.md +18 -0
  52. package/src/iris_bundle/.iris/aidlc/context/context-map.md +25 -0
  53. package/src/iris_bundle/.iris/aidlc/context/exclusion-rules.md +13 -0
  54. package/src/iris_bundle/.iris/aidlc/context/load-order.md +25 -0
  55. package/src/iris_bundle/.iris/aidlc/memory/intent-rules.md +9 -0
  56. package/src/iris_bundle/.iris/aidlc/memory/log-rules.md +5 -0
  57. package/src/iris_bundle/.iris/aidlc/memory/memory-bank.yaml +39 -0
  58. package/src/iris_bundle/.iris/aidlc/memory/unit-rules.md +9 -0
  59. package/src/iris_bundle/.iris/aidlc/quick-start.md +24 -0
  60. package/src/iris_bundle/.iris/aidlc/skills/execution/implementation.md +14 -0
  61. package/src/iris_bundle/.iris/aidlc/skills/execution/refactoring.md +13 -0
  62. package/src/iris_bundle/.iris/aidlc/skills/execution/scaffold-generation.md +15 -0
  63. package/src/iris_bundle/.iris/aidlc/skills/governance/escalation.md +13 -0
  64. package/src/iris_bundle/.iris/aidlc/skills/governance/quality-gates.md +14 -0
  65. package/src/iris_bundle/.iris/aidlc/skills/governance/stop-conditions.md +11 -0
  66. package/src/iris_bundle/.iris/aidlc/skills/reasoning/decomposition.md +23 -0
  67. package/src/iris_bundle/.iris/aidlc/skills/reasoning/risk-analysis.md +14 -0
  68. package/src/iris_bundle/.iris/aidlc/skills/reasoning/verification.md +21 -0
  69. package/src/iris_bundle/.iris/aidlc/standards/artifacts-registry.md +38 -0
  70. package/src/iris_bundle/.iris/aidlc/standards/decision-logging.md +16 -0
  71. package/src/iris_bundle/.iris/aidlc/standards/doctrine-structure.md +31 -0
  72. package/src/iris_bundle/.iris/aidlc/standards/documentation-rules.md +15 -0
  73. package/src/iris_bundle/.iris/aidlc/standards/file-structure.md +21 -0
  74. package/src/iris_bundle/.iris/aidlc/standards/naming-conventions.md +18 -0
  75. package/src/iris_bundle/.iris/aidlc/standards/phases-and-gates.md +25 -0
  76. package/src/iris_bundle/.iris/aidlc/standards/routes-and-routing.md +35 -0
  77. package/src/iris_bundle/.iris/aidlc/standards/tool-wrappers.md +32 -0
  78. package/src/iris_bundle/.iris/aidlc/templates/bolt.md +23 -0
  79. package/src/iris_bundle/.iris/aidlc/templates/doctrine-doc-template.md +33 -0
  80. package/src/iris_bundle/.iris/aidlc/templates/intent.md +23 -0
  81. package/src/iris_bundle/.iris/aidlc/templates/log.md +24 -0
  82. package/src/iris_bundle/.iris/aidlc/templates/review.md +21 -0
  83. package/src/iris_bundle/.iris/aidlc/templates/unit.md +31 -0
  84. package/src/iris_bundle/.iris/aidlc/validation/failure-modes.md +16 -0
  85. package/src/iris_bundle/.iris/aidlc/validation/phase-preconditions.md +21 -0
  86. package/src/iris_bundle/.iris/aidlc/validation/quality-checklist.md +20 -0
  87. package/src/iris_bundle/.iris/policy.yaml +27 -0
  88. package/src/iris_bundle/.iris/routes.yaml +98 -0
  89. package/src/iris_bundle/.iris/state.yaml +7 -0
  90. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/knowledge/IRIS.md +6 -0
  91. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-construction-agent.md +25 -0
  92. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-inception-agent.md +25 -0
  93. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-master-agent.md +25 -0
  94. package/src/iris_bundle/.iris/tools/antigravity/.antigravity/workflows/iris-operations-agent.md +25 -0
  95. package/src/iris_bundle/.iris/tools/claude/.claude/claude.md +9 -0
  96. package/src/iris_bundle/.iris/tools/claude/.claude/commands/compare-specs.md +203 -0
  97. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-construction-agent.md +25 -0
  98. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-inception-agent.md +25 -0
  99. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-master-agent.md +25 -0
  100. package/src/iris_bundle/.iris/tools/claude/.claude/commands/iris-operations-agent.md +25 -0
  101. package/src/iris_bundle/.iris/tools/codex/AGENTS.md +15 -0
  102. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-construction-agent.md +25 -0
  103. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-inception-agent.md +25 -0
  104. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-master-agent.md +25 -0
  105. package/src/iris_bundle/.iris/tools/cursor/.cursor/commands/iris-operations-agent.md +25 -0
  106. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-construction-agent.toml +29 -0
  107. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-inception-agent.toml +29 -0
  108. package/src/iris_bundle/.iris/tools/gemini/.gemini/commands/iris-master-agent.toml +29 -0
  109. 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
+ }