@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 readdir3, chmod } from "fs/promises";
8844
- import { join as join4 } from "path";
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(this.commandsDir, { recursive: true });
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(this.commandsDir, `${cmd}.md`);
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(this.commandsDir, { recursive: true });
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(this.commandsDir, `${cmd}.md`);
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 = join4(ReapPaths.packageTemplatesDir, "presets", preset);
9455
- const presetExists = await fileExists(join4(presetDir, "principles.md"));
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 ? join4(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
9798
+ const genomeSourceDir = preset ? join5(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
9478
9799
  for (const file of genomeTemplates) {
9479
- const src = join4(genomeSourceDir, file);
9480
- const dest = join4(paths.genome, file);
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 = join4(ReapPaths.packageArtifactsDir, file);
9488
- const dest = join4(ReapPaths.userReapTemplates, file);
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 = join4(ReapPaths.packageGenomeDir, "domain/README.md");
9492
- const domainGuideDest = join4(ReapPaths.userReapTemplates, "domain-guide.md");
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 = join4(ReapPaths.userReapTemplates, "merge");
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 = join4(ReapPaths.packageArtifactsDir, "merge");
9823
+ const mergeSourceDir = join5(ReapPaths.packageArtifactsDir, "merge");
9498
9824
  for (const file of mergeArtifactFiles) {
9499
- const src = join4(mergeSourceDir, file);
9500
- const dest = join4(mergeTemplatesDir, file);
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 = join4(ReapPaths.packageTemplatesDir, "brainstorm");
9505
- const brainstormDestDir = join4(paths.root, "brainstorm");
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 = join4(brainstormSourceDir, file);
9510
- const dest = join4(brainstormDestDir, file);
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 = join4(ReapPaths.packageTemplatesDir, "conditions");
9517
- const conditionsDestDir = join4(paths.hooks, "conditions");
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 readdir3(conditionsSourceDir);
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 = join4(conditionsSourceDir, file);
9524
- const dest = join4(conditionsDestDir, file);
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 readdir8, unlink as unlink3, rm as rm2, mkdir as mkdir6, chmod as chmod2 } from "fs/promises";
9545
- import { join as join9 } from "path";
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 readdir7, rename as rename2 } from "fs/promises";
9563
- import { join as join8 } from "path";
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 readdir6, mkdir as mkdir4, rename } from "fs/promises";
9570
- import { join as join7 } from "path";
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 readdir4, rm } from "fs/promises";
9626
- import { join as join5 } from "path";
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 readdir4(dirPath, { withFileTypes: true });
9994
+ const entries = await readdir5(dirPath, { withFileTypes: true });
9664
9995
  for (const entry of entries) {
9665
- const fullPath = join5(dirPath, entry.name);
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(join5(dirPath, "meta.yml"));
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 readdir4(paths.lineage, { withFileTypes: true });
10025
+ const items = await readdir5(paths.lineage, { withFileTypes: true });
9695
10026
  for (const item of items) {
9696
- const fullPath = join5(paths.lineage, item.name);
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 = join5(paths.lineage, entry.name);
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(join5(genDir, "01-objective.md"));
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(join5(genDir, "05-completion.md"));
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(join5(genDir, "05-completion.md"));
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(join5(genDir, "04-validation.md"));
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(join5(genDir, "03-implementation.md"));
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 = join5(paths.lineage, dir.name);
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 = join5(paths.lineage, `${genId}.md`);
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: join5(paths.lineage, e.name)
10298
+ path: join6(paths.lineage, e.name)
9968
10299
  }));
9969
10300
  const compressed = await compressLevel2(files, epochNum);
