@c-d-cc/reap 0.3.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.ja.md +97 -11
  2. package/README.ko.md +97 -11
  3. package/README.md +97 -11
  4. package/README.zh-CN.md +97 -11
  5. package/dist/cli.js +627 -217
  6. package/dist/templates/artifacts/merge/01-detect.md +18 -0
  7. package/dist/templates/artifacts/merge/02-mate.md +12 -0
  8. package/dist/templates/artifacts/merge/03-merge.md +15 -0
  9. package/dist/templates/artifacts/merge/04-sync.md +18 -0
  10. package/dist/templates/artifacts/merge/05-validation.md +11 -0
  11. package/dist/templates/artifacts/merge/06-completion.md +13 -0
  12. package/dist/templates/commands/reap.completion.md +1 -1
  13. package/dist/templates/commands/reap.evolve.md +7 -0
  14. package/dist/templates/commands/reap.merge.completion.md +20 -0
  15. package/dist/templates/commands/reap.merge.detect.md +20 -0
  16. package/dist/templates/commands/reap.merge.evolve.md +28 -0
  17. package/dist/templates/commands/reap.merge.mate.md +27 -0
  18. package/dist/templates/commands/reap.merge.md +47 -0
  19. package/dist/templates/commands/reap.merge.merge.md +22 -0
  20. package/dist/templates/commands/reap.merge.start.md +21 -0
  21. package/dist/templates/commands/reap.merge.sync.md +32 -0
  22. package/dist/templates/commands/reap.merge.validation.md +25 -0
  23. package/dist/templates/commands/reap.next.md +14 -1
  24. package/dist/templates/commands/reap.pull.md +51 -0
  25. package/dist/templates/commands/reap.push.md +18 -0
  26. package/dist/templates/commands/reap.start.md +4 -4
  27. package/dist/templates/hooks/genome-loader.cjs +34 -12
  28. package/dist/templates/hooks/opencode-session-start.js +2 -2
  29. package/dist/templates/hooks/reap-guide.md +1 -1
  30. package/dist/templates/hooks/session-start.cjs +2 -2
  31. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8999,6 +8999,18 @@ class ConfigManager {
8999
8999
  const content = import_yaml.default.stringify(config);
9000
9000
  await writeTextFile(paths.config, content);
9001
9001
  }
9002
+ static resolveStrict(strict) {
9003
+ if (strict === undefined || strict === false) {
9004
+ return { edit: false, merge: false };
9005
+ }
9006
+ if (strict === true) {
9007
+ return { edit: true, merge: true };
9008
+ }
9009
+ return {
9010
+ edit: strict.edit ?? false,
9011
+ merge: strict.merge ?? false
9012
+ };
9013
+ }
9002
9014
  }
9003
9015
 
9004
9016
  // src/core/agents/claude-code.ts
@@ -9419,7 +9431,18 @@ var COMMAND_NAMES = [
9419
9431
  "reap.status",
9420
9432
  "reap.sync",
9421
9433
  "reap.help",
9422
- "reap.update"
9434
+ "reap.update",
9435
+ "reap.merge.start",
9436
+ "reap.merge.detect",
9437
+ "reap.merge.mate",
9438
+ "reap.merge.merge",
9439
+ "reap.merge.sync",
9440
+ "reap.merge.validation",
9441
+ "reap.merge.completion",
9442
+ "reap.merge.evolve",
9443
+ "reap.merge",
9444
+ "reap.pull",
9445
+ "reap.push"
9423
9446
  ];
9424
9447
  async function initProject(projectRoot, projectName, entryMode, preset, onProgress) {
9425
9448
  const log = onProgress ?? (() => {});
@@ -9468,6 +9491,15 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
9468
9491
  const domainGuideSrc = join4(ReapPaths.packageGenomeDir, "domain/README.md");
9469
9492
  const domainGuideDest = join4(ReapPaths.userReapTemplates, "domain-guide.md");
9470
9493
  await writeTextFile(domainGuideDest, await readTextFileOrThrow(domainGuideSrc));
9494
+ const mergeTemplatesDir = join4(ReapPaths.userReapTemplates, "merge");
9495
+ await mkdir3(mergeTemplatesDir, { recursive: true });
9496
+ 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");
9498
+ for (const file of mergeArtifactFiles) {
9499
+ const src = join4(mergeSourceDir, file);
9500
+ const dest = join4(mergeTemplatesDir, file);
9501
+ await writeTextFile(dest, await readTextFileOrThrow(src));
9502
+ }
9471
9503
  log("Installing hook conditions...");
9472
9504
  const conditionsSourceDir = join4(ReapPaths.packageTemplatesDir, "conditions");
9473
9505
  const conditionsDestDir = join4(paths.hooks, "conditions");
@@ -9497,8 +9529,8 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
9497
9529
  }
9498
9530
 
9499
9531
  // src/cli/commands/update.ts
9500
- import { readdir as readdir4, unlink as unlink3, rm, mkdir as mkdir4 } from "fs/promises";
9501
- import { join as join5 } from "path";
9532
+ import { readdir as readdir8, unlink as unlink3, rm as rm2, mkdir as mkdir6 } from "fs/promises";
9533
+ import { join as join9 } from "path";
9502
9534
 
9503
9535
  // src/core/hooks.ts
