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,38 @@
|
|
|
1
|
+
import kleur from "kleur";
|
|
2
|
+
import { validate } from "./validator.js";
|
|
3
|
+
import { EXIT_CODES } from "../utils/exit-codes.js";
|
|
4
|
+
export async function requireValidRepoOrExit(options = {}) {
|
|
5
|
+
// Run validation in read-only mode first?
|
|
6
|
+
// Should we write back validation results for 'run'?
|
|
7
|
+
// Specs: "Runs the same core validator used by navi validate"
|
|
8
|
+
// If we run 'navi run', it probably SHOULD update the last_validation timestamp to show we checked.
|
|
9
|
+
// So writeBack: true (default) is appropriate here.
|
|
10
|
+
const result = await validate({
|
|
11
|
+
apply: false,
|
|
12
|
+
strict: options.strict || false,
|
|
13
|
+
writeBack: true
|
|
14
|
+
});
|
|
15
|
+
if (!result.valid) {
|
|
16
|
+
console.log("");
|
|
17
|
+
console.log(kleur.red().bold("⛔ Repo invalid; run navi validate for details"));
|
|
18
|
+
console.log("");
|
|
19
|
+
// List 1-3 most important issues
|
|
20
|
+
const topErrors = result.errors.slice(0, 3);
|
|
21
|
+
topErrors.forEach((err) => {
|
|
22
|
+
console.log(kleur.red(` [${err.code}] ${err.path || ""}`));
|
|
23
|
+
});
|
|
24
|
+
if (result.errors.length > 3) {
|
|
25
|
+
console.log(kleur.dim(` ... and ${result.errors.length - 3} more.`));
|
|
26
|
+
}
|
|
27
|
+
console.log("");
|
|
28
|
+
process.exit(EXIT_CODES.INVALID);
|
|
29
|
+
}
|
|
30
|
+
// If validated with warnings, we usually proceed unless strict is on.
|
|
31
|
+
// Validator handles strict check internally?
|
|
32
|
+
// No, validate() returns warnings=true. command/validate.ts handled exit.
|
|
33
|
+
if (result.warnings && options.strict) {
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log(kleur.red().bold("⛔ Repo has warnings (strict mode enabled)"));
|
|
36
|
+
process.exit(EXIT_CODES.INVALID);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
import { repoRoot } from "../lib.js";
|
|
4
|
+
import { resolveArtifactPath } from "./resolver.js";
|
|
5
|
+
export const DEFAULT_EXCLUDES = [
|
|
6
|
+
"**/node_modules/**",
|
|
7
|
+
"**/.git/**",
|
|
8
|
+
"**/dist/**",
|
|
9
|
+
"**/build/**",
|
|
10
|
+
"**/.iris/cache/**",
|
|
11
|
+
"**/.DS_Store"
|
|
12
|
+
];
|
|
13
|
+
export async function resolveIncludes(includeGlobs, excludeGlobs = []) {
|
|
14
|
+
const root = repoRoot();
|
|
15
|
+
if (includeGlobs.length === 0)
|
|
16
|
+
return [];
|
|
17
|
+
const entries = await fg(includeGlobs, {
|
|
18
|
+
cwd: root,
|
|
19
|
+
ignore: [...DEFAULT_EXCLUDES, ...excludeGlobs],
|
|
20
|
+
dot: true,
|
|
21
|
+
absolute: false,
|
|
22
|
+
onlyFiles: true
|
|
23
|
+
});
|
|
24
|
+
// Sort for determinism
|
|
25
|
+
return entries.sort();
|
|
26
|
+
}
|
|
27
|
+
export function isBinary(buffer) {
|
|
28
|
+
const chunk = buffer.subarray(0, Math.min(buffer.length, 8000));
|
|
29
|
+
return chunk.includes(0);
|
|
30
|
+
}
|
|
31
|
+
const MAX_INDIVIDUAL_FILE_SIZE = 1_000_000;
|
|
32
|
+
export function getFileContent(relPath) {
|
|
33
|
+
const root = repoRoot();
|
|
34
|
+
const absPath = resolveArtifactPath(root, relPath);
|
|
35
|
+
try {
|
|
36
|
+
const stats = fs.statSync(absPath);
|
|
37
|
+
if (stats.size > MAX_INDIVIDUAL_FILE_SIZE) {
|
|
38
|
+
return { path: relPath, size: stats.size, skipped: "too_large" };
|
|
39
|
+
}
|
|
40
|
+
const buffer = fs.readFileSync(absPath);
|
|
41
|
+
if (isBinary(buffer)) {
|
|
42
|
+
return { path: relPath, size: stats.size, skipped: "binary" };
|
|
43
|
+
}
|
|
44
|
+
return { path: relPath, size: stats.size, content: buffer.toString("utf8") };
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return { path: relPath, size: 0, skipped: "read_error" };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import kleur from "kleur";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import { ensureDir, sanitizeFolderName, spawnAsync } from "../lib.js";
|
|
6
|
+
import { updateManifest } from "./manifest.js";
|
|
7
|
+
export const TOOLS = ["claude", "cursor", "gemini", "antigravity", "codex"];
|
|
8
|
+
const BUNDLE_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../src/iris_bundle");
|
|
9
|
+
/**
|
|
10
|
+
* Ensure package.json exists in the target directory.
|
|
11
|
+
* If missing, create a minimal one.
|
|
12
|
+
*/
|
|
13
|
+
function ensurePackageJson(root) {
|
|
14
|
+
const pkgPath = path.join(root, "package.json");
|
|
15
|
+
if (fs.existsSync(pkgPath)) {
|
|
16
|
+
console.log(kleur.gray("✓ package.json already exists"));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const folderName = path.basename(root);
|
|
20
|
+
const sanitizedName = sanitizeFolderName(folderName);
|
|
21
|
+
const minimalPackage = {
|
|
22
|
+
name: sanitizedName,
|
|
23
|
+
version: "0.0.0",
|
|
24
|
+
private: true,
|
|
25
|
+
description: "Project using IRIS framework",
|
|
26
|
+
scripts: {}
|
|
27
|
+
};
|
|
28
|
+
fs.writeFileSync(pkgPath, JSON.stringify(minimalPackage, null, 2) + "\n", "utf8");
|
|
29
|
+
console.log(kleur.green(`✓ Created package.json (name: ${sanitizedName})`));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run npm install to generate package-lock.json and install any dependencies
|
|
33
|
+
*/
|
|
34
|
+
async function runNpmInstall(root) {
|
|
35
|
+
console.log("");
|
|
36
|
+
console.log(kleur.bold("Running npm install to generate lockfile..."));
|
|
37
|
+
try {
|
|
38
|
+
const result = await spawnAsync("npm", ["install"], root);
|
|
39
|
+
if (result.code === 0) {
|
|
40
|
+
console.log(kleur.green("✓ npm install completed successfully"));
|
|
41
|
+
// Verify lockfile was created
|
|
42
|
+
const lockPath = path.join(root, "package-lock.json");
|
|
43
|
+
if (fs.existsSync(lockPath)) {
|
|
44
|
+
console.log(kleur.green("✓ package-lock.json generated"));
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.error(kleur.red(`✗ npm install failed with code ${result.code}`));
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error(kleur.red("✗ Failed to run npm install"));
|
|
55
|
+
console.error(kleur.gray(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function detectTools(root) {
|
|
60
|
+
const checks = {
|
|
61
|
+
claude: fs.existsSync(path.join(root, ".claude")),
|
|
62
|
+
cursor: fs.existsSync(path.join(root, ".cursor")),
|
|
63
|
+
gemini: fs.existsSync(path.join(root, ".gemini")),
|
|
64
|
+
antigravity: fs.existsSync(path.join(root, ".antigravity")),
|
|
65
|
+
codex: fs.existsSync(path.join(root, "AGENTS.md")),
|
|
66
|
+
};
|
|
67
|
+
return TOOLS.filter(t => checks[t]);
|
|
68
|
+
}
|
|
69
|
+
export async function installIris(root, tools) {
|
|
70
|
+
// Step 0: Ensure package.json exists (for empty folder support)
|
|
71
|
+
ensurePackageJson(root);
|
|
72
|
+
const installedPaths = [];
|
|
73
|
+
const createdDirs = [];
|
|
74
|
+
// Bundle locations
|
|
75
|
+
const bundleIris = path.join(BUNDLE_ROOT, ".iris");
|
|
76
|
+
const bundleDoctrine = path.join(bundleIris, "aidlc");
|
|
77
|
+
const bundleTools = path.join(bundleIris, "tools");
|
|
78
|
+
// Target locations
|
|
79
|
+
const targetIris = path.join(root, ".iris");
|
|
80
|
+
const targetDoctrine = path.join(targetIris, "aidlc");
|
|
81
|
+
const targetTools = path.join(targetIris, "tools");
|
|
82
|
+
// Step C: Install IRIS core
|
|
83
|
+
ensureDir(targetIris);
|
|
84
|
+
// 1. Doctrine
|
|
85
|
+
let backupPath = "";
|
|
86
|
+
if (fs.existsSync(targetDoctrine)) {
|
|
87
|
+
const { overwrite } = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: "confirm",
|
|
90
|
+
name: "overwrite",
|
|
91
|
+
message: "IRIS doctrine (.iris/aidlc) already exists. Overwrite with fresh bundle?",
|
|
92
|
+
default: false
|
|
93
|
+
}
|
|
94
|
+
]);
|
|
95
|
+
if (overwrite) {
|
|
96
|
+
backupPath = `${targetDoctrine}.backup.${Date.now()}`;
|
|
97
|
+
fs.renameSync(targetDoctrine, backupPath);
|
|
98
|
+
console.log(kleur.yellow(`Backed up existing doctrine to ${path.relative(root, backupPath)}`));
|
|
99
|
+
copyDirRec(bundleDoctrine, targetDoctrine, installedPaths, root); // copy new
|
|
100
|
+
}
|
|
101
|
+
// else keep existing
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
copyDirRec(bundleDoctrine, targetDoctrine, installedPaths, root);
|
|
105
|
+
}
|
|
106
|
+
// 2. Policy & Routes (Overwrite or keep? Usually overwrite core config if missing, but maybe prompt if exists?)
|
|
107
|
+
// Spec says: "Install .iris/policy.yaml... .iris/routes.yaml"
|
|
108
|
+
// Let's safe install: only if missing? Or overwrite?
|
|
109
|
+
// Spec doesn't explicitly say prompt for policy.
|
|
110
|
+
// "Install IRIS core... .iris/policy.yaml"
|
|
111
|
+
// Let's assume non-destructive for config if it exists, to preserve user edits.
|
|
112
|
+
safeCopy(path.join(bundleIris, "policy.yaml"), path.join(targetIris, "policy.yaml"), installedPaths, root);
|
|
113
|
+
safeCopy(path.join(bundleIris, "routes.yaml"), path.join(targetIris, "routes.yaml"), installedPaths, root);
|
|
114
|
+
// 3. State (only if missing)
|
|
115
|
+
if (!fs.existsSync(path.join(targetIris, "state.yaml"))) {
|
|
116
|
+
fs.copyFileSync(path.join(bundleIris, "state.yaml"), path.join(targetIris, "state.yaml"));
|
|
117
|
+
// Don't track state in manifest as "installed" usually, as it changes?
|
|
118
|
+
// Or do track it so we know we created it? "remove only paths that were installed"
|
|
119
|
+
// If we track it, uninstall might delete it. Good.
|
|
120
|
+
installedPaths.push({
|
|
121
|
+
path: ".iris/state.yaml",
|
|
122
|
+
type: "file",
|
|
123
|
+
status: "installed"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Step D: Memory Bank Skeleton
|
|
127
|
+
const memoryDirs = ["intents", "units", "bolts", "standards", "logs"];
|
|
128
|
+
for (const d of memoryDirs) {
|
|
129
|
+
const p = path.join(root, "memory-bank", d);
|
|
130
|
+
if (!fs.existsSync(p)) {
|
|
131
|
+
ensureDir(p);
|
|
132
|
+
createdDirs.push(path.relative(root, p));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Step E: Install Tool Wrappers & F: Mirror
|
|
136
|
+
for (const t of tools) {
|
|
137
|
+
const toolBundlePath = path.join(bundleTools, t);
|
|
138
|
+
if (!fs.existsSync(toolBundlePath)) {
|
|
139
|
+
console.warn(kleur.yellow(`Warning: No bundle found for tool '${t}' at ${toolBundlePath}`));
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Mirror first (always safe/overwrite in .iris/tools)
|
|
143
|
+
const mirrorPath = path.join(targetTools, t);
|
|
144
|
+
ensureDir(mirrorPath);
|
|
145
|
+
// We copy the WHOLE bundles content for that tool into mirror.
|
|
146
|
+
// Actually, we should mirror what we *attempt* to install.
|
|
147
|
+
// Spec: "mirror exactly what was installed (and skipped) into .iris/tools/<tool>"
|
|
148
|
+
// "still mirror the bundle version ... and record skipped_existing"
|
|
149
|
+
// So: copy bundle -> mirror.
|
|
150
|
+
copyDirRec(toolBundlePath, mirrorPath, [], root, true); // Don't track mirror files in manifest individually? Or do we?
|
|
151
|
+
// "mirror installed tool payloads... create but only filled for selected tools"
|
|
152
|
+
// Uninstall: "Remove .iris/tools/<tool> mirrors ... Always safe"
|
|
153
|
+
// So we don't strictly need to track every file inside mirror in manifest, just the tool selection implies it.
|
|
154
|
+
// Install to Repo Root
|
|
155
|
+
await installToolWrapper(t, toolBundlePath, root, installedPaths);
|
|
156
|
+
}
|
|
157
|
+
// Step G: Update Manifest
|
|
158
|
+
updateManifest({
|
|
159
|
+
tools_selected: tools,
|
|
160
|
+
paths_installed: installedPaths,
|
|
161
|
+
created_dirs: createdDirs,
|
|
162
|
+
doctrine_path: ".iris/aidlc",
|
|
163
|
+
policy_path: ".iris/policy.yaml"
|
|
164
|
+
});
|
|
165
|
+
// Step H: Run npm install to generate lockfile
|
|
166
|
+
await runNpmInstall(root);
|
|
167
|
+
}
|
|
168
|
+
async function installToolWrapper(tool, srcDir, root, installedPaths) {
|
|
169
|
+
// We need to walk the srcDir and copy to root, respecting collisions.
|
|
170
|
+
// For Claude: srcDir is .../tools/claude. contains .claude/...
|
|
171
|
+
// So we just copy * recursively from srcDir to root.
|
|
172
|
+
// Check for collisions first?
|
|
173
|
+
// Recursive walker needed.
|
|
174
|
+
const entries = getAllFiles(srcDir);
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
const relPath = path.relative(srcDir, entry);
|
|
177
|
+
const targetPath = path.join(root, relPath);
|
|
178
|
+
if (fs.existsSync(targetPath)) {
|
|
179
|
+
// Prompt? "Prompt per tool: File exists... Overwrite?"
|
|
180
|
+
// Actually prompting check-per-file might be spammy.
|
|
181
|
+
// "Prompt per tool: File exists... Overwrite?" -> implies one prompt per file clash?
|
|
182
|
+
// "If a target file exists... Prompt per tool (maybe once?)... No, spec says: 'Prompt per tool: "File exists: <path>. Overwrite? (y/N)"'"
|
|
183
|
+
// This looks like per-file.
|
|
184
|
+
// Let's implement per-file prompt for safety.
|
|
185
|
+
// Check content difference!
|
|
186
|
+
if (areFilesEqual(entry, targetPath)) {
|
|
187
|
+
// Identical, skip prompt, mark installed (or skipped? effectively same)
|
|
188
|
+
installedPaths.push({ path: relPath, type: "file", status: "installed" });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const { overwrite } = await inquirer.prompt([
|
|
192
|
+
{
|
|
193
|
+
type: "confirm",
|
|
194
|
+
name: "overwrite",
|
|
195
|
+
message: `File exists: ${relPath}. Overwrite?`,
|
|
196
|
+
default: false
|
|
197
|
+
}
|
|
198
|
+
]);
|
|
199
|
+
if (overwrite) {
|
|
200
|
+
ensureDir(path.dirname(targetPath));
|
|
201
|
+
fs.copyFileSync(entry, targetPath);
|
|
202
|
+
installedPaths.push({ path: relPath, type: "file", status: "overwritten" });
|
|
203
|
+
console.log(kleur.green(`Overwrote ${relPath}`));
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(kleur.gray(`Skipped ${relPath}`));
|
|
207
|
+
installedPaths.push({ path: relPath, type: "file", status: "skipped_existing" });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
ensureDir(path.dirname(targetPath));
|
|
212
|
+
fs.copyFileSync(entry, targetPath);
|
|
213
|
+
installedPaths.push({ path: relPath, type: "file", status: "installed" });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Helpers
|
|
218
|
+
function getAllFiles(dir, fileList = []) {
|
|
219
|
+
const files = fs.readdirSync(dir);
|
|
220
|
+
files.forEach(file => {
|
|
221
|
+
const filePath = path.join(dir, file);
|
|
222
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
223
|
+
getAllFiles(filePath, fileList);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
fileList.push(filePath);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
return fileList;
|
|
230
|
+
}
|
|
231
|
+
function copyDirRec(src, dest, trackList, root, silentTrack = false) {
|
|
232
|
+
ensureDir(dest);
|
|
233
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
const srcPath = path.join(src, entry.name);
|
|
236
|
+
const destPath = path.join(dest, entry.name);
|
|
237
|
+
if (entry.isDirectory()) {
|
|
238
|
+
copyDirRec(srcPath, destPath, trackList, root, silentTrack);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
fs.copyFileSync(srcPath, destPath);
|
|
242
|
+
if (!silentTrack) {
|
|
243
|
+
trackList.push({
|
|
244
|
+
path: path.relative(root, destPath),
|
|
245
|
+
type: "file",
|
|
246
|
+
status: "installed"
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function safeCopy(src, dest, trackList, root) {
|
|
253
|
+
if (fs.existsSync(dest))
|
|
254
|
+
return; // Skip if exists
|
|
255
|
+
if (!fs.existsSync(src))
|
|
256
|
+
return; // Skip if source missing
|
|
257
|
+
ensureDir(path.dirname(dest));
|
|
258
|
+
fs.copyFileSync(src, dest);
|
|
259
|
+
trackList.push({
|
|
260
|
+
path: path.relative(root, dest),
|
|
261
|
+
type: "file",
|
|
262
|
+
status: "installed"
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function areFilesEqual(a, b) {
|
|
266
|
+
const bufA = fs.readFileSync(a);
|
|
267
|
+
const bufB = fs.readFileSync(b);
|
|
268
|
+
return bufA.equals(bufB);
|
|
269
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { writeFile } from "../lib.js";
|
|
5
|
+
const MANIFEST_PATH = path.join(process.cwd(), ".iris/manifest.yaml");
|
|
6
|
+
export function loadManifest() {
|
|
7
|
+
if (!fs.existsSync(MANIFEST_PATH))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const doc = yaml.load(fs.readFileSync(MANIFEST_PATH, "utf8"));
|
|
11
|
+
// Basic migration/defaulting if needed
|
|
12
|
+
return {
|
|
13
|
+
version: 1,
|
|
14
|
+
installed_at: new Date().toISOString(),
|
|
15
|
+
tools_selected: [],
|
|
16
|
+
paths_installed: [],
|
|
17
|
+
created_dirs: [],
|
|
18
|
+
...doc
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
console.error("Failed to parse manifest:", e);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function saveManifest(manifest) {
|
|
27
|
+
writeFile(MANIFEST_PATH, yaml.dump(manifest));
|
|
28
|
+
}
|
|
29
|
+
export function updateManifest(updates) {
|
|
30
|
+
const current = loadManifest() || {
|
|
31
|
+
version: 1,
|
|
32
|
+
installed_at: new Date().toISOString(),
|
|
33
|
+
tools_selected: [],
|
|
34
|
+
paths_installed: [],
|
|
35
|
+
created_dirs: [],
|
|
36
|
+
};
|
|
37
|
+
const updated = {
|
|
38
|
+
...current,
|
|
39
|
+
...updates,
|
|
40
|
+
installed_at: new Date().toISOString(), // Always update timestamp
|
|
41
|
+
tools_selected: Array.from(new Set([...current.tools_selected, ...(updates.tools_selected || [])])),
|
|
42
|
+
paths_installed: mergePaths(current.paths_installed, updates.paths_installed || []),
|
|
43
|
+
created_dirs: Array.from(new Set([...current.created_dirs, ...(updates.created_dirs || [])])),
|
|
44
|
+
};
|
|
45
|
+
saveManifest(updated);
|
|
46
|
+
}
|
|
47
|
+
function mergePaths(current, updates) {
|
|
48
|
+
// Map current paths by path string for easy update
|
|
49
|
+
const map = new Map();
|
|
50
|
+
current.forEach(p => map.set(p.path, p));
|
|
51
|
+
// Apply updates
|
|
52
|
+
updates.forEach(p => map.set(p.path, p));
|
|
53
|
+
return Array.from(map.values());
|
|
54
|
+
}
|