9970
- const outPath = join5(paths.lineage, `epoch-${String(epochNum).padStart(3, "0")}.md`);
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 readdir5 } from "fs/promises";
9985
- import { join as join6 } from "path";
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 readdir5(paths.lineage);
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 = join6(paths.lineage, lineageDirName, "meta.yml");
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 readdir5(paths.lineage, { withFileTypes: true });
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(join6(paths.lineage, entry.name));
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 readdir6(genomePath, { recursive: true, withFileTypes: true })).filter((e) => e.isFile()).sort((a, b) => {
10066
- const pathA = join7(e2path(a), a.name);
10067
- const pathB = join7(e2path(b), b.name);
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 = join7(e2path(entry), entry.name);
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(join7(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
10169
- const lifeEntries = await readdir6(this.paths.life);
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(join7(this.paths.life, entry), join7(genDir, entry));
10507
+ await rename(join8(this.paths.life, entry), join8(genDir, entry));
10173
10508
  }
10174
10509
  }
10175
- const backlogDir = join7(genDir, "backlog");
10510
+ const backlogDir = join8(genDir, "backlog");
10176
10511
  await mkdir4(backlogDir, { recursive: true });
10177
10512
  try {
10178
- const backlogEntries = await readdir6(this.paths.backlog);
10513
+ const backlogEntries = await readdir7(this.paths.backlog);
10179
10514
  for (const entry of backlogEntries) {
10180
- await rename(join7(this.paths.backlog, entry), join7(backlogDir, entry));
10515
+ await rename(join8(this.paths.backlog, entry), join8(backlogDir, entry));
10181
10516
  }
10182
10517
  } catch {}
10183
10518
  try {
10184
- const mutEntries = await readdir6(this.paths.mutations);
10519
+ const mutEntries = await readdir7(this.paths.mutations);
10185
10520
  if (mutEntries.length > 0) {
10186
- const mutDir = join7(genDir, "mutations");
10521
+ const mutDir = join8(genDir, "mutations");
10187
10522
  await mkdir4(mutDir, { recursive: true });
10188
10523
  for (const entry of mutEntries) {
10189
- await rename(join7(this.paths.mutations, entry), join7(mutDir, entry));
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 readdir7(paths.lineage, { withFileTypes: true });
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 = join8(paths.lineage, entry.name, "meta.yml");
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 readdir7(paths.lineage, { withFileTypes: true });
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 = join8(paths.lineage, dirName, "meta.yml");
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(join8(paths.lineage, dirName, "01-objective.md"));
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(join8(paths.lineage, entry.dirName, "meta.yml"));
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(join8(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
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(join8(paths.lineage, entry.dirName), join8(paths.lineage, newDirName));
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 readdir8(commandsDir);
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 src = await readTextFileOrThrow(join9(commandsDir, file));
10340
- const dest = join9(agentCmdDir, file);
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 === src) {
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, src);
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(join9(ReapPaths.packageArtifactsDir, file));
10361
- const dest = join9(ReapPaths.userReapTemplates, file);
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(join9(ReapPaths.packageGenomeDir, "domain/README.md"));
10372
- const domainGuideDest = join9(ReapPaths.userReapTemplates, "domain-guide.md");
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 = join9(ReapPaths.userReapTemplates, "merge");
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 = join9(ReapPaths.packageArtifactsDir, "merge");
10739
+ const mergeSourceDir = join10(ReapPaths.packageArtifactsDir, "merge");
10385
10740
  for (const file of mergeArtifactFiles) {
10386
- const src = await readTextFileOrThrow(join9(mergeSourceDir, file));
10387
- const dest = join9(mergeTemplatesDir, file);
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 = join9(ReapPaths.packageTemplatesDir, "brainstorm");
10398
- const brainstormDestDir = join9(paths.root, "brainstorm");
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(join9(brainstormSourceDir, file));
10403
- const dest = join9(brainstormDestDir, file);
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 readdir8(claudeCmdDir);
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(join9(claudeCmdDir, file));
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 readdir8(dirPath);
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 stat2 } from "fs/promises";
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 stat2(path);
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
- import { join as join10 } from "path";
10592
- program.name("reap").description("REAP Recursive Evolutionary Autonomous Pipeline").version("0.6.1");
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 = join10(ReapPaths.packageTemplatesDir, "help");
10715
- let helpText = await readTextFile(join10(helpDir, `${lang}.txt`));
11071
+ const helpDir = join11(ReapPaths.packageTemplatesDir, "help");
11072
+ let helpText = await readTextFile(join11(helpDir, `${lang}.txt`));
10716
11073
  if (!helpText)
10717
- helpText = await readTextFile(join10(helpDir, "en.txt"));
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. The merge generation is archived upon completion
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 = 6;
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 = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-d-cc/reap",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Recursive Evolutionary Autonomous Pipeline — AI and humans evolve software across generations",
5
5
  "type": "module",
6
6
  "license": "MIT",