9504
9536
  async function migrateHooks(dryRun = false) {
@@ -9513,147 +9545,16 @@ async function migrateHooks(dryRun = false) {
9513
9545
  return { results };
9514
9546
  }
9515
9547
 
9516
- // src/cli/commands/update.ts
9517
- async function updateProject(projectRoot, dryRun = false) {
9518
- const paths = new ReapPaths(projectRoot);
9519
- if (!await paths.isReapProject()) {
9520
- throw new Error("Not a REAP project. Run 'reap init' first.");
9521
- }
9522
- const result = { updated: [], skipped: [], removed: [] };
9523
- const config = await ConfigManager.read(paths);
9524
- const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
9525
- const commandsDir = ReapPaths.packageCommandsDir;
9526
- const commandFiles = await readdir4(commandsDir);
9527
- for (const adapter of adapters) {
9528
- const agentCmdDir = adapter.getCommandsDir();
9529
- const label = `${adapter.displayName}`;
9530
- for (const file of commandFiles) {
9531
- if (!file.endsWith(".md"))
9532
- continue;
9533
- const src = await readTextFileOrThrow(join5(commandsDir, file));
9534
- const dest = join5(agentCmdDir, file);
9535
- const existingContent = await readTextFile(dest);
9536
- if (existingContent !== null && existingContent === src) {
9537
- result.skipped.push(`[${label}] commands/${file}`);
9538
- } else {
9539
- if (!dryRun) {
9540
- await mkdir4(agentCmdDir, { recursive: true });
9541
- await writeTextFile(dest, src);
9542
- }
9543
- result.updated.push(`[${label}] commands/${file}`);
9544
- }
9545
- }
9546
- const validCommandFiles = new Set(commandFiles);
9547
- if (!dryRun) {
9548
- await adapter.removeStaleCommands(validCommandFiles);
9549
- }
9550
- }
9551
- await mkdir4(ReapPaths.userReapTemplates, { recursive: true });
9552
- const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
9553
- for (const file of artifactFiles) {
9554
- const src = await readTextFileOrThrow(join5(ReapPaths.packageArtifactsDir, file));
9555
- const dest = join5(ReapPaths.userReapTemplates, file);
9556
- const existingContent = await readTextFile(dest);
9557
- if (existingContent !== null && existingContent === src) {
9558
- result.skipped.push(`~/.reap/templates/${file}`);
9559
- } else {
9560
- if (!dryRun)
9561
- await writeTextFile(dest, src);
9562
- result.updated.push(`~/.reap/templates/${file}`);
9563
- }
9564
- }
9565
- const domainGuideSrc = await readTextFileOrThrow(join5(ReapPaths.packageGenomeDir, "domain/README.md"));
9566
- const domainGuideDest = join5(ReapPaths.userReapTemplates, "domain-guide.md");
9567
- const domainExistingContent = await readTextFile(domainGuideDest);
9568
- if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
9569
- result.skipped.push(`~/.reap/templates/domain-guide.md`);
9570
- } else {
9571
- if (!dryRun)
9572
- await writeTextFile(domainGuideDest, domainGuideSrc);
9573
- result.updated.push(`~/.reap/templates/domain-guide.md`);
9574
- }
9575
- const migrations = await migrateHooks(dryRun);
9576
- for (const m of migrations.results) {
9577
- if (m.action === "migrated") {
9578
- result.updated.push(`[${m.agent}] hooks (migrated)`);
9579
- }
9580
- }
9581
- for (const adapter of adapters) {
9582
- const hookResult = await adapter.syncSessionHook(dryRun);
9583
- if (hookResult.action === "updated") {
9584
- result.updated.push(`[${adapter.displayName}] session hook`);
9585
- } else {
9586
- result.skipped.push(`[${adapter.displayName}] session hook`);
9587
- }
9588
- }
9589
- await migrateLegacyFiles(paths, dryRun, result);
9590
- return result;
9591
- }
9592
- async function migrateLegacyFiles(paths, dryRun, result) {
9593
- await removeDirIfExists(paths.legacyCommands, ".reap/commands/", dryRun, result);
9594
- await removeDirIfExists(paths.legacyTemplates, ".reap/templates/", dryRun, result);
9595
- try {
9596
- const claudeCmdDir = paths.legacyClaudeCommands;
9597
- const files = await readdir4(claudeCmdDir);
9598
- for (const file of files) {
9599
- if (file.startsWith("reap.") && file.endsWith(".md")) {
9600
- if (!dryRun)
9601
- await unlink3(join5(claudeCmdDir, file));
9602
- result.removed.push(`.claude/commands/${file}`);
9603
- }
9604
- }
9605
- } catch {}
9606
- try {
9607
- const legacyHooksJson = paths.legacyClaudeHooksJson;
9608
- const fileContent = await readTextFile(legacyHooksJson);
9609
- if (fileContent !== null) {
9610
- const content = JSON.parse(fileContent);
9611
- const sessionStart = content["SessionStart"];
9612
- if (Array.isArray(sessionStart)) {
9613
- const filtered = sessionStart.filter((entry) => {
9614
- if (typeof entry !== "object" || entry === null)
9615
- return true;
9616
- const hooks = entry["hooks"];
9617
- if (!Array.isArray(hooks))
9618
- return true;
9619
- return !hooks.some((h) => {
9620
- if (typeof h !== "object" || h === null)
9621
- return false;
9622
- const cmd = h["command"];
9623
- return typeof cmd === "string" && cmd.includes(".reap/hooks/");
9624
- });
9625
- });
9626
- if (filtered.length !== sessionStart.length) {
9627
- if (!dryRun) {
9628
- if (filtered.length === 0 && Object.keys(content).length === 1) {
9629
- await unlink3(legacyHooksJson);
9630
- result.removed.push(`.claude/hooks.json (legacy)`);
9631
- } else {
9632
- content["SessionStart"] = filtered;
9633
- await writeTextFile(legacyHooksJson, JSON.stringify(content, null, 2) + `
9634
- `);
9635
- result.removed.push(`.claude/hooks.json (legacy REAP hook entry)`);
9636
- }
9637
- }
9638
- }
9639
- }
9640
- }
9641
- } catch {}
9642
- }
9643
- async function removeDirIfExists(dirPath, label, dryRun, result) {
9644
- try {
9645
- const entries = await readdir4(dirPath);
9646
- if (entries.length > 0 || true) {
9647
- if (!dryRun)
9648
- await rm(dirPath, { recursive: true });
9649
- result.removed.push(label);
9650
- }
9651
- } catch {}
9652
- }
9548
+ // src/core/migration.ts
9549
+ var import_yaml5 = __toESM(require_dist(), 1);
9550
+ import { readdir as readdir7, rename as rename2 } from "fs/promises";
9551
+ import { join as join8 } from "path";
9653
9552
 
9654
9553
  // src/core/generation.ts
9655
- var import_yaml2 = __toESM(require_dist(), 1);
9656
- import { readdir as readdir6, mkdir as mkdir5, rename } from "fs/promises";
9554
+ var import_yaml4 = __toESM(require_dist(), 1);
9555
+ import { createHash } from "crypto";
9556
+ import { hostname } from "os";
9557
+ import { readdir as readdir6, mkdir as mkdir4, rename } from "fs/promises";
9657
9558
  import { join as join7 } from "path";
9658
9559
 
9659
9560
  // src/types/index.ts
@@ -9708,14 +9609,35 @@ class LifeCycle {
9708
9609
  }
9709
9610
 
9710
9611
  // src/core/compression.ts
9711
- import { readdir as readdir5, rm as rm2 } from "fs/promises";
9712
- import { join as join6 } from "path";
9612
+ var import_yaml2 = __toESM(require_dist(), 1);
9613
+ import { readdir as readdir4, rm } from "fs/promises";
9614
+ import { join as join5 } from "path";
9713
9615
  var LINEAGE_MAX_LINES = 5000;
9714
9616
  var MIN_GENERATIONS_FOR_COMPRESSION = 5;
9715
9617
  var LEVEL1_MAX_LINES = 40;
9716
9618
  var LEVEL2_MAX_LINES = 60;
9717
9619
  var LEVEL2_BATCH_SIZE = 5;
9718
9620
  var RECENT_PROTECTED_COUNT = 3;
