@c-d-cc/reap 0.15.0 → 0.15.2

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
@@ -9027,7 +9027,8 @@ class ConfigManager {
9027
9027
  strict: false,
9028
9028
  autoUpdate: true,
9029
9029
  autoSubagent: true,
9030
- autoIssueReport: false
9030
+ autoIssueReport: false,
9031
+ lastSyncedGeneration: ""
9031
9032
  };
9032
9033
  for (const [key, defaultValue] of Object.entries(defaults)) {
9033
9034
  if (config[key] === undefined) {
@@ -9035,6 +9036,14 @@ class ConfigManager {
9035
9036
  added.push(key);
9036
9037
  }
9037
9038
  }
9039
+ if (config.lastSyncedCommit !== undefined) {
9040
+ if (!config.lastSyncedGeneration && config.lastSyncedCommit) {
9041
+ config.lastSyncedGeneration = "legacy";
9042
+ added.push("lastSyncedGeneration(migrated)");
9043
+ }
9044
+ delete config.lastSyncedCommit;
9045
+ added.push("lastSyncedCommit(removed)");
9046
+ }
9038
9047
  if (added.length > 0) {
9039
9048
  await ConfigManager.write(paths, config);
9040
9049
  }
@@ -9064,8 +9073,8 @@ var exports_genome_sync = {};
9064
9073
  __export(exports_genome_sync, {
9065
9074
  syncGenomeFromProject: () => syncGenomeFromProject
9066
9075
  });
9067
- import { join as join4 } from "path";
9068
- import { readdir as readdir3, stat as stat2 } from "fs/promises";
9076
+ import { join as join5 } from "path";
9077
+ import { readdir as readdir4, stat as stat2 } from "fs/promises";
9069
9078
  async function scanProject(projectRoot) {
9070
9079
  const scan = {
9071
9080
  language: "Unknown",
@@ -9084,7 +9093,7 @@ async function scanProject(projectRoot) {
9084
9093
  directories: [],
9085
9094
  existingDocs: []
9086
9095
  };
9087
- const pkgContent = await readTextFile(join4(projectRoot, "package.json"));
9096
+ const pkgContent = await readTextFile(join5(projectRoot, "package.json"));
9088
9097
  if (pkgContent) {
9089
9098
  try {
9090
9099
  const pkg = JSON.parse(pkgContent);
@@ -9143,16 +9152,16 @@ async function scanProject(projectRoot) {
9143
9152
  scan.buildTool = "Turbopack";
9144
9153
  } catch {}
9145
9154
  }
9146
- if (await fileExists(join4(projectRoot, "go.mod"))) {
9155
+ if (await fileExists(join5(projectRoot, "go.mod"))) {
9147
9156
  scan.language = "Go";
9148
9157
  scan.runtime = "Go";
9149
9158
  scan.packageManager = "Go Modules";
9150
9159
  }
9151
- if (await fileExists(join4(projectRoot, "pyproject.toml")) || await fileExists(join4(projectRoot, "requirements.txt"))) {
9160
+ if (await fileExists(join5(projectRoot, "pyproject.toml")) || await fileExists(join5(projectRoot, "requirements.txt"))) {
9152
9161
  scan.language = "Python";
9153
9162
  scan.runtime = "Python";
9154
- if (await fileExists(join4(projectRoot, "pyproject.toml"))) {
9155
- const pyproject = await readTextFile(join4(projectRoot, "pyproject.toml"));
9163
+ if (await fileExists(join5(projectRoot, "pyproject.toml"))) {
9164
+ const pyproject = await readTextFile(join5(projectRoot, "pyproject.toml"));
9156
9165
  if (pyproject?.includes("[tool.poetry]"))
9157
9166
  scan.packageManager = "Poetry";
9158
9167
  else if (pyproject?.includes("[tool.uv]") || pyproject?.includes("[project]"))
@@ -9167,23 +9176,23 @@ async function scanProject(projectRoot) {
9167
9176
  scan.framework = "Flask";
9168
9177
  }
9169
9178
  }
9170
- if (await fileExists(join4(projectRoot, "Cargo.toml"))) {
9179
+ if (await fileExists(join5(projectRoot, "Cargo.toml"))) {
9171
9180
  scan.language = "Rust";
9172
9181
  scan.runtime = "Rust";
9173
9182
  scan.packageManager = "Cargo";
9174
9183
  }
9175
- if (await fileExists(join4(projectRoot, "tsconfig.json"))) {
9184
+ if (await fileExists(join5(projectRoot, "tsconfig.json"))) {
9176
9185
  scan.hasTypeScript = true;
9177
9186
  scan.language = "TypeScript";
9178
9187
  }
9179
- scan.hasDocker = await fileExists(join4(projectRoot, "Dockerfile")) || await fileExists(join4(projectRoot, "docker-compose.yml"));
9188
+ scan.hasDocker = await fileExists(join5(projectRoot, "Dockerfile")) || await fileExists(join5(projectRoot, "docker-compose.yml"));
9180
9189
  try {
9181
- const entries = await readdir3(projectRoot);
9190
+ const entries = await readdir4(projectRoot);
9182
9191
  for (const entry of entries) {
9183
9192
  if (entry.startsWith(".") || entry === "node_modules")
9184
9193
  continue;
9185
9194
  try {
9186
- const s = await stat2(join4(projectRoot, entry));
9195
+ const s = await stat2(join5(projectRoot, entry));
9187
9196
  if (s.isDirectory())
9188
9197
  scan.directories.push(entry);
9189
9198
  } catch {}
@@ -9191,7 +9200,7 @@ async function scanProject(projectRoot) {
9191
9200
  } catch {}
9192
9201
  const docFiles = ["README.md", "CLAUDE.md", "AGENTS.md", "CONTRIBUTING.md", "ARCHITECTURE.md"];
9193
9202
  for (const file of docFiles) {
9194
- const content = await readTextFile(join4(projectRoot, file));
9203
+ const content = await readTextFile(join5(projectRoot, file));
9195
9204
  if (content) {
9196
9205
  scan.existingDocs.push({ file, content: content.substring(0, 2000) });
9197
9206
  }
@@ -9320,17 +9329,17 @@ async function syncGenomeFromProject(projectRoot, genomePath, onProgress) {
9320
9329
  log(`Detected: ${scan.language}, ${scan.framework !== "None" ? scan.framework : "no framework"}, ${scan.packageManager}`);
9321
9330
  const { writeTextFile: writeTextFile2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
9322
9331
  log("Generating constraints.md...");
9323
- await writeTextFile2(join4(genomePath, "constraints.md"), generateConstraints(scan));
9332
+ await writeTextFile2(join5(genomePath, "constraints.md"), generateConstraints(scan));
9324
9333
  log("Generating conventions.md...");
9325
- await writeTextFile2(join4(genomePath, "conventions.md"), generateConventions(scan));
9334
+ await writeTextFile2(join5(genomePath, "conventions.md"), generateConventions(scan));
9326
9335
  log("Generating principles.md...");
9327
- await writeTextFile2(join4(genomePath, "principles.md"), generatePrinciples(scan));
9336
+ await writeTextFile2(join5(genomePath, "principles.md"), generatePrinciples(scan));
9328
9337
  log("Generating source-map.md...");
9329
- await writeTextFile2(join4(genomePath, "source-map.md"), generateSourceMap(scan));
9330
- const { mkdir: mkdir3 } = await import("fs/promises");
9331
- const domainDir = join4(genomePath, "domain");
9332
- await mkdir3(domainDir, { recursive: true });
9333
- const domainReadme = join4(domainDir, "README.md");
9338
+ await writeTextFile2(join5(genomePath, "source-map.md"), generateSourceMap(scan));
9339
+ const { mkdir: mkdir4 } = await import("fs/promises");
9340
+ const domainDir = join5(genomePath, "domain");
9341
+ await mkdir4(domainDir, { recursive: true });
9342
+ const domainReadme = join5(domainDir, "README.md");
9334
9343
  if (!await fileExists(domainReadme)) {
9335
9344
  await writeTextFile2(domainReadme, [
9336
9345
  "# Domain Rules",
@@ -9473,8 +9482,12 @@ function gitCurrentBranch(cwd) {
9473
9482
  var init_git = () => {};
9474
9483
 
9475
9484
  // src/core/compression.ts
9476
- import { readdir as readdir5, rm } from "fs/promises";
9477
- import { join as join6 } from "path";
9485
+ import { readdir as readdir6, rm } from "fs/promises";
9486
+ import { join as join7 } from "path";
9487
+ function safeCompletedAtTime(dateStr) {
9488
+ const t = new Date(dateStr).getTime();
9489
+ return Number.isNaN(t) ? 0 : t;
9490
+ }
9478
9491
  function extractGenNum(name) {
9479
9492
  const match = name.match(/^gen-(\d{3})/);
9480
9493
  return match ? parseInt(match[1], 10) : 0;
@@ -9505,9 +9518,9 @@ async function countLines(filePath) {
9505
9518
  async function countDirLines(dirPath) {
9506
9519
  let total = 0;
9507
9520
  try {
9508
- const entries = await readdir5(dirPath, { withFileTypes: true });
9521
+ const entries = await readdir6(dirPath, { withFileTypes: true });
9509
9522
  for (const entry of entries) {
9510
- const fullPath = join6(dirPath, entry.name);
9523
+ const fullPath = join7(dirPath, entry.name);
9511
9524
  if (entry.isFile() && entry.name.endsWith(".md")) {
9512
9525
  total += await countLines(fullPath);
9513
9526
  } else if (entry.isDirectory()) {
@@ -9518,7 +9531,7 @@ async function countDirLines(dirPath) {
9518
9531
  return total;
9519
9532
  }
9520
9533
  async function readDirMeta(dirPath) {
9521
- const content = await readTextFile(join6(dirPath, "meta.yml"));
9534
+ const content = await readTextFile(join7(dirPath, "meta.yml"));
9522
9535
  if (content === null)
9523
9536
  return null;
9524
9537
  try {
@@ -9536,9 +9549,9 @@ async function readFileMeta(filePath) {
9536
9549
  async function scanLineage(paths) {
9537
9550
  const entries = [];
9538
9551
  try {
9539
- const items = await readdir5(paths.lineage, { withFileTypes: true });
9552
+ const items = await readdir6(paths.lineage, { withFileTypes: true });
9540
9553
  for (const item of items) {
9541
- const fullPath = join6(paths.lineage, item.name);
9554
+ const fullPath = join7(paths.lineage, item.name);
9542
9555
  if (item.isDirectory() && item.name.startsWith("gen-")) {
9543
9556
  const genNum = extractGenNum(item.name);
9544
9557
  const lines = await countDirLines(fullPath);
@@ -9579,11 +9592,10 @@ async function scanLineage(paths) {
9579
9592
  }
9580
9593
  } catch {}
9581
9594
  return entries.sort((a, b) => {
9582
- if (a.completedAt && b.completedAt) {
9583
- const cmp = a.completedAt.localeCompare(b.completedAt);
9584
- if (cmp !== 0)
9585
- return cmp;
9586
- }
9595
+ const aTime = safeCompletedAtTime(a.completedAt);
9596
+ const bTime = safeCompletedAtTime(b.completedAt);
9597
+ if (aTime !== bTime)
9598
+ return aTime - bTime;
9587
9599
  return a.genNum - b.genNum;
9588
9600
  });
9589
9601
  }
@@ -9594,7 +9606,7 @@ async function findLeafNodes(paths, entries) {
9594
9606
  if (entry.type === "level2")
9595
9607
  continue;
9596
9608
  let meta = null;
9597
- const fullPath = join6(paths.lineage, entry.name);
9609
+ const fullPath = join7(paths.lineage, entry.name);
9598
9610
  if (entry.type === "dir") {
9599
9611
  meta = await readDirMeta(fullPath);
9600
9612
  } else {
@@ -9625,7 +9637,7 @@ async function compressLevel1(genDir, genName) {
9625
9637
  }
9626
9638
  let goal = "", completionConditions = "";
9627
9639
  {
9628
- const objective = await readTextFile(join6(genDir, "01-objective.md"));
9640
+ const objective = await readTextFile(join7(genDir, "01-objective.md"));
9629
9641
  if (objective) {
9630
9642
  const goalMatch = objective.match(/## Goal\n([\s\S]*?)(?=\n##)/);
9631
9643
  if (goalMatch)
@@ -9637,7 +9649,7 @@ async function compressLevel1(genDir, genName) {
9637
9649
  }
9638
9650
  let lessons = "", genomeChanges = "", nextBacklog = "";
9639
9651
  {
9640
- const completion = await readTextFile(join6(genDir, "05-completion.md"));
9652
+ const completion = await readTextFile(join7(genDir, "05-completion.md"));
9641
9653
  if (completion) {
9642
9654
  const lessonsMatch = completion.match(/### Lessons Learned\n([\s\S]*?)(?=\n###)/);
9643
9655
  if (lessonsMatch)
@@ -9652,7 +9664,7 @@ async function compressLevel1(genDir, genName) {
9652
9664
  }
9653
9665
  let summaryText = "";
9654
9666
  {
9655
- const completion = await readTextFile(join6(genDir, "05-completion.md"));
9667
+ const completion = await readTextFile(join7(genDir, "05-completion.md"));
9656
9668
  if (completion) {
9657
9669
  const summaryMatch = completion.match(/## Summary\n([\s\S]*?)(?=\n##)/);
9658
9670
  if (summaryMatch)
@@ -9661,7 +9673,7 @@ async function compressLevel1(genDir, genName) {
9661
9673
  }
9662
9674
  let validationResult = "";
9663
9675
  {
9664
- const validation = await readTextFile(join6(genDir, "04-validation.md"));
9676
+ const validation = await readTextFile(join7(genDir, "04-validation.md"));
9665
9677
  if (validation) {
9666
9678
  const resultMatch = validation.match(/## Result: (.+)/);
9667
9679
  if (resultMatch)
@@ -9670,7 +9682,7 @@ async function compressLevel1(genDir, genName) {
9670
9682
  }
9671
9683
  let deferred = "";
9672
9684
  {
9673
- const impl = await readTextFile(join6(genDir, "03-implementation.md"));
9685
+ const impl = await readTextFile(join7(genDir, "03-implementation.md"));
9674
9686
  if (impl) {
9675
9687
  const deferredMatch = impl.match(/## Deferred Tasks\n([\s\S]*?)(?=\n##)/);
9676
9688
  if (deferredMatch) {
@@ -9756,7 +9768,7 @@ async function findForkedByOtherBranches(paths, cwd) {
9756
9768
  }
9757
9769
  async function compressLevel2Single(level1Files, paths) {
9758
9770
  const compressed = [];
9759
- const epochPath = join6(paths.lineage, "epoch.md");
9771
+ const epochPath = join7(paths.lineage, "epoch.md");
9760
9772
  let existingMeta = { generations: [] };
9761
9773
  let existingBody = "";
9762
9774
  const existingContent = await readTextFile(epochPath);
@@ -9828,10 +9840,10 @@ async function compressLineageIfNeeded(paths, projectRoot) {
9828
9840
  const currentTotal = await countDirLines(paths.lineage);
9829
9841
  if (currentTotal <= LINEAGE_MAX_LINES)
9830
9842
  break;
9831
- const dirPath = join6(paths.lineage, dir.name);
9843
+ const dirPath = join7(paths.lineage, dir.name);
9832
9844
  const compressed = await compressLevel1(dirPath, dir.name);
9833
9845
  const genId = dir.name.match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? dir.name;
9834
- const outPath = join6(paths.lineage, `${genId}.md`);
9846
+ const outPath = join7(paths.lineage, `${genId}.md`);
9835
9847
  await writeTextFile(outPath, compressed);
9836
9848
  await rm(dirPath, { recursive: true });
9837
9849
  result.level1.push(genId);
@@ -9852,8 +9864,8 @@ async function compressLineageIfNeeded(paths, projectRoot) {
9852
9864
  if (compressible.length > 0) {
9853
9865
  const filesWithMeta = await Promise.all(compressible.map(async (e) => ({
9854
9866
  name: e.name,
9855
- path: join6(paths.lineage, e.name),
9856
- meta: await readFileMeta(join6(paths.lineage, e.name))
9867
+ path: join7(paths.lineage, e.name),
9868
+ meta: await readFileMeta(join7(paths.lineage, e.name))
9857
9869
  })));
9858
9870
  const compressed = await compressLevel2Single(filesWithMeta, paths);
9859
9871
  result.level2.push(...compressed);
@@ -9869,18 +9881,18 @@ var init_compression = __esm(() => {
9869
9881
  });
9870
9882
 
9871
9883
  // src/core/lineage.ts
9872
- import { readdir as readdir6 } from "fs/promises";
9873
- import { join as join7 } from "path";
9884
+ import { readdir as readdir7 } from "fs/promises";
9885
+ import { join as join8 } from "path";
9874
9886
  async function listCompleted(paths) {
9875
9887
  try {
9876
- const entries = await readdir6(paths.lineage);
9888
+ const entries = await readdir7(paths.lineage);
9877
9889
  return entries.filter((e) => e.startsWith("gen-")).sort();
9878
9890
  } catch {
9879
9891
  return [];
9880
9892
  }
9881
9893
  }
9882
9894
  async function readMeta(paths, lineageDirName) {
9883
- const metaPath = join7(paths.lineage, lineageDirName, "meta.yml");
9895
+ const metaPath = join8(paths.lineage, lineageDirName, "meta.yml");
9884
9896
  const content = await readTextFile(metaPath);
9885
9897
  if (content === null)
9886
9898
  return null;
@@ -9889,14 +9901,14 @@ async function readMeta(paths, lineageDirName) {
9889
9901
  async function listMeta(paths) {
9890
9902
  const metas = [];
9891
9903
  try {
9892
- const entries = await readdir6(paths.lineage, { withFileTypes: true });
9904
+ const entries = await readdir7(paths.lineage, { withFileTypes: true });
9893
9905
  for (const entry of entries) {
9894
9906
  if (entry.isDirectory() && entry.name.startsWith("gen-")) {
9895
9907
  const meta = await readMeta(paths, entry.name);
9896
9908
  if (meta)
9897
9909
  metas.push(meta);
9898
9910
  } else if (entry.isFile() && entry.name.startsWith("gen-") && entry.name.endsWith(".md")) {
9899
- const content = await readTextFile(join7(paths.lineage, entry.name));
9911
+ const content = await readTextFile(join8(paths.lineage, entry.name));
9900
9912
  if (content) {
9901
9913
  const meta = parseFrontmatter(content);
9902
9914
  if (meta)
@@ -9923,10 +9935,14 @@ async function nextSeq(paths, currentId) {
9923
9935
  }
9924
9936
  return maxSeq + 1;
9925
9937
  }
9938
+ function safeCompletedAtTime2(dateStr) {
9939
+ const t = new Date(dateStr).getTime();
9940
+ return Number.isNaN(t) ? 0 : t;
9941
+ }
9926
9942
  async function resolveParents(paths) {
9927
9943
  const metas = await listMeta(paths);
9928
9944
  if (metas.length > 0) {
9929
- const sorted = metas.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime());
9945
+ const sorted = metas.sort((a, b) => safeCompletedAtTime2(b.completedAt) - safeCompletedAtTime2(a.completedAt));
9930
9946
  return [sorted[0].id];
9931
9947
  }
9932
9948
  const dirs = await listCompleted(paths);
@@ -9949,8 +9965,8 @@ var init_lineage = __esm(() => {
9949
9965
  // src/core/generation.ts
9950
9966
  import { createHash, randomBytes } from "crypto";
9951
9967
  import { hostname } from "os";
9952
- import { readdir as readdir7, mkdir as mkdir4, rename, unlink as unlink3 } from "fs/promises";
9953
- import { join as join8 } from "path";
9968
+ import { readdir as readdir8, mkdir as mkdir5, rename, unlink as unlink3 } from "fs/promises";
9969
+ import { join as join9 } from "path";
9954
9970
  function generateToken(genId, stage, phase) {
9955
9971
  const nonce = randomBytes(16).toString("hex");
9956
9972
  const input = phase ? `${nonce}${genId}${stage}:${phase}` : `${nonce}${genId}${stage}`;
@@ -9971,13 +9987,13 @@ function getMachineId() {
9971
9987
  async function computeGenomeHash(genomePath) {
9972
9988
  const hash = createHash("sha256");
9973
9989
  try {
9974
- const entries = (await readdir7(genomePath, { recursive: true, withFileTypes: true })).filter((e) => e.isFile()).sort((a, b) => {
9975
- const pathA = join8(e2path(a), a.name);
9976
- const pathB = join8(e2path(b), b.name);
9990
+ const entries = (await readdir8(genomePath, { recursive: true, withFileTypes: true })).filter((e) => e.isFile()).sort((a, b) => {
9991
+ const pathA = join9(e2path(a), a.name);
9992
+ const pathB = join9(e2path(b), b.name);
9977
9993
  return pathA.localeCompare(pathB);
9978
9994
  });
9979
9995
  for (const entry of entries) {
9980
- const filePath = join8(e2path(entry), entry.name);
9996
+ const filePath = join9(e2path(entry), entry.name);
9981
9997
  const content = await readTextFile(filePath);
9982
9998
  if (content !== null) {
9983
9999
  hash.update(filePath.replace(genomePath, ""));
@@ -10039,6 +10055,32 @@ class GenerationManager {
10039
10055
  await writeTextFile(this.paths.currentYml, CURRENT_YML_HEADER + import_yaml4.default.stringify(state));
10040
10056
  return state;
10041
10057
  }
10058
+ async createRecoveryGeneration(goal, genomeVersion, recovers) {
10059
+ if (recovers.length === 0) {
10060
+ throw new Error("Recovery generation requires at least one target generation ID");
10061
+ }
10062
+ const seq = await this.nextSeq();
10063
+ const now = new Date().toISOString();
10064
+ const genomeHash = await computeGenomeHash(this.paths.genome);
10065
+ const machineId = getMachineId();
10066
+ const parents = await this.resolveParents();
10067
+ const hash = generateGenHash(parents, goal, genomeHash, machineId, now);
10068
+ const id = formatGenId(seq, hash);
10069
+ const state = {
10070
+ id,
10071
+ goal,
10072
+ stage: "objective",
10073
+ genomeVersion,
10074
+ startedAt: now,
10075
+ timeline: [{ stage: "objective", at: now }],
10076
+ type: "recovery",
10077
+ parents,
10078
+ genomeHash,
10079
+ recovers
10080
+ };
10081
+ await writeTextFile(this.paths.currentYml, CURRENT_YML_HEADER + import_yaml4.default.stringify(state));
10082
+ return state;
10083
+ }
10042
10084
  async advance() {
10043
10085
  const state = await this.current();
10044
10086
  if (!state)
@@ -10064,7 +10106,7 @@ class GenerationManager {
10064
10106
  const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
10065
10107
  const genDirName = `${state.id}-${goalSlug}`;
10066
10108
  const genDir = this.paths.generationDir(genDirName);
10067
- await mkdir4(genDir, { recursive: true });
10109
+ await mkdir5(genDir, { recursive: true });
10068
10110
  const meta = {
10069
10111
  id: state.id,
10070
10112
  type: state.type ?? "normal",
@@ -10072,14 +10114,15 @@ class GenerationManager {
10072
10114
  goal: state.goal,
10073
10115
  genomeHash: state.genomeHash ?? "unknown",
10074
10116
  startedAt: state.startedAt,
10075
- completedAt: now
10117
+ completedAt: now,
10118
+ ...state.type === "recovery" && state.recovers ? { recovers: state.recovers } : {}
10076
10119
  };
10077
- await writeTextFile(join8(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
10078
- const lifeEntries = await readdir7(this.paths.life);
10120
+ await writeTextFile(join9(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
10121
+ const lifeEntries = await readdir8(this.paths.life);
10079
10122
  for (const entry of lifeEntries) {
10080
10123
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
10081
- const srcPath = join8(this.paths.life, entry);
10082
- const destPath = join8(genDir, entry);
10124
+ const srcPath = join9(this.paths.life, entry);
10125
+ const destPath = join9(genDir, entry);
10083
10126
  let content = await readTextFile(srcPath);
10084
10127
  if (content && content.startsWith("# REAP MANAGED")) {
10085
10128
  content = content.replace(/^# REAP MANAGED[^\n]*\n/, "");
@@ -10088,28 +10131,28 @@ class GenerationManager {
10088
10131
  await unlink3(srcPath);
10089
10132
  }
10090
10133
  }
10091
- const backlogDir = join8(genDir, "backlog");
10092
- await mkdir4(backlogDir, { recursive: true });
10134
+ const backlogDir = join9(genDir, "backlog");
10135
+ await mkdir5(backlogDir, { recursive: true });
10093
10136
  try {
10094
- const backlogEntries = await readdir7(this.paths.backlog);
10137
+ const backlogEntries = await readdir8(this.paths.backlog);
10095
10138
  for (const entry of backlogEntries) {
10096
- const content = await readTextFile(join8(this.paths.backlog, entry));
10139
+ const content = await readTextFile(join9(this.paths.backlog, entry));
10097
10140
  if (!content)
10098
10141
  continue;
10099
10142
  const isConsumed = /status:\s*consumed/i.test(content) || /consumed:\s*true/i.test(content);
10100
- await writeTextFile(join8(backlogDir, entry), content);
10143
+ await writeTextFile(join9(backlogDir, entry), content);
10101
10144
  if (isConsumed) {
10102
- await unlink3(join8(this.paths.backlog, entry));
10145
+ await unlink3(join9(this.paths.backlog, entry));
10103
10146
  }
10104
10147
  }
10105
10148
  } catch {}
10106
10149
  try {
10107
- const mutEntries = await readdir7(this.paths.mutations);
10150
+ const mutEntries = await readdir8(this.paths.mutations);
10108
10151
  if (mutEntries.length > 0) {
10109
- const mutDir = join8(genDir, "mutations");
10110
- await mkdir4(mutDir, { recursive: true });
10152
+ const mutDir = join9(genDir, "mutations");
10153
+ await mkdir5(mutDir, { recursive: true });
10111
10154
  for (const entry of mutEntries) {
10112
- await rename(join8(this.paths.mutations, entry), join8(mutDir, entry));
10155
+ await rename(join9(this.paths.mutations, entry), join9(mutDir, entry));
10113
10156
  }
10114
10157
  }
10115
10158
  } catch {}
@@ -10279,14 +10322,14 @@ var init_merge_lifecycle = __esm(() => {
10279
10322
  });
10280
10323
 
10281
10324
  // src/core/hook-engine.ts
10282
- import { readdir as readdir12 } from "fs/promises";
10283
- import { join as join14 } from "path";
10325
+ import { readdir as readdir14 } from "fs/promises";
10326
+ import { join as join16 } from "path";
10284
10327
  import { execSync as execSync4 } from "child_process";
10285
10328
  async function executeHooks(hooksDir, event, projectRoot) {
10286
10329
  const hooks = await scanHooks(hooksDir, event);
10287
10330
  if (hooks.length === 0)
10288
10331
  return [];
10289
- const conditionsDir = join14(hooksDir, "conditions");
10332
+ const conditionsDir = join16(hooksDir, "conditions");
10290
10333
  const results = [];
10291
10334
  for (const hook of hooks) {
10292
10335
  const conditionMet = await evaluateCondition(conditionsDir, hook.condition, projectRoot);
@@ -10311,7 +10354,7 @@ async function executeHooks(hooksDir, event, projectRoot) {
10311
10354
  async function scanHooks(hooksDir, event) {
10312
10355
  let entries;
10313
10356
  try {
10314
- entries = await readdir12(hooksDir);
10357
+ entries = await readdir14(hooksDir);
10315
10358
  } catch {
10316
10359
  return [];
10317
10360
  }
@@ -10321,7 +10364,7 @@ async function scanHooks(hooksDir, event) {
10321
10364
  const match = filename.match(pattern);
10322
10365
  if (!match)
10323
10366
  continue;
10324
- const meta = await parseHookMeta(join14(hooksDir, filename), match[2]);
10367
+ const meta = await parseHookMeta(join16(hooksDir, filename), match[2]);
10325
10368
  hooks.push({
10326
10369
  filename,
10327
10370
  name: match[1],
@@ -10342,7 +10385,7 @@ async function parseHookMeta(filePath, ext) {
10342
10385
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
10343
10386
  if (fmMatch) {
10344
10387
  try {
10345
- const fm = import_yaml8.default.parse(fmMatch[1]) ?? {};
10388
+ const fm = import_yaml9.default.parse(fmMatch[1]) ?? {};
10346
10389
  return {
10347
10390
  condition: String(fm.condition ?? "always"),
10348
10391
  order: Number(fm.order ?? 50)
@@ -10368,7 +10411,7 @@ async function parseHookMeta(filePath, ext) {
10368
10411
  async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
10369
10412
  if (conditionName === "always")
10370
10413
  return { met: true };
10371
- const scriptPath = join14(conditionsDir, `${conditionName}.sh`);
10414
+ const scriptPath = join16(conditionsDir, `${conditionName}.sh`);
10372
10415
  if (!await fileExists(scriptPath)) {
10373
10416
  return { met: false, reason: `condition script not found: ${conditionName}.sh` };
10374
10417
  }
@@ -10381,7 +10424,7 @@ async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
10381
10424
  }
10382
10425
  async function executeShHook(hook, event, projectRoot, hooksDir) {
10383
10426
  try {
10384
- const stdout = execSync4(`bash "${join14(hooksDir, hook.filename)}"`, {
10427
+ const stdout = execSync4(`bash "${join16(hooksDir, hook.filename)}"`, {
10385
10428
  cwd: projectRoot,
10386
10429
  timeout: 60000,
10387
10430
  stdio: "pipe"
@@ -10407,7 +10450,7 @@ async function executeShHook(hook, event, projectRoot, hooksDir) {
10407
10450
  }
10408
10451
  }
10409
10452
  async function executeMdHook(hook, event, hooksDir) {
10410
- const content = await readTextFile(join14(hooksDir, hook.filename));
10453
+ const content = await readTextFile(join16(hooksDir, hook.filename));
10411
10454
  const body = content?.replace(/^---\n[\s\S]*?\n---\n?/, "").trim() ?? "";
10412
10455
  return {
10413
10456
  name: hook.name,
@@ -10417,10 +10460,10 @@ async function executeMdHook(hook, event, hooksDir) {
10417
10460
  content: body
10418
10461
  };
10419
10462
  }
10420
- var import_yaml8;
10463
+ var import_yaml9;
10421
10464
  var init_hook_engine = __esm(() => {
10422
10465
  init_fs();
10423
- import_yaml8 = __toESM(require_dist(), 1);
10466
+ import_yaml9 = __toESM(require_dist(), 1);
10424
10467
  });
10425
10468
 
10426
10469
  // src/cli/commands/run/back.ts
@@ -10526,12 +10569,12 @@ var init_back = __esm(() => {
10526
10569
  });
10527
10570
 
10528
10571
  // src/core/backlog.ts
10529
- import { readdir as readdir13 } from "fs/promises";
10530
- import { join as join15 } from "path";
10572
+ import { readdir as readdir15 } from "fs/promises";
10573
+ import { join as join17 } from "path";
10531
10574
  async function scanBacklog(backlogDir) {
10532
10575
  let entries;
10533
10576
  try {
10534
- entries = await readdir13(backlogDir);
10577
+ entries = await readdir15(backlogDir);
10535
10578
  } catch {
10536
10579
  return [];
10537
10580
  }
@@ -10539,7 +10582,7 @@ async function scanBacklog(backlogDir) {
10539
10582
  for (const filename of entries) {
10540
10583
  if (!filename.endsWith(".md"))
10541
10584
  continue;
10542
- const content = await readTextFile(join15(backlogDir, filename));
10585
+ const content = await readTextFile(join17(backlogDir, filename));
10543
10586
  if (!content)
10544
10587
  continue;
10545
10588
  const { frontmatter, body } = parseFrontmatter2(content);
@@ -10557,7 +10600,7 @@ async function scanBacklog(backlogDir) {
10557
10600
  return items;
10558
10601
  }
10559
10602
  async function markBacklogConsumed(backlogDir, filename, genId) {
10560
- const filePath = join15(backlogDir, filename);
10603
+ const filePath = join17(backlogDir, filename);
10561
10604
  const content = await readTextFile(filePath);
10562
10605
  if (!content)
10563
10606
  return;
@@ -10566,7 +10609,7 @@ async function markBacklogConsumed(backlogDir, filename, genId) {
10566
10609
  frontmatter.consumedBy = genId;
10567
10610
  delete frontmatter.consumed;
10568
10611
  const newContent = `---
10569
- ${import_yaml9.default.stringify(frontmatter).trim()}
10612
+ ${import_yaml10.default.stringify(frontmatter).trim()}
10570
10613
  ---
10571
10614
  ${body}`;
10572
10615
  await writeTextFile(filePath, newContent);
@@ -10574,14 +10617,14 @@ ${body}`;
10574
10617
  async function revertBacklogConsumed(backlogDir, genId) {
10575
10618
  let entries;
10576
10619
  try {
10577
- entries = await readdir13(backlogDir);
10620
+ entries = await readdir15(backlogDir);
10578
10621
  } catch {
10579
10622
  return;
10580
10623
  }
10581
10624
  for (const filename of entries) {
10582
10625
  if (!filename.endsWith(".md"))
10583
10626
  continue;
10584
- const filePath = join15(backlogDir, filename);
10627
+ const filePath = join17(backlogDir, filename);
10585
10628
  const content = await readTextFile(filePath);
10586
10629
  if (!content)
10587
10630
  continue;
@@ -10590,7 +10633,7 @@ async function revertBacklogConsumed(backlogDir, genId) {
10590
10633
  frontmatter.status = "pending";
10591
10634
  delete frontmatter.consumedBy;
10592
10635
  const newContent = `---
10593
- ${import_yaml9.default.stringify(frontmatter).trim()}
10636
+ ${import_yaml10.default.stringify(frontmatter).trim()}
10594
10637
  ---
10595
10638
  ${body}`;
10596
10639
  await writeTextFile(filePath, newContent);
@@ -10602,15 +10645,15 @@ function parseFrontmatter2(content) {
10602
10645
  if (!match)
10603
10646
  return { frontmatter: {}, body: content };
10604
10647
  try {
10605
- return { frontmatter: import_yaml9.default.parse(match[1]) ?? {}, body: match[2] };
10648
+ return { frontmatter: import_yaml10.default.parse(match[1]) ?? {}, body: match[2] };
10606
10649
  } catch {
10607
10650
  return { frontmatter: {}, body: content };
10608
10651
  }
10609
10652
  }
10610
- var import_yaml9;
10653
+ var import_yaml10;
10611
10654
  var init_backlog = __esm(() => {
10612
10655
  init_fs();
10613
- import_yaml9 = __toESM(require_dist(), 1);
10656
+ import_yaml10 = __toESM(require_dist(), 1);
10614
10657
  });
10615
10658
 
10616
10659
  // src/cli/commands/run/start.ts
@@ -10618,8 +10661,8 @@ var exports_start = {};
10618
10661
  __export(exports_start, {
10619
10662
  execute: () => execute3
10620
10663
  });
10621
- import { join as join16 } from "path";
10622
- import { readdir as readdir14 } from "fs/promises";
10664
+ import { join as join18 } from "path";
10665
+ import { readdir as readdir16 } from "fs/promises";
10623
10666
  function getFlag2(args, name) {
10624
10667
  const idx = args.indexOf(`--${name}`);
10625
10668
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
@@ -10668,7 +10711,7 @@ async function execute3(paths, phase, argv = []) {
10668
10711
  }
10669
10712
  let genomeVersion = 1;
10670
10713
  try {
10671
- const lineageEntries = await readdir14(paths.lineage);
10714
+ const lineageEntries = await readdir16(paths.lineage);
10672
10715
  genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length + 1;
10673
10716
  } catch {}
10674
10717
  const state = await gm.create(goal, genomeVersion);
@@ -10678,8 +10721,8 @@ async function execute3(paths, phase, argv = []) {
10678
10721
  if (backlogFile) {
10679
10722
  await markBacklogConsumed(paths.backlog, backlogFile, state.id);
10680
10723
  }
10681
- const templateDir = join16(__require("os").homedir(), ".reap", "templates");
10682
- const templatePath = join16(templateDir, "01-objective.md");
10724
+ const templateDir = join18(__require("os").homedir(), ".reap", "templates");
10725
+ const templatePath = join18(templateDir, "01-objective.md");
10683
10726
  const destPath = paths.artifact("01-objective.md");
10684
10727
  if (await fileExists(templatePath)) {
10685
10728
  let template = await readTextFile(templatePath);
@@ -10739,7 +10782,7 @@ function checkSubmodules(projectRoot) {
10739
10782
  var init_commit = () => {};
10740
10783
 
10741
10784
  // src/core/stage-transition.ts
10742
- import { join as join17 } from "path";
10785
+ import { join as join19 } from "path";
10743
10786
  function verifyStageEntry(command, state) {
10744
10787
  if (!state.lastNonce) {
10745
10788
  return;
@@ -10804,8 +10847,8 @@ async function performTransition(paths, state, saveFn) {
10804
10847
  await saveFn(state);
10805
10848
  const artifactFile = isMerge ? MERGE_ARTIFACT[nextStage] : NORMAL_ARTIFACT[nextStage];
10806
10849
  if (artifactFile) {
10807
- const templateDir = join17(__require("os").homedir(), ".reap", "templates");
10808
- const templatePath = join17(templateDir, artifactFile);
10850
+ const templateDir = join19(__require("os").homedir(), ".reap", "templates");
10851
+ const templatePath = join19(templateDir, artifactFile);
10809
10852
  const destPath = paths.artifact(artifactFile);
10810
10853
  if (await fileExists(templatePath) && !await fileExists(destPath)) {
10811
10854
  const templateContent = await readTextFile(templatePath);
@@ -10864,7 +10907,7 @@ var exports_completion = {};
10864
10907
  __export(exports_completion, {
10865
10908
  execute: () => execute4
10866
10909
  });
10867
- import { join as join18 } from "path";
10910
+ import { join as join20 } from "path";
10868
10911
  import { execSync as execSync6 } from "child_process";
10869
10912
  function detectGenomeImpact(projectRoot) {
10870
10913
  const impact = {
@@ -10957,8 +11000,8 @@ async function execute4(paths, phase) {
10957
11000
  const implContent = await readTextFile(paths.artifact("03-implementation.md"));
10958
11001
  const destPath = paths.artifact("05-completion.md");
10959
11002
  if (!await fileExists(destPath)) {
10960
- const templateDir = join18(__require("os").homedir(), ".reap", "templates");
10961
- const templatePath = join18(templateDir, "05-completion.md");
11003
+ const templateDir = join20(__require("os").homedir(), ".reap", "templates");
11004
+ const templatePath = join20(templateDir, "05-completion.md");
10962
11005
  if (await fileExists(templatePath)) {
10963
11006
  const { writeTextFile: writeTextFile2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
10964
11007
  const template = await readTextFile(templatePath);
@@ -11086,8 +11129,8 @@ var exports_abort = {};
11086
11129
  __export(exports_abort, {
11087
11130
  execute: () => execute5
11088
11131
  });
11089
- import { join as join19 } from "path";
11090
- import { readdir as readdir15, unlink as unlink6 } from "fs/promises";
11132
+ import { join as join21 } from "path";
11133
+ import { readdir as readdir17, unlink as unlink6 } from "fs/promises";
11091
11134
  function getFlag3(args, name) {
11092
11135
  const idx = args.indexOf(`--${name}`);
11093
11136
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
@@ -11162,15 +11205,15 @@ async function execute5(paths, phase, argv = []) {
11162
11205
  sourceAction === "stash" ? "git stash pop으로 코드 복구" : sourceAction === "hold" ? "코드 변경이 working tree에 유지됨" : sourceAction === "rollback" ? "코드 변경이 revert됨. objective부터 재시작 필요" : "소스 코드 변경 없음"
11163
11206
  ].filter((line) => line !== null).join(`
11164
11207
  `);
11165
- await writeTextFile(join19(paths.backlog, `aborted-${state.id}.md`), backlogContent);
11208
+ await writeTextFile(join21(paths.backlog, `aborted-${state.id}.md`), backlogContent);
11166
11209
  backlogSaved = true;
11167
11210
  }
11168
11211
  await revertBacklogConsumed(paths.backlog, state.id);
11169
11212
  try {
11170
- const lifeEntries = await readdir15(paths.life);
11213
+ const lifeEntries = await readdir17(paths.life);
11171
11214
  for (const entry of lifeEntries) {
11172
11215
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
11173
- await unlink6(join19(paths.life, entry));
11216
+ await unlink6(join21(paths.life, entry));
11174
11217
  }
11175
11218
  }
11176
11219
  } catch {}
@@ -11229,8 +11272,8 @@ var exports_objective = {};
11229
11272
  __export(exports_objective, {
11230
11273
  execute: () => execute7
11231
11274
  });
11232
- import { join as join20 } from "path";
11233
- import { readdir as readdir16 } from "fs/promises";
11275
+ import { join as join22 } from "path";
11276
+ import { readdir as readdir18 } from "fs/promises";
11234
11277
  async function execute7(paths, phase) {
11235
11278
  const gm = new GenerationManager(paths);
11236
11279
  const state = await gm.current();
@@ -11247,13 +11290,13 @@ async function execute7(paths, phase) {
11247
11290
  const envSummary = await readTextFile(paths.environmentSummary);
11248
11291
  let prevCompletion = null;
11249
11292
  try {
11250
- const lineageEntries = await readdir16(paths.lineage);
11293
+ const lineageEntries = await readdir18(paths.lineage);
11251
11294
  const genDirs = lineageEntries.filter((e) => e.startsWith("gen-")).sort();
11252
11295
  if (genDirs.length > 0) {
11253
11296
  const lastGen = genDirs[genDirs.length - 1];
11254
- prevCompletion = await readTextFile(join20(paths.lineage, lastGen, "05-completion.md"));
11297
+ prevCompletion = await readTextFile(join22(paths.lineage, lastGen, "05-completion.md"));
11255
11298
  if (!prevCompletion) {
11256
- const compressed = await readTextFile(join20(paths.lineage, `${lastGen}.md`));
11299
+ const compressed = await readTextFile(join22(paths.lineage, `${lastGen}.md`));
11257
11300
  if (compressed)
11258
11301
  prevCompletion = compressed.slice(0, 2000);
11259
11302
  }
@@ -11266,12 +11309,12 @@ async function execute7(paths, phase) {
11266
11309
  const configContent = await readTextFile(paths.config);
11267
11310
  let lineageCount = 0;
11268
11311
  try {
11269
- const entries = await readdir16(paths.lineage);
11312
+ const entries = await readdir18(paths.lineage);
11270
11313
  lineageCount = entries.filter((e) => e.startsWith("gen-")).length;
11271
11314
  } catch {}
11272
11315
  if (!isReentry) {
11273
- const templateDir = join20(__require("os").homedir(), ".reap", "templates");
11274
- const templatePath = join20(templateDir, "01-objective.md");
11316
+ const templateDir = join22(__require("os").homedir(), ".reap", "templates");
11317
+ const templatePath = join22(templateDir, "01-objective.md");
11275
11318
  if (await fileExists(templatePath)) {
11276
11319
  let template = await readTextFile(templatePath);
11277
11320
  if (template) {
@@ -11389,7 +11432,7 @@ var exports_planning = {};
11389
11432
  __export(exports_planning, {
11390
11433
  execute: () => execute8
11391
11434
  });
11392
- import { join as join21 } from "path";
11435
+ import { join as join23 } from "path";
11393
11436
  async function execute8(paths, phase) {
11394
11437
  const gm = new GenerationManager(paths);
11395
11438
  const state = await gm.current();
@@ -11415,8 +11458,8 @@ async function execute8(paths, phase) {
11415
11458
  const principlesContent = await readTextFile(paths.principles);
11416
11459
  const implContent = await readTextFile(paths.artifact("03-implementation.md"));
11417
11460
  if (!isReentry) {
11418
- const templateDir = join21(__require("os").homedir(), ".reap", "templates");
11419
- const templatePath = join21(templateDir, "02-planning.md");
11461
+ const templateDir = join23(__require("os").homedir(), ".reap", "templates");
11462
+ const templatePath = join23(templateDir, "02-planning.md");
11420
11463
  if (await fileExists(templatePath)) {
11421
11464
  const template = await readTextFile(templatePath);
11422
11465
  if (template)
@@ -11526,7 +11569,7 @@ var exports_implementation = {};
11526
11569
  __export(exports_implementation, {
11527
11570
  execute: () => execute9
11528
11571
  });
11529
- import { join as join22 } from "path";
11572
+ import { join as join24 } from "path";
11530
11573
  async function execute9(paths, phase) {
11531
11574
  const gm = new GenerationManager(paths);
11532
11575
  const state = await gm.current();
@@ -11550,8 +11593,8 @@ async function execute9(paths, phase) {
11550
11593
  const conventionsContent = await readTextFile(paths.conventions);
11551
11594
  const constraintsContent = await readTextFile(paths.constraints);
11552
11595
  if (!isReentry) {
11553
- const templateDir = join22(__require("os").homedir(), ".reap", "templates");
11554
- const templatePath = join22(templateDir, "03-implementation.md");
11596
+ const templateDir = join24(__require("os").homedir(), ".reap", "templates");
11597
+ const templatePath = join24(templateDir, "03-implementation.md");
11555
11598
  if (await fileExists(templatePath)) {
11556
11599
  const template = await readTextFile(templatePath);
11557
11600
  if (template)
@@ -11660,7 +11703,7 @@ var exports_validation = {};
11660
11703
  __export(exports_validation, {
11661
11704
  execute: () => execute10
11662
11705
  });
11663
- import { join as join23 } from "path";
11706
+ import { join as join25 } from "path";
11664
11707
  async function execute10(paths, phase) {
11665
11708
  const gm = new GenerationManager(paths);
11666
11709
  const state = await gm.current();
@@ -11685,8 +11728,8 @@ async function execute10(paths, phase) {
11685
11728
  const implContent = await readTextFile(implArtifact);
11686
11729
  const objectiveContent = await readTextFile(paths.artifact("01-objective.md"));
11687
11730
  if (!isReentry) {
11688
- const templateDir = join23(__require("os").homedir(), ".reap", "templates");
11689
- const templatePath = join23(templateDir, "04-validation.md");
11731
+ const templateDir = join25(__require("os").homedir(), ".reap", "templates");
11732
+ const templatePath = join25(templateDir, "04-validation.md");
11690
11733
  if (await fileExists(templatePath)) {
11691
11734
  const template = await readTextFile(templatePath);
11692
11735
  if (template)
@@ -11893,6 +11936,16 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
11893
11936
  lines.push("- Hook prompt는 `.md` hook 파일의 내용으로, AI가 따라야 할 지시사항이다.");
11894
11937
  lines.push("- Hook prompt에 유저 확인이 필요한 경우(예: 프리뷰+컨펌), autonomous mode에서도 이를 존중하라.");
11895
11938
  lines.push("");
11939
+ lines.push("## Artifact Consistency & Design Pivot Detection");
11940
+ lines.push("- Before proceeding to a new stage, verify that previous stage artifacts align with the current design direction.");
11941
+ lines.push("- If the prompt context contains design corrections that contradict existing artifacts:");
11942
+ lines.push(" 1. Prioritize prompt instructions over the goal text.");
11943
+ lines.push(" 2. Use `/reap.back` to regress to the stage with the inconsistent artifact and rewrite it.");
11944
+ lines.push("- Regression triggers (use `/reap.back` in these situations):");
11945
+ lines.push(" - Artifacts contradict the design direction given in the prompt");
11946
+ lines.push(" - Implementation approach differs from what objective/planning described");
11947
+ lines.push(" - Prompt provides explicit design changes but earlier artifacts still reflect the old design");
11948
+ lines.push("");
11896
11949
  lines.push("## Interrupt Protection");
11897
11950
  lines.push('- 사용자의 새 메시지가 중간에 들어와도, 명시적 kill/중단 요청("중단", "stop", "abort")이 아닌 한 현재 작업을 끝까지 완료하라.');
11898
11951
  lines.push("- 작업을 shortcut으로 건너뛰거나 결과를 추정하지 마라. 모든 validation은 실제 실행 결과를 확인하라.");
@@ -12026,6 +12079,9 @@ async function execute11(paths, phase) {
12026
12079
  "",
12027
12080
  "### Handling Issues",
12028
12081
  "- If validation fails: `/reap.back` to return to implementation (or earlier), then resume the loop",
12082
+ "- If artifacts contradict the design direction in the prompt: `/reap.back` to fix the inconsistent artifact",
12083
+ "- If implementation approach differs from what objective/planning described: `/reap.back` to objective or planning",
12084
+ "- If the prompt provides design corrections that differ from existing artifacts: prioritize prompt instructions, `/reap.back` to rewrite artifacts with the corrected design",
12029
12085
  "- If the human wants to pause: stop the loop"
12030
12086
  ].join(`
12031
12087
  `)
@@ -12072,8 +12128,8 @@ var exports_sync_genome = {};
12072
12128
  __export(exports_sync_genome, {
12073
12129
  execute: () => execute13
12074
12130
  });
12075
- import { readdir as readdir17 } from "fs/promises";
12076
- import { join as join24 } from "path";
12131
+ import { readdir as readdir19 } from "fs/promises";
12132
+ import { join as join26 } from "path";
12077
12133
  async function execute13(paths, phase) {
12078
12134
  const gm = new GenerationManager(paths);
12079
12135
  const state = await gm.current();
@@ -12085,10 +12141,10 @@ async function execute13(paths, phase) {
12085
12141
  const sourceMapContent = await readTextFile(paths.sourceMap);
12086
12142
  const domainFiles = {};
12087
12143
  try {
12088
- const entries = await readdir17(paths.domain);
12144
+ const entries = await readdir19(paths.domain);
12089
12145
  for (const entry of entries) {
12090
12146
  if (entry.endsWith(".md")) {
12091
- const content = await readTextFile(join24(paths.domain, entry));
12147
+ const content = await readTextFile(join26(paths.domain, entry));
12092
12148
  if (content)
12093
12149
  domainFiles[entry] = content.slice(0, 1000);
12094
12150
  }
@@ -12096,7 +12152,7 @@ async function execute13(paths, phase) {
12096
12152
  } catch {}
12097
12153
  let genomeVersion = 0;
12098
12154
  try {
12099
- const lineageEntries = await readdir17(paths.lineage);
12155
+ const lineageEntries = await readdir19(paths.lineage);
12100
12156
  genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length;
12101
12157
  } catch {}
12102
12158
  emitOutput({
@@ -12147,20 +12203,26 @@ async function execute13(paths, phase) {
12147
12203
  });
12148
12204
  }
12149
12205
  if (phase === "complete") {
12206
+ const genId = state?.id || "manual";
12207
+ const config = await ConfigManager.read(paths);
12208
+ config.lastSyncedGeneration = genId;
12209
+ await ConfigManager.write(paths, config);
12150
12210
  emitOutput({
12151
12211
  status: "ok",
12152
12212
  command: "sync-genome",
12153
12213
  phase: "complete",
12154
- completed: ["gate", "context-collect", "analyze", "apply"],
12214
+ completed: ["gate", "context-collect", "analyze", "apply", "update-sync-state"],
12155
12215
  context: {
12156
- hasActiveGeneration: hasActiveGen
12216
+ hasActiveGeneration: hasActiveGen,
12217
+ lastSyncedGeneration: genId
12157
12218
  },
12158
- message: hasActiveGen ? "Genome differences recorded as backlog items. Apply during Completion." : "Genome synchronized."
12219
+ message: hasActiveGen ? "Genome differences recorded as backlog items. Apply during Completion." : `Genome synchronized. lastSyncedGeneration: ${genId}`
12159
12220
  });
12160
12221
  }
12161
12222
  }
12162
12223
  var init_sync_genome = __esm(() => {
12163
12224
  init_generation();
12225
+ init_config();
12164
12226
  init_fs();
12165
12227
  });
12166
12228
 
@@ -12169,8 +12231,8 @@ var exports_sync_environment = {};
12169
12231
  __export(exports_sync_environment, {
12170
12232
  execute: () => execute14
12171
12233
  });
12172
- import { readdir as readdir18 } from "fs/promises";
12173
- import { join as join25 } from "path";
12234
+ import { readdir as readdir20 } from "fs/promises";
12235
+ import { join as join27 } from "path";
12174
12236
  async function execute14(paths, phase) {
12175
12237
  const gm = new GenerationManager(paths);
12176
12238
  const state = await gm.current();
@@ -12179,10 +12241,10 @@ async function execute14(paths, phase) {
12179
12241
  const envSummary = await readTextFile(paths.environmentSummary);
12180
12242
  const envDocs = {};
12181
12243
  try {
12182
- const docsEntries = await readdir18(paths.environmentDocs);
12244
+ const docsEntries = await readdir20(paths.environmentDocs);
12183
12245
  for (const entry of docsEntries) {
12184
12246
  if (entry.endsWith(".md")) {
12185
- const content = await readTextFile(join25(paths.environmentDocs, entry));
12247
+ const content = await readTextFile(join27(paths.environmentDocs, entry));
12186
12248
  if (content)
12187
12249
  envDocs[entry] = content.slice(0, 1000);
12188
12250
  }
@@ -12190,7 +12252,7 @@ async function execute14(paths, phase) {
12190
12252
  } catch {}
12191
12253
  let linksContent = null;
12192
12254
  try {
12193
- linksContent = await readTextFile(join25(paths.environmentResources, "links.md"));
12255
+ linksContent = await readTextFile(join27(paths.environmentResources, "links.md"));
12194
12256
  } catch {}
12195
12257
  emitOutput({
12196
12258
  status: "prompt",
@@ -12262,7 +12324,7 @@ var exports_help = {};
12262
12324
  __export(exports_help, {
12263
12325
  execute: () => execute15
12264
12326
  });
12265
- import { join as join26 } from "path";
12327
+ import { join as join28 } from "path";
12266
12328
  function detectLanguage(configContent) {
12267
12329
  const raw = configContent?.match(/language:\s*(\S+)/)?.[1] ?? null;
12268
12330
  if (raw && raw in LANGUAGE_ALIASES)
@@ -12297,7 +12359,7 @@ async function execute15(paths) {
12297
12359
  const gm = new GenerationManager(paths);
12298
12360
  const state = await gm.current();
12299
12361
  const configContent = await readTextFile(paths.config);
12300
- const installedVersion = "0.15.0";
12362
+ const installedVersion = "0.15.2";
12301
12363
  const autoUpdate = configContent?.match(/autoUpdate:\s*(true|false)/)?.[1] === "true";
12302
12364
  const versionDisplay = formatVersionLine(installedVersion, !autoUpdate);
12303
12365
  const rawLang = detectLanguage(configContent);
@@ -12308,7 +12370,7 @@ async function execute15(paths) {
12308
12370
  const stateDisplay = state?.id ? `Active: **${state.id}** — ${state.goal} (Stage: ${state.stage})` : "No active Generation → `/reap.start` or `/reap.evolve`";
12309
12371
  const lines = buildLines(versionDisplay, lang, stateDisplay);
12310
12372
  if (topic) {
12311
- const guidePath = join26(ReapPaths.packageHooksDir, "reap-guide.md");
12373
+ const guidePath = join28(ReapPaths.packageHooksDir, "reap-guide.md");
12312
12374
  const reapGuide = await readTextFile(guidePath) ?? "";
12313
12375
  emitOutput({
12314
12376
  status: "prompt",
@@ -12613,8 +12675,8 @@ var init_merge = __esm(() => {
12613
12675
  });
12614
12676
 
12615
12677
  // src/core/merge-generation.ts
12616
- import { readdir as readdir19, mkdir as mkdir9, rename as rename3 } from "fs/promises";
12617
- import { join as join27 } from "path";
12678
+ import { readdir as readdir21, mkdir as mkdir10, rename as rename3 } from "fs/promises";
12679
+ import { join as join29 } from "path";
12618
12680
 
12619
12681
  class MergeGenerationManager {
12620
12682
  paths;
@@ -12625,7 +12687,7 @@ class MergeGenerationManager {
12625
12687
  const content = await readTextFile(this.paths.currentYml);
12626
12688
  if (content === null || !content.trim())
12627
12689
  return null;
12628
- const state = import_yaml10.default.parse(content);
12690
+ const state = import_yaml11.default.parse(content);
12629
12691
  if (!state.type)
12630
12692
  state.type = "normal";
12631
12693
  if (!state.parents)
@@ -12658,7 +12720,7 @@ class MergeGenerationManager {
12658
12720
  genomeHash,
12659
12721
  commonAncestor: commonAncestor ?? undefined
12660
12722
  };
12661
- await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12723
+ await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12662
12724
  return state;
12663
12725
  }
12664
12726
  async createFromBranch(targetBranch, projectRoot) {
@@ -12702,7 +12764,7 @@ class MergeGenerationManager {
12702
12764
  genomeHash,
12703
12765
  commonAncestor: commonAncestor ?? undefined
12704
12766
  };
12705
- await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12767
+ await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12706
12768
  return { state, report };
12707
12769
  }
12708
12770
  async resolveLatestGenId(branch, cwd) {
@@ -12727,7 +12789,7 @@ class MergeGenerationManager {
12727
12789
  const content = gitShow(ref, metaFile, cwd);
12728
12790
  if (content) {
12729
12791
  try {
12730
- const meta = import_yaml10.default.parse(content);
12792
+ const meta = import_yaml11.default.parse(content);
12731
12793
  if (meta?.id)
12732
12794
  metas.push(meta);
12733
12795
  } catch {}
@@ -12774,7 +12836,7 @@ class MergeGenerationManager {
12774
12836
  if (!state.timeline)
12775
12837
  state.timeline = [];
12776
12838
  state.timeline.push({ stage: next, at: new Date().toISOString() });
12777
- await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12839
+ await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12778
12840
  return state;
12779
12841
  }
12780
12842
  async complete() {
@@ -12790,7 +12852,7 @@ class MergeGenerationManager {
12790
12852
  const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
12791
12853
  const genDirName = `${state.id}-${goalSlug}`;
12792
12854
  const genDir = this.paths.generationDir(genDirName);
12793
- await mkdir9(genDir, { recursive: true });
12855
+ await mkdir10(genDir, { recursive: true });
12794
12856
  const meta = {
12795
12857
  id: state.id,
12796
12858
  type: "merge",
@@ -12800,19 +12862,19 @@ class MergeGenerationManager {
12800
12862
  startedAt: state.startedAt,
12801
12863
  completedAt: now
12802
12864
  };
12803
- await writeTextFile(join27(genDir, "meta.yml"), import_yaml10.default.stringify(meta));
12804
- const lifeEntries = await readdir19(this.paths.life);
12865
+ await writeTextFile(join29(genDir, "meta.yml"), import_yaml11.default.stringify(meta));
12866
+ const lifeEntries = await readdir21(this.paths.life);
12805
12867
  for (const entry of lifeEntries) {
12806
12868
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
12807
- await rename3(join27(this.paths.life, entry), join27(genDir, entry));
12869
+ await rename3(join29(this.paths.life, entry), join29(genDir, entry));
12808
12870
  }
12809
12871
  }
12810
- const backlogDir = join27(genDir, "backlog");
12811
- await mkdir9(backlogDir, { recursive: true });
12872
+ const backlogDir = join29(genDir, "backlog");
12873
+ await mkdir10(backlogDir, { recursive: true });
12812
12874
  try {
12813
- const backlogEntries = await readdir19(this.paths.backlog);
12875
+ const backlogEntries = await readdir21(this.paths.backlog);
12814
12876
  for (const entry of backlogEntries) {
12815
- await rename3(join27(this.paths.backlog, entry), join27(backlogDir, entry));
12877
+ await rename3(join29(this.paths.backlog, entry), join29(backlogDir, entry));
12816
12878
  }
12817
12879
  } catch {}
12818
12880
  await writeTextFile(this.paths.currentYml, "");
@@ -12820,7 +12882,7 @@ class MergeGenerationManager {
12820
12882
  return compression;
12821
12883
  }
12822
12884
  async save(state) {
12823
- await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12885
+ await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12824
12886
  }
12825
12887
  }
12826
12888
  function canFastForward(localLatestId, remoteLatestId, allMetas) {
@@ -12882,7 +12944,7 @@ function findCommonAncestor(idA, idB, metas) {
12882
12944
  }
12883
12945
  return null;
12884
12946
  }
12885
- var import_yaml10;
12947
+ var import_yaml11;
12886
12948
  var init_merge_generation = __esm(() => {
12887
12949
  init_merge_lifecycle();
12888
12950
  init_compression();
@@ -12892,7 +12954,7 @@ var init_merge_generation = __esm(() => {
12892
12954
  init_merge();
12893
12955
  init_lineage();
12894
12956
  init_compression();
12895
- import_yaml10 = __toESM(require_dist(), 1);
12957
+ import_yaml11 = __toESM(require_dist(), 1);
12896
12958
  });
12897
12959
 
12898
12960
  // src/cli/commands/run/merge-start.ts
@@ -13740,12 +13802,174 @@ var init_merge2 = __esm(() => {
13740
13802
  init_lineage();
13741
13803
  });
13742
13804
 
13805
+ // src/cli/commands/run/evolve-recovery.ts
13806
+ var exports_evolve_recovery = {};
13807
+ __export(exports_evolve_recovery, {
13808
+ execute: () => execute26
13809
+ });
13810
+ import { join as join30 } from "path";
13811
+ import { readdir as readdir22 } from "fs/promises";
13812
+ function getFlag4(args, name) {
13813
+ const idx = args.indexOf(`--${name}`);
13814
+ return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
13815
+ }
13816
+ function getPositionals3(args, valueFlags) {
13817
+ const result = [];
13818
+ for (let i = 0;i < args.length; i++) {
13819
+ if (args[i].startsWith("--")) {
13820
+ const flagName = args[i].slice(2);
13821
+ if (valueFlags.includes(flagName) && i + 1 < args.length)
13822
+ i++;
13823
+ continue;
13824
+ }
13825
+ result.push(args[i]);
13826
+ }
13827
+ return result;
13828
+ }
13829
+ async function loadLineageArtifacts(paths, genId) {
13830
+ const completed = await listCompleted(paths);
13831
+ const genDir = completed.find((d) => d.startsWith(genId));
13832
+ if (genDir) {
13833
+ const dirPath = join30(paths.lineage, genDir);
13834
+ const [objective, planning, implementation, completion] = await Promise.all([
13835
+ readTextFile(join30(dirPath, "01-objective.md")),
13836
+ readTextFile(join30(dirPath, "02-planning.md")),
13837
+ readTextFile(join30(dirPath, "03-implementation.md")),
13838
+ readTextFile(join30(dirPath, "05-completion.md"))
13839
+ ]);
13840
+ return {
13841
+ objective: objective ?? "(not found)",
13842
+ planning: planning ?? "(not found)",
13843
+ implementation: implementation ?? "(not found)",
13844
+ completion: completion ?? "(not found)"
13845
+ };
13846
+ }
13847
+ try {
13848
+ const entries = await readdir22(paths.lineage);
13849
+ const compressedFile = entries.find((e) => e.startsWith(genId) && e.endsWith(".md"));
13850
+ if (compressedFile) {
13851
+ const content = await readTextFile(join30(paths.lineage, compressedFile));
13852
+ if (content) {
13853
+ return {
13854
+ objective: content,
13855
+ planning: "(compressed - see above)",
13856
+ implementation: "(compressed - see above)",
13857
+ completion: "(compressed - see above)"
13858
+ };
13859
+ }
13860
+ }
13861
+ } catch {}
13862
+ return null;
13863
+ }
13864
+ async function execute26(paths, phase, argv = []) {
13865
+ const positionals = getPositionals3(argv, ["reason"]);
13866
+ const targetGenIds = positionals;
13867
+ const reason = getFlag4(argv, "reason");
13868
+ const gm = new GenerationManager(paths);
13869
+ if (!phase || phase === "review") {
13870
+ if (targetGenIds.length === 0) {
13871
+ emitError("evolve-recovery", 'Target generation ID(s) required. Usage: reap run evolve-recovery <gen-id> [<gen-id>...] [--reason "..."]');
13872
+ }
13873
+ const state = await gm.current();
13874
+ if (state && state.id) {
13875
+ emitError("evolve-recovery", `Generation ${state.id} is in progress (stage: ${state.stage}). Complete it before starting a recovery.`);
13876
+ }
13877
+ const artifactsByGen = {};
13878
+ const notFound = [];
13879
+ for (const genId of targetGenIds) {
13880
+ const artifacts = await loadLineageArtifacts(paths, genId);
13881
+ if (artifacts) {
13882
+ artifactsByGen[genId] = artifacts;
13883
+ } else {
13884
+ notFound.push(genId);
13885
+ }
13886
+ }
13887
+ if (notFound.length > 0) {
13888
+ emitError("evolve-recovery", `Generation(s) not found in lineage: ${notFound.join(", ")}`);
13889
+ }
13890
+ const artifactSections = [];
13891
+ for (const [genId, artifacts] of Object.entries(artifactsByGen)) {
13892
+ artifactSections.push(`### Generation: ${genId}`, "", "#### 01-objective.md", artifacts.objective.slice(0, 2000), "", "#### 02-planning.md", artifacts.planning.slice(0, 2000), "", "#### 03-implementation.md", artifacts.implementation.slice(0, 2000), "", "#### 05-completion.md", artifacts.completion.slice(0, 2000), "");
13893
+ }
13894
+ emitOutput({
13895
+ status: "prompt",
13896
+ command: "evolve-recovery",
13897
+ phase: "review",
13898
+ completed: ["gate", "artifact-load"],
13899
+ context: {
13900
+ targetGenIds,
13901
+ reason: reason ?? null,
13902
+ artifactsByGen
13903
+ },
13904
+ prompt: [
13905
+ "## Recovery Review",
13906
+ "",
13907
+ "Review the following generation artifacts and determine if a recovery generation is needed.",
13908
+ "",
13909
+ "### Review Criteria",
13910
+ "(a) **Artifact Inconsistency**: Are there contradictions or misalignments between objective, planning, implementation, and completion artifacts?",
13911
+ "(b) **Structural Defects**: Are there architectural issues, missing edge cases, or technical debt introduced?",
13912
+ "(c) **Human-Specified Corrections**: " + (reason ? `The human specified: "${reason}"` : "No specific corrections requested."),
13913
+ "",
13914
+ "### Target Generation Artifacts",
13915
+ "",
13916
+ ...artifactSections,
13917
+ "",
13918
+ "### Decision",
13919
+ "If recovery is needed, run: `reap run evolve-recovery --phase create " + targetGenIds.join(" ") + (reason ? ` --reason "${reason}"` : "") + "`",
13920
+ 'If no recovery is needed, report: "No recovery needed" with your reasoning.'
13921
+ ].join(`
13922
+ `)
13923
+ });
13924
+ }
13925
+ if (phase === "create") {
13926
+ if (targetGenIds.length === 0) {
13927
+ emitError("evolve-recovery", "Target generation ID(s) required for create phase.");
13928
+ }
13929
+ const existing = await gm.current();
13930
+ if (existing && existing.id) {
13931
+ emitError("evolve-recovery", `Generation ${existing.id} is already in progress.`);
13932
+ }
13933
+ let genomeVersion = 1;
13934
+ try {
13935
+ const lineageEntries = await readdir22(paths.lineage);
13936
+ genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length + 1;
13937
+ } catch {}
13938
+ const goal = reason ? `Recovery: ${reason} (corrects ${targetGenIds.join(", ")})` : `Recovery for ${targetGenIds.join(", ")}`;
13939
+ const state = await gm.createRecoveryGeneration(goal, genomeVersion, targetGenIds);
13940
+ const { nonce, hash } = generateToken(state.id, state.stage);
13941
+ state.expectedHash = hash;
13942
+ await gm.save(state);
13943
+ emitOutput({
13944
+ status: "prompt",
13945
+ command: "evolve-recovery",
13946
+ phase: "created",
13947
+ completed: ["gate", "create-generation"],
13948
+ context: {
13949
+ generationId: state.id,
13950
+ goal: state.goal,
13951
+ type: state.type,
13952
+ recovers: state.recovers,
13953
+ parents: state.parents,
13954
+ genomeHash: state.genomeHash
13955
+ },
13956
+ prompt: `Recovery generation ${state.id} created (corrects: ${targetGenIds.join(", ")}). Proceed with /reap.objective or /reap.evolve.`,
13957
+ message: `Recovery generation ${state.id} started.`
13958
+ });
13959
+ }
13960
+ }
13961
+ var init_evolve_recovery = __esm(() => {
13962
+ init_generation();
13963
+ init_fs();
13964
+ init_lineage();
13965
+ });
13966
+
13743
13967
  // src/cli/commands/run/pull.ts
13744
13968
  var exports_pull = {};
13745
13969
  __export(exports_pull, {
13746
- execute: () => execute26
13970
+ execute: () => execute27
13747
13971
  });
13748
- async function execute26(paths, phase, argv = []) {
13972
+ async function execute27(paths, phase, argv = []) {
13749
13973
  const positionals = argv.filter((a) => !a.startsWith("--"));
13750
13974
  const targetBranchArg = positionals[0];
13751
13975
  const gm = new GenerationManager(paths);
@@ -13866,9 +14090,9 @@ var init_pull = __esm(() => {
13866
14090
  // src/cli/commands/run/config.ts
13867
14091
  var exports_config = {};
13868
14092
  __export(exports_config, {
13869
- execute: () => execute27
14093
+ execute: () => execute28
13870
14094
  });
13871
- async function execute27(paths) {
14095
+ async function execute28(paths) {
13872
14096
  const config = await ConfigManager.read(paths);
13873
14097
  const lines = [
13874
14098
  `REAP Configuration (${paths.config})`,
@@ -13897,25 +14121,119 @@ var init_config2 = __esm(() => {
13897
14121
  init_config();
13898
14122
  });
13899
14123
 
14124
+ // src/cli/commands/run/update-genome.ts
14125
+ var exports_update_genome = {};
14126
+ __export(exports_update_genome, {
14127
+ execute: () => execute29
14128
+ });
14129
+ async function execute29(paths, phase) {
14130
+ const gm = new GenerationManager(paths);
14131
+ const current = await gm.current();
14132
+ if (current !== null) {
14133
+ emitError(COMMAND, `Active generation exists (${current.id}). Cannot run update-genome during a generation.`);
14134
+ }
14135
+ const backlogItems = await scanBacklog(paths.backlog);
14136
+ const pending = backlogItems.filter((b) => b.type === "genome-change" && b.status === "pending");
14137
+ if (phase === "apply") {
14138
+ await applyPhase(paths, pending);
14139
+ } else {
14140
+ scanPhase(pending);
14141
+ }
14142
+ }
14143
+ function scanPhase(pending) {
14144
+ if (pending.length === 0) {
14145
+ emitOutput({
14146
+ status: "ok",
14147
+ command: COMMAND,
14148
+ phase: "scan",
14149
+ completed: ["gate", "scan"],
14150
+ message: "No pending genome changes."
14151
+ });
14152
+ }
14153
+ emitOutput({
14154
+ status: "prompt",
14155
+ command: COMMAND,
14156
+ phase: "scan",
14157
+ completed: ["gate", "scan"],
14158
+ context: {
14159
+ pendingCount: pending.length,
14160
+ items: pending.map((b) => ({
14161
+ filename: b.filename,
14162
+ title: b.title,
14163
+ body: b.body
14164
+ }))
14165
+ },
14166
+ prompt: [
14167
+ "Pending genome-change backlog items are listed in context.items.",
14168
+ "For each item, apply the described changes to the corresponding .reap/genome/ files.",
14169
+ "Only modify files under .reap/genome/. Do NOT modify source code.",
14170
+ "After all changes are applied, run: reap run update-genome --phase apply"
14171
+ ].join(`
14172
+ `),
14173
+ nextCommand: "reap run update-genome --phase apply"
14174
+ });
14175
+ }
14176
+ async function applyPhase(paths, pending) {
14177
+ if (pending.length === 0) {
14178
+ emitOutput({
14179
+ status: "ok",
14180
+ command: COMMAND,
14181
+ phase: "apply",
14182
+ completed: ["gate", "scan", "apply"],
14183
+ message: "No pending genome changes to apply."
14184
+ });
14185
+ }
14186
+ for (const item of pending) {
14187
+ await markBacklogConsumed(paths.backlog, item.filename, "update-genome");
14188
+ }
14189
+ const config = await ConfigManager.read(paths);
14190
+ config.genomeVersion = (config.genomeVersion ?? 0) + 1;
14191
+ await ConfigManager.write(paths, config);
14192
+ emitOutput({
14193
+ status: "ok",
14194
+ command: COMMAND,
14195
+ phase: "apply",
14196
+ completed: ["gate", "scan", "apply", "version-bump"],
14197
+ context: {
14198
+ consumedCount: pending.length,
14199
+ consumedFiles: pending.map((b) => b.filename),
14200
+ genomeVersion: config.genomeVersion
14201
+ },
14202
+ message: `Applied ${pending.length} genome-change(s). genomeVersion → v${config.genomeVersion}. Please commit the changes.`,
14203
+ prompt: [
14204
+ `${pending.length} genome-change backlog item(s) consumed. genomeVersion bumped to v${config.genomeVersion}.`,
14205
+ "Commit the genome changes with message:",
14206
+ ` chore: update-genome v${config.genomeVersion} — ${pending.map((b) => b.title).join(", ")}`
14207
+ ].join(`
14208
+ `)
14209
+ });
14210
+ }
14211
+ var COMMAND = "update-genome";
14212
+ var init_update_genome = __esm(() => {
14213
+ init_config();
14214
+ init_generation();
14215
+ init_backlog();
14216
+ });
14217
+
13900
14218
  // src/cli/commands/run/refresh-knowledge.ts
13901
14219
  var exports_refresh_knowledge = {};
13902
14220
  __export(exports_refresh_knowledge, {
13903
- execute: () => execute28
14221
+ execute: () => execute30
13904
14222
  });
13905
- import { join as join28 } from "path";
13906
- import { readdir as readdir20 } from "fs/promises";
14223
+ import { join as join31 } from "path";
14224
+ import { readdir as readdir23 } from "fs/promises";
13907
14225
  async function loadGenome(genomeDir) {
13908
14226
  let content = "";
13909
14227
  let l1Lines = 0;
13910
14228
  let smLimit = null;
13911
- const smContent = await readTextFile(join28(genomeDir, "source-map.md"));
14229
+ const smContent = await readTextFile(join31(genomeDir, "source-map.md"));
13912
14230
  if (smContent) {
13913
14231
  const limitMatch = smContent.match(/줄 수 한도:\s*~?(\d+)줄/);
13914
14232
  if (limitMatch)
13915
14233
  smLimit = parseInt(limitMatch[1], 10);
13916
14234
  }
13917
14235
  for (const file of L1_FILES) {
13918
- const fileContent = await readTextFile(join28(genomeDir, file));
14236
+ const fileContent = await readTextFile(join31(genomeDir, file));
13919
14237
  if (!fileContent)
13920
14238
  continue;
13921
14239
  const lines = fileContent.split(`
@@ -13937,14 +14255,14 @@ ${fileContent.split(`
13937
14255
  `;
13938
14256
  }
13939
14257
  }
13940
- const domainDir = join28(genomeDir, "domain");
14258
+ const domainDir = join31(genomeDir, "domain");
13941
14259
  if (await fileExists(domainDir)) {
13942
14260
  let l2Lines = 0;
13943
14261
  let l2Overflow = false;
13944
14262
  try {
13945
- const domainFiles = (await readdir20(domainDir)).filter((f) => f.endsWith(".md")).sort();
14263
+ const domainFiles = (await readdir23(domainDir)).filter((f) => f.endsWith(".md")).sort();
13946
14264
  for (const file of domainFiles) {
13947
- const fileContent = await readTextFile(join28(domainDir, file));
14265
+ const fileContent = await readTextFile(join31(domainDir, file));
13948
14266
  if (!fileContent)
13949
14267
  continue;
13950
14268
  const lines = fileContent.split(`
@@ -13994,8 +14312,8 @@ function buildStrictSection(strict, genStage) {
13994
14312
  }
13995
14313
  return sections;
13996
14314
  }
13997
- async function execute28(paths) {
13998
- const guidePath = join28(ReapPaths.packageHooksDir, "reap-guide.md");
14315
+ async function execute30(paths) {
14316
+ const guidePath = join31(ReapPaths.packageHooksDir, "reap-guide.md");
13999
14317
  const reapGuide = await readTextFile(guidePath) || "";
14000
14318
  const { content: genomeContent } = await loadGenome(paths.genome);
14001
14319
  const envSummary = await readTextFile(paths.environmentSummary) || "";
@@ -14085,7 +14403,7 @@ async function runCommand(command, phase, argv = []) {
14085
14403
  try {
14086
14404
  const config = await ConfigManager.read(paths);
14087
14405
  if (config.autoIssueReport) {
14088
- const version = "0.15.0";
14406
+ const version = "0.15.2";
14089
14407
  const errMsg = err instanceof Error ? err.message : String(err);
14090
14408
  const title = `[auto] reap run ${command}: ${errMsg.slice(0, 80)}`;
14091
14409
  const body = [
@@ -14131,8 +14449,10 @@ var init_run = __esm(() => {
14131
14449
  "merge-completion": () => Promise.resolve().then(() => (init_merge_completion(), exports_merge_completion)),
14132
14450
  "merge-evolve": () => Promise.resolve().then(() => (init_merge_evolve(), exports_merge_evolve)),
14133
14451
  merge: () => Promise.resolve().then(() => (init_merge2(), exports_merge)),
14452
+ "evolve-recovery": () => Promise.resolve().then(() => (init_evolve_recovery(), exports_evolve_recovery)),
14134
14453
  pull: () => Promise.resolve().then(() => (init_pull(), exports_pull)),
14135
14454
  config: () => Promise.resolve().then(() => (init_config2(), exports_config)),
14455
+ "update-genome": () => Promise.resolve().then(() => (init_update_genome(), exports_update_genome)),
14136
14456
  refreshKnowledge: () => Promise.resolve().then(() => (init_refresh_knowledge(), exports_refresh_knowledge))
14137
14457
  };
14138
14458
  });
@@ -14159,8 +14479,8 @@ import { createInterface } from "readline";
14159
14479
  // src/cli/commands/init.ts
14160
14480
  init_paths();
14161
14481
  init_config();
14162
- import { mkdir as mkdir3, readdir as readdir4, chmod } from "fs/promises";
14163
- import { join as join5 } from "path";
14482
+ import { mkdir as mkdir4, readdir as readdir5, chmod } from "fs/promises";
14483
+ import { join as join6 } from "path";
14164
14484
 
14165
14485
  // src/core/agents/claude-code.ts
14166
14486
  init_fs();
@@ -14610,6 +14930,42 @@ class AgentRegistry {
14610
14930
 
14611
14931
  // src/cli/commands/init.ts
14612
14932
  init_fs();
14933
+
14934
+ // src/core/skills.ts
14935
+ init_paths();
14936
+ init_fs();
14937
+ import { readdir as readdir3, mkdir as mkdir3 } from "fs/promises";
14938
+ import { join as join4 } from "path";
14939
+ async function syncSkillsToProject(projectRoot, dryRun = false) {
14940
+ const projectClaudeSkills = join4(projectRoot, ".claude", "skills");
14941
+ const reapCmdFiles = (await readdir3(ReapPaths.userReapCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
14942
+ let installed = 0;
14943
+ for (const file of reapCmdFiles) {
14944
+ const src = await readTextFileOrThrow(join4(ReapPaths.userReapCommands, file));
14945
+ const name = file.replace(/\.md$/, "");
14946
+ const skillDir = join4(projectClaudeSkills, name);
14947
+ const skillFile = join4(skillDir, "SKILL.md");
14948
+ const fmMatch = src.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
14949
+ const description = fmMatch ? fmMatch[1].match(/^description:\s*"?([^"\n]*)"?/m)?.[1]?.trim() ?? "" : "";
14950
+ const body = fmMatch ? fmMatch[2] : src;
14951
+ const skillContent = `---
14952
+ name: ${name}
14953
+ description: "${description}"
14954
+ ---
14955
+ ${body}`;
14956
+ const existing = await readTextFile(skillFile);
14957
+ if (existing !== null && existing === skillContent)
14958
+ continue;
14959
+ if (!dryRun) {
14960
+ await mkdir3(skillDir, { recursive: true });
14961
+ await writeTextFile(skillFile, skillContent);
14962
+ }
14963
+ installed++;
14964
+ }
14965
+ return { installed, total: reapCmdFiles.length };
14966
+ }
14967
+
14968
+ // src/cli/commands/init.ts
14613
14969
  var COMMAND_NAMES = [
14614
14970
  "reap.objective",
14615
14971
  "reap.planning",
@@ -14636,10 +14992,12 @@ var COMMAND_NAMES = [
14636
14992
  "reap.merge.validation",
14637
14993
  "reap.merge.completion",
14638
14994
  "reap.merge.evolve",
14995
+ "reap.evolve.recovery",
14639
14996
  "reap.merge",
14640
14997
  "reap.pull",
14641
14998
  "reap.push",
14642
14999
  "reap.config",
15000
+ "reap.update-genome",
14643
15001
  "reap.refreshKnowledge"
14644
15002
  ];
14645
15003
  async function initProject(projectRoot, projectName, entryMode, preset, onProgress) {
@@ -14649,21 +15007,21 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14649
15007
  throw new Error(".reap/ already exists. This is already a REAP project.");
14650
15008
  }
14651
15009
  if (preset) {
14652
- const presetDir = join5(ReapPaths.packageTemplatesDir, "presets", preset);
14653
- const presetExists = await fileExists(join5(presetDir, "principles.md"));
15010
+ const presetDir = join6(ReapPaths.packageTemplatesDir, "presets", preset);
15011
+ const presetExists = await fileExists(join6(presetDir, "principles.md"));
14654
15012
  if (!presetExists) {
14655
15013
  throw new Error(`Unknown preset: "${preset}". Available presets: bun-hono-react`);
14656
15014
  }
14657
15015
  }
14658
15016
  log("Creating .reap/ directory structure...");
14659
- await mkdir3(paths.genome, { recursive: true });
14660
- await mkdir3(paths.domain, { recursive: true });
14661
- await mkdir3(paths.environment, { recursive: true });
14662
- await mkdir3(join5(paths.environment, "docs"), { recursive: true });
14663
- await mkdir3(join5(paths.environment, "resources"), { recursive: true });
14664
- await mkdir3(paths.life, { recursive: true });
14665
- await mkdir3(paths.backlog, { recursive: true });
14666
- await mkdir3(paths.lineage, { recursive: true });
15017
+ await mkdir4(paths.genome, { recursive: true });
15018
+ await mkdir4(paths.domain, { recursive: true });
15019
+ await mkdir4(paths.environment, { recursive: true });
15020
+ await mkdir4(join6(paths.environment, "docs"), { recursive: true });
15021
+ await mkdir4(join6(paths.environment, "resources"), { recursive: true });
15022
+ await mkdir4(paths.life, { recursive: true });
15023
+ await mkdir4(paths.backlog, { recursive: true });
15024
+ await mkdir4(paths.lineage, { recursive: true });
14667
15025
  log("Writing config.yml...");
14668
15026
  let hasGhCli = false;
14669
15027
  try {
@@ -14676,7 +15034,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14676
15034
  }
14677
15035
  const detectedLanguage = await AgentRegistry.readLanguage();
14678
15036
  const config = {
14679
- version: "0.15.0",
15037
+ version: "0.15.2",
14680
15038
  project: projectName,
14681
15039
  entryMode,
14682
15040
  strict: false,
@@ -14689,10 +15047,10 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14689
15047
  await ConfigManager.write(paths, config);
14690
15048
  log("Setting up Genome templates...");
14691
15049
  const genomeTemplates = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
14692
- const genomeSourceDir = preset ? join5(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
15050
+ const genomeSourceDir = preset ? join6(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
14693
15051
  for (const file of genomeTemplates) {
14694
- const src = join5(genomeSourceDir, file);
14695
- const dest = join5(paths.genome, file);
15052
+ const src = join6(genomeSourceDir, file);
15053
+ const dest = join6(paths.genome, file);
14696
15054
  await writeTextFile(dest, await readTextFileOrThrow(src));
14697
15055
  }
14698
15056
  if ((entryMode === "adoption" || entryMode === "migration") && !preset) {
@@ -14701,35 +15059,47 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14701
15059
  await syncGenomeFromProject2(projectRoot, paths.genome, log);
14702
15060
  }
14703
15061
  log("Installing artifact templates...");
14704
- await mkdir3(ReapPaths.userReapTemplates, { recursive: true });
15062
+ await mkdir4(ReapPaths.userReapTemplates, { recursive: true });
14705
15063
  const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
14706
15064
  for (const file of artifactFiles) {
14707
- const src = join5(ReapPaths.packageArtifactsDir, file);
14708
- const dest = join5(ReapPaths.userReapTemplates, file);
15065
+ const src = join6(ReapPaths.packageArtifactsDir, file);
15066
+ const dest = join6(ReapPaths.userReapTemplates, file);
14709
15067
  await writeTextFile(dest, await readTextFileOrThrow(src));
14710
15068
  }
14711
- const domainGuideSrc = join5(ReapPaths.packageGenomeDir, "domain/README.md");
14712
- const domainGuideDest = join5(ReapPaths.userReapTemplates, "domain-guide.md");
15069
+ const domainGuideSrc = join6(ReapPaths.packageGenomeDir, "domain/README.md");
15070
+ const domainGuideDest = join6(ReapPaths.userReapTemplates, "domain-guide.md");
14713
15071
  await writeTextFile(domainGuideDest, await readTextFileOrThrow(domainGuideSrc));
14714
- const mergeTemplatesDir = join5(ReapPaths.userReapTemplates, "merge");
14715
- await mkdir3(mergeTemplatesDir, { recursive: true });
15072
+ const mergeTemplatesDir = join6(ReapPaths.userReapTemplates, "merge");
15073
+ await mkdir4(mergeTemplatesDir, { recursive: true });
14716
15074
  const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
14717
- const mergeSourceDir = join5(ReapPaths.packageArtifactsDir, "merge");
15075
+ const mergeSourceDir = join6(ReapPaths.packageArtifactsDir, "merge");
14718
15076
  for (const file of mergeArtifactFiles) {
14719
- const src = join5(mergeSourceDir, file);
14720
- const dest = join5(mergeTemplatesDir, file);
15077
+ const src = join6(mergeSourceDir, file);
15078
+ const dest = join6(mergeTemplatesDir, file);
14721
15079
  await writeTextFile(dest, await readTextFileOrThrow(src));
14722
15080
  }
14723
15081
  log("Installing hook conditions...");
14724
- const conditionsSourceDir = join5(ReapPaths.packageTemplatesDir, "conditions");
14725
- const conditionsDestDir = join5(paths.hooks, "conditions");
14726
- await mkdir3(conditionsDestDir, { recursive: true });
14727
- const conditionFiles = await readdir4(conditionsSourceDir);
15082
+ const conditionsSourceDir = join6(ReapPaths.packageTemplatesDir, "conditions");
15083
+ const conditionsDestDir = join6(paths.hooks, "conditions");
15084
+ await mkdir4(conditionsDestDir, { recursive: true });
15085
+ const conditionFiles = await readdir5(conditionsSourceDir);
14728
15086
  for (const file of conditionFiles) {
14729
15087
  if (!file.endsWith(".sh"))
14730
15088
  continue;
14731
- const src = join5(conditionsSourceDir, file);
14732
- const dest = join5(conditionsDestDir, file);
15089
+ const src = join6(conditionsSourceDir, file);
15090
+ const dest = join6(conditionsDestDir, file);
15091
+ await writeTextFile(dest, await readTextFileOrThrow(src));
15092
+ await chmod(dest, 493);
15093
+ }
15094
+ log("Installing hook scripts...");
15095
+ const hooksSourceDir = join6(ReapPaths.packageTemplatesDir, "hooks");
15096
+ const { readdir: readdirAsync } = await import("fs/promises");
15097
+ const hookFiles = await readdirAsync(hooksSourceDir);
15098
+ for (const file of hookFiles) {
15099
+ if (!file.endsWith(".sh") || !file.startsWith("on"))
15100
+ continue;
15101
+ const src = join6(hooksSourceDir, file);
15102
+ const dest = join6(paths.hooks, file);
14733
15103
  await writeTextFile(dest, await readTextFileOrThrow(src));
14734
15104
  await chmod(dest, 493);
14735
15105
  }
@@ -14749,6 +15119,11 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14749
15119
  if (detectedAgents.length === 0) {
14750
15120
  log(" No AI agents detected.");
14751
15121
  }
15122
+ log("Syncing skills to project...");
15123
+ const skillsResult = await syncSkillsToProject(projectRoot);
15124
+ if (skillsResult.installed > 0) {
15125
+ log(` .claude/skills/ (${skillsResult.installed} synced)`);
15126
+ }
14752
15127
  const autoSynced = (entryMode === "adoption" || entryMode === "migration") && !preset;
14753
15128
  if (!autoSynced) {
14754
15129
  log(`
@@ -14759,8 +15134,8 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14759
15134
 
14760
15135
  // src/cli/commands/update.ts
14761
15136
  init_paths();
14762
- import { readdir as readdir9, unlink as unlink4, rm as rm2, mkdir as mkdir6 } from "fs/promises";
14763
- import { join as join10 } from "path";
15137
+ import { readdir as readdir10, unlink as unlink4, rm as rm2, mkdir as mkdir7 } from "fs/promises";
15138
+ import { join as join11 } from "path";
14764
15139
  import { execSync as execSync2 } from "child_process";
14765
15140
 
14766
15141
  // src/core/hooks.ts
@@ -14787,15 +15162,15 @@ init_config();
14787
15162
  init_fs();
14788
15163
  init_generation();
14789
15164
  var import_yaml5 = __toESM(require_dist(), 1);
14790
- import { readdir as readdir8, rename as rename2 } from "fs/promises";
14791
- import { join as join9 } from "path";
15165
+ import { readdir as readdir9, rename as rename2 } from "fs/promises";
15166
+ import { join as join10 } from "path";
14792
15167
  async function needsMigration(paths) {
14793
15168
  try {
14794
- const entries = await readdir8(paths.lineage, { withFileTypes: true });
15169
+ const entries = await readdir9(paths.lineage, { withFileTypes: true });
14795
15170
  for (const entry of entries) {
14796
15171
  if (!entry.isDirectory() || !entry.name.startsWith("gen-"))
14797
15172
  continue;
14798
- const metaPath = join9(paths.lineage, entry.name, "meta.yml");
15173
+ const metaPath = join10(paths.lineage, entry.name, "meta.yml");
14799
15174
  const content = await readTextFile(metaPath);
14800
15175
  if (content === null)
14801
15176
  return true;
@@ -14809,18 +15184,18 @@ async function migrateLineage(paths) {
14809
15184
  const result = { migrated: [], skipped: [], errors: [] };
14810
15185
  let entries;
14811
15186
  try {
14812
- const dirEntries = await readdir8(paths.lineage, { withFileTypes: true });
15187
+ const dirEntries = await readdir9(paths.lineage, { withFileTypes: true });
14813
15188
  entries = dirEntries.filter((e) => e.isDirectory() && e.name.startsWith("gen-")).map((e) => e.name).sort();
14814
15189
  } catch {
14815
15190
  return result;
14816
15191
  }
14817
15192
  const plan = [];
14818
15193
  for (const dirName of entries) {
14819
- const metaPath = join9(paths.lineage, dirName, "meta.yml");
15194
+ const metaPath = join10(paths.lineage, dirName, "meta.yml");
14820
15195
  const metaContent = await readTextFile(metaPath);
14821
15196
  const seq = parseGenSeq(dirName);
14822
15197
  let goal = "";
14823
- const objContent = await readTextFile(join9(paths.lineage, dirName, "01-objective.md"));
15198
+ const objContent = await readTextFile(join10(paths.lineage, dirName, "01-objective.md"));
14824
15199
  if (objContent) {
14825
15200
  const goalMatch = objContent.match(/## Goal\n+([\s\S]*?)(?=\n##)/);
14826
15201
  if (goalMatch)
@@ -14835,7 +15210,7 @@ async function migrateLineage(paths) {
14835
15210
  let prevId = null;
14836
15211
  for (const entry of plan) {
14837
15212
  if (entry.hasMeta) {
14838
- const metaContent = await readTextFile(join9(paths.lineage, entry.dirName, "meta.yml"));
15213
+ const metaContent = await readTextFile(join10(paths.lineage, entry.dirName, "meta.yml"));
14839
15214
  if (metaContent) {
14840
15215
  const meta = import_yaml5.default.parse(metaContent);
14841
15216
  prevId = meta.id;
@@ -14856,11 +15231,11 @@ async function migrateLineage(paths) {
14856
15231
  startedAt: `legacy-${entry.seq}`,
14857
15232
  completedAt: `legacy-${entry.seq}`
14858
15233
  };
14859
- await writeTextFile(join9(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
15234
+ await writeTextFile(join10(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
14860
15235
  const oldSlug = entry.dirName.replace(/^gen-\d{3}/, "");
14861
15236
  const newDirName = `${newId}${oldSlug}`;
14862
15237
  if (newDirName !== entry.dirName) {
14863
- await rename2(join9(paths.lineage, entry.dirName), join9(paths.lineage, newDirName));
15238
+ await rename2(join10(paths.lineage, entry.dirName), join10(paths.lineage, newDirName));
14864
15239
  }
14865
15240
  prevId = newId;
14866
15241
  result.migrated.push(`${entry.dirName} → ${newDirName}`);
@@ -15137,13 +15512,13 @@ async function updateProject(projectRoot, dryRun = false) {
15137
15512
  const config = await paths.isReapProject() ? await ConfigManager.read(paths) : null;
15138
15513
  const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
15139
15514
  const commandsDir = ReapPaths.packageCommandsDir;
15140
- const commandFiles = await readdir9(commandsDir);
15141
- await mkdir6(ReapPaths.userReapCommands, { recursive: true });
15515
+ const commandFiles = await readdir10(commandsDir);
15516
+ await mkdir7(ReapPaths.userReapCommands, { recursive: true });
15142
15517
  for (const file of commandFiles) {
15143
15518
  if (!file.endsWith(".md"))
15144
15519
  continue;
15145
- const src = await readTextFileOrThrow(join10(commandsDir, file));
15146
- const dest = join10(ReapPaths.userReapCommands, file);
15520
+ const src = await readTextFileOrThrow(join11(commandsDir, file));
15521
+ const dest = join11(ReapPaths.userReapCommands, file);
15147
15522
  const existing = await readTextFile(dest);
15148
15523
  if (existing !== null && existing === src) {
15149
15524
  result.skipped.push(`~/.reap/commands/${file}`);
@@ -15157,11 +15532,11 @@ async function updateProject(projectRoot, dryRun = false) {
15157
15532
  const agentCmdDir = adapter.getCommandsDir();
15158
15533
  const label = adapter.displayName;
15159
15534
  try {
15160
- const existing = await readdir9(agentCmdDir);
15535
+ const existing = await readdir10(agentCmdDir);
15161
15536
  for (const file of existing) {
15162
15537
  if (!file.startsWith("reap.") || !file.endsWith(".md"))
15163
15538
  continue;
15164
- const filePath = join10(agentCmdDir, file);
15539
+ const filePath = join11(agentCmdDir, file);
15165
15540
  const content = await readTextFile(filePath);
15166
15541
  if (content !== null && content.includes("redirected to ~/.reap/commands/")) {
15167
15542
  if (!dryRun)
@@ -15179,11 +15554,11 @@ async function updateProject(projectRoot, dryRun = false) {
15179
15554
  }
15180
15555
  }
15181
15556
  }
15182
- await mkdir6(ReapPaths.userReapTemplates, { recursive: true });
15557
+ await mkdir7(ReapPaths.userReapTemplates, { recursive: true });
15183
15558
  const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
15184
15559
  for (const file of artifactFiles) {
15185
- const src = await readTextFileOrThrow(join10(ReapPaths.packageArtifactsDir, file));
15186
- const dest = join10(ReapPaths.userReapTemplates, file);
15560
+ const src = await readTextFileOrThrow(join11(ReapPaths.packageArtifactsDir, file));
15561
+ const dest = join11(ReapPaths.userReapTemplates, file);
15187
15562
  const existingContent = await readTextFile(dest);
15188
15563
  if (existingContent !== null && existingContent === src) {
15189
15564
  result.skipped.push(`~/.reap/templates/${file}`);
@@ -15193,8 +15568,8 @@ async function updateProject(projectRoot, dryRun = false) {
15193
15568
  result.updated.push(`~/.reap/templates/${file}`);
15194
15569
  }
15195
15570
  }
15196
- const domainGuideSrc = await readTextFileOrThrow(join10(ReapPaths.packageGenomeDir, "domain/README.md"));
15197
- const domainGuideDest = join10(ReapPaths.userReapTemplates, "domain-guide.md");
15571
+ const domainGuideSrc = await readTextFileOrThrow(join11(ReapPaths.packageGenomeDir, "domain/README.md"));
15572
+ const domainGuideDest = join11(ReapPaths.userReapTemplates, "domain-guide.md");
15198
15573
  const domainExistingContent = await readTextFile(domainGuideDest);
15199
15574
  if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
15200
15575
  result.skipped.push(`~/.reap/templates/domain-guide.md`);
@@ -15203,13 +15578,13 @@ async function updateProject(projectRoot, dryRun = false) {
15203
15578
  await writeTextFile(domainGuideDest, domainGuideSrc);
15204
15579
  result.updated.push(`~/.reap/templates/domain-guide.md`);
15205
15580
  }
15206
- const mergeTemplatesDir = join10(ReapPaths.userReapTemplates, "merge");
15207
- await mkdir6(mergeTemplatesDir, { recursive: true });
15581
+ const mergeTemplatesDir = join11(ReapPaths.userReapTemplates, "merge");
15582
+ await mkdir7(mergeTemplatesDir, { recursive: true });
15208
15583
  const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
15209
- const mergeSourceDir = join10(ReapPaths.packageArtifactsDir, "merge");
15584
+ const mergeSourceDir = join11(ReapPaths.packageArtifactsDir, "merge");
15210
15585
  for (const file of mergeArtifactFiles) {
15211
- const src = await readTextFileOrThrow(join10(mergeSourceDir, file));
15212
- const dest = join10(mergeTemplatesDir, file);
15586
+ const src = await readTextFileOrThrow(join11(mergeSourceDir, file));
15587
+ const dest = join11(mergeTemplatesDir, file);
15213
15588
  const existing = await readTextFile(dest);
15214
15589
  if (existing !== null && existing === src) {
15215
15590
  result.skipped.push(`~/.reap/templates/merge/${file}`);
@@ -15250,47 +15625,23 @@ async function updateProject(projectRoot, dryRun = false) {
15250
15625
  result.updated.push(`Config: added ${backfillResult.added.join(", ")}`);
15251
15626
  }
15252
15627
  }
15253
- const projectClaudeSkills = join10(paths.projectRoot, ".claude", "skills");
15254
- const reapCmdFiles = (await readdir9(ReapPaths.userReapCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
15255
- let cmdInstalled = 0;
15256
- for (const file of reapCmdFiles) {
15257
- const src = await readTextFileOrThrow(join10(ReapPaths.userReapCommands, file));
15258
- const name = file.replace(/\.md$/, "");
15259
- const skillDir = join10(projectClaudeSkills, name);
15260
- const skillFile = join10(skillDir, "SKILL.md");
15261
- const fmMatch = src.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
15262
- const description = fmMatch ? fmMatch[1].match(/^description:\s*"?([^"\n]*)"?/m)?.[1]?.trim() ?? "" : "";
15263
- const body = fmMatch ? fmMatch[2] : src;
15264
- const skillContent = `---
15265
- name: ${name}
15266
- description: "${description}"
15267
- ---
15268
- ${body}`;
15269
- const existing = await readTextFile(skillFile);
15270
- if (existing !== null && existing === skillContent)
15271
- continue;
15272
- if (!dryRun) {
15273
- await mkdir6(skillDir, { recursive: true });
15274
- await writeTextFile(skillFile, skillContent);
15275
- }
15276
- cmdInstalled++;
15277
- }
15278
- if (cmdInstalled > 0) {
15279
- result.updated.push(`.claude/skills/ (${cmdInstalled} synced)`);
15628
+ const skillsResult = await syncSkillsToProject(paths.projectRoot, dryRun);
15629
+ if (skillsResult.installed > 0) {
15630
+ result.updated.push(`.claude/skills/ (${skillsResult.installed} synced)`);
15280
15631
  } else {
15281
- result.skipped.push(`.claude/skills/ (${reapCmdFiles.length} unchanged)`);
15632
+ result.skipped.push(`.claude/skills/ (${skillsResult.total} unchanged)`);
15282
15633
  }
15283
- const projectClaudeCommands = join10(paths.projectRoot, ".claude", "commands");
15634
+ const projectClaudeCommands = join11(paths.projectRoot, ".claude", "commands");
15284
15635
  try {
15285
- const legacyFiles = (await readdir9(projectClaudeCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
15636
+ const legacyFiles = (await readdir10(projectClaudeCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
15286
15637
  for (const file of legacyFiles) {
15287
15638
  if (!dryRun)
15288
- await unlink4(join10(projectClaudeCommands, file));
15639
+ await unlink4(join11(projectClaudeCommands, file));
15289
15640
  result.removed.push(`.claude/commands/${file} (legacy)`);
15290
15641
  }
15291
15642
  } catch {}
15292
15643
  await migrateLegacyFiles(paths, dryRun, result);
15293
- const currentVersion = "0.15.0";
15644
+ const currentVersion = "0.15.2";
15294
15645
  const migrationResult = await MigrationRunner.run(paths, currentVersion, dryRun);
15295
15646
  for (const m of migrationResult.migrated) {
15296
15647
  result.updated.push(`[migration] ${m}`);
@@ -15366,7 +15717,7 @@ async function migrateLegacyFiles(paths, dryRun, result) {
15366
15717
  }
15367
15718
  async function removeDirIfExists(dirPath, label, dryRun, result) {
15368
15719
  try {
15369
- const entries = await readdir9(dirPath);
15720
+ const entries = await readdir10(dirPath);
15370
15721
  if (entries.length > 0 || true) {
15371
15722
  if (!dryRun)
15372
15723
  await rm2(dirPath, { recursive: true });
@@ -15389,6 +15740,7 @@ async function getStatus(projectRoot) {
15389
15740
  version: config.version,
15390
15741
  project: config.project,
15391
15742
  entryMode: config.entryMode,
15743
+ lastSyncedGeneration: config.lastSyncedGeneration,
15392
15744
  generation: current ? {
15393
15745
  id: current.id,
15394
15746
  goal: current.goal,
@@ -15407,12 +15759,352 @@ async function getStatus(projectRoot) {
15407
15759
  init_paths();
15408
15760
  init_lifecycle();
15409
15761
  init_fs();
15762
+ var import_yaml8 = __toESM(require_dist(), 1);
15763
+ import { mkdir as mkdir8, stat as stat4, copyFile } from "fs/promises";
15764
+ import { join as join13 } from "path";
15765
+
15766
+ // src/core/integrity.ts
15767
+ init_fs();
15768
+ init_lifecycle();
15769
+ init_types();
15410
15770
  var import_yaml7 = __toESM(require_dist(), 1);
15411
- import { mkdir as mkdir7, stat as stat3, copyFile } from "fs/promises";
15412
- import { join as join11 } from "path";
15771
+ import { readdir as readdir11 } from "fs/promises";
15772
+ import { join as join12 } from "path";
15773
+ var VALID_BACKLOG_TYPES = ["genome-change", "environment-change", "task"];
15774
+ var VALID_BACKLOG_STATUSES = ["pending", "consumed"];
15775
+ var VALID_GENERATION_TYPES = ["normal", "merge", "recovery"];
15776
+ var GENOME_LINE_WARNING_THRESHOLD = 100;
15777
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
15778
+ function isISODate(s) {
15779
+ return ISO_DATE_RE.test(s) && !Number.isNaN(new Date(s).getTime());
15780
+ }
15781
+ async function checkIntegrity(paths) {
15782
+ const errors = [];
15783
+ const warnings = [];
15784
+ await checkConfig(paths, errors, warnings);
15785
+ const state = await checkCurrentYml(paths, errors, warnings);
15786
+ await checkLineage(paths, errors, warnings);
15787
+ await checkGenome(paths, errors, warnings);
15788
+ await checkBacklog(paths, errors, warnings);
15789
+ if (state) {
15790
+ await checkArtifacts(paths, state, errors, warnings);
15791
+ }
15792
+ return { errors, warnings };
15793
+ }
15794
+ async function checkConfig(paths, errors, warnings) {
15795
+ const content = await readTextFile(paths.config);
15796
+ if (content === null) {
15797
+ errors.push("config.yml does not exist");
15798
+ return;
15799
+ }
15800
+ let config;
15801
+ try {
15802
+ config = import_yaml7.default.parse(content) ?? {};
15803
+ } catch {
15804
+ errors.push("config.yml is not valid YAML");
15805
+ return;
15806
+ }
15807
+ if (typeof config !== "object" || Array.isArray(config)) {
15808
+ errors.push("config.yml root must be a YAML mapping");
15809
+ return;
15810
+ }
15811
+ if (!config.project || typeof config.project !== "string") {
15812
+ errors.push("config.yml: missing or invalid 'project' field (string required)");
15813
+ }
15814
+ if (!config.entryMode || typeof config.entryMode !== "string") {
15815
+ errors.push("config.yml: missing or invalid 'entryMode' field (string required)");
15816
+ } else {
15817
+ const validModes = ["greenfield", "migration", "adoption"];
15818
+ if (!validModes.includes(config.entryMode)) {
15819
+ errors.push(`config.yml: invalid entryMode "${config.entryMode}" (valid: ${validModes.join(", ")})`);
15820
+ }
15821
+ }
15822
+ if (config.version !== undefined && typeof config.version !== "string") {
15823
+ warnings.push("config.yml: 'version' should be a string");
15824
+ }
15825
+ if (config.strict !== undefined && typeof config.strict !== "boolean" && typeof config.strict !== "object") {
15826
+ warnings.push("config.yml: 'strict' should be boolean or object");
15827
+ }
15828
+ if (config.autoUpdate !== undefined && typeof config.autoUpdate !== "boolean") {
15829
+ warnings.push("config.yml: 'autoUpdate' should be boolean");
15830
+ }
15831
+ if (config.autoSubagent !== undefined && typeof config.autoSubagent !== "boolean") {
15832
+ warnings.push("config.yml: 'autoSubagent' should be boolean");
15833
+ }
15834
+ }
15835
+ async function checkCurrentYml(paths, errors, warnings) {
15836
+ const content = await readTextFile(paths.currentYml);
15837
+ if (content === null || !content.trim()) {
15838
+ return null;
15839
+ }
15840
+ let state;
15841
+ try {
15842
+ state = import_yaml7.default.parse(content) ?? {};
15843
+ } catch {
15844
+ errors.push("current.yml is not valid YAML");
15845
+ return null;
15846
+ }
15847
+ if (typeof state !== "object" || Array.isArray(state)) {
15848
+ errors.push("current.yml root must be a YAML mapping");
15849
+ return null;
15850
+ }
15851
+ const requiredStrings = ["id", "goal", "stage", "startedAt"];
15852
+ for (const field of requiredStrings) {
15853
+ if (!state[field] || typeof state[field] !== "string") {
15854
+ errors.push(`current.yml: missing or invalid '${field}' field (string required)`);
15855
+ }
15856
+ }
15857
+ if (state.genomeVersion === undefined || typeof state.genomeVersion !== "number") {
15858
+ errors.push("current.yml: missing or invalid 'genomeVersion' field (number required)");
15859
+ }
15860
+ if (!Array.isArray(state.timeline)) {
15861
+ errors.push("current.yml: missing or invalid 'timeline' field (array required)");
15862
+ }
15863
+ if (state.type !== undefined) {
15864
+ if (!VALID_GENERATION_TYPES.includes(state.type)) {
15865
+ errors.push(`current.yml: invalid type "${state.type}" (valid: ${VALID_GENERATION_TYPES.join(", ")})`);
15866
+ }
15867
+ }
15868
+ if (state.parents !== undefined && !Array.isArray(state.parents)) {
15869
+ errors.push("current.yml: 'parents' must be an array");
15870
+ }
15871
+ if (typeof state.stage === "string" && !LifeCycle.isValid(state.stage)) {
15872
+ const mergeStages = ["detect", "mate", "merge", "sync", "validation", "completion"];
15873
+ if (!mergeStages.includes(state.stage)) {
15874
+ errors.push(`current.yml: invalid stage "${state.stage}"`);
15875
+ }
15876
+ }
15877
+ if (state.type === "recovery") {
15878
+ if (!state.recovers || !Array.isArray(state.recovers) || state.recovers.length === 0) {
15879
+ errors.push("current.yml: recovery generation must have non-empty 'recovers' array");
15880
+ }
15881
+ }
15882
+ return state;
15883
+ }
15884
+ async function checkLineage(paths, errors, warnings) {
15885
+ let entries;
15886
+ try {
15887
+ const items2 = await readdir11(paths.lineage, { withFileTypes: true });
15888
+ entries = items2.map((i) => ({ name: i.name, isDir: i.isDirectory(), isFile: i.isFile() }));
15889
+ } catch {
15890
+ return;
15891
+ }
15892
+ const items = await readdir11(paths.lineage, { withFileTypes: true });
15893
+ const allMetaIds = new Set;
15894
+ for (const item of items) {
15895
+ if (item.isDirectory() && item.name.startsWith("gen-")) {
15896
+ const metaPath = join12(paths.lineage, item.name, "meta.yml");
15897
+ const metaContent = await readTextFile(metaPath);
15898
+ if (metaContent === null) {
15899
+ errors.push(`lineage/${item.name}: missing meta.yml`);
15900
+ continue;
15901
+ }
15902
+ let meta;
15903
+ try {
15904
+ meta = import_yaml7.default.parse(metaContent) ?? {};
15905
+ } catch {
15906
+ errors.push(`lineage/${item.name}/meta.yml: invalid YAML`);
15907
+ continue;
15908
+ }
15909
+ validateLineageMeta(meta, `lineage/${item.name}/meta.yml`, errors, warnings);
15910
+ if (meta.id)
15911
+ allMetaIds.add(meta.id);
15912
+ } else if (item.isFile() && item.name.startsWith("gen-") && item.name.endsWith(".md")) {
15913
+ const content = await readTextFile(join12(paths.lineage, item.name));
15914
+ if (!content)
15915
+ continue;
15916
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
15917
+ if (!fmMatch) {
15918
+ warnings.push(`lineage/${item.name}: compressed file missing frontmatter`);
15919
+ continue;
15920
+ }
15921
+ let meta;
15922
+ try {
15923
+ meta = import_yaml7.default.parse(fmMatch[1]) ?? {};
15924
+ } catch {
15925
+ errors.push(`lineage/${item.name}: invalid frontmatter YAML`);
15926
+ continue;
15927
+ }
15928
+ validateLineageMeta(meta, `lineage/${item.name}`, errors, warnings);
15929
+ if (meta.id)
15930
+ allMetaIds.add(meta.id);
15931
+ }
15932
+ }
15933
+ for (const item of items) {
15934
+ let parents = [];
15935
+ if (item.isDirectory() && item.name.startsWith("gen-")) {
15936
+ const metaContent = await readTextFile(join12(paths.lineage, item.name, "meta.yml"));
15937
+ if (metaContent) {
15938
+ try {
15939
+ const meta = import_yaml7.default.parse(metaContent);
15940
+ parents = meta?.parents ?? [];
15941
+ } catch {}
15942
+ }
15943
+ } else if (item.isFile() && item.name.startsWith("gen-") && item.name.endsWith(".md")) {
15944
+ const content = await readTextFile(join12(paths.lineage, item.name));
15945
+ if (content) {
15946
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
15947
+ if (fmMatch) {
15948
+ try {
15949
+ const meta = import_yaml7.default.parse(fmMatch[1]);
15950
+ parents = meta?.parents ?? [];
15951
+ } catch {}
15952
+ }
15953
+ }
15954
+ }
15955
+ for (const parent of parents) {
15956
+ if (parent && !allMetaIds.has(parent)) {
15957
+ const epochContent = await readTextFile(join12(paths.lineage, "epoch.md"));
15958
+ let inEpoch = false;
15959
+ if (epochContent) {
15960
+ inEpoch = epochContent.includes(parent);
15961
+ }
15962
+ if (!inEpoch) {
15963
+ warnings.push(`lineage/${item.name}: parent "${parent}" not found in lineage (may be in a compressed epoch)`);
15964
+ }
15965
+ }
15966
+ }
15967
+ }
15968
+ }
15969
+ function validateLineageMeta(meta, location, errors, warnings) {
15970
+ const requiredStrings = ["id", "goal", "startedAt", "completedAt"];
15971
+ for (const field of requiredStrings) {
15972
+ if (!meta[field] || typeof meta[field] !== "string") {
15973
+ errors.push(`${location}: missing or invalid '${field}' field`);
15974
+ }
15975
+ }
15976
+ if (typeof meta.completedAt === "string") {
15977
+ if (!isISODate(meta.completedAt)) {
15978
+ errors.push(`${location}: completedAt "${meta.completedAt}" is not a valid ISO date`);
15979
+ }
15980
+ }
15981
+ if (typeof meta.startedAt === "string") {
15982
+ if (!isISODate(meta.startedAt)) {
15983
+ warnings.push(`${location}: startedAt "${meta.startedAt}" is not a valid ISO date`);
15984
+ }
15985
+ }
15986
+ if (!Array.isArray(meta.parents)) {
15987
+ errors.push(`${location}: missing or invalid 'parents' field (array required)`);
15988
+ }
15989
+ if (!meta.type || typeof meta.type !== "string") {
15990
+ errors.push(`${location}: missing or invalid 'type' field`);
15991
+ } else if (!VALID_GENERATION_TYPES.includes(meta.type)) {
15992
+ errors.push(`${location}: invalid type "${meta.type}"`);
15993
+ }
15994
+ if (meta.genomeHash !== undefined && typeof meta.genomeHash !== "string") {
15995
+ warnings.push(`${location}: genomeHash should be a string`);
15996
+ }
15997
+ }
15998
+ async function checkGenome(paths, errors, warnings) {
15999
+ const l1Files = [
16000
+ { path: paths.principles, name: "principles.md" },
16001
+ { path: paths.conventions, name: "conventions.md" },
16002
+ { path: paths.constraints, name: "constraints.md" },
16003
+ { path: paths.sourceMap, name: "source-map.md" }
16004
+ ];
16005
+ for (const gf of l1Files) {
16006
+ if (!await fileExists(gf.path)) {
16007
+ errors.push(`genome/${gf.name} does not exist`);
16008
+ continue;
16009
+ }
16010
+ const content = await readTextFile(gf.path);
16011
+ if (content === null)
16012
+ continue;
16013
+ const lines = content.split(`
16014
+ `).length;
16015
+ if (lines > GENOME_LINE_WARNING_THRESHOLD) {
16016
+ warnings.push(`genome/${gf.name}: ${lines} lines (exceeds ~${GENOME_LINE_WARNING_THRESHOLD} line guideline)`);
16017
+ }
16018
+ const stripped = content.split(`
16019
+ `).filter((l) => !l.startsWith("#") && !l.startsWith(">") && !l.startsWith("-") && l.trim() !== "").join("").trim();
16020
+ if (stripped.length === 0) {
16021
+ warnings.push(`genome/${gf.name}: appears to be placeholder-only (no substantive content)`);
16022
+ }
16023
+ }
16024
+ }
16025
+ async function checkBacklog(paths, errors, warnings) {
16026
+ let entries;
16027
+ try {
16028
+ entries = await readdir11(paths.backlog);
16029
+ } catch {
16030
+ return;
16031
+ }
16032
+ for (const filename of entries) {
16033
+ if (!filename.endsWith(".md"))
16034
+ continue;
16035
+ const content = await readTextFile(join12(paths.backlog, filename));
16036
+ if (!content)
16037
+ continue;
16038
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
16039
+ if (!fmMatch) {
16040
+ errors.push(`backlog/${filename}: missing frontmatter`);
16041
+ continue;
16042
+ }
16043
+ let fm;
16044
+ try {
16045
+ fm = import_yaml7.default.parse(fmMatch[1]) ?? {};
16046
+ } catch {
16047
+ errors.push(`backlog/${filename}: invalid frontmatter YAML`);
16048
+ continue;
16049
+ }
16050
+ if (!fm.type || typeof fm.type !== "string") {
16051
+ errors.push(`backlog/${filename}: missing or invalid 'type' field in frontmatter`);
16052
+ } else if (!VALID_BACKLOG_TYPES.includes(fm.type)) {
16053
+ errors.push(`backlog/${filename}: invalid type "${fm.type}" (valid: ${VALID_BACKLOG_TYPES.join(", ")})`);
16054
+ }
16055
+ if (!fm.status || typeof fm.status !== "string") {
16056
+ errors.push(`backlog/${filename}: missing or invalid 'status' field in frontmatter`);
16057
+ } else if (!VALID_BACKLOG_STATUSES.includes(fm.status)) {
16058
+ errors.push(`backlog/${filename}: invalid status "${fm.status}" (valid: ${VALID_BACKLOG_STATUSES.join(", ")})`);
16059
+ }
16060
+ if (fm.status === "consumed" && !fm.consumedBy) {
16061
+ errors.push(`backlog/${filename}: status is 'consumed' but missing 'consumedBy' field`);
16062
+ }
16063
+ }
16064
+ }
16065
+ var STAGE_ARTIFACT_MAP = {
16066
+ objective: "01-objective.md",
16067
+ planning: "02-planning.md",
16068
+ implementation: "03-implementation.md",
16069
+ validation: "04-validation.md",
16070
+ completion: "05-completion.md"
16071
+ };
16072
+ async function checkArtifacts(paths, state, errors, warnings) {
16073
+ const currentStageIdx = LIFECYCLE_ORDER.indexOf(state.stage);
16074
+ if (currentStageIdx < 0)
16075
+ return;
16076
+ for (let i = 0;i < currentStageIdx; i++) {
16077
+ const stage = LIFECYCLE_ORDER[i];
16078
+ const artifactName = STAGE_ARTIFACT_MAP[stage];
16079
+ if (!artifactName)
16080
+ continue;
16081
+ const artifactPath = paths.artifact(artifactName);
16082
+ if (!await fileExists(artifactPath)) {
16083
+ errors.push(`artifact ${artifactName} missing (expected for completed stage '${stage}')`);
16084
+ continue;
16085
+ }
16086
+ const content = await readTextFile(artifactPath);
16087
+ if (content && !content.startsWith("# REAP MANAGED")) {
16088
+ warnings.push(`artifact ${artifactName}: missing 'REAP MANAGED' header`);
16089
+ }
16090
+ }
16091
+ const currentArtifact = STAGE_ARTIFACT_MAP[state.stage];
16092
+ if (currentArtifact) {
16093
+ const artifactPath = paths.artifact(currentArtifact);
16094
+ if (!await fileExists(artifactPath)) {
16095
+ warnings.push(`artifact ${currentArtifact} not yet created for current stage '${state.stage}'`);
16096
+ }
16097
+ }
16098
+ }
16099
+
16100
+ // src/cli/commands/fix.ts
16101
+ async function checkProject(projectRoot) {
16102
+ const paths = new ReapPaths(projectRoot);
16103
+ return checkIntegrity(paths);
16104
+ }
15413
16105
  async function dirExists(path) {
15414
16106
  try {
15415
- const s = await stat3(path);
16107
+ const s = await stat4(path);
15416
16108
  return s.isDirectory();
15417
16109
  } catch {
15418
16110
  return false;
@@ -15432,7 +16124,7 @@ async function fixProject(projectRoot) {
15432
16124
  ];
15433
16125
  for (const dir of requiredDirs) {
15434
16126
  if (!await dirExists(dir.path)) {
15435
- await mkdir7(dir.path, { recursive: true });
16127
+ await mkdir8(dir.path, { recursive: true });
15436
16128
  fixed.push(`Recreated missing directory: ${dir.name}/`);
15437
16129
  }
15438
16130
  }
@@ -15444,7 +16136,7 @@ async function fixProject(projectRoot) {
15444
16136
  ];
15445
16137
  for (const gf of genomeFiles) {
15446
16138
  if (!await fileExists(gf.path)) {
15447
- const templateSrc = join11(ReapPaths.packageGenomeDir, gf.name);
16139
+ const templateSrc = join13(ReapPaths.packageGenomeDir, gf.name);
15448
16140
  if (await fileExists(templateSrc)) {
15449
16141
  await copyFile(templateSrc, gf.path);
15450
16142
  fixed.push(`Restored missing genome/${gf.name} from template`);
@@ -15460,7 +16152,7 @@ async function fixProject(projectRoot) {
15460
16152
  if (currentContent !== null) {
15461
16153
  if (currentContent.trim()) {
15462
16154
  try {
15463
- const state = import_yaml7.default.parse(currentContent);
16155
+ const state = import_yaml8.default.parse(currentContent);
15464
16156
  if (!state.stage || !LifeCycle.isValid(state.stage)) {
15465
16157
  issues.push(`Invalid stage "${state.stage}" in current.yml. Valid stages: ${LifeCycle.stages().join(", ")}. Manual correction required.`);
15466
16158
  }
@@ -15469,7 +16161,7 @@ async function fixProject(projectRoot) {
15469
16161
  if (!state.goal)
15470
16162
  issues.push("current.yml is missing 'goal' field. Manual correction required.");
15471
16163
  if (!await dirExists(paths.backlog)) {
15472
- await mkdir7(paths.backlog, { recursive: true });
16164
+ await mkdir8(paths.backlog, { recursive: true });
15473
16165
  fixed.push("Recreated missing backlog/ directory for active generation");
15474
16166
  }
15475
16167
  } catch {
@@ -15485,8 +16177,8 @@ async function fixProject(projectRoot) {
15485
16177
  init_fs();
15486
16178
  init_paths();
15487
16179
  init_config();
15488
- import { rm as rm3, readdir as readdir10, unlink as unlink5 } from "fs/promises";
15489
- import { join as join12 } from "path";
16180
+ import { rm as rm3, readdir as readdir12, unlink as unlink5 } from "fs/promises";
16181
+ import { join as join14 } from "path";
15490
16182
  async function getProjectName(projectRoot) {
15491
16183
  try {
15492
16184
  const paths = new ReapPaths(projectRoot);
@@ -15499,16 +16191,16 @@ async function getProjectName(projectRoot) {
15499
16191
  async function destroyProject(projectRoot) {
15500
16192
  const removed = [];
15501
16193
  const skipped = [];
15502
- const reapDir = join12(projectRoot, ".reap");
16194
+ const reapDir = join14(projectRoot, ".reap");
15503
16195
  if (await fileExists(reapDir)) {
15504
16196
  await rm3(reapDir, { recursive: true, force: true });
15505
16197
  removed.push(".reap/");
15506
16198
  } else {
15507
16199
  skipped.push(".reap/ (not found)");
15508
16200
  }
15509
- const claudeCommandsDir = join12(projectRoot, ".claude", "commands");
16201
+ const claudeCommandsDir = join14(projectRoot, ".claude", "commands");
15510
16202
  await removeGlobFiles(claudeCommandsDir, "reap.", removed, skipped, ".claude/commands/");
15511
- const claudeSkillsDir = join12(projectRoot, ".claude", "skills");
16203
+ const claudeSkillsDir = join14(projectRoot, ".claude", "skills");
15512
16204
  await removeGlobDirs(claudeSkillsDir, "reap.", removed, skipped, ".claude/skills/");
15513
16205
  await cleanClaudeMd(projectRoot, removed, skipped);
15514
16206
  await cleanGitignore(projectRoot, removed, skipped);
@@ -15516,14 +16208,14 @@ async function destroyProject(projectRoot) {
15516
16208
  }
15517
16209
  async function removeGlobFiles(dir, prefix, removed, skipped, displayPrefix) {
15518
16210
  try {
15519
- const files = await readdir10(dir);
16211
+ const files = await readdir12(dir);
15520
16212
  const matched = files.filter((f) => f.startsWith(prefix));
15521
16213
  if (matched.length === 0) {
15522
16214
  skipped.push(`${displayPrefix}${prefix}* (none found)`);
15523
16215
  return;
15524
16216
  }
15525
16217
  for (const file of matched) {
15526
- await unlink5(join12(dir, file));
16218
+ await unlink5(join14(dir, file));
15527
16219
  removed.push(`${displayPrefix}${file}`);
15528
16220
  }
15529
16221
  } catch {
@@ -15532,14 +16224,14 @@ async function removeGlobFiles(dir, prefix, removed, skipped, displayPrefix) {
15532
16224
  }
15533
16225
  async function removeGlobDirs(dir, prefix, removed, skipped, displayPrefix) {
15534
16226
  try {
15535
- const entries = await readdir10(dir);
16227
+ const entries = await readdir12(dir);
15536
16228
  const matched = entries.filter((e) => e.startsWith(prefix));
15537
16229
  if (matched.length === 0) {
15538
16230
  skipped.push(`${displayPrefix}${prefix}* (none found)`);
15539
16231
  return;
15540
16232
  }
15541
16233
  for (const entry of matched) {
15542
- await rm3(join12(dir, entry), { recursive: true, force: true });
16234
+ await rm3(join14(dir, entry), { recursive: true, force: true });
15543
16235
  removed.push(`${displayPrefix}${entry}`);
15544
16236
  }
15545
16237
  } catch {
@@ -15547,7 +16239,7 @@ async function removeGlobDirs(dir, prefix, removed, skipped, displayPrefix) {
15547
16239
  }
15548
16240
  }
15549
16241
  async function cleanClaudeMd(projectRoot, removed, skipped) {
15550
- const claudeMdPath = join12(projectRoot, ".claude", "CLAUDE.md");
16242
+ const claudeMdPath = join14(projectRoot, ".claude", "CLAUDE.md");
15551
16243
  const content = await readTextFile(claudeMdPath);
15552
16244
  if (content === null) {
15553
16245
  skipped.push(".claude/CLAUDE.md (not found)");
@@ -15569,7 +16261,7 @@ async function cleanClaudeMd(projectRoot, removed, skipped) {
15569
16261
  }
15570
16262
  }
15571
16263
  async function cleanGitignore(projectRoot, removed, skipped) {
15572
- const gitignorePath = join12(projectRoot, ".gitignore");
16264
+ const gitignorePath = join14(projectRoot, ".gitignore");
15573
16265
  const content = await readTextFile(gitignorePath);
15574
16266
  if (content === null) {
15575
16267
  skipped.push(".gitignore (not found)");
@@ -15605,8 +16297,8 @@ async function cleanGitignore(projectRoot, removed, skipped) {
15605
16297
  init_paths();
15606
16298
  init_fs();
15607
16299
  init_generation();
15608
- import { rm as rm4, readdir as readdir11, mkdir as mkdir8 } from "fs/promises";
15609
- import { join as join13 } from "path";
16300
+ import { rm as rm4, readdir as readdir13, mkdir as mkdir9 } from "fs/promises";
16301
+ import { join as join15 } from "path";
15610
16302
  async function hasActiveGeneration(projectRoot) {
15611
16303
  const paths = new ReapPaths(projectRoot);
15612
16304
  try {
@@ -15623,7 +16315,7 @@ async function cleanProject(projectRoot, options) {
15623
16315
  const warnings = [];
15624
16316
  if (options.lineage === "delete") {
15625
16317
  await rm4(paths.lineage, { recursive: true, force: true });
15626
- await mkdir8(paths.lineage, { recursive: true });
16318
+ await mkdir9(paths.lineage, { recursive: true });
15627
16319
  actions.push("Lineage: 전체 삭제됨");
15628
16320
  } else {
15629
16321
  await compressLineage(paths, actions);
@@ -15633,7 +16325,7 @@ async function cleanProject(projectRoot, options) {
15633
16325
  const hooksDir = paths.hooks;
15634
16326
  if (await fileExists(hooksDir)) {
15635
16327
  await rm4(hooksDir, { recursive: true, force: true });
15636
- await mkdir8(hooksDir, { recursive: true });
16328
+ await mkdir9(hooksDir, { recursive: true });
15637
16329
  actions.push("Hooks: 초기화됨");
15638
16330
  } else {
15639
16331
  actions.push("Hooks: 디렉토리 없음 (skip)");
@@ -15652,7 +16344,7 @@ async function cleanProject(projectRoot, options) {
15652
16344
  if (options.backlog === "delete") {
15653
16345
  if (await fileExists(backlogDir)) {
15654
16346
  await rm4(backlogDir, { recursive: true, force: true });
15655
- await mkdir8(backlogDir, { recursive: true });
16347
+ await mkdir9(backlogDir, { recursive: true });
15656
16348
  actions.push("Backlog: 삭제됨");
15657
16349
  } else {
15658
16350
  actions.push("Backlog: 디렉토리 없음 (skip)");
@@ -15670,7 +16362,7 @@ async function compressLineage(paths, actions) {
15670
16362
  }
15671
16363
  let entries;
15672
16364
  try {
15673
- entries = await readdir11(lineageDir);
16365
+ entries = await readdir13(lineageDir);
15674
16366
  } catch {
15675
16367
  actions.push("Lineage: 읽기 실패 (skip)");
15676
16368
  return;
@@ -15692,9 +16384,9 @@ async function compressLineage(paths, actions) {
15692
16384
  ].join(`
15693
16385
  `);
15694
16386
  for (const dir of genDirs) {
15695
- await rm4(join13(lineageDir, dir), { recursive: true, force: true });
16387
+ await rm4(join15(lineageDir, dir), { recursive: true, force: true });
15696
16388
  }
15697
- await writeTextFile(join13(lineageDir, `${epochId}.md`), summary);
16389
+ await writeTextFile(join15(lineageDir, `${epochId}.md`), summary);
15698
16390
  actions.push(`Lineage: ${genDirs.length}개 세대를 ${epochId}로 압축`);
15699
16391
  }
15700
16392
  async function cleanLife(paths, actions) {
@@ -15705,7 +16397,7 @@ async function cleanLife(paths, actions) {
15705
16397
  }
15706
16398
  let entries;
15707
16399
  try {
15708
- entries = await readdir11(lifeDir);
16400
+ entries = await readdir13(lifeDir);
15709
16401
  } catch {
15710
16402
  actions.push("Life: 읽기 실패 (skip)");
15711
16403
  return;
@@ -15714,7 +16406,7 @@ async function cleanLife(paths, actions) {
15714
16406
  for (const entry of entries) {
15715
16407
  if (entry === "backlog")
15716
16408
  continue;
15717
- const entryPath = join13(lifeDir, entry);
16409
+ const entryPath = join15(lifeDir, entry);
15718
16410
  await rm4(entryPath, { recursive: true, force: true });
15719
16411
  removedCount++;
15720
16412
  }
@@ -15723,14 +16415,14 @@ async function cleanLife(paths, actions) {
15723
16415
  async function resetGenomeToTemplate(paths, actions) {
15724
16416
  const genomeFiles = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
15725
16417
  for (const file of genomeFiles) {
15726
- const templatePath = join13(ReapPaths.packageGenomeDir, file);
15727
- const destPath = join13(paths.genome, file);
16418
+ const templatePath = join15(ReapPaths.packageGenomeDir, file);
16419
+ const destPath = join15(paths.genome, file);
15728
16420
  try {
15729
16421
  const templateContent = await readTextFileOrThrow(templatePath);
15730
16422
  await writeTextFile(destPath, templateContent);
15731
16423
  } catch {}
15732
16424
  }
15733
- const envSummaryTemplate = join13(ReapPaths.packageTemplatesDir, "environment", "summary.md");
16425
+ const envSummaryTemplate = join15(ReapPaths.packageTemplatesDir, "environment", "summary.md");
15734
16426
  if (await fileExists(envSummaryTemplate)) {
15735
16427
  try {
15736
16428
  const content = await readTextFileOrThrow(envSummaryTemplate);
@@ -15746,8 +16438,8 @@ init_paths();
15746
16438
  init_fs();
15747
16439
  init_version();
15748
16440
  init_config();
15749
- import { join as join29 } from "path";
15750
- program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.0");
16441
+ import { join as join32 } from "path";
16442
+ program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.2");
15751
16443
  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) => {
15752
16444
  try {
15753
16445
  const cwd = process.cwd();
@@ -15785,8 +16477,9 @@ Initializing REAP project "${name}" (${mode} mode)...
15785
16477
  console.log(`
15786
16478
  Getting started:`);
15787
16479
  console.log(` 1. Open your AI agent (${initResult.agents[0] || "Claude Code or OpenCode"})`);
15788
- console.log(` 2. Run /reap.start to begin your first Generation`);
15789
- console.log(` 3. Or run /reap.evolve for autonomous execution`);
16480
+ console.log(` 2. Run /reap.sync to synchronize Genome with your project`);
16481
+ console.log(` 3. Run /reap.start to begin your first Generation`);
16482
+ console.log(` 4. Or run /reap.evolve for autonomous execution`);
15790
16483
  if (initResult.agents.length === 0) {
15791
16484
  console.log(`
15792
16485
  ⚠ No AI agents detected. Install Claude Code or OpenCode, then run 'reap update'.`);
@@ -15803,10 +16496,12 @@ program.command("status").description("Show current project and Generation statu
15803
16496
  const paths = new ReapPaths(cwd);
15804
16497
  const config = await ConfigManager.read(paths);
15805
16498
  const skipCheck = config.autoUpdate === false;
15806
- const installedVersion = "0.15.0";
16499
+ const installedVersion = "0.15.2";
15807
16500
  const versionLine = formatVersionLine(installedVersion, skipCheck);
15808
16501
  console.log(`${versionLine} | Project: ${status.project} (${status.entryMode})`);
15809
16502
  console.log(`Completed Generations: ${status.totalGenerations}`);
16503
+ const syncLabel = status.lastSyncedGeneration ? `synced (${status.lastSyncedGeneration})` : "never synced";
16504
+ console.log(`Genome Sync: ${syncLabel}`);
15810
16505
  if (status.generation) {
15811
16506
  console.log(`
15812
16507
  Active Generation: ${status.generation.id}`);
@@ -15822,19 +16517,38 @@ No active Generation. Run '/reap.start' to start one.`);
15822
16517
  process.exit(1);
15823
16518
  }
15824
16519
  });
15825
- program.command("fix").description("Diagnose and repair .reap/ directory structure").action(async () => {
16520
+ program.command("fix").description("Diagnose and repair .reap/ directory structure").option("--check", "Check-only mode: report issues without fixing anything").action(async (options) => {
15826
16521
  try {
15827
- const result = await fixProject(process.cwd());
15828
- if (result.fixed.length === 0 && result.issues.length === 0) {
15829
- console.log("✓ Project is healthy. No issues found.");
15830
- } else {
15831
- if (result.fixed.length > 0) {
15832
- console.log("Fixed:");
15833
- result.fixed.forEach((f) => console.log(` ✓ ${f}`));
16522
+ if (options.check) {
16523
+ const result = await checkProject(process.cwd());
16524
+ if (result.errors.length === 0 && result.warnings.length === 0) {
16525
+ console.log("✓ Integrity check passed. No issues found.");
16526
+ } else {
16527
+ if (result.errors.length > 0) {
16528
+ console.log("Errors:");
16529
+ result.errors.forEach((e) => console.log(` ✗ ${e}`));
16530
+ }
16531
+ if (result.warnings.length > 0) {
16532
+ console.log("Warnings:");
16533
+ result.warnings.forEach((w) => console.log(` ⚠ ${w}`));
16534
+ }
16535
+ }
16536
+ if (result.errors.length > 0) {
16537
+ process.exit(1);
15834
16538
  }
15835
- if (result.issues.length > 0) {
15836
- console.log("Issues (require manual intervention):");
15837
- result.issues.forEach((i) => console.log(` ✗ ${i}`));
16539
+ } else {
16540
+ const result = await fixProject(process.cwd());
16541
+ if (result.fixed.length === 0 && result.issues.length === 0) {
16542
+ console.log("✓ Project is healthy. No issues found.");
16543
+ } else {
16544
+ if (result.fixed.length > 0) {
16545
+ console.log("Fixed:");
16546
+ result.fixed.forEach((f) => console.log(` ✓ ${f}`));
16547
+ }
16548
+ if (result.issues.length > 0) {
16549
+ console.log("Issues (require manual intervention):");
16550
+ result.issues.forEach((i) => console.log(` ✗ ${i}`));
16551
+ }
15838
16552
  }
15839
16553
  }
15840
16554
  } catch (e) {
@@ -15882,10 +16596,10 @@ program.command("help").description("Show REAP commands, slash commands, and wor
15882
16596
  if (l === "korean" || l === "ko")
15883
16597
  lang = "ko";
15884
16598
  }
15885
- const helpDir = join29(ReapPaths.packageTemplatesDir, "help");
15886
- let helpText = await readTextFile(join29(helpDir, `${lang}.txt`));
16599
+ const helpDir = join32(ReapPaths.packageTemplatesDir, "help");
16600
+ let helpText = await readTextFile(join32(helpDir, `${lang}.txt`));
15887
16601
  if (!helpText)
15888
- helpText = await readTextFile(join29(helpDir, "en.txt"));
16602
+ helpText = await readTextFile(join32(helpDir, "en.txt"));
15889
16603
  if (!helpText) {
15890
16604
  console.log("Help file not found. Run 'reap update' to install templates.");
15891
16605
  return;
@@ -15900,7 +16614,7 @@ program.command("destroy").description("Remove all REAP files from this project"
15900
16614
  console.error("Error: Not a REAP project (cannot read .reap/config.yml).");
15901
16615
  process.exit(1);
15902
16616
  }
15903
- const expectedInput = `destroy ${projectName}`;
16617
+ const expectedInput = "yes destroy";
15904
16618
  console.log(`
15905
16619
  This will permanently remove all REAP files from this project.`);
15906
16620
  console.log(`To confirm, type '${expectedInput}':