@c-d-cc/reap 0.6.1 → 0.7.1
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/dist/cli.js
CHANGED
|
@@ -31,6 +31,20 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
31
31
|
return to;
|
|
32
32
|
};
|
|
33
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
38
|
+
var __export = (target, all) => {
|
|
39
|
+
for (var name in all)
|
|
40
|
+
__defProp(target, name, {
|
|
41
|
+
get: all[name],
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
34
48
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
35
49
|
|
|
36
50
|
// node_modules/commander/lib/error.js
|
|
@@ -8823,6 +8837,311 @@ var require_dist = __commonJS((exports) => {
|
|
|
8823
8837
|
exports.visitAsync = visit.visitAsync;
|
|
8824
8838
|
});
|
|
8825
8839
|
|
|
8840
|
+
// src/core/fs.ts
|
|
8841
|
+
var exports_fs = {};
|
|
8842
|
+
__export(exports_fs, {
|
|
8843
|
+
writeTextFile: () => writeTextFile,
|
|
8844
|
+
readTextFileOrThrow: () => readTextFileOrThrow,
|
|
8845
|
+
readTextFile: () => readTextFile,
|
|
8846
|
+
fileExists: () => fileExists
|
|
8847
|
+
});
|
|
8848
|
+
import { readFile, writeFile, access } from "fs/promises";
|
|
8849
|
+
async function readTextFile(path) {
|
|
8850
|
+
try {
|
|
8851
|
+
return await readFile(path, "utf-8");
|
|
8852
|
+
} catch {
|
|
8853
|
+
return null;
|
|
8854
|
+
}
|
|
8855
|
+
}
|
|
8856
|
+
async function readTextFileOrThrow(path) {
|
|
8857
|
+
return await readFile(path, "utf-8");
|
|
8858
|
+
}
|
|
8859
|
+
async function writeTextFile(path, content) {
|
|
8860
|
+
await writeFile(path, content, "utf-8");
|
|
8861
|
+
}
|
|
8862
|
+
async function fileExists(path) {
|
|
8863
|
+
try {
|
|
8864
|
+
await access(path);
|
|
8865
|
+
return true;
|
|
8866
|
+
} catch {
|
|
8867
|
+
return false;
|
|
8868
|
+
}
|
|
8869
|
+
}
|
|
8870
|
+
var init_fs = () => {};
|
|
8871
|
+
|
|
8872
|
+
// src/core/genome-sync.ts
|
|
8873
|
+
var exports_genome_sync = {};
|
|
8874
|
+
__export(exports_genome_sync, {
|
|
8875
|
+
syncGenomeFromProject: () => syncGenomeFromProject
|
|
8876
|
+
});
|
|
8877
|
+
import { join as join4 } from "path";
|
|
8878
|
+
import { readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
8879
|
+
async function scanProject(projectRoot) {
|
|
8880
|
+
const scan = {
|
|
8881
|
+
language: "Unknown",
|
|
8882
|
+
runtime: "Unknown",
|
|
8883
|
+
framework: "None",
|
|
8884
|
+
packageManager: "Unknown",
|
|
8885
|
+
testFramework: "None",
|
|
8886
|
+
linter: "None",
|
|
8887
|
+
formatter: "None",
|
|
8888
|
+
buildTool: "None",
|
|
8889
|
+
scripts: {},
|
|
8890
|
+
dependencies: [],
|
|
8891
|
+
devDependencies: [],
|
|
8892
|
+
hasTypeScript: false,
|
|
8893
|
+
hasDocker: false,
|
|
8894
|
+
directories: [],
|
|
8895
|
+
existingDocs: []
|
|
8896
|
+
};
|
|
8897
|
+
const pkgContent = await readTextFile(join4(projectRoot, "package.json"));
|
|
8898
|
+
if (pkgContent) {
|
|
8899
|
+
try {
|
|
8900
|
+
const pkg = JSON.parse(pkgContent);
|
|
8901
|
+
scan.dependencies = Object.keys(pkg.dependencies ?? {});
|
|
8902
|
+
scan.devDependencies = Object.keys(pkg.devDependencies ?? {});
|
|
8903
|
+
scan.scripts = pkg.scripts ?? {};
|
|
8904
|
+
scan.packageManager = pkg.packageManager?.startsWith("bun") ? "Bun" : pkg.packageManager?.startsWith("pnpm") ? "pnpm" : pkg.packageManager?.startsWith("yarn") ? "Yarn" : "npm";
|
|
8905
|
+
scan.language = "JavaScript";
|
|
8906
|
+
scan.runtime = "Node.js";
|
|
8907
|
+
const allDeps = [...scan.dependencies, ...scan.devDependencies];
|
|
8908
|
+
if (allDeps.includes("next"))
|
|
8909
|
+
scan.framework = "Next.js";
|
|
8910
|
+
else if (allDeps.includes("nuxt"))
|
|
8911
|
+
scan.framework = "Nuxt";
|
|
8912
|
+
else if (allDeps.includes("react"))
|
|
8913
|
+
scan.framework = "React";
|
|
8914
|
+
else if (allDeps.includes("vue"))
|
|
8915
|
+
scan.framework = "Vue";
|
|
8916
|
+
else if (allDeps.includes("svelte") || allDeps.includes("@sveltejs/kit"))
|
|
8917
|
+
scan.framework = "Svelte/SvelteKit";
|
|
8918
|
+
else if (allDeps.includes("express"))
|
|
8919
|
+
scan.framework = "Express";
|
|
8920
|
+
else if (allDeps.includes("hono"))
|
|
8921
|
+
scan.framework = "Hono";
|
|
8922
|
+
else if (allDeps.includes("fastify"))
|
|
8923
|
+
scan.framework = "Fastify";
|
|
8924
|
+
else if (allDeps.includes("nestjs") || allDeps.includes("@nestjs/core"))
|
|
8925
|
+
scan.framework = "NestJS";
|
|
8926
|
+
else if (allDeps.includes("astro"))
|
|
8927
|
+
scan.framework = "Astro";
|
|
8928
|
+
if (allDeps.includes("vitest"))
|
|
8929
|
+
scan.testFramework = "Vitest";
|
|
8930
|
+
else if (allDeps.includes("jest"))
|
|
8931
|
+
scan.testFramework = "Jest";
|
|
8932
|
+
else if (allDeps.includes("mocha"))
|
|
8933
|
+
scan.testFramework = "Mocha";
|
|
8934
|
+
else if (scan.scripts.test?.includes("bun test"))
|
|
8935
|
+
scan.testFramework = "Bun Test";
|
|
8936
|
+
if (allDeps.includes("eslint"))
|
|
8937
|
+
scan.linter = "ESLint";
|
|
8938
|
+
else if (allDeps.includes("biome") || allDeps.includes("@biomejs/biome"))
|
|
8939
|
+
scan.linter = "Biome";
|
|
8940
|
+
if (allDeps.includes("prettier"))
|
|
8941
|
+
scan.formatter = "Prettier";
|
|
8942
|
+
else if (allDeps.includes("biome") || allDeps.includes("@biomejs/biome"))
|
|
8943
|
+
scan.formatter = "Biome";
|
|
8944
|
+
if (allDeps.includes("vite"))
|
|
8945
|
+
scan.buildTool = "Vite";
|
|
8946
|
+
else if (allDeps.includes("webpack"))
|
|
8947
|
+
scan.buildTool = "Webpack";
|
|
8948
|
+
else if (allDeps.includes("esbuild"))
|
|
8949
|
+
scan.buildTool = "esbuild";
|
|
8950
|
+
else if (allDeps.includes("rollup"))
|
|
8951
|
+
scan.buildTool = "Rollup";
|
|
8952
|
+
else if (allDeps.includes("turbo") || allDeps.includes("turbopack"))
|
|
8953
|
+
scan.buildTool = "Turbopack";
|
|
8954
|
+
} catch {}
|
|
8955
|
+
}
|
|
8956
|
+
if (await fileExists(join4(projectRoot, "go.mod"))) {
|
|
8957
|
+
scan.language = "Go";
|
|
8958
|
+
scan.runtime = "Go";
|
|
8959
|
+
scan.packageManager = "Go Modules";
|
|
8960
|
+
}
|
|
8961
|
+
if (await fileExists(join4(projectRoot, "pyproject.toml")) || await fileExists(join4(projectRoot, "requirements.txt"))) {
|
|
8962
|
+
scan.language = "Python";
|
|
8963
|
+
scan.runtime = "Python";
|
|
8964
|
+
if (await fileExists(join4(projectRoot, "pyproject.toml"))) {
|
|
8965
|
+
const pyproject = await readTextFile(join4(projectRoot, "pyproject.toml"));
|
|
8966
|
+
if (pyproject?.includes("[tool.poetry]"))
|
|
8967
|
+
scan.packageManager = "Poetry";
|
|
8968
|
+
else if (pyproject?.includes("[tool.uv]") || pyproject?.includes("[project]"))
|
|
8969
|
+
scan.packageManager = "uv/pip";
|
|
8970
|
+
if (pyproject?.includes("pytest"))
|
|
8971
|
+
scan.testFramework = "pytest";
|
|
8972
|
+
if (pyproject?.includes("django"))
|
|
8973
|
+
scan.framework = "Django";
|
|
8974
|
+
else if (pyproject?.includes("fastapi"))
|
|
8975
|
+
scan.framework = "FastAPI";
|
|
8976
|
+
else if (pyproject?.includes("flask"))
|
|
8977
|
+
scan.framework = "Flask";
|
|
8978
|
+
}
|
|
8979
|
+
}
|
|
8980
|
+
if (await fileExists(join4(projectRoot, "Cargo.toml"))) {
|
|
8981
|
+
scan.language = "Rust";
|
|
8982
|
+
scan.runtime = "Rust";
|
|
8983
|
+
scan.packageManager = "Cargo";
|
|
8984
|
+
}
|
|
8985
|
+
if (await fileExists(join4(projectRoot, "tsconfig.json"))) {
|
|
8986
|
+
scan.hasTypeScript = true;
|
|
8987
|
+
scan.language = "TypeScript";
|
|
8988
|
+
}
|
|
8989
|
+
scan.hasDocker = await fileExists(join4(projectRoot, "Dockerfile")) || await fileExists(join4(projectRoot, "docker-compose.yml"));
|
|
8990
|
+
try {
|
|
8991
|
+
const entries = await readdir3(projectRoot);
|
|
8992
|
+
for (const entry of entries) {
|
|
8993
|
+
if (entry.startsWith(".") || entry === "node_modules")
|
|
8994
|
+
continue;
|
|
8995
|
+
try {
|
|
8996
|
+
const s = await stat2(join4(projectRoot, entry));
|
|
8997
|
+
if (s.isDirectory())
|
|
8998
|
+
scan.directories.push(entry);
|
|
8999
|
+
} catch {}
|
|
9000
|
+
}
|
|
9001
|
+
} catch {}
|
|
9002
|
+
const docFiles = ["README.md", "CLAUDE.md", "AGENTS.md", "CONTRIBUTING.md", "ARCHITECTURE.md"];
|
|
9003
|
+
for (const file of docFiles) {
|
|
9004
|
+
const content = await readTextFile(join4(projectRoot, file));
|
|
9005
|
+
if (content) {
|
|
9006
|
+
scan.existingDocs.push({ file, content: content.substring(0, 2000) });
|
|
9007
|
+
}
|
|
9008
|
+
}
|
|
9009
|
+
return scan;
|
|
9010
|
+
}
|
|
9011
|
+
function generateConstraints(scan) {
|
|
9012
|
+
const lines = [
|
|
9013
|
+
"# Technical Constraints",
|
|
9014
|
+
"",
|
|
9015
|
+
"> Auto-generated by `reap init` from project scan. Refine during the first Generation's Objective stage.",
|
|
9016
|
+
"",
|
|
9017
|
+
"## Tech Stack",
|
|
9018
|
+
"",
|
|
9019
|
+
`- **Language**: ${scan.language}${scan.hasTypeScript ? " (TypeScript)" : ""}`,
|
|
9020
|
+
`- **Runtime**: ${scan.runtime}`
|
|
9021
|
+
];
|
|
9022
|
+
if (scan.framework !== "None")
|
|
9023
|
+
lines.push(`- **Framework**: ${scan.framework}`);
|
|
9024
|
+
if (scan.packageManager !== "Unknown")
|
|
9025
|
+
lines.push(`- **Package Manager**: ${scan.packageManager}`);
|
|
9026
|
+
if (scan.buildTool !== "None")
|
|
9027
|
+
lines.push(`- **Build Tool**: ${scan.buildTool}`);
|
|
9028
|
+
lines.push("", "## Constraints", "");
|
|
9029
|
+
if (scan.hasDocker)
|
|
9030
|
+
lines.push("- Docker containerized deployment");
|
|
9031
|
+
if (scan.hasTypeScript)
|
|
9032
|
+
lines.push("- TypeScript strict mode (check tsconfig.json for details)");
|
|
9033
|
+
lines.push("", "## Validation Commands", "", "| Purpose | Command | Description |", "|---------|---------|-------------|");
|
|
9034
|
+
if (scan.testFramework !== "None") {
|
|
9035
|
+
const testCmd = scan.scripts.test ?? `${scan.testFramework.toLowerCase()} test`;
|
|
9036
|
+
lines.push(`| Test | \`${testCmd}\` | Run tests |`);
|
|
9037
|
+
}
|
|
9038
|
+
if (scan.scripts.lint)
|
|
9039
|
+
lines.push(`| Lint | \`${scan.scripts.lint.includes("npm") ? "npm run lint" : scan.scripts.lint}\` | Lint check |`);
|
|
9040
|
+
else if (scan.scripts.lint)
|
|
9041
|
+
lines.push(`| Lint | \`npm run lint\` | Lint check |`);
|
|
9042
|
+
if (scan.scripts.build)
|
|
9043
|
+
lines.push(`| Build | \`npm run build\` | Build project |`);
|
|
9044
|
+
if (scan.hasTypeScript)
|
|
9045
|
+
lines.push("| Type check | `npx tsc --noEmit` | TypeScript compile check |");
|
|
9046
|
+
lines.push("", "## External Dependencies", "", "- (review and fill in during first generation)");
|
|
9047
|
+
return lines.join(`
|
|
9048
|
+
`) + `
|
|
9049
|
+
`;
|
|
9050
|
+
}
|
|
9051
|
+
function generateConventions(scan) {
|
|
9052
|
+
const lines = [
|
|
9053
|
+
"# Development Conventions",
|
|
9054
|
+
"",
|
|
9055
|
+
"> Auto-generated by `reap init` from project scan. Refine during the first Generation's Objective stage.",
|
|
9056
|
+
"",
|
|
9057
|
+
"## Code Style",
|
|
9058
|
+
""
|
|
9059
|
+
];
|
|
9060
|
+
if (scan.linter !== "None")
|
|
9061
|
+
lines.push(`- Linter: ${scan.linter}`);
|
|
9062
|
+
if (scan.formatter !== "None")
|
|
9063
|
+
lines.push(`- Formatter: ${scan.formatter}`);
|
|
9064
|
+
if (scan.hasTypeScript)
|
|
9065
|
+
lines.push("- TypeScript strict mode");
|
|
9066
|
+
lines.push("", "## Naming Conventions", "", "- (review and fill in during first generation)");
|
|
9067
|
+
lines.push("", "## Git Conventions", "", "- (review and fill in during first generation)");
|
|
9068
|
+
if (scan.testFramework !== "None") {
|
|
9069
|
+
lines.push("", "## Testing", "", `- Test framework: ${scan.testFramework}`);
|
|
9070
|
+
}
|
|
9071
|
+
lines.push("", "## Enforced Rules", "", "| Rule | Tool | Command |", "|------|------|---------|");
|
|
9072
|
+
if (scan.scripts.test)
|
|
9073
|
+
lines.push(`| Tests pass | ${scan.testFramework} | \`npm test\` |`);
|
|
9074
|
+
if (scan.scripts.lint)
|
|
9075
|
+
lines.push(`| Lint clean | ${scan.linter} | \`npm run lint\` |`);
|
|
9076
|
+
if (scan.scripts.build)
|
|
9077
|
+
lines.push("| Build succeeds | Build | `npm run build` |");
|
|
9078
|
+
if (scan.hasTypeScript)
|
|
9079
|
+
lines.push("| TypeScript compiles | tsc | `npx tsc --noEmit` |");
|
|
9080
|
+
return lines.join(`
|
|
9081
|
+
`) + `
|
|
9082
|
+
`;
|
|
9083
|
+
}
|
|
9084
|
+
function generatePrinciples(scan) {
|
|
9085
|
+
const lines = [
|
|
9086
|
+
"# Architecture Principles",
|
|
9087
|
+
"",
|
|
9088
|
+
"> Auto-generated by `reap init` from project scan. Refine during the first Generation's Objective stage.",
|
|
9089
|
+
"",
|
|
9090
|
+
"## Core Beliefs",
|
|
9091
|
+
"",
|
|
9092
|
+
"- (define your project's core architectural beliefs during the first generation)",
|
|
9093
|
+
"",
|
|
9094
|
+
"## Architecture Decisions",
|
|
9095
|
+
"",
|
|
9096
|
+
"| ID | Decision | Rationale | Date |",
|
|
9097
|
+
"|----|----------|-----------|------|"
|
|
9098
|
+
];
|
|
9099
|
+
if (scan.framework !== "None")
|
|
9100
|
+
lines.push(`| ADR-001 | ${scan.framework} | Detected from project dependencies | Auto |`);
|
|
9101
|
+
if (scan.hasTypeScript)
|
|
9102
|
+
lines.push(`| ADR-002 | TypeScript | Type safety | Auto |`);
|
|
9103
|
+
lines.push("", "## Source Map", "", "→ `genome/source-map.md`");
|
|
9104
|
+
return lines.join(`
|
|
9105
|
+
`) + `
|
|
9106
|
+
`;
|
|
9107
|
+
}
|
|
9108
|
+
function generateSourceMap(scan) {
|
|
9109
|
+
const lines = [
|
|
9110
|
+
"# Source Map",
|
|
9111
|
+
"",
|
|
9112
|
+
"> Auto-generated by `reap init` from project scan. Refine during the first Generation's Objective stage.",
|
|
9113
|
+
"",
|
|
9114
|
+
"## Project Structure",
|
|
9115
|
+
"",
|
|
9116
|
+
"```"
|
|
9117
|
+
];
|
|
9118
|
+
for (const dir of scan.directories.sort()) {
|
|
9119
|
+
lines.push(`${dir}/`);
|
|
9120
|
+
}
|
|
9121
|
+
lines.push("```");
|
|
9122
|
+
return lines.join(`
|
|
9123
|
+
`) + `
|
|
9124
|
+
`;
|
|
9125
|
+
}
|
|
9126
|
+
async function syncGenomeFromProject(projectRoot, genomePath, onProgress) {
|
|
9127
|
+
const log = onProgress ?? (() => {});
|
|
9128
|
+
log("Scanning project structure...");
|
|
9129
|
+
const scan = await scanProject(projectRoot);
|
|
9130
|
+
log(`Detected: ${scan.language}, ${scan.framework !== "None" ? scan.framework : "no framework"}, ${scan.packageManager}`);
|
|
9131
|
+
const { writeTextFile: writeTextFile2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
|
|
9132
|
+
log("Generating constraints.md...");
|
|
9133
|
+
await writeTextFile2(join4(genomePath, "constraints.md"), generateConstraints(scan));
|
|
9134
|
+
log("Generating conventions.md...");
|
|
9135
|
+
await writeTextFile2(join4(genomePath, "conventions.md"), generateConventions(scan));
|
|
9136
|
+
log("Generating principles.md...");
|
|
9137
|
+
await writeTextFile2(join4(genomePath, "principles.md"), generatePrinciples(scan));
|
|
9138
|
+
log("Generating source-map.md...");
|
|
9139
|
+
await writeTextFile2(join4(genomePath, "source-map.md"), generateSourceMap(scan));
|
|
9140
|
+
}
|
|
9141
|
+
var init_genome_sync = __esm(() => {
|
|
9142
|
+
init_fs();
|
|
9143
|
+
});
|
|
9144
|
+
|
|
8826
9145
|
// node_modules/commander/esm.mjs
|
|
8827
9146
|
var import__ = __toESM(require_commander(), 1);
|
|
8828
9147
|
var {
|
|
@@ -8840,8 +9159,8 @@ var {
|
|
|
8840
9159
|
} = import__.default;
|
|
8841
9160
|
|
|
8842
9161
|
// src/cli/commands/init.ts
|
|
8843
|
-
import { mkdir as mkdir3, readdir as
|
|
8844
|
-
import { join as
|
|
9162
|
+
import { mkdir as mkdir3, readdir as readdir4, chmod } from "fs/promises";
|
|
9163
|
+
import { join as join5 } from "path";
|
|
8845
9164
|
|
|
8846
9165
|
// src/core/paths.ts
|
|
8847
9166
|
import { join, dirname } from "path";
|
|
@@ -8918,6 +9237,9 @@ class ReapPaths {
|
|
|
8918
9237
|
static get userReapTemplates() {
|
|
8919
9238
|
return join(ReapPaths.userReapDir, "templates");
|
|
8920
9239
|
}
|
|
9240
|
+
static get userReapCommands() {
|
|
9241
|
+
return join(ReapPaths.userReapDir, "commands");
|
|
9242
|
+
}
|
|
8921
9243
|
static get packageTemplatesDir() {
|
|
8922
9244
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
8923
9245
|
const distPath = join(dir, "templates");
|
|
@@ -8963,33 +9285,9 @@ class ReapPaths {
|
|
|
8963
9285
|
}
|
|
8964
9286
|
|
|
8965
9287
|
// src/core/config.ts
|
|
9288
|
+
init_fs();
|
|
8966
9289
|
var import_yaml = __toESM(require_dist(), 1);
|
|
8967
9290
|
|
|
8968
|
-
// src/core/fs.ts
|
|
8969
|
-
import { readFile, writeFile, access } from "fs/promises";
|
|
8970
|
-
async function readTextFile(path) {
|
|
8971
|
-
try {
|
|
8972
|
-
return await readFile(path, "utf-8");
|
|
8973
|
-
} catch {
|
|
8974
|
-
return null;
|
|
8975
|
-
}
|
|
8976
|
-
}
|
|
8977
|
-
async function readTextFileOrThrow(path) {
|
|
8978
|
-
return await readFile(path, "utf-8");
|
|
8979
|
-
}
|
|
8980
|
-
async function writeTextFile(path, content) {
|
|
8981
|
-
await writeFile(path, content, "utf-8");
|
|
8982
|
-
}
|
|
8983
|
-
async function fileExists(path) {
|
|
8984
|
-
try {
|
|
8985
|
-
await access(path);
|
|
8986
|
-
return true;
|
|
8987
|
-
} catch {
|
|
8988
|
-
return false;
|
|
8989
|
-
}
|
|
8990
|
-
}
|
|
8991
|
-
|
|
8992
|
-
// src/core/config.ts
|
|
8993
9291
|
class ConfigManager {
|
|
8994
9292
|
static async read(paths) {
|
|
8995
9293
|
const content = await readTextFileOrThrow(paths.config);
|
|
@@ -9014,6 +9312,7 @@ class ConfigManager {
|
|
|
9014
9312
|
}
|
|
9015
9313
|
|
|
9016
9314
|
// src/core/agents/claude-code.ts
|
|
9315
|
+
init_fs();
|
|
9017
9316
|
import { join as join2 } from "path";
|
|
9018
9317
|
import { homedir as homedir2 } from "os";
|
|
9019
9318
|
import { mkdir, readdir, unlink } from "fs/promises";
|
|
@@ -9045,12 +9344,22 @@ class ClaudeCodeAdapter {
|
|
|
9045
9344
|
return this.commandsDir;
|
|
9046
9345
|
}
|
|
9047
9346
|
async installCommands(commandNames, sourceDir) {
|
|
9048
|
-
await mkdir(
|
|
9347
|
+
await mkdir(ReapPaths.userReapCommands, { recursive: true });
|
|
9049
9348
|
for (const cmd of commandNames) {
|
|
9050
9349
|
const src = join2(sourceDir, `${cmd}.md`);
|
|
9051
|
-
const dest = join2(
|
|
9350
|
+
const dest = join2(ReapPaths.userReapCommands, `${cmd}.md`);
|
|
9052
9351
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9053
9352
|
}
|
|
9353
|
+
await mkdir(this.commandsDir, { recursive: true });
|
|
9354
|
+
for (const cmd of commandNames) {
|
|
9355
|
+
const dest = join2(this.commandsDir, `${cmd}.md`);
|
|
9356
|
+
const redirectContent = `---
|
|
9357
|
+
description: "REAP — redirected to ~/.reap/commands/"
|
|
9358
|
+
---
|
|
9359
|
+
Read \`~/.reap/commands/${cmd}.md\` and follow the instructions there.
|
|
9360
|
+
`;
|
|
9361
|
+
await writeTextFile(dest, redirectContent);
|
|
9362
|
+
}
|
|
9054
9363
|
}
|
|
9055
9364
|
async removeStaleCommands(validNames) {
|
|
9056
9365
|
try {
|
|
@@ -9229,6 +9538,7 @@ class ClaudeCodeAdapter {
|
|
|
9229
9538
|
}
|
|
9230
9539
|
|
|
9231
9540
|
// src/core/agents/opencode.ts
|
|
9541
|
+
init_fs();
|
|
9232
9542
|
import { join as join3 } from "path";
|
|
9233
9543
|
import { homedir as homedir3 } from "os";
|
|
9234
9544
|
import { mkdir as mkdir2, readdir as readdir2, unlink as unlink2 } from "fs/promises";
|
|
@@ -9265,12 +9575,22 @@ class OpenCodeAdapter {
|
|
|
9265
9575
|
return this.commandsDir;
|
|
9266
9576
|
}
|
|
9267
9577
|
async installCommands(commandNames, sourceDir) {
|
|
9268
|
-
await mkdir2(
|
|
9578
|
+
await mkdir2(ReapPaths.userReapCommands, { recursive: true });
|
|
9269
9579
|
for (const cmd of commandNames) {
|
|
9270
9580
|
const src = join3(sourceDir, `${cmd}.md`);
|
|
9271
|
-
const dest = join3(
|
|
9581
|
+
const dest = join3(ReapPaths.userReapCommands, `${cmd}.md`);
|
|
9272
9582
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9273
9583
|
}
|
|
9584
|
+
await mkdir2(this.commandsDir, { recursive: true });
|
|
9585
|
+
for (const cmd of commandNames) {
|
|
9586
|
+
const dest = join3(this.commandsDir, `${cmd}.md`);
|
|
9587
|
+
const redirectContent = `---
|
|
9588
|
+
description: "REAP — redirected to ~/.reap/commands/"
|
|
9589
|
+
---
|
|
9590
|
+
Read \`~/.reap/commands/${cmd}.md\` and follow the instructions there.
|
|
9591
|
+
`;
|
|
9592
|
+
await writeTextFile(dest, redirectContent);
|
|
9593
|
+
}
|
|
9274
9594
|
}
|
|
9275
9595
|
async removeStaleCommands(validNames) {
|
|
9276
9596
|
try {
|
|
@@ -9418,6 +9738,7 @@ class AgentRegistry {
|
|
|
9418
9738
|
}
|
|
9419
9739
|
|
|
9420
9740
|
// src/cli/commands/init.ts
|
|
9741
|
+
init_fs();
|
|
9421
9742
|
var COMMAND_NAMES = [
|
|
9422
9743
|
"reap.objective",
|
|
9423
9744
|
"reap.planning",
|
|
@@ -9451,8 +9772,8 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9451
9772
|
throw new Error(".reap/ already exists. This is already a REAP project.");
|
|
9452
9773
|
}
|
|
9453
9774
|
if (preset) {
|
|
9454
|
-
const presetDir =
|
|
9455
|
-
const presetExists = await fileExists(
|
|
9775
|
+
const presetDir = join5(ReapPaths.packageTemplatesDir, "presets", preset);
|
|
9776
|
+
const presetExists = await fileExists(join5(presetDir, "principles.md"));
|
|
9456
9777
|
if (!presetExists) {
|
|
9457
9778
|
throw new Error(`Unknown preset: "${preset}". Available presets: bun-hono-react`);
|
|
9458
9779
|
}
|
|
@@ -9474,54 +9795,59 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9474
9795
|
await ConfigManager.write(paths, config);
|
|
9475
9796
|
log("Setting up Genome templates...");
|
|
9476
9797
|
const genomeTemplates = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
|
|
9477
|
-
const genomeSourceDir = preset ?
|
|
9798
|
+
const genomeSourceDir = preset ? join5(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
|
|
9478
9799
|
for (const file of genomeTemplates) {
|
|
9479
|
-
const src =
|
|
9480
|
-
const dest =
|
|
9800
|
+
const src = join5(genomeSourceDir, file);
|
|
9801
|
+
const dest = join5(paths.genome, file);
|
|
9481
9802
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9482
9803
|
}
|
|
9804
|
+
if ((entryMode === "adoption" || entryMode === "migration") && !preset) {
|
|
9805
|
+
log("Scanning project for genome auto-sync...");
|
|
9806
|
+
const { syncGenomeFromProject: syncGenomeFromProject2 } = await Promise.resolve().then(() => (init_genome_sync(), exports_genome_sync));
|
|
9807
|
+
await syncGenomeFromProject2(projectRoot, paths.genome, log);
|
|
9808
|
+
}
|
|
9483
9809
|
log("Installing artifact templates...");
|
|
9484
9810
|
await mkdir3(ReapPaths.userReapTemplates, { recursive: true });
|
|
9485
9811
|
const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
|
|
9486
9812
|
for (const file of artifactFiles) {
|
|
9487
|
-
const src =
|
|
9488
|
-
const dest =
|
|
9813
|
+
const src = join5(ReapPaths.packageArtifactsDir, file);
|
|
9814
|
+
const dest = join5(ReapPaths.userReapTemplates, file);
|
|
9489
9815
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9490
9816
|
}
|
|
9491
|
-
const domainGuideSrc =
|
|
9492
|
-
const domainGuideDest =
|
|
9817
|
+
const domainGuideSrc = join5(ReapPaths.packageGenomeDir, "domain/README.md");
|
|
9818
|
+
const domainGuideDest = join5(ReapPaths.userReapTemplates, "domain-guide.md");
|
|
9493
9819
|
await writeTextFile(domainGuideDest, await readTextFileOrThrow(domainGuideSrc));
|
|
9494
|
-
const mergeTemplatesDir =
|
|
9820
|
+
const mergeTemplatesDir = join5(ReapPaths.userReapTemplates, "merge");
|
|
9495
9821
|
await mkdir3(mergeTemplatesDir, { recursive: true });
|
|
9496
9822
|
const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
|
|
9497
|
-
const mergeSourceDir =
|
|
9823
|
+
const mergeSourceDir = join5(ReapPaths.packageArtifactsDir, "merge");
|
|
9498
9824
|
for (const file of mergeArtifactFiles) {
|
|
9499
|
-
const src =
|
|
9500
|
-
const dest =
|
|
9825
|
+
const src = join5(mergeSourceDir, file);
|
|
9826
|
+
const dest = join5(mergeTemplatesDir, file);
|
|
9501
9827
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9502
9828
|
}
|
|
9503
9829
|
log("Installing brainstorm server...");
|
|
9504
|
-
const brainstormSourceDir =
|
|
9505
|
-
const brainstormDestDir =
|
|
9830
|
+
const brainstormSourceDir = join5(ReapPaths.packageTemplatesDir, "brainstorm");
|
|
9831
|
+
const brainstormDestDir = join5(paths.root, "brainstorm");
|
|
9506
9832
|
await mkdir3(brainstormDestDir, { recursive: true });
|
|
9507
9833
|
const brainstormFiles = ["server.cjs", "frame.html", "start-server.sh"];
|
|
9508
9834
|
for (const file of brainstormFiles) {
|
|
9509
|
-
const src =
|
|
9510
|
-
const dest =
|
|
9835
|
+
const src = join5(brainstormSourceDir, file);
|
|
9836
|
+
const dest = join5(brainstormDestDir, file);
|
|
9511
9837
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9512
9838
|
if (file.endsWith(".sh"))
|
|
9513
9839
|
await chmod(dest, 493);
|
|
9514
9840
|
}
|
|
9515
9841
|
log("Installing hook conditions...");
|
|
9516
|
-
const conditionsSourceDir =
|
|
9517
|
-
const conditionsDestDir =
|
|
9842
|
+
const conditionsSourceDir = join5(ReapPaths.packageTemplatesDir, "conditions");
|
|
9843
|
+
const conditionsDestDir = join5(paths.hooks, "conditions");
|
|
9518
9844
|
await mkdir3(conditionsDestDir, { recursive: true });
|
|
9519
|
-
const conditionFiles = await
|
|
9845
|
+
const conditionFiles = await readdir4(conditionsSourceDir);
|
|
9520
9846
|
for (const file of conditionFiles) {
|
|
9521
9847
|
if (!file.endsWith(".sh"))
|
|
9522
9848
|
continue;
|
|
9523
|
-
const src =
|
|
9524
|
-
const dest =
|
|
9849
|
+
const src = join5(conditionsSourceDir, file);
|
|
9850
|
+
const dest = join5(conditionsDestDir, file);
|
|
9525
9851
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9526
9852
|
await chmod(dest, 493);
|
|
9527
9853
|
}
|
|
@@ -9541,8 +9867,8 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9541
9867
|
}
|
|
9542
9868
|
|
|
9543
9869
|
// src/cli/commands/update.ts
|
|
9544
|
-
import { readdir as
|
|
9545
|
-
import { join as
|
|
9870
|
+
import { readdir as readdir9, unlink as unlink3, rm as rm2, mkdir as mkdir6, chmod as chmod2 } from "fs/promises";
|
|
9871
|
+
import { join as join10 } from "path";
|
|
9546
9872
|
|
|
9547
9873
|
// src/core/hooks.ts
|
|
9548
9874
|
async function migrateHooks(dryRun = false) {
|
|
@@ -9557,17 +9883,21 @@ async function migrateHooks(dryRun = false) {
|
|
|
9557
9883
|
return { results };
|
|
9558
9884
|
}
|
|
9559
9885
|
|
|
9886
|
+
// src/cli/commands/update.ts
|
|
9887
|
+
init_fs();
|
|
9888
|
+
|
|
9560
9889
|
// src/core/migration.ts
|
|
9890
|
+
init_fs();
|
|
9561
9891
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
9562
|
-
import { readdir as
|
|
9563
|
-
import { join as
|
|
9892
|
+
import { readdir as readdir8, rename as rename2 } from "fs/promises";
|
|
9893
|
+
import { join as join9 } from "path";
|
|
9564
9894
|
|
|
9565
9895
|
// src/core/generation.ts
|
|
9566
9896
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
9567
9897
|
import { createHash } from "crypto";
|
|
9568
9898
|
import { hostname } from "os";
|
|
9569
|
-
import { readdir as
|
|
9570
|
-
import { join as
|
|
9899
|
+
import { readdir as readdir7, mkdir as mkdir4, rename } from "fs/promises";
|
|
9900
|
+
import { join as join8 } from "path";
|
|
9571
9901
|
|
|
9572
9902
|
// src/types/index.ts
|
|
9573
9903
|
var LIFECYCLE_ORDER = [
|
|
@@ -9621,9 +9951,10 @@ class LifeCycle {
|
|
|
9621
9951
|
}
|
|
9622
9952
|
|
|
9623
9953
|
// src/core/compression.ts
|
|
9954
|
+
init_fs();
|
|
9624
9955
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
9625
|
-
import { readdir as
|
|
9626
|
-
import { join as
|
|
9956
|
+
import { readdir as readdir5, rm } from "fs/promises";
|
|
9957
|
+
import { join as join6 } from "path";
|
|
9627
9958
|
var LINEAGE_MAX_LINES = 5000;
|
|
9628
9959
|
var MIN_GENERATIONS_FOR_COMPRESSION = 5;
|
|
9629
9960
|
var LEVEL1_MAX_LINES = 40;
|
|
@@ -9660,9 +9991,9 @@ async function countLines(filePath) {
|
|
|
9660
9991
|
async function countDirLines(dirPath) {
|
|
9661
9992
|
let total = 0;
|
|
9662
9993
|
try {
|
|
9663
|
-
const entries = await
|
|
9994
|
+
const entries = await readdir5(dirPath, { withFileTypes: true });
|
|
9664
9995
|
for (const entry of entries) {
|
|
9665
|
-
const fullPath =
|
|
9996
|
+
const fullPath = join6(dirPath, entry.name);
|
|
9666
9997
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9667
9998
|
total += await countLines(fullPath);
|
|
9668
9999
|
} else if (entry.isDirectory()) {
|
|
@@ -9673,7 +10004,7 @@ async function countDirLines(dirPath) {
|
|
|
9673
10004
|
return total;
|
|
9674
10005
|
}
|
|
9675
10006
|
async function readDirMeta(dirPath) {
|
|
9676
|
-
const content = await readTextFile(
|
|
10007
|
+
const content = await readTextFile(join6(dirPath, "meta.yml"));
|
|
9677
10008
|
if (content === null)
|
|
9678
10009
|
return null;
|
|
9679
10010
|
try {
|
|
@@ -9691,9 +10022,9 @@ async function readFileMeta(filePath) {
|
|
|
9691
10022
|
async function scanLineage(paths) {
|
|
9692
10023
|
const entries = [];
|
|
9693
10024
|
try {
|
|
9694
|
-
const items = await
|
|
10025
|
+
const items = await readdir5(paths.lineage, { withFileTypes: true });
|
|
9695
10026
|
for (const item of items) {
|
|
9696
|
-
const fullPath =
|
|
10027
|
+
const fullPath = join6(paths.lineage, item.name);
|
|
9697
10028
|
if (item.isDirectory() && item.name.startsWith("gen-")) {
|
|
9698
10029
|
const genNum = extractGenNum(item.name);
|
|
9699
10030
|
const lines = await countDirLines(fullPath);
|
|
@@ -9747,7 +10078,7 @@ async function findLeafNodes(paths, entries) {
|
|
|
9747
10078
|
if (entry.type === "level2")
|
|
9748
10079
|
continue;
|
|
9749
10080
|
let meta = null;
|
|
9750
|
-
const fullPath =
|
|
10081
|
+
const fullPath = join6(paths.lineage, entry.name);
|
|
9751
10082
|
if (entry.type === "dir") {
|
|
9752
10083
|
meta = await readDirMeta(fullPath);
|
|
9753
10084
|
} else {
|
|
@@ -9778,7 +10109,7 @@ async function compressLevel1(genDir, genName) {
|
|
|
9778
10109
|
}
|
|
9779
10110
|
let goal = "", completionConditions = "";
|
|
9780
10111
|
{
|
|
9781
|
-
const objective = await readTextFile(
|
|
10112
|
+
const objective = await readTextFile(join6(genDir, "01-objective.md"));
|
|
9782
10113
|
if (objective) {
|
|
9783
10114
|
const goalMatch = objective.match(/## Goal\n([\s\S]*?)(?=\n##)/);
|
|
9784
10115
|
if (goalMatch)
|
|
@@ -9790,7 +10121,7 @@ async function compressLevel1(genDir, genName) {
|
|
|
9790
10121
|
}
|
|
9791
10122
|
let lessons = "", genomeChanges = "", nextBacklog = "";
|
|
9792
10123
|
{
|
|
9793
|
-
const completion = await readTextFile(
|
|
10124
|
+
const completion = await readTextFile(join6(genDir, "05-completion.md"));
|
|
9794
10125
|
if (completion) {
|
|
9795
10126
|
const lessonsMatch = completion.match(/### Lessons Learned\n([\s\S]*?)(?=\n###)/);
|
|
9796
10127
|
if (lessonsMatch)
|
|
@@ -9805,7 +10136,7 @@ async function compressLevel1(genDir, genName) {
|
|
|
9805
10136
|
}
|
|
9806
10137
|
let summaryText = "";
|
|
9807
10138
|
{
|
|
9808
|
-
const completion = await readTextFile(
|
|
10139
|
+
const completion = await readTextFile(join6(genDir, "05-completion.md"));
|
|
9809
10140
|
if (completion) {
|
|
9810
10141
|
const summaryMatch = completion.match(/## Summary\n([\s\S]*?)(?=\n##)/);
|
|
9811
10142
|
if (summaryMatch)
|
|
@@ -9814,7 +10145,7 @@ async function compressLevel1(genDir, genName) {
|
|
|
9814
10145
|
}
|
|
9815
10146
|
let validationResult = "";
|
|
9816
10147
|
{
|
|
9817
|
-
const validation = await readTextFile(
|
|
10148
|
+
const validation = await readTextFile(join6(genDir, "04-validation.md"));
|
|
9818
10149
|
if (validation) {
|
|
9819
10150
|
const resultMatch = validation.match(/## Result: (.+)/);
|
|
9820
10151
|
if (resultMatch)
|
|
@@ -9823,7 +10154,7 @@ async function compressLevel1(genDir, genName) {
|
|
|
9823
10154
|
}
|
|
9824
10155
|
let deferred = "";
|
|
9825
10156
|
{
|
|
9826
|
-
const impl = await readTextFile(
|
|
10157
|
+
const impl = await readTextFile(join6(genDir, "03-implementation.md"));
|
|
9827
10158
|
if (impl) {
|
|
9828
10159
|
const deferredMatch = impl.match(/## Deferred Tasks\n([\s\S]*?)(?=\n##)/);
|
|
9829
10160
|
if (deferredMatch) {
|
|
@@ -9947,10 +10278,10 @@ async function compressLineageIfNeeded(paths) {
|
|
|
9947
10278
|
const currentTotal = await countDirLines(paths.lineage);
|
|
9948
10279
|
if (currentTotal <= LINEAGE_MAX_LINES)
|
|
9949
10280
|
break;
|
|
9950
|
-
const dirPath =
|
|
10281
|
+
const dirPath = join6(paths.lineage, dir.name);
|
|
9951
10282
|
const compressed = await compressLevel1(dirPath, dir.name);
|
|
9952
10283
|
const genId = dir.name.match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? dir.name;
|
|
9953
|
-
const outPath =
|
|
10284
|
+
const outPath = join6(paths.lineage, `${genId}.md`);
|
|
9954
10285
|
await writeTextFile(outPath, compressed);
|
|
9955
10286
|
await rm(dirPath, { recursive: true });
|
|
9956
10287
|
result.level1.push(genId);
|
|
@@ -9964,10 +10295,10 @@ async function compressLineageIfNeeded(paths) {
|
|
|
9964
10295
|
const batch = level1s.slice(i * LEVEL2_BATCH_SIZE, (i + 1) * LEVEL2_BATCH_SIZE);
|
|
9965
10296
|
const files = batch.map((e) => ({
|
|
9966
10297
|
name: e.name,
|
|
9967
|
-
path:
|
|
10298
|
+
path: join6(paths.lineage, e.name)
|
|
9968
10299
|
}));
|
|
9969
10300
|
const compressed = await compressLevel2(files, epochNum);
|
|
9970
|
-
const outPath =
|
|
10301
|
+
const outPath = join6(paths.lineage, `epoch-${String(epochNum).padStart(3, "0")}.md`);
|
|
9971
10302
|
await writeTextFile(outPath, compressed);
|
|
9972
10303
|
for (const file of files) {
|
|
9973
10304
|
await rm(file.path);
|
|
@@ -9979,20 +10310,24 @@ async function compressLineageIfNeeded(paths) {
|
|
|
9979
10310
|
return result;
|
|
9980
10311
|
}
|
|
9981
10312
|
|
|
10313
|
+
// src/core/generation.ts
|
|
10314
|
+
init_fs();
|
|
10315
|
+
|
|
9982
10316
|
// src/core/lineage.ts
|
|
10317
|
+
init_fs();
|
|
9983
10318
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
9984
|
-
import { readdir as
|
|
9985
|
-
import { join as
|
|
10319
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
10320
|
+
import { join as join7 } from "path";
|
|
9986
10321
|
async function listCompleted(paths) {
|
|
9987
10322
|
try {
|
|
9988
|
-
const entries = await
|
|
10323
|
+
const entries = await readdir6(paths.lineage);
|
|
9989
10324
|
return entries.filter((e) => e.startsWith("gen-")).sort();
|
|
9990
10325
|
} catch {
|
|
9991
10326
|
return [];
|
|
9992
10327
|
}
|
|
9993
10328
|
}
|
|
9994
10329
|
async function readMeta(paths, lineageDirName) {
|
|
9995
|
-
const metaPath =
|
|
10330
|
+
const metaPath = join7(paths.lineage, lineageDirName, "meta.yml");
|
|
9996
10331
|
const content = await readTextFile(metaPath);
|
|
9997
10332
|
if (content === null)
|
|
9998
10333
|
return null;
|
|
@@ -10001,14 +10336,14 @@ async function readMeta(paths, lineageDirName) {
|
|
|
10001
10336
|
async function listMeta(paths) {
|
|
10002
10337
|
const metas = [];
|
|
10003
10338
|
try {
|
|
10004
|
-
const entries = await
|
|
10339
|
+
const entries = await readdir6(paths.lineage, { withFileTypes: true });
|
|
10005
10340
|
for (const entry of entries) {
|
|
10006
10341
|
if (entry.isDirectory() && entry.name.startsWith("gen-")) {
|
|
10007
10342
|
const meta = await readMeta(paths, entry.name);
|
|
10008
10343
|
if (meta)
|
|
10009
10344
|
metas.push(meta);
|
|
10010
10345
|
} else if (entry.isFile() && entry.name.startsWith("gen-") && entry.name.endsWith(".md")) {
|
|
10011
|
-
const content = await readTextFile(
|
|
10346
|
+
const content = await readTextFile(join7(paths.lineage, entry.name));
|
|
10012
10347
|
if (content) {
|
|
10013
10348
|
const meta = parseFrontmatter(content);
|
|
10014
10349
|
if (meta)
|
|
@@ -10062,13 +10397,13 @@ function getMachineId() {
|
|
|
10062
10397
|
async function computeGenomeHash(genomePath) {
|
|
10063
10398
|
const hash = createHash("sha256");
|
|
10064
10399
|
try {
|
|
10065
|
-
const entries = (await
|
|
10066
|
-
const pathA =
|
|
10067
|
-
const pathB =
|
|
10400
|
+
const entries = (await readdir7(genomePath, { recursive: true, withFileTypes: true })).filter((e) => e.isFile()).sort((a, b) => {
|
|
10401
|
+
const pathA = join8(e2path(a), a.name);
|
|
10402
|
+
const pathB = join8(e2path(b), b.name);
|
|
10068
10403
|
return pathA.localeCompare(pathB);
|
|
10069
10404
|
});
|
|
10070
10405
|
for (const entry of entries) {
|
|
10071
|
-
const filePath =
|
|
10406
|
+
const filePath = join8(e2path(entry), entry.name);
|
|
10072
10407
|
const content = await readTextFile(filePath);
|
|
10073
10408
|
if (content !== null) {
|
|
10074
10409
|
hash.update(filePath.replace(genomePath, ""));
|
|
@@ -10165,28 +10500,28 @@ class GenerationManager {
|
|
|
10165
10500
|
startedAt: state.startedAt,
|
|
10166
10501
|
completedAt: now
|
|
10167
10502
|
};
|
|
10168
|
-
await writeTextFile(
|
|
10169
|
-
const lifeEntries = await
|
|
10503
|
+
await writeTextFile(join8(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
|
|
10504
|
+
const lifeEntries = await readdir7(this.paths.life);
|
|
10170
10505
|
for (const entry of lifeEntries) {
|
|
10171
10506
|
if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
|
|
10172
|
-
await rename(
|
|
10507
|
+
await rename(join8(this.paths.life, entry), join8(genDir, entry));
|
|
10173
10508
|
}
|
|
10174
10509
|
}
|
|
10175
|
-
const backlogDir =
|
|
10510
|
+
const backlogDir = join8(genDir, "backlog");
|
|
10176
10511
|
await mkdir4(backlogDir, { recursive: true });
|
|
10177
10512
|
try {
|
|
10178
|
-
const backlogEntries = await
|
|
10513
|
+
const backlogEntries = await readdir7(this.paths.backlog);
|
|
10179
10514
|
for (const entry of backlogEntries) {
|
|
10180
|
-
await rename(
|
|
10515
|
+
await rename(join8(this.paths.backlog, entry), join8(backlogDir, entry));
|
|
10181
10516
|
}
|
|
10182
10517
|
} catch {}
|
|
10183
10518
|
try {
|
|
10184
|
-
const mutEntries = await
|
|
10519
|
+
const mutEntries = await readdir7(this.paths.mutations);
|
|
10185
10520
|
if (mutEntries.length > 0) {
|
|
10186
|
-
const mutDir =
|
|
10521
|
+
const mutDir = join8(genDir, "mutations");
|
|
10187
10522
|
await mkdir4(mutDir, { recursive: true });
|
|
10188
10523
|
for (const entry of mutEntries) {
|
|
10189
|
-
await rename(
|
|
10524
|
+
await rename(join8(this.paths.mutations, entry), join8(mutDir, entry));
|
|
10190
10525
|
}
|
|
10191
10526
|
}
|
|
10192
10527
|
} catch {}
|
|
@@ -10222,11 +10557,11 @@ class GenerationManager {
|
|
|
10222
10557
|
// src/core/migration.ts
|
|
10223
10558
|
async function needsMigration(paths) {
|
|
10224
10559
|
try {
|
|
10225
|
-
const entries = await
|
|
10560
|
+
const entries = await readdir8(paths.lineage, { withFileTypes: true });
|
|
10226
10561
|
for (const entry of entries) {
|
|
10227
10562
|
if (!entry.isDirectory() || !entry.name.startsWith("gen-"))
|
|
10228
10563
|
continue;
|
|
10229
|
-
const metaPath =
|
|
10564
|
+
const metaPath = join9(paths.lineage, entry.name, "meta.yml");
|
|
10230
10565
|
const content = await readTextFile(metaPath);
|
|
10231
10566
|
if (content === null)
|
|
10232
10567
|
return true;
|
|
@@ -10240,18 +10575,18 @@ async function migrateLineage(paths) {
|
|
|
10240
10575
|
const result = { migrated: [], skipped: [], errors: [] };
|
|
10241
10576
|
let entries;
|
|
10242
10577
|
try {
|
|
10243
|
-
const dirEntries = await
|
|
10578
|
+
const dirEntries = await readdir8(paths.lineage, { withFileTypes: true });
|
|
10244
10579
|
entries = dirEntries.filter((e) => e.isDirectory() && e.name.startsWith("gen-")).map((e) => e.name).sort();
|
|
10245
10580
|
} catch {
|
|
10246
10581
|
return result;
|
|
10247
10582
|
}
|
|
10248
10583
|
const plan = [];
|
|
10249
10584
|
for (const dirName of entries) {
|
|
10250
|
-
const metaPath =
|
|
10585
|
+
const metaPath = join9(paths.lineage, dirName, "meta.yml");
|
|
10251
10586
|
const metaContent = await readTextFile(metaPath);
|
|
10252
10587
|
const seq = parseGenSeq(dirName);
|
|
10253
10588
|
let goal = "";
|
|
10254
|
-
const objContent = await readTextFile(
|
|
10589
|
+
const objContent = await readTextFile(join9(paths.lineage, dirName, "01-objective.md"));
|
|
10255
10590
|
if (objContent) {
|
|
10256
10591
|
const goalMatch = objContent.match(/## Goal\n+([\s\S]*?)(?=\n##)/);
|
|
10257
10592
|
if (goalMatch)
|
|
@@ -10266,7 +10601,7 @@ async function migrateLineage(paths) {
|
|
|
10266
10601
|
let prevId = null;
|
|
10267
10602
|
for (const entry of plan) {
|
|
10268
10603
|
if (entry.hasMeta) {
|
|
10269
|
-
const metaContent = await readTextFile(
|
|
10604
|
+
const metaContent = await readTextFile(join9(paths.lineage, entry.dirName, "meta.yml"));
|
|
10270
10605
|
if (metaContent) {
|
|
10271
10606
|
const meta = import_yaml5.default.parse(metaContent);
|
|
10272
10607
|
prevId = meta.id;
|
|
@@ -10287,11 +10622,11 @@ async function migrateLineage(paths) {
|
|
|
10287
10622
|
startedAt: `legacy-${entry.seq}`,
|
|
10288
10623
|
completedAt: `legacy-${entry.seq}`
|
|
10289
10624
|
};
|
|
10290
|
-
await writeTextFile(
|
|
10625
|
+
await writeTextFile(join9(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
|
|
10291
10626
|
const oldSlug = entry.dirName.replace(/^gen-\d{3}/, "");
|
|
10292
10627
|
const newDirName = `${newId}${oldSlug}`;
|
|
10293
10628
|
if (newDirName !== entry.dirName) {
|
|
10294
|
-
await rename2(
|
|
10629
|
+
await rename2(join9(paths.lineage, entry.dirName), join9(paths.lineage, newDirName));
|
|
10295
10630
|
}
|
|
10296
10631
|
prevId = newId;
|
|
10297
10632
|
result.migrated.push(`${entry.dirName} → ${newDirName}`);
|
|
@@ -10329,22 +10664,42 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10329
10664
|
const config = await ConfigManager.read(paths);
|
|
10330
10665
|
const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
|
|
10331
10666
|
const commandsDir = ReapPaths.packageCommandsDir;
|
|
10332
|
-
const commandFiles = await
|
|
10667
|
+
const commandFiles = await readdir9(commandsDir);
|
|
10668
|
+
await mkdir6(ReapPaths.userReapCommands, { recursive: true });
|
|
10669
|
+
for (const file of commandFiles) {
|
|
10670
|
+
if (!file.endsWith(".md"))
|
|
10671
|
+
continue;
|
|
10672
|
+
const src = await readTextFileOrThrow(join10(commandsDir, file));
|
|
10673
|
+
const dest = join10(ReapPaths.userReapCommands, file);
|
|
10674
|
+
const existing = await readTextFile(dest);
|
|
10675
|
+
if (existing !== null && existing === src) {
|
|
10676
|
+
result.skipped.push(`~/.reap/commands/${file}`);
|
|
10677
|
+
} else {
|
|
10678
|
+
if (!dryRun)
|
|
10679
|
+
await writeTextFile(dest, src);
|
|
10680
|
+
result.updated.push(`~/.reap/commands/${file}`);
|
|
10681
|
+
}
|
|
10682
|
+
}
|
|
10333
10683
|
for (const adapter of adapters) {
|
|
10334
10684
|
const agentCmdDir = adapter.getCommandsDir();
|
|
10335
10685
|
const label = `${adapter.displayName}`;
|
|
10336
10686
|
for (const file of commandFiles) {
|
|
10337
10687
|
if (!file.endsWith(".md"))
|
|
10338
10688
|
continue;
|
|
10339
|
-
const
|
|
10340
|
-
const
|
|
10689
|
+
const cmdName = file.replace(/\.md$/, "");
|
|
10690
|
+
const redirectContent = `---
|
|
10691
|
+
description: "REAP — redirected to ~/.reap/commands/"
|
|
10692
|
+
---
|
|
10693
|
+
Read \`~/.reap/commands/${cmdName}.md\` and follow the instructions there.
|
|
10694
|
+
`;
|
|
10695
|
+
const dest = join10(agentCmdDir, file);
|
|
10341
10696
|
const existingContent = await readTextFile(dest);
|
|
10342
|
-
if (existingContent !== null && existingContent ===
|
|
10697
|
+
if (existingContent !== null && existingContent === redirectContent) {
|
|
10343
10698
|
result.skipped.push(`[${label}] commands/${file}`);
|
|
10344
10699
|
} else {
|
|
10345
10700
|
if (!dryRun) {
|
|
10346
10701
|
await mkdir6(agentCmdDir, { recursive: true });
|
|
10347
|
-
await writeTextFile(dest,
|
|
10702
|
+
await writeTextFile(dest, redirectContent);
|
|
10348
10703
|
}
|
|
10349
10704
|
result.updated.push(`[${label}] commands/${file}`);
|
|
10350
10705
|
}
|
|
@@ -10357,8 +10712,8 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10357
10712
|
await mkdir6(ReapPaths.userReapTemplates, { recursive: true });
|
|
10358
10713
|
const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
|
|
10359
10714
|
for (const file of artifactFiles) {
|
|
10360
|
-
const src = await readTextFileOrThrow(
|
|
10361
|
-
const dest =
|
|
10715
|
+
const src = await readTextFileOrThrow(join10(ReapPaths.packageArtifactsDir, file));
|
|
10716
|
+
const dest = join10(ReapPaths.userReapTemplates, file);
|
|
10362
10717
|
const existingContent = await readTextFile(dest);
|
|
10363
10718
|
if (existingContent !== null && existingContent === src) {
|
|
10364
10719
|
result.skipped.push(`~/.reap/templates/${file}`);
|
|
@@ -10368,8 +10723,8 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10368
10723
|
result.updated.push(`~/.reap/templates/${file}`);
|
|
10369
10724
|
}
|
|
10370
10725
|
}
|
|
10371
|
-
const domainGuideSrc = await readTextFileOrThrow(
|
|
10372
|
-
const domainGuideDest =
|
|
10726
|
+
const domainGuideSrc = await readTextFileOrThrow(join10(ReapPaths.packageGenomeDir, "domain/README.md"));
|
|
10727
|
+
const domainGuideDest = join10(ReapPaths.userReapTemplates, "domain-guide.md");
|
|
10373
10728
|
const domainExistingContent = await readTextFile(domainGuideDest);
|
|
10374
10729
|
if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
|
|
10375
10730
|
result.skipped.push(`~/.reap/templates/domain-guide.md`);
|
|
@@ -10378,13 +10733,13 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10378
10733
|
await writeTextFile(domainGuideDest, domainGuideSrc);
|
|
10379
10734
|
result.updated.push(`~/.reap/templates/domain-guide.md`);
|
|
10380
10735
|
}
|
|
10381
|
-
const mergeTemplatesDir =
|
|
10736
|
+
const mergeTemplatesDir = join10(ReapPaths.userReapTemplates, "merge");
|
|
10382
10737
|
await mkdir6(mergeTemplatesDir, { recursive: true });
|
|
10383
10738
|
const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
|
|
10384
|
-
const mergeSourceDir =
|
|
10739
|
+
const mergeSourceDir = join10(ReapPaths.packageArtifactsDir, "merge");
|
|
10385
10740
|
for (const file of mergeArtifactFiles) {
|
|
10386
|
-
const src = await readTextFileOrThrow(
|
|
10387
|
-
const dest =
|
|
10741
|
+
const src = await readTextFileOrThrow(join10(mergeSourceDir, file));
|
|
10742
|
+
const dest = join10(mergeTemplatesDir, file);
|
|
10388
10743
|
const existing = await readTextFile(dest);
|
|
10389
10744
|
if (existing !== null && existing === src) {
|
|
10390
10745
|
result.skipped.push(`~/.reap/templates/merge/${file}`);
|
|
@@ -10394,13 +10749,13 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10394
10749
|
result.updated.push(`~/.reap/templates/merge/${file}`);
|
|
10395
10750
|
}
|
|
10396
10751
|
}
|
|
10397
|
-
const brainstormSourceDir =
|
|
10398
|
-
const brainstormDestDir =
|
|
10752
|
+
const brainstormSourceDir = join10(ReapPaths.packageTemplatesDir, "brainstorm");
|
|
10753
|
+
const brainstormDestDir = join10(paths.root, "brainstorm");
|
|
10399
10754
|
await mkdir6(brainstormDestDir, { recursive: true });
|
|
10400
10755
|
const brainstormFiles = ["server.cjs", "frame.html", "start-server.sh"];
|
|
10401
10756
|
for (const file of brainstormFiles) {
|
|
10402
|
-
const src = await readTextFileOrThrow(
|
|
10403
|
-
const dest =
|
|
10757
|
+
const src = await readTextFileOrThrow(join10(brainstormSourceDir, file));
|
|
10758
|
+
const dest = join10(brainstormDestDir, file);
|
|
10404
10759
|
const existing = await readTextFile(dest);
|
|
10405
10760
|
if (existing !== null && existing === src) {
|
|
10406
10761
|
result.skipped.push(`.reap/brainstorm/${file}`);
|
|
@@ -10449,11 +10804,11 @@ async function migrateLegacyFiles(paths, dryRun, result) {
|
|
|
10449
10804
|
await removeDirIfExists(paths.legacyTemplates, ".reap/templates/", dryRun, result);
|
|
10450
10805
|
try {
|
|
10451
10806
|
const claudeCmdDir = paths.legacyClaudeCommands;
|
|
10452
|
-
const files = await
|
|
10807
|
+
const files = await readdir9(claudeCmdDir);
|
|
10453
10808
|
for (const file of files) {
|
|
10454
10809
|
if (file.startsWith("reap.") && file.endsWith(".md")) {
|
|
10455
10810
|
if (!dryRun)
|
|
10456
|
-
await unlink3(
|
|
10811
|
+
await unlink3(join10(claudeCmdDir, file));
|
|
10457
10812
|
result.removed.push(`.claude/commands/${file}`);
|
|
10458
10813
|
}
|
|
10459
10814
|
}
|
|
@@ -10497,7 +10852,7 @@ async function migrateLegacyFiles(paths, dryRun, result) {
|
|
|
10497
10852
|
}
|
|
10498
10853
|
async function removeDirIfExists(dirPath, label, dryRun, result) {
|
|
10499
10854
|
try {
|
|
10500
|
-
const entries = await
|
|
10855
|
+
const entries = await readdir9(dirPath);
|
|
10501
10856
|
if (entries.length > 0 || true) {
|
|
10502
10857
|
if (!dryRun)
|
|
10503
10858
|
await rm2(dirPath, { recursive: true });
|
|
@@ -10532,10 +10887,11 @@ async function getStatus(projectRoot) {
|
|
|
10532
10887
|
|
|
10533
10888
|
// src/cli/commands/fix.ts
|
|
10534
10889
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
10535
|
-
import { mkdir as mkdir7, stat as
|
|
10890
|
+
import { mkdir as mkdir7, stat as stat3 } from "fs/promises";
|
|
10891
|
+
init_fs();
|
|
10536
10892
|
async function dirExists(path) {
|
|
10537
10893
|
try {
|
|
10538
|
-
const s = await
|
|
10894
|
+
const s = await stat3(path);
|
|
10539
10895
|
return s.isDirectory();
|
|
10540
10896
|
} catch {
|
|
10541
10897
|
return false;
|
|
@@ -10588,8 +10944,9 @@ async function fixProject(projectRoot) {
|
|
|
10588
10944
|
}
|
|
10589
10945
|
|
|
10590
10946
|
// src/cli/index.ts
|
|
10591
|
-
|
|
10592
|
-
|
|
10947
|
+
init_fs();
|
|
10948
|
+
import { join as join11 } from "path";
|
|
10949
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.7.1");
|
|
10593
10950
|
program.command("init").description("Initialize a new REAP project (Genesis)").argument("[project-name]", "Project name (defaults to current directory name)").option("-m, --mode <mode>", "Entry mode: greenfield, migration, adoption", "greenfield").option("-p, --preset <preset>", "Bootstrap with a genome preset (e.g., bun-hono-react)").action(async (projectName, options) => {
|
|
10594
10951
|
try {
|
|
10595
10952
|
const cwd = process.cwd();
|
|
@@ -10711,10 +11068,10 @@ program.command("help").description("Show REAP commands, slash commands, and wor
|
|
|
10711
11068
|
if (l === "korean" || l === "ko")
|
|
10712
11069
|
lang = "ko";
|
|
10713
11070
|
}
|
|
10714
|
-
const helpDir =
|
|
10715
|
-
let helpText = await readTextFile(
|
|
11071
|
+
const helpDir = join11(ReapPaths.packageTemplatesDir, "help");
|
|
11072
|
+
let helpText = await readTextFile(join11(helpDir, `${lang}.txt`));
|
|
10716
11073
|
if (!helpText)
|
|
10717
|
-
helpText = await readTextFile(
|
|
11074
|
+
helpText = await readTextFile(join11(helpDir, "en.txt"));
|
|
10718
11075
|
if (!helpText) {
|
|
10719
11076
|
console.log("Help file not found. Run 'reap update' to install templates.");
|
|
10720
11077
|
return;
|
|
@@ -56,8 +56,14 @@ Genome(`.reap/genome/`)에 프로젝트의 설계 원칙과 규칙을 기록하
|
|
|
56
56
|
| `/reap.sync` | 현재 소스코드와 Genome 간 차이를 분석하고 동기화 |
|
|
57
57
|
| `/reap.update` | REAP 최신 버전 확인 및 업그레이드 |
|
|
58
58
|
| `/reap.help` | 도움말. `/reap.help {topic}`으로 주제별 상세 설명 |
|
|
59
|
+
| | **Collaboration** |
|
|
60
|
+
| `/reap.pull {branch}` | fetch + divergence 감지 + merge generation 자동 실행 |
|
|
61
|
+
| `/reap.push` | REAP 상태 검증 + git push |
|
|
62
|
+
| `/reap.merge {branch}` | merge generation 전체 lifecycle 실행 |
|
|
63
|
+
| `/reap.merge.start` | merge generation 생성 + detect |
|
|
64
|
+
| `/reap.merge.evolve` | merge 6단계 자율 실행 |
|
|
59
65
|
|
|
60
|
-
💡 `/reap.help {topic}` — workflow, genome, backlog, strict, agents, hooks, config, evolve, regression, author ...
|
|
66
|
+
💡 `/reap.help {topic}` — workflow, genome, backlog, strict, agents, hooks, config, evolve, regression, merge, pull, push, author ...
|
|
61
67
|
|
|
62
68
|
**Config**: Strict: {on/off} · Auto-Update: {on/off} · Language: {value}
|
|
63
69
|
|
|
@@ -82,5 +88,7 @@ For command-name topics: read `reap.{name}.md` from the same directory as this f
|
|
|
82
88
|
- **regression** — `/reap.back`: 이전 stage 회귀. timeline + artifact에 Regression 섹션 기록.
|
|
83
89
|
- **minor-fix** — 5분 이내, 설계 변경 없는 수정. stage 전환 없이 현재 artifact에 기록.
|
|
84
90
|
- **compression** — 10,000줄 + 5세대 이상 시 lineage 자동 압축. L1(40줄), L2(60줄).
|
|
91
|
+
- **merge** / **collaboration** — 분산 협업: genome-first merge 6단계 (detect→mate→merge→sync→validation→completion). `/reap.pull`로 fetch+merge, `/reap.push`로 검증+push. 상세: `domain/collaboration.md`, `domain/merge-lifecycle.md`.
|
|
85
92
|
- **author** — HyeonIL Choi. Email: hichoi@c-d.cc / Homepage: https://c-d.cc / LinkedIn: https://www.linkedin.com/in/hichoi-dev / GitHub: https://github.com/casamia918
|
|
86
93
|
- **start** / **objective** / **planning** / **implementation** / **validation** / **completion** / **next** / **back** / **sync** / **status** / **update** / **help** — Read `reap.{name}.md` from same directory, explain.
|
|
94
|
+
- **pull** / **push** / **merge.start** / **merge.detect** / **merge.mate** / **merge.merge** / **merge.sync** / **merge.validation** / **merge.completion** / **merge.evolve** — Read `reap.{name}.md` from same directory, explain.
|
|
@@ -34,6 +34,7 @@ If the target branch already includes all local work (fast-forward), skip the me
|
|
|
34
34
|
9. Check if the local latest generation is an ancestor of the remote latest generation:
|
|
35
35
|
- **If yes (fast-forward possible)**:
|
|
36
36
|
- Run `git merge --ff {branch}`
|
|
37
|
+
- Run `git submodule update --init` to sync submodules
|
|
37
38
|
- Report: "Fast-forwarded to {branch}. Local lineage now includes {remote-latest-id}. No merge generation needed."
|
|
38
39
|
- **STOP** — no merge lifecycle needed
|
|
39
40
|
- **If same generation**: "Already up to date." → **STOP**
|
|
@@ -44,7 +45,8 @@ If the target branch already includes all local work (fast-forward), skip the me
|
|
|
44
45
|
- This creates the merge generation and runs detect (01-detect.md)
|
|
45
46
|
11. Execute `/reap.merge.evolve` to run the full merge lifecycle:
|
|
46
47
|
- Detect → Mate → Merge → Sync → Validation → Completion
|
|
47
|
-
12.
|
|
48
|
+
12. Run `git submodule update --init` to sync submodules after merge
|
|
49
|
+
13. The merge generation is archived upon completion
|
|
48
50
|
|
|
49
51
|
## Completion
|
|
50
52
|
- Fast-forward: "Fast-forwarded to {branch}. No merge generation needed."
|
|
@@ -6,7 +6,7 @@ const gl = require('./genome-loader.cjs');
|
|
|
6
6
|
|
|
7
7
|
const startTime = Date.now();
|
|
8
8
|
let step = 0;
|
|
9
|
-
const totalSteps =
|
|
9
|
+
const totalSteps = 7;
|
|
10
10
|
|
|
11
11
|
function log(msg) {
|
|
12
12
|
step++;
|
|
@@ -28,6 +28,47 @@ if (!gl.dirExists(reapDir)) {
|
|
|
28
28
|
process.exit(0);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Step 0: Install project-level command symlinks
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const os = require('os');
|
|
34
|
+
const userReapCommands = path.join(os.homedir(), '.reap', 'commands');
|
|
35
|
+
const projectClaudeCommands = path.join(projectRoot, '.claude', 'commands');
|
|
36
|
+
|
|
37
|
+
if (gl.dirExists(userReapCommands)) {
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(projectClaudeCommands, { recursive: true });
|
|
40
|
+
const cmdFiles = fs.readdirSync(userReapCommands).filter(f => f.startsWith('reap.') && f.endsWith('.md'));
|
|
41
|
+
for (const file of cmdFiles) {
|
|
42
|
+
const src = path.join(userReapCommands, file);
|
|
43
|
+
const dest = path.join(projectClaudeCommands, file);
|
|
44
|
+
try {
|
|
45
|
+
const stat = fs.lstatSync(dest);
|
|
46
|
+
if (stat.isSymbolicLink()) {
|
|
47
|
+
const target = fs.readlinkSync(dest);
|
|
48
|
+
if (target === src) continue; // already correct
|
|
49
|
+
fs.unlinkSync(dest); // stale symlink
|
|
50
|
+
} else {
|
|
51
|
+
// Regular file (old redirect or original) — replace with symlink
|
|
52
|
+
fs.unlinkSync(dest);
|
|
53
|
+
}
|
|
54
|
+
} catch { /* dest doesn't exist */ }
|
|
55
|
+
fs.symlinkSync(src, dest);
|
|
56
|
+
}
|
|
57
|
+
// Ensure .gitignore excludes these symlinks
|
|
58
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
59
|
+
const gitignoreEntry = '.claude/commands/reap.*';
|
|
60
|
+
try {
|
|
61
|
+
const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
62
|
+
if (!gitignore.includes(gitignoreEntry)) {
|
|
63
|
+
fs.appendFileSync(gitignorePath, `\n# REAP command symlinks (managed by session-start hook)\n${gitignoreEntry}\n`);
|
|
64
|
+
}
|
|
65
|
+
} catch { /* best effort */ }
|
|
66
|
+
process.stderr.write(`[REAP] Symlinked ${cmdFiles.length} commands to .claude/commands/\n`);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
process.stderr.write(`[REAP] Warning: failed to create command symlinks: ${err.message}\n`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
31
72
|
// Step 1: Version check + Auto-update
|
|
32
73
|
log('Checking for updates...');
|
|
33
74
|
let autoUpdateMessage = '';
|