9621
+ function extractGenNum(name) {
9622
+ const match = name.match(/^gen-(\d{3})/);
9623
+ return match ? parseInt(match[1], 10) : 0;
9624
+ }
9625
+ function parseFrontmatter(content) {
9626
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
9627
+ if (!match)
9628
+ return null;
9629
+ try {
9630
+ return import_yaml2.default.parse(match[1]);
9631
+ } catch {
9632
+ return null;
9633
+ }
9634
+ }
9635
+ function buildFrontmatter(meta) {
9636
+ return `---
9637
+ ${import_yaml2.default.stringify(meta).trim()}
9638
+ ---
9639
+ `;
9640
+ }
9719
9641
  async function countLines(filePath) {
9720
9642
  const content = await readTextFile(filePath);
9721
9643
  if (content === null)
@@ -9726,9 +9648,9 @@ async function countLines(filePath) {
9726
9648
  async function countDirLines(dirPath) {
9727
9649
  let total = 0;
9728
9650
  try {
9729
- const entries = await readdir5(dirPath, { withFileTypes: true });
9651
+ const entries = await readdir4(dirPath, { withFileTypes: true });
9730
9652
  for (const entry of entries) {
9731
- const fullPath = join6(dirPath, entry.name);
9653
+ const fullPath = join5(dirPath, entry.name);
9732
9654
  if (entry.isFile() && entry.name.endsWith(".md")) {
9733
9655
  total += await countLines(fullPath);
9734
9656
  } else if (entry.isDirectory()) {
@@ -9738,33 +9660,113 @@ async function countDirLines(dirPath) {
9738
9660
  } catch {}
9739
9661
  return total;
9740
9662
  }
9663
+ async function readDirMeta(dirPath) {
9664
+ const content = await readTextFile(join5(dirPath, "meta.yml"));
9665
+ if (content === null)
9666
+ return null;
9667
+ try {
9668
+ return import_yaml2.default.parse(content);
9669
+ } catch {
9670
+ return null;
9671
+ }
9672
+ }
9673
+ async function readFileMeta(filePath) {
9674
+ const content = await readTextFile(filePath);
9675
+ if (content === null)
9676
+ return null;
9677
+ return parseFrontmatter(content);
9678
+ }
9741
9679
  async function scanLineage(paths) {
9742
9680
  const entries = [];
9743
9681
  try {
9744
- const items = await readdir5(paths.lineage, { withFileTypes: true });
9682
+ const items = await readdir4(paths.lineage, { withFileTypes: true });
9745
9683
  for (const item of items) {
9746
- const fullPath = join6(paths.lineage, item.name);
9684
+ const fullPath = join5(paths.lineage, item.name);
9747
9685
  if (item.isDirectory() && item.name.startsWith("gen-")) {
9748
- const genNum = parseInt(item.name.replace("gen-", ""), 10);
9686
+ const genNum = extractGenNum(item.name);
9749
9687
  const lines = await countDirLines(fullPath);
9750
- entries.push({ name: item.name, type: "dir", lines, genNum });
9688
+ const meta = await readDirMeta(fullPath);
9689
+ const genId = meta?.id ?? item.name.match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? item.name;
9690
+ entries.push({
9691
+ name: item.name,
9692
+ type: "dir",
9693
+ lines,
9694
+ genNum,
9695
+ completedAt: meta?.completedAt ?? "",
9696
+ genId
9697
+ });
9751
9698
  } else if (item.isFile() && item.name.startsWith("gen-") && item.name.endsWith(".md")) {
9752
- const genNum = parseInt(item.name.replace("gen-", ""), 10);
9699
+ const genNum = extractGenNum(item.name);
9753
9700
  const lines = await countLines(fullPath);
9754
- entries.push({ name: item.name, type: "level1", lines, genNum });
9701
+ const meta = await readFileMeta(fullPath);
9702
+ const genId = meta?.id ?? item.name.replace(".md", "").match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? item.name;
9703
+ entries.push({
9704
+ name: item.name,
9705
+ type: "level1",
9706
+ lines,
9707
+ genNum,
9708
+ completedAt: meta?.completedAt ?? "",
9709
+ genId
9710
+ });
9755
9711
  } else if (item.isFile() && item.name.startsWith("epoch-") && item.name.endsWith(".md")) {
9756
9712
  const lines = await countLines(fullPath);
9757
- entries.push({ name: item.name, type: "level2", lines, genNum: 0 });
9713
+ entries.push({
9714
+ name: item.name,
9715
+ type: "level2",
9716
+ lines,
9717
+ genNum: 0,
9718
+ completedAt: "",
9719
+ genId: ""
9720
+ });
9758
9721
  }
9759
9722
  }
9760
9723
  } catch {}
9761
- return entries.sort((a, b) => a.genNum - b.genNum);
9724
+ return entries.sort((a, b) => {
9725
+ if (a.completedAt && b.completedAt) {
9726
+ return a.completedAt.localeCompare(b.completedAt);
9727
+ }
9728
+ return a.genNum - b.genNum;
9729
+ });
9730
+ }
9731
+ async function findLeafNodes(paths, entries) {
9732
+ const allIds = new Set;
9733
+ const referencedAsParent = new Set;
9734
+ for (const entry of entries) {
9735
+ if (entry.type === "level2")
9736
+ continue;
9737
+ let meta = null;
9738
+ const fullPath = join5(paths.lineage, entry.name);
9739
+ if (entry.type === "dir") {
9740
+ meta = await readDirMeta(fullPath);
9741
+ } else {
9742
+ meta = await readFileMeta(fullPath);
9743
+ }
9744
+ if (meta) {
9745
+ allIds.add(meta.id);
9746
+ for (const parent of meta.parents) {
9747
+ referencedAsParent.add(parent);
9748
+ }
9749
+ } else {
9750
+ allIds.add(entry.genId);
9751
+ }
9752
+ }
9753
+ const leaves = new Set;
9754
+ for (const id of allIds) {
9755
+ if (!referencedAsParent.has(id)) {
9756
+ leaves.add(id);
9757
+ }
9758
+ }
9759
+ return leaves;
9762
9760
  }
9763
9761
  async function compressLevel1(genDir, genName) {
9764
9762
  const lines = [];
9763
+ const meta = await readDirMeta(genDir);
9764
+ if (meta) {
9765
+ lines.push(buildFrontmatter(meta));
9766
+ }
9765
9767
  let goal = "", completionConditions = "";
9766
9768
  {
9767
- const objective = await readTextFile(join6(genDir, "01-objective.md"));
9769
+ const objective = await readTextFile(join5(genDir, "01-objective.md"));
9768
9770
  if (objective) {
9769
9771
  const goalMatch = objective.match(/## Goal\n([\s\S]*?)(?=\n##)/);
9770
9772
  if (goalMatch)
@@ -9776,7 +9778,7 @@ async function compressLevel1(genDir, genName) {
9776
9778
  }
9777
9779
  let lessons = "", genomeChanges = "", nextBacklog = "";
9778
9780
  {
9779
- const completion = await readTextFile(join6(genDir, "05-completion.md"));
9781
+ const completion = await readTextFile(join5(genDir, "05-completion.md"));
9780
9782
  if (completion) {
9781
9783
  const lessonsMatch = completion.match(/### Lessons Learned\n([\s\S]*?)(?=\n###)/);
9782
9784
  if (lessonsMatch)
@@ -9789,18 +9791,18 @@ async function compressLevel1(genDir, genName) {
9789
9791
  nextBacklog = backlogMatch[1].trim();
9790
9792
  }
9791
9793
  }
9792
- let metadata = "";
9794
+ let summaryText = "";
9793
9795
  {
9794
- const completion = await readTextFile(join6(genDir, "05-completion.md"));
9796
+ const completion = await readTextFile(join5(genDir, "05-completion.md"));
9795
9797
  if (completion) {
9796
9798
  const summaryMatch = completion.match(/## Summary\n([\s\S]*?)(?=\n##)/);
9797
9799
  if (summaryMatch)
9798
- metadata = summaryMatch[1].trim();
9800
+ summaryText = summaryMatch[1].trim();
9799
9801
  }
9800
9802
  }
9801
9803
  let validationResult = "";
9802
9804
  {
9803
- const validation = await readTextFile(join6(genDir, "04-validation.md"));
9805
+ const validation = await readTextFile(join5(genDir, "04-validation.md"));
9804
9806
  if (validation) {
9805
9807
  const resultMatch = validation.match(/## Result: (.+)/);
9806
9808
  if (resultMatch)
@@ -9809,7 +9811,7 @@ async function compressLevel1(genDir, genName) {
9809
9811
  }
9810
9812
  let deferred = "";
9811
9813
  {
9812
- const impl = await readTextFile(join6(genDir, "03-implementation.md"));
9814
+ const impl = await readTextFile(join5(genDir, "03-implementation.md"));
9813
9815
  if (impl) {
9814
9816
  const deferredMatch = impl.match(/## Deferred Tasks\n([\s\S]*?)(?=\n##)/);
9815
9817
  if (deferredMatch) {
@@ -9820,10 +9822,10 @@ async function compressLevel1(genDir, genName) {
9820
9822
  }
9821
9823
  }
9822
9824
  }
9823
- const genId = genName.match(/^gen-\d+/)?.[0] ?? genName;
9825
+ const genId = genName.match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? genName;
9824
9826
  lines.push(`# ${genId}`);
9825
- if (metadata) {
9826
- lines.push(metadata.replace(/^# .+\n/, "").trim());
9827
+ if (summaryText) {
9828
+ lines.push(summaryText.replace(/^# .+\n/, "").trim());
9827
9829
  }
9828
9830
  lines.push("");
9829
9831
  if (goal) {
@@ -9873,18 +9875,19 @@ async function compressLevel1(genDir, genName) {
9873
9875
  }
9874
9876
  async function compressLevel2(level1Files, epochNum) {
9875
9877
  const lines = [];
9876
- const genIds = level1Files.map((f) => f.name.replace(".md", "").match(/^gen-\d+/)?.[0] ?? f.name);
9878
+ const genIds = level1Files.map((f) => f.name.replace(".md", "").match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? f.name);
9877
9879
  const first = genIds[0];
9878
9880
  const last = genIds[genIds.length - 1];
9879
9881
  lines.push(`# Epoch ${String(epochNum).padStart(3, "0")} (${first} ~ ${last})`);
9880
9882
  lines.push("");
9881
9883
  for (const file of level1Files) {
9882
9884
  const content = await readTextFileOrThrow(file.path);
9883
- const headerMatch = content.match(/^# (gen-\d+)/m);
9884
- const goalMatch = content.match(/- Goal: (.+)/);
9885
- const periodMatch = content.match(/- (?:Started|Period): (.+)/);
9886
- const genomeMatch = content.match(/- Genome.*: (.+)/);
9887
- const resultMatch = content.match(/## Result: (.+)/);
9885
+ const bodyContent = content.replace(/^---\n[\s\S]*?\n---\n?/, "");
9886
+ const headerMatch = bodyContent.match(/^# (gen-\d{3}(?:-[a-f0-9]{6})?)/m);
9887
+ const goalMatch = bodyContent.match(/- Goal: (.+)/);
9888
+ const periodMatch = bodyContent.match(/- (?:Started|Period): (.+)/);
9889
+ const genomeMatch = bodyContent.match(/- Genome.*: (.+)/);
9890
+ const resultMatch = bodyContent.match(/## Result: (.+)/);
9888
9891
  const genId = headerMatch?.[1] ?? "unknown";
9889
9892
  const goal = goalMatch?.[1] ?? "";
9890
9893
  const result2 = resultMatch?.[1] ?? "";
@@ -9895,7 +9898,7 @@ async function compressLevel2(level1Files, epochNum) {
9895
9898
  lines.push(`- ${genomeMatch[0].trim()}`);
9896
9899
  if (result2)
9897
9900
  lines.push(`- Result: ${result2}`);
9898
- const changeSection = content.match(/## Genome Changes\n([\s\S]*?)(?=\n##|$)/);
9901
+ const changeSection = bodyContent.match(/## Genome Changes\n([\s\S]*?)(?=\n##|$)/);
9899
9902
  if (changeSection && !changeSection[1].match(/^\|\s*\|\s*\|\s*\|\s*\|$/)) {
9900
9903
  lines.push(`- Genome Changes: ${changeSection[1].trim().split(`
9901
9904
  `)[0]}`);
@@ -9924,21 +9927,23 @@ async function compressLineageIfNeeded(paths) {
9924
9927
  if (totalLines <= LINEAGE_MAX_LINES) {
9925
9928
  return result;
9926
9929
  }
9927
- const allDirs = entries.filter((e) => e.type === "dir").sort((a, b) => a.genNum - b.genNum);
9928
- const dirs = allDirs.slice(0, Math.max(0, allDirs.length - RECENT_PROTECTED_COUNT));
9929
- for (const dir of dirs) {
9930
+ const leafNodes = await findLeafNodes(paths, entries);
9931
+ const allDirs = entries.filter((e) => e.type === "dir");
9932
+ const recentIds = new Set(allDirs.slice(Math.max(0, allDirs.length - RECENT_PROTECTED_COUNT)).map((e) => e.genId));
9933
+ const compressibleDirs = allDirs.filter((dir) => !recentIds.has(dir.genId) && !leafNodes.has(dir.genId));
9934
+ for (const dir of compressibleDirs) {
9930
9935
  const currentTotal = await countDirLines(paths.lineage);
9931
9936
  if (currentTotal <= LINEAGE_MAX_LINES)
9932
9937
  break;
9933
- const dirPath = join6(paths.lineage, dir.name);
9938
+ const dirPath = join5(paths.lineage, dir.name);
9934
9939
  const compressed = await compressLevel1(dirPath, dir.name);
9935
- const genId = dir.name.match(/^gen-\d+/)?.[0] ?? dir.name;
9936
- const outPath = join6(paths.lineage, `${genId}.md`);
9940
+ const genId = dir.name.match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? dir.name;
9941
+ const outPath = join5(paths.lineage, `${genId}.md`);
9937
9942
  await writeTextFile(outPath, compressed);
9938
- await rm2(dirPath, { recursive: true });
9943
+ await rm(dirPath, { recursive: true });
9939
9944
  result.level1.push(genId);
9940
9945
  }
9941
- const level1s = (await scanLineage(paths)).filter((e) => e.type === "level1").sort((a, b) => a.genNum - b.genNum);
9946
+ const level1s = (await scanLineage(paths)).filter((e) => e.type === "level1");
9942
9947
  if (level1s.length >= LEVEL2_BATCH_SIZE) {
9943
9948
  const existingEpochs = (await scanLineage(paths)).filter((e) => e.type === "level2");
9944
9949
  let epochNum = existingEpochs.length + 1;
@@ -9947,13 +9952,13 @@ async function compressLineageIfNeeded(paths) {
9947
9952
  const batch = level1s.slice(i * LEVEL2_BATCH_SIZE, (i + 1) * LEVEL2_BATCH_SIZE);
9948
9953
  const files = batch.map((e) => ({
9949
9954
  name: e.name,
9950
- path: join6(paths.lineage, e.name)
9955
+ path: join5(paths.lineage, e.name)
9951
9956
  }));
9952
9957
  const compressed = await compressLevel2(files, epochNum);
9953
- const outPath = join6(paths.lineage, `epoch-${String(epochNum).padStart(3, "0")}.md`);
9958
+ const outPath = join5(paths.lineage, `epoch-${String(epochNum).padStart(3, "0")}.md`);
9954
9959
  await writeTextFile(outPath, compressed);
9955
9960
  for (const file of files) {
9956
- await rm2(file.path);
9961
+ await rm(file.path);
9957
9962
  }
9958
9963
  result.level2.push(`epoch-${String(epochNum).padStart(3, "0")}`);
9959
9964
  epochNum++;
@@ -9962,7 +9967,119 @@ async function compressLineageIfNeeded(paths) {
9962
9967
  return result;
9963
9968
  }
9964
9969
 
9970
+ // src/core/lineage.ts
9971
+ var import_yaml3 = __toESM(require_dist(), 1);
9972
+ import { readdir as readdir5 } from "fs/promises";
9973
+ import { join as join6 } from "path";
9974
+ async function listCompleted(paths) {
9975
+ try {
9976
+ const entries = await readdir5(paths.lineage);
9977
+ return entries.filter((e) => e.startsWith("gen-")).sort();
9978
+ } catch {
9979
+ return [];
9980
+ }
9981
+ }
9982
+ async function readMeta(paths, lineageDirName) {
9983
+ const metaPath = join6(paths.lineage, lineageDirName, "meta.yml");
9984
+ const content = await readTextFile(metaPath);
9985
+ if (content === null)
9986
+ return null;
9987
+ return import_yaml3.default.parse(content);
9988
+ }
9989
+ async function listMeta(paths) {
9990
+ const metas = [];
9991
+ try {
9992
+ const entries = await readdir5(paths.lineage, { withFileTypes: true });
9993
+ for (const entry of entries) {
9994
+ if (entry.isDirectory() && entry.name.startsWith("gen-")) {
9995
+ const meta = await readMeta(paths, entry.name);
9996
+ if (meta)
9997
+ metas.push(meta);
9998
+ } else if (entry.isFile() && entry.name.startsWith("gen-") && entry.name.endsWith(".md")) {
9999
+ const content = await readTextFile(join6(paths.lineage, entry.name));
10000
+ if (content) {
10001
+ const meta = parseFrontmatter(content);
10002
+ if (meta)
10003
+ metas.push(meta);
10004
+ }
10005
+ }
10006
+ }
10007
+ } catch {}
10008
+ return metas;
10009
+ }
10010
+ async function nextSeq(paths, currentId) {
10011
+ const genDirs = await listCompleted(paths);
10012
+ if (genDirs.length === 0) {
10013
+ if (currentId) {
10014
+ return parseGenSeq(currentId) + 1;
10015
+ }
10016
+ return 1;
10017
+ }
10018
+ let maxSeq = 0;
10019
+ for (const dir of genDirs) {
10020
+ const seq = parseGenSeq(dir);
10021
+ if (seq > maxSeq)
10022
+ maxSeq = seq;
10023
+ }
10024
+ return maxSeq + 1;
10025
+ }
10026
+ async function resolveParents(paths) {
10027
+ const metas = await listMeta(paths);
10028
+ if (metas.length > 0) {
10029
+ const sorted = metas.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime());
10030
+ return [sorted[0].id];
10031
+ }
10032
+ const dirs = await listCompleted(paths);
10033
+ if (dirs.length > 0) {
10034
+ const lastDir = dirs[dirs.length - 1];
10035
+ const legacyId = lastDir.match(/^(gen-\d{3}(?:-[a-f0-9]{6})?)/)?.[1];
10036
+ if (legacyId)
10037
+ return [legacyId];
10038
+ }
10039
+ return [];
10040
+ }
10041
+
9965
10042
  // src/core/generation.ts
10043
+ function generateGenHash(parents, goal, genomeHash, machineId, startedAt) {
10044
+ const input = JSON.stringify({ parents, goal, genomeHash, machineId, startedAt });
10045
+ return createHash("sha256").update(input).digest("hex").slice(0, 6);
10046
+ }
10047
+ function getMachineId() {
10048
+ return hostname();
10049
+ }
10050
+ async function computeGenomeHash(genomePath) {
10051
+ const hash = createHash("sha256");
10052
+ try {
10053
+ const entries = (await readdir6(genomePath, { recursive: true, withFileTypes: true })).filter((e) => e.isFile()).sort((a, b) => {
10054
+ const pathA = join7(e2path(a), a.name);
10055
+ const pathB = join7(e2path(b), b.name);
10056
+ return pathA.localeCompare(pathB);
10057
+ });
10058
+ for (const entry of entries) {
10059
+ const filePath = join7(e2path(entry), entry.name);
10060
+ const content = await readTextFile(filePath);
10061
+ if (content !== null) {
10062
+ hash.update(filePath.replace(genomePath, ""));
10063
+ hash.update(content);
10064
+ }
10065
+ }
10066
+ } catch {}
10067
+ return hash.digest("hex").slice(0, 8);
10068
+ }
10069
+ function e2path(entry) {
10070
+ return entry.parentPath ?? entry.path ?? "";
10071
+ }
10072
+ function formatGenId(seq, hash) {
10073
+ return `gen-${String(seq).padStart(3, "0")}-${hash}`;
10074
+ }
10075
+ function parseGenSeq(id) {
10076
+ const match = id.match(/^gen-(\d{3})/);
10077
+ return match ? parseInt(match[1], 10) : 0;
10078
+ }
10079
+ function isLegacyId(id) {
10080
+ return /^gen-\d{3}$/.test(id);
10081
+ }
10082
+
9966
10083
  class GenerationManager {
9967
10084
  paths;
9968
10085
  constructor(paths) {
@@ -9972,20 +10089,33 @@ class GenerationManager {
9972
10089
  const content = await readTextFile(this.paths.currentYml);
9973
10090
  if (content === null || !content.trim())
9974
10091
  return null;
9975
- return import_yaml2.default.parse(content);
10092
+ const state = import_yaml4.default.parse(content);
10093
+ if (!state.type)
10094
+ state.type = "normal";
10095
+ if (!state.parents)
10096
+ state.parents = [];
10097
+ return state;
9976
10098
  }
9977
10099
  async create(goal, genomeVersion) {
9978
- const id = await this.nextGenId();
10100
+ const seq = await this.nextSeq();
9979
10101
  const now = new Date().toISOString();
10102
+ const genomeHash = await computeGenomeHash(this.paths.genome);
10103
+ const machineId = getMachineId();
10104
+ const parents = await this.resolveParents();
10105
+ const hash = generateGenHash(parents, goal, genomeHash, machineId, now);
10106
+ const id = formatGenId(seq, hash);
9980
10107
  const state = {
9981
10108
  id,
9982
10109
  goal,
9983
10110
  stage: "objective",
9984
10111
  genomeVersion,
9985
10112
  startedAt: now,
9986
- timeline: [{ stage: "objective", at: now }]
10113
+ timeline: [{ stage: "objective", at: now }],
10114
+ type: "normal",
10115
+ parents,
10116
+ genomeHash
9987
10117
  };
9988
- await writeTextFile(this.paths.currentYml, import_yaml2.default.stringify(state));
10118
+ await writeTextFile(this.paths.currentYml, import_yaml4.default.stringify(state));
9989
10119
  return state;
9990
10120
  }
9991
10121
  async advance() {
@@ -9999,7 +10129,7 @@ class GenerationManager {
9999
10129
  if (!state.timeline)
10000
10130
  state.timeline = [];
10001
10131
  state.timeline.push({ stage: next, at: new Date().toISOString() });
10002
- await writeTextFile(this.paths.currentYml, import_yaml2.default.stringify(state));
10132
+ await writeTextFile(this.paths.currentYml, import_yaml4.default.stringify(state));
10003
10133
  return state;
10004
10134
  }
10005
10135
  async complete() {
@@ -10008,10 +10138,22 @@ class GenerationManager {
10008
10138
  throw new Error("No active generation");
10009
10139
  if (state.stage !== "completion")
10010
10140
  throw new Error("Generation must be in completion stage to complete");
10141
+ const now = new Date().toISOString();
10142
+ state.completedAt = now;
10011
10143
  const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
10012
10144
  const genDirName = `${state.id}-${goalSlug}`;
10013
10145
  const genDir = this.paths.generationDir(genDirName);
10014
- await mkdir5(genDir, { recursive: true });
10146
+ await mkdir4(genDir, { recursive: true });
10147
+ const meta = {
10148
+ id: state.id,
10149
+ type: state.type ?? "normal",
10150
+ parents: state.parents ?? [],
10151
+ goal: state.goal,
10152
+ genomeHash: state.genomeHash ?? "unknown",
10153
+ startedAt: state.startedAt,
10154
+ completedAt: now
10155
+ };
10156
+ await writeTextFile(join7(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
10015
10157
  const lifeEntries = await readdir6(this.paths.life);
10016
10158
  for (const entry of lifeEntries) {
10017
10159
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
@@ -10019,7 +10161,7 @@ class GenerationManager {
10019
10161
  }
10020
10162
  }
10021
10163
  const backlogDir = join7(genDir, "backlog");
10022
- await mkdir5(backlogDir, { recursive: true });
10164
+ await mkdir4(backlogDir, { recursive: true });
10023
10165
  try {
10024
10166
  const backlogEntries = await readdir6(this.paths.backlog);
10025
10167
  for (const entry of backlogEntries) {
@@ -10030,7 +10172,7 @@ class GenerationManager {
10030
10172
  const mutEntries = await readdir6(this.paths.mutations);
10031
10173
  if (mutEntries.length > 0) {
10032
10174
  const mutDir = join7(genDir, "mutations");
10033
- await mkdir5(mutDir, { recursive: true });
10175
+ await mkdir4(mutDir, { recursive: true });
10034
10176
  for (const entry of mutEntries) {
10035
10177
  await rename(join7(this.paths.mutations, entry), join7(mutDir, entry));
10036
10178
  }
@@ -10041,30 +10183,295 @@ class GenerationManager {
10041
10183
  return compression;
10042
10184
  }
10043
10185
  async save(state) {
10044
- await writeTextFile(this.paths.currentYml, import_yaml2.default.stringify(state));
10186
+ await writeTextFile(this.paths.currentYml, import_yaml4.default.stringify(state));
10045
10187
  }
10046
10188
  async listCompleted() {
10189
+ return listCompleted(this.paths);
10190
+ }
10191
+ async readMeta(lineageDirName) {
10192
+ return readMeta(this.paths, lineageDirName);
10193
+ }
10194
+ async listMeta() {
10195
+ return listMeta(this.paths);
10196
+ }
10197
+ async resolveParents() {
10198
+ return resolveParents(this.paths);
10199
+ }
10200
+ async nextSeq() {
10201
+ const current = await this.current();
10202
+ return nextSeq(this.paths, current?.id);
10203
+ }
10204
+ async nextGenId() {
10205
+ const seq = await this.nextSeq();
10206
+ return `gen-${String(seq).padStart(3, "0")}`;
10207
+ }
10208
+ }
10209
+
10210
+ // src/core/migration.ts
10211
+ async function needsMigration(paths) {
10212
+ try {
10213
+ const entries = await readdir7(paths.lineage, { withFileTypes: true });
10214
+ for (const entry of entries) {
10215
+ if (!entry.isDirectory() || !entry.name.startsWith("gen-"))
10216
+ continue;
10217
+ const metaPath = join8(paths.lineage, entry.name, "meta.yml");
10218
+ const content = await readTextFile(metaPath);
10219
+ if (content === null)
10220
+ return true;
10221
+ }
10222
+ } catch {
10223
+ return false;
10224
+ }
10225
+ return false;
10226
+ }
10227
+ async function migrateLineage(paths) {
10228
+ const result = { migrated: [], skipped: [], errors: [] };
10229
+ let entries;
10230
+ try {
10231
+ const dirEntries = await readdir7(paths.lineage, { withFileTypes: true });
10232
+ entries = dirEntries.filter((e) => e.isDirectory() && e.name.startsWith("gen-")).map((e) => e.name).sort();
10233
+ } catch {
10234
+ return result;
10235
+ }
10236
+ const plan = [];
10237
+ for (const dirName of entries) {
10238
+ const metaPath = join8(paths.lineage, dirName, "meta.yml");
10239
+ const metaContent = await readTextFile(metaPath);
10240
+ const seq = parseGenSeq(dirName);
10241
+ let goal = "";
10242
+ const objContent = await readTextFile(join8(paths.lineage, dirName, "01-objective.md"));
10243
+ if (objContent) {
10244
+ const goalMatch = objContent.match(/## Goal\n+([\s\S]*?)(?=\n##)/);
10245
+ if (goalMatch)
10246
+ goal = goalMatch[1].trim();
10247
+ }
10248
+ if (!goal) {
10249
+ const slugMatch = dirName.match(/^gen-\d{3}(?:-[a-f0-9]{6})?-(.+)$/);
10250
+ goal = slugMatch ? slugMatch[1].replace(/-/g, " ") : `Generation ${seq}`;
10251
+ }
10252
+ plan.push({ dirName, seq, goal, hasMeta: metaContent !== null });
10253
+ }
10254
+ let prevId = null;
10255
+ for (const entry of plan) {
10256
+ if (entry.hasMeta) {
10257
+ const metaContent = await readTextFile(join8(paths.lineage, entry.dirName, "meta.yml"));
10258
+ if (metaContent) {
10259
+ const meta = import_yaml5.default.parse(metaContent);
10260
+ prevId = meta.id;
10261
+ }
10262
+ result.skipped.push(entry.dirName);
10263
+ continue;
10264
+ }
10047
10265
  try {
10048
- const entries = await readdir6(this.paths.lineage);
10049
- return entries.filter((e) => e.startsWith("gen-")).sort();
10050
- } catch {
10051
- return [];
10266
+ const parents = prevId ? [prevId] : [];
10267
+ const hash = generateGenHash(parents, entry.goal, "legacy", "migration", `legacy-${entry.seq}`);
10268
+ const newId = formatGenId(entry.seq, hash);
10269
+ const meta = {
10270
+ id: newId,
10271
+ type: "normal",
10272
+ parents,
10273
+ goal: entry.goal,
10274
+ genomeHash: "legacy",
10275
+ startedAt: `legacy-${entry.seq}`,
10276
+ completedAt: `legacy-${entry.seq}`
10277
+ };
10278
+ await writeTextFile(join8(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
10279
+ const oldSlug = entry.dirName.replace(/^gen-\d{3}/, "");
10280
+ const newDirName = `${newId}${oldSlug}`;
10281
+ if (newDirName !== entry.dirName) {
10282
+ await rename2(join8(paths.lineage, entry.dirName), join8(paths.lineage, newDirName));
10283
+ }
10284
+ prevId = newId;
10285
+ result.migrated.push(`${entry.dirName} → ${newDirName}`);
10286
+ } catch (err) {
10287
+ result.errors.push(`${entry.dirName}: ${err instanceof Error ? err.message : String(err)}`);
10052
10288
  }
10053
10289
  }
10054
- async nextGenId() {
10055
- const genDirs = await this.listCompleted();
10056
- if (genDirs.length === 0) {
10057
- const current = await this.current();
10058
- if (current) {
10059
- const num2 = parseInt(current.id.replace("gen-", ""), 10);
10060
- return `gen-${String(num2 + 1).padStart(3, "0")}`;
10290
+ try {
10291
+ const currentContent = await readTextFile(paths.currentYml);
10292
+ if (currentContent && currentContent.trim()) {
10293
+ const state = import_yaml5.default.parse(currentContent);
10294
+ if (isLegacyId(state.id)) {
10295
+ const parents = prevId ? [prevId] : [];
10296
+ const genomeHash = "legacy";
10297
+ const hash = generateGenHash(parents, state.goal, genomeHash, "migration", state.startedAt);
10298
+ state.id = formatGenId(parseGenSeq(state.id), hash);
10299
+ state.type = state.type ?? "normal";
10300
+ state.parents = parents;
10301
+ state.genomeHash = genomeHash;
10302
+ await writeTextFile(paths.currentYml, import_yaml5.default.stringify(state));
10303
+ result.migrated.push(`current.yml: ${state.id}`);
10061
10304
  }
10062
- return "gen-001";
10063
10305
  }
10064
- const last = genDirs[genDirs.length - 1];
10065
- const num = parseInt(last.replace("gen-", ""), 10);
10066
- return `gen-${String(num + 1).padStart(3, "0")}`;
10306
+ } catch {}
10307
+ return result;
10308
+ }
10309
+
10310
+ // src/cli/commands/update.ts
10311
+ async function updateProject(projectRoot, dryRun = false) {
10312
+ const paths = new ReapPaths(projectRoot);
10313
+ if (!await paths.isReapProject()) {
10314
+ throw new Error("Not a REAP project. Run 'reap init' first.");
10067
10315
  }
10316
+ const result = { updated: [], skipped: [], removed: [] };
10317
+ const config = await ConfigManager.read(paths);
10318
+ const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
10319
+ const commandsDir = ReapPaths.packageCommandsDir;
10320
+ const commandFiles = await readdir8(commandsDir);
10321
+ for (const adapter of adapters) {
10322
+ const agentCmdDir = adapter.getCommandsDir();
10323
+ const label = `${adapter.displayName}`;
10324
+ for (const file of commandFiles) {
10325
+ if (!file.endsWith(".md"))
10326
+ continue;
10327
+ const src = await readTextFileOrThrow(join9(commandsDir, file));
10328
+ const dest = join9(agentCmdDir, file);
10329
+ const existingContent = await readTextFile(dest);
10330
+ if (existingContent !== null && existingContent === src) {
10331
+ result.skipped.push(`[${label}] commands/${file}`);
10332
+ } else {
10333
+ if (!dryRun) {
10334
+ await mkdir6(agentCmdDir, { recursive: true });
10335
+ await writeTextFile(dest, src);
10336
+ }
10337
+ result.updated.push(`[${label}] commands/${file}`);
10338
+ }
10339
+ }
10340
+ const validCommandFiles = new Set(commandFiles);
10341
+ if (!dryRun) {
10342
+ await adapter.removeStaleCommands(validCommandFiles);
10343
+ }
10344
+ }
10345
+ await mkdir6(ReapPaths.userReapTemplates, { recursive: true });
10346
+ const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
10347
+ for (const file of artifactFiles) {
10348
+ const src = await readTextFileOrThrow(join9(ReapPaths.packageArtifactsDir, file));
10349
+ const dest = join9(ReapPaths.userReapTemplates, file);
10350
+ const existingContent = await readTextFile(dest);
10351
+ if (existingContent !== null && existingContent === src) {
10352
+ result.skipped.push(`~/.reap/templates/${file}`);
10353
+ } else {
10354
+ if (!dryRun)
10355
+ await writeTextFile(dest, src);
10356
+ result.updated.push(`~/.reap/templates/${file}`);
10357
+ }
10358
+ }
10359
+ const domainGuideSrc = await readTextFileOrThrow(join9(ReapPaths.packageGenomeDir, "domain/README.md"));
10360
+ const domainGuideDest = join9(ReapPaths.userReapTemplates, "domain-guide.md");
10361
+ const domainExistingContent = await readTextFile(domainGuideDest);
10362
+ if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
10363
+ result.skipped.push(`~/.reap/templates/domain-guide.md`);
10364
+ } else {
10365
+ if (!dryRun)
10366
+ await writeTextFile(domainGuideDest, domainGuideSrc);
10367
+ result.updated.push(`~/.reap/templates/domain-guide.md`);
10368
+ }
10369
+ const mergeTemplatesDir = join9(ReapPaths.userReapTemplates, "merge");
10370
+ await mkdir6(mergeTemplatesDir, { recursive: true });
10371
+ const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
10372
+ const mergeSourceDir = join9(ReapPaths.packageArtifactsDir, "merge");
10373
+ for (const file of mergeArtifactFiles) {
10374
+ const src = await readTextFileOrThrow(join9(mergeSourceDir, file));
10375
+ const dest = join9(mergeTemplatesDir, file);
10376
+ const existing = await readTextFile(dest);
10377
+ if (existing !== null && existing === src) {
10378
+ result.skipped.push(`~/.reap/templates/merge/${file}`);
10379
+ } else {
10380
+ if (!dryRun)
10381
+ await writeTextFile(dest, src);
10382
+ result.updated.push(`~/.reap/templates/merge/${file}`);
10383
+ }
10384
+ }
10385
+ const migrations = await migrateHooks(dryRun);
10386
+ for (const m of migrations.results) {
10387
+ if (m.action === "migrated") {
10388
+ result.updated.push(`[${m.agent}] hooks (migrated)`);
10389
+ }
10390
+ }
10391
+ for (const adapter of adapters) {
10392
+ const hookResult = await adapter.syncSessionHook(dryRun);
10393
+ if (hookResult.action === "updated") {
10394
+ result.updated.push(`[${adapter.displayName}] session hook`);
10395
+ } else {
10396
+ result.skipped.push(`[${adapter.displayName}] session hook`);
10397
+ }
10398
+ }
10399
+ await migrateLegacyFiles(paths, dryRun, result);
10400
+ if (await needsMigration(paths)) {
10401
+ if (!dryRun) {
10402
+ const migrationResult = await migrateLineage(paths);
10403
+ for (const m of migrationResult.migrated) {
10404
+ result.updated.push(`[lineage] ${m}`);
10405
+ }
10406
+ for (const e of migrationResult.errors) {
10407
+ result.removed.push(`[lineage error] ${e}`);
10408
+ }
10409
+ } else {
10410
+ result.updated.push("[lineage] DAG migration pending (dry-run)");
10411
+ }
10412
+ }
10413
+ return result;
10414
+ }
10415
+ async function migrateLegacyFiles(paths, dryRun, result) {
10416
+ await removeDirIfExists(paths.legacyCommands, ".reap/commands/", dryRun, result);
10417
+ await removeDirIfExists(paths.legacyTemplates, ".reap/templates/", dryRun, result);
10418
+ try {
10419
+ const claudeCmdDir = paths.legacyClaudeCommands;
10420
+ const files = await readdir8(claudeCmdDir);
10421
+ for (const file of files) {
10422
+ if (file.startsWith("reap.") && file.endsWith(".md")) {
10423
+ if (!dryRun)
10424
+ await unlink3(join9(claudeCmdDir, file));
10425
+ result.removed.push(`.claude/commands/${file}`);
10426
+ }
10427
+ }
10428
+ } catch {}
10429
+ try {
10430
+ const legacyHooksJson = paths.legacyClaudeHooksJson;
10431
+ const fileContent = await readTextFile(legacyHooksJson);
10432
+ if (fileContent !== null) {
10433
+ const content = JSON.parse(fileContent);
10434
+ const sessionStart = content["SessionStart"];
10435
+ if (Array.isArray(sessionStart)) {
10436
+ const filtered = sessionStart.filter((entry) => {
10437
+ if (typeof entry !== "object" || entry === null)
10438
+ return true;
10439
+ const hooks = entry["hooks"];
10440
+ if (!Array.isArray(hooks))
10441
+ return true;
10442
+ return !hooks.some((h) => {
10443
+ if (typeof h !== "object" || h === null)
10444
+ return false;
10445
+ const cmd = h["command"];
10446
+ return typeof cmd === "string" && cmd.includes(".reap/hooks/");
10447
+ });
10448
+ });
10449
+ if (filtered.length !== sessionStart.length) {
10450
+ if (!dryRun) {
10451
+ if (filtered.length === 0 && Object.keys(content).length === 1) {
10452
+ await unlink3(legacyHooksJson);
10453
+ result.removed.push(`.claude/hooks.json (legacy)`);
10454
+ } else {
10455
+ content["SessionStart"] = filtered;
10456
+ await writeTextFile(legacyHooksJson, JSON.stringify(content, null, 2) + `
10457
+ `);
10458
+ result.removed.push(`.claude/hooks.json (legacy REAP hook entry)`);
10459
+ }
10460
+ }
10461
+ }
10462
+ }
10463
+ }
10464
+ } catch {}
10465
+ }
10466
+ async function removeDirIfExists(dirPath, label, dryRun, result) {
10467
+ try {
10468
+ const entries = await readdir8(dirPath);
10469
+ if (entries.length > 0 || true) {
10470
+ if (!dryRun)
10471
+ await rm2(dirPath, { recursive: true });
10472
+ result.removed.push(label);
10473
+ }
10474
+ } catch {}
10068
10475
  }
10069
10476
 
10070
10477
  // src/cli/commands/status.ts
@@ -10082,15 +10489,18 @@ async function getStatus(projectRoot) {
10082
10489
  goal: current.goal,
10083
10490
  stage: current.stage,
10084
10491
  genomeVersion: current.genomeVersion,
10085
- startedAt: current.startedAt
10492
+ startedAt: current.startedAt,
10493
+ type: current.type,
10494
+ parents: current.parents,
10495
+ genomeHash: current.genomeHash
10086
10496
  } : null,
10087
10497
  totalGenerations: completedGens.length
10088
10498
  };
10089
10499
  }
10090
10500
 
10091
10501
  // src/cli/commands/fix.ts
10092
- var import_yaml3 = __toESM(require_dist(), 1);
10093
- import { mkdir as mkdir6, stat as stat2 } from "fs/promises";
10502
+ var import_yaml6 = __toESM(require_dist(), 1);
10503
+ import { mkdir as mkdir7, stat as stat2 } from "fs/promises";
10094
10504
  async function dirExists(path) {
10095
10505
  try {
10096
10506
  const s = await stat2(path);
@@ -10113,7 +10523,7 @@ async function fixProject(projectRoot) {
10113
10523
  ];
10114
10524
  for (const dir of requiredDirs) {
10115
10525
  if (!await dirExists(dir.path)) {
10116
- await mkdir6(dir.path, { recursive: true });
10526
+ await mkdir7(dir.path, { recursive: true });
10117
10527
  fixed.push(`Recreated missing directory: ${dir.name}/`);
10118
10528
  }
10119
10529
  }
@@ -10124,7 +10534,7 @@ async function fixProject(projectRoot) {
10124
10534
  if (currentContent !== null) {
10125
10535
  if (currentContent.trim()) {
10126
10536
  try {
10127
- const state = import_yaml3.default.parse(currentContent);
10537
+ const state = import_yaml6.default.parse(currentContent);
10128
10538
  if (!state.stage || !LifeCycle.isValid(state.stage)) {
10129
10539
  issues.push(`Invalid stage "${state.stage}" in current.yml. Valid stages: ${LifeCycle.stages().join(", ")}. Manual correction required.`);
10130
10540
  }
@@ -10133,7 +10543,7 @@ async function fixProject(projectRoot) {
10133
10543
  if (!state.goal)
10134
10544
  issues.push("current.yml is missing 'goal' field. Manual correction required.");
10135
10545
  if (!await dirExists(paths.backlog)) {
10136
- await mkdir6(paths.backlog, { recursive: true });
10546
+ await mkdir7(paths.backlog, { recursive: true });
10137
10547
  fixed.push("Recreated missing backlog/ directory for active generation");
10138
10548
  }
10139
10549
  } catch {
@@ -10146,8 +10556,8 @@ async function fixProject(projectRoot) {
10146
10556
  }
10147
10557
 
10148
10558
  // src/cli/index.ts
10149
- import { join as join8 } from "path";
10150
- program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.3.5");
10559
+ import { join as join10 } from "path";
10560
+ program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.5.0");
10151
10561
  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) => {
10152
10562
  try {
10153
10563
  const cwd = process.cwd();
@@ -10269,10 +10679,10 @@ program.command("help").description("Show REAP commands, slash commands, and wor
10269
10679
  if (l === "korean" || l === "ko")
10270
10680
  lang = "ko";
10271
10681
  }
10272
- const helpDir = join8(ReapPaths.packageTemplatesDir, "help");
10273
- let helpText = await readTextFile(join8(helpDir, `${lang}.txt`));
10682
+ const helpDir = join10(ReapPaths.packageTemplatesDir, "help");
10683
+ let helpText = await readTextFile(join10(helpDir, `${lang}.txt`));
10274
10684
  if (!helpText)
10275
- helpText = await readTextFile(join8(helpDir, "en.txt"));
10685
+ helpText = await readTextFile(join10(helpDir, "en.txt"));
10276
10686
  if (!helpText) {
10277
10687
  console.log("Help file not found. Run 'reap update' to install templates.");
10278
10688
  return;