@c-d-cc/reap 0.15.2 → 0.15.3

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
@@ -9073,8 +9073,8 @@ var exports_genome_sync = {};
9073
9073
  __export(exports_genome_sync, {
9074
9074
  syncGenomeFromProject: () => syncGenomeFromProject
9075
9075
  });
9076
- import { join as join5 } from "path";
9077
- import { readdir as readdir4, stat as stat2 } from "fs/promises";
9076
+ import { join as join6 } from "path";
9077
+ import { readdir as readdir5, stat as stat2 } from "fs/promises";
9078
9078
  async function scanProject(projectRoot) {
9079
9079
  const scan = {
9080
9080
  language: "Unknown",
@@ -9093,7 +9093,7 @@ async function scanProject(projectRoot) {
9093
9093
  directories: [],
9094
9094
  existingDocs: []
9095
9095
  };
9096
- const pkgContent = await readTextFile(join5(projectRoot, "package.json"));
9096
+ const pkgContent = await readTextFile(join6(projectRoot, "package.json"));
9097
9097
  if (pkgContent) {
9098
9098
  try {
9099
9099
  const pkg = JSON.parse(pkgContent);
@@ -9152,16 +9152,16 @@ async function scanProject(projectRoot) {
9152
9152
  scan.buildTool = "Turbopack";
9153
9153
  } catch {}
9154
9154
  }
9155
- if (await fileExists(join5(projectRoot, "go.mod"))) {
9155
+ if (await fileExists(join6(projectRoot, "go.mod"))) {
9156
9156
  scan.language = "Go";
9157
9157
  scan.runtime = "Go";
9158
9158
  scan.packageManager = "Go Modules";
9159
9159
  }
9160
- if (await fileExists(join5(projectRoot, "pyproject.toml")) || await fileExists(join5(projectRoot, "requirements.txt"))) {
9160
+ if (await fileExists(join6(projectRoot, "pyproject.toml")) || await fileExists(join6(projectRoot, "requirements.txt"))) {
9161
9161
  scan.language = "Python";
9162
9162
  scan.runtime = "Python";
9163
- if (await fileExists(join5(projectRoot, "pyproject.toml"))) {
9164
- const pyproject = await readTextFile(join5(projectRoot, "pyproject.toml"));
9163
+ if (await fileExists(join6(projectRoot, "pyproject.toml"))) {
9164
+ const pyproject = await readTextFile(join6(projectRoot, "pyproject.toml"));
9165
9165
  if (pyproject?.includes("[tool.poetry]"))
9166
9166
  scan.packageManager = "Poetry";
9167
9167
  else if (pyproject?.includes("[tool.uv]") || pyproject?.includes("[project]"))
@@ -9176,23 +9176,23 @@ async function scanProject(projectRoot) {
9176
9176
  scan.framework = "Flask";
9177
9177
  }
9178
9178
  }
9179
- if (await fileExists(join5(projectRoot, "Cargo.toml"))) {
9179
+ if (await fileExists(join6(projectRoot, "Cargo.toml"))) {
9180
9180
  scan.language = "Rust";
9181
9181
  scan.runtime = "Rust";
9182
9182
  scan.packageManager = "Cargo";
9183
9183
  }
9184
- if (await fileExists(join5(projectRoot, "tsconfig.json"))) {
9184
+ if (await fileExists(join6(projectRoot, "tsconfig.json"))) {
9185
9185
  scan.hasTypeScript = true;
9186
9186
  scan.language = "TypeScript";
9187
9187
  }
9188
- scan.hasDocker = await fileExists(join5(projectRoot, "Dockerfile")) || await fileExists(join5(projectRoot, "docker-compose.yml"));
9188
+ scan.hasDocker = await fileExists(join6(projectRoot, "Dockerfile")) || await fileExists(join6(projectRoot, "docker-compose.yml"));
9189
9189
  try {
9190
- const entries = await readdir4(projectRoot);
9190
+ const entries = await readdir5(projectRoot);
9191
9191
  for (const entry of entries) {
9192
9192
  if (entry.startsWith(".") || entry === "node_modules")
9193
9193
  continue;
9194
9194
  try {
9195
- const s = await stat2(join5(projectRoot, entry));
9195
+ const s = await stat2(join6(projectRoot, entry));
9196
9196
  if (s.isDirectory())
9197
9197
  scan.directories.push(entry);
9198
9198
  } catch {}
@@ -9200,7 +9200,7 @@ async function scanProject(projectRoot) {
9200
9200
  } catch {}
9201
9201
  const docFiles = ["README.md", "CLAUDE.md", "AGENTS.md", "CONTRIBUTING.md", "ARCHITECTURE.md"];
9202
9202
  for (const file of docFiles) {
9203
- const content = await readTextFile(join5(projectRoot, file));
9203
+ const content = await readTextFile(join6(projectRoot, file));
9204
9204
  if (content) {
9205
9205
  scan.existingDocs.push({ file, content: content.substring(0, 2000) });
9206
9206
  }
@@ -9329,17 +9329,17 @@ async function syncGenomeFromProject(projectRoot, genomePath, onProgress) {
9329
9329
  log(`Detected: ${scan.language}, ${scan.framework !== "None" ? scan.framework : "no framework"}, ${scan.packageManager}`);
9330
9330
  const { writeTextFile: writeTextFile2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
9331
9331
  log("Generating constraints.md...");
9332
- await writeTextFile2(join5(genomePath, "constraints.md"), generateConstraints(scan));
9332
+ await writeTextFile2(join6(genomePath, "constraints.md"), generateConstraints(scan));
9333
9333
  log("Generating conventions.md...");
9334
- await writeTextFile2(join5(genomePath, "conventions.md"), generateConventions(scan));
9334
+ await writeTextFile2(join6(genomePath, "conventions.md"), generateConventions(scan));
9335
9335
  log("Generating principles.md...");
9336
- await writeTextFile2(join5(genomePath, "principles.md"), generatePrinciples(scan));
9336
+ await writeTextFile2(join6(genomePath, "principles.md"), generatePrinciples(scan));
9337
9337
  log("Generating source-map.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");
9338
+ await writeTextFile2(join6(genomePath, "source-map.md"), generateSourceMap(scan));
9339
+ const { mkdir: mkdir5 } = await import("fs/promises");
9340
+ const domainDir = join6(genomePath, "domain");
9341
+ await mkdir5(domainDir, { recursive: true });
9342
+ const domainReadme = join6(domainDir, "README.md");
9343
9343
  if (!await fileExists(domainReadme)) {
9344
9344
  await writeTextFile2(domainReadme, [
9345
9345
  "# Domain Rules",
@@ -9482,8 +9482,8 @@ function gitCurrentBranch(cwd) {
9482
9482
  var init_git = () => {};
9483
9483
 
9484
9484
  // src/core/compression.ts
9485
- import { readdir as readdir6, rm } from "fs/promises";
9486
- import { join as join7 } from "path";
9485
+ import { readdir as readdir7, rm as rm2 } from "fs/promises";
9486
+ import { join as join8 } from "path";
9487
9487
  function safeCompletedAtTime(dateStr) {
9488
9488
  const t = new Date(dateStr).getTime();
9489
9489
  return Number.isNaN(t) ? 0 : t;
@@ -9518,9 +9518,9 @@ async function countLines(filePath) {
9518
9518
  async function countDirLines(dirPath) {
9519
9519
  let total = 0;
9520
9520
  try {
9521
- const entries = await readdir6(dirPath, { withFileTypes: true });
9521
+ const entries = await readdir7(dirPath, { withFileTypes: true });
9522
9522
  for (const entry of entries) {
9523
- const fullPath = join7(dirPath, entry.name);
9523
+ const fullPath = join8(dirPath, entry.name);
9524
9524
  if (entry.isFile() && entry.name.endsWith(".md")) {
9525
9525
  total += await countLines(fullPath);
9526
9526
  } else if (entry.isDirectory()) {
@@ -9531,7 +9531,7 @@ async function countDirLines(dirPath) {
9531
9531
  return total;
9532
9532
  }
9533
9533
  async function readDirMeta(dirPath) {
9534
- const content = await readTextFile(join7(dirPath, "meta.yml"));
9534
+ const content = await readTextFile(join8(dirPath, "meta.yml"));
9535
9535
  if (content === null)
9536
9536
  return null;
9537
9537
  try {
@@ -9549,9 +9549,9 @@ async function readFileMeta(filePath) {
9549
9549
  async function scanLineage(paths) {
9550
9550
  const entries = [];
9551
9551
  try {
9552
- const items = await readdir6(paths.lineage, { withFileTypes: true });
9552
+ const items = await readdir7(paths.lineage, { withFileTypes: true });
9553
9553
  for (const item of items) {
9554
- const fullPath = join7(paths.lineage, item.name);
9554
+ const fullPath = join8(paths.lineage, item.name);
9555
9555
  if (item.isDirectory() && item.name.startsWith("gen-")) {
9556
9556
  const genNum = extractGenNum(item.name);
9557
9557
  const lines = await countDirLines(fullPath);
@@ -9606,7 +9606,7 @@ async function findLeafNodes(paths, entries) {
9606
9606
  if (entry.type === "level2")
9607
9607
  continue;
9608
9608
  let meta = null;
9609
- const fullPath = join7(paths.lineage, entry.name);
9609
+ const fullPath = join8(paths.lineage, entry.name);
9610
9610
  if (entry.type === "dir") {
9611
9611
  meta = await readDirMeta(fullPath);
9612
9612
  } else {
@@ -9637,7 +9637,7 @@ async function compressLevel1(genDir, genName) {
9637
9637
  }
9638
9638
  let goal = "", completionConditions = "";
9639
9639
  {
9640
- const objective = await readTextFile(join7(genDir, "01-objective.md"));
9640
+ const objective = await readTextFile(join8(genDir, "01-objective.md"));
9641
9641
  if (objective) {
9642
9642
  const goalMatch = objective.match(/## Goal\n([\s\S]*?)(?=\n##)/);
9643
9643
  if (goalMatch)
@@ -9649,7 +9649,7 @@ async function compressLevel1(genDir, genName) {
9649
9649
  }
9650
9650
  let lessons = "", genomeChanges = "", nextBacklog = "";
9651
9651
  {
9652
- const completion = await readTextFile(join7(genDir, "05-completion.md"));
9652
+ const completion = await readTextFile(join8(genDir, "05-completion.md"));
9653
9653
  if (completion) {
9654
9654
  const lessonsMatch = completion.match(/### Lessons Learned\n([\s\S]*?)(?=\n###)/);
9655
9655
  if (lessonsMatch)
@@ -9664,7 +9664,7 @@ async function compressLevel1(genDir, genName) {
9664
9664
  }
9665
9665
  let summaryText = "";
9666
9666
  {
9667
- const completion = await readTextFile(join7(genDir, "05-completion.md"));
9667
+ const completion = await readTextFile(join8(genDir, "05-completion.md"));
9668
9668
  if (completion) {
9669
9669
  const summaryMatch = completion.match(/## Summary\n([\s\S]*?)(?=\n##)/);
9670
9670
  if (summaryMatch)
@@ -9673,7 +9673,7 @@ async function compressLevel1(genDir, genName) {
9673
9673
  }
9674
9674
  let validationResult = "";
9675
9675
  {
9676
- const validation = await readTextFile(join7(genDir, "04-validation.md"));
9676
+ const validation = await readTextFile(join8(genDir, "04-validation.md"));
9677
9677
  if (validation) {
9678
9678
  const resultMatch = validation.match(/## Result: (.+)/);
9679
9679
  if (resultMatch)
@@ -9682,7 +9682,7 @@ async function compressLevel1(genDir, genName) {
9682
9682
  }
9683
9683
  let deferred = "";
9684
9684
  {
9685
- const impl = await readTextFile(join7(genDir, "03-implementation.md"));
9685
+ const impl = await readTextFile(join8(genDir, "03-implementation.md"));
9686
9686
  if (impl) {
9687
9687
  const deferredMatch = impl.match(/## Deferred Tasks\n([\s\S]*?)(?=\n##)/);
9688
9688
  if (deferredMatch) {
@@ -9768,7 +9768,7 @@ async function findForkedByOtherBranches(paths, cwd) {
9768
9768
  }
9769
9769
  async function compressLevel2Single(level1Files, paths) {
9770
9770
  const compressed = [];
9771
- const epochPath = join7(paths.lineage, "epoch.md");
9771
+ const epochPath = join8(paths.lineage, "epoch.md");
9772
9772
  let existingMeta = { generations: [] };
9773
9773
  let existingBody = "";
9774
9774
  const existingContent = await readTextFile(epochPath);
@@ -9817,7 +9817,7 @@ ${import_yaml2.default.stringify(existingMeta).trim()}
9817
9817
  await writeTextFile(epochPath, frontmatter + header + body.trim() + `
9818
9818
  `);
9819
9819
  for (const file of level1Files) {
9820
- await rm(file.path);
9820
+ await rm2(file.path);
9821
9821
  }
9822
9822
  return compressed;
9823
9823
  }
@@ -9840,12 +9840,12 @@ async function compressLineageIfNeeded(paths, projectRoot) {
9840
9840
  const currentTotal = await countDirLines(paths.lineage);
9841
9841
  if (currentTotal <= LINEAGE_MAX_LINES)
9842
9842
  break;
9843
- const dirPath = join7(paths.lineage, dir.name);
9843
+ const dirPath = join8(paths.lineage, dir.name);
9844
9844
  const compressed = await compressLevel1(dirPath, dir.name);
9845
9845
  const genId = dir.name.match(/^gen-\d{3}(?:-[a-f0-9]{6})?/)?.[0] ?? dir.name;
9846
- const outPath = join7(paths.lineage, `${genId}.md`);
9846
+ const outPath = join8(paths.lineage, `${genId}.md`);
9847
9847
  await writeTextFile(outPath, compressed);
9848
- await rm(dirPath, { recursive: true });
9848
+ await rm2(dirPath, { recursive: true });
9849
9849
  result.level1.push(genId);
9850
9850
  }
9851
9851
  const level1s = (await scanLineage(paths)).filter((e) => e.type === "level1");
@@ -9864,8 +9864,8 @@ async function compressLineageIfNeeded(paths, projectRoot) {
9864
9864
  if (compressible.length > 0) {
9865
9865
  const filesWithMeta = await Promise.all(compressible.map(async (e) => ({
9866
9866
  name: e.name,
9867
- path: join7(paths.lineage, e.name),
9868
- meta: await readFileMeta(join7(paths.lineage, e.name))
9867
+ path: join8(paths.lineage, e.name),
9868
+ meta: await readFileMeta(join8(paths.lineage, e.name))
9869
9869
  })));
9870
9870
  const compressed = await compressLevel2Single(filesWithMeta, paths);
9871
9871
  result.level2.push(...compressed);
@@ -9881,18 +9881,40 @@ var init_compression = __esm(() => {
9881
9881
  });
9882
9882
 
9883
9883
  // src/core/lineage.ts
9884
- import { readdir as readdir7 } from "fs/promises";
9885
- import { join as join8 } from "path";
9884
+ import { readdir as readdir8 } from "fs/promises";
9885
+ import { join as join9 } from "path";
9886
9886
  async function listCompleted(paths) {
9887
9887
  try {
9888
- const entries = await readdir7(paths.lineage);
9888
+ const entries = await readdir8(paths.lineage);
9889
9889
  return entries.filter((e) => e.startsWith("gen-")).sort();
9890
9890
  } catch {
9891
9891
  return [];
9892
9892
  }
9893
9893
  }
9894
+ async function listEpochGenerations(paths) {
9895
+ const epochPath = join9(paths.lineage, "epoch.md");
9896
+ const content = await readTextFile(epochPath);
9897
+ if (!content)
9898
+ return [];
9899
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
9900
+ if (!match)
9901
+ return [];
9902
+ try {
9903
+ const meta = import_yaml3.default.parse(match[1]);
9904
+ if (!meta?.generations)
9905
+ return [];
9906
+ return meta.generations.map((g) => g.id).filter(Boolean);
9907
+ } catch {
9908
+ return [];
9909
+ }
9910
+ }
9911
+ async function countAllCompleted(paths) {
9912
+ const genDirs = await listCompleted(paths);
9913
+ const epochGens = await listEpochGenerations(paths);
9914
+ return genDirs.length + epochGens.length;
9915
+ }
9894
9916
  async function readMeta(paths, lineageDirName) {
9895
- const metaPath = join8(paths.lineage, lineageDirName, "meta.yml");
9917
+ const metaPath = join9(paths.lineage, lineageDirName, "meta.yml");
9896
9918
  const content = await readTextFile(metaPath);
9897
9919
  if (content === null)
9898
9920
  return null;
@@ -9901,14 +9923,14 @@ async function readMeta(paths, lineageDirName) {
9901
9923
  async function listMeta(paths) {
9902
9924
  const metas = [];
9903
9925
  try {
9904
- const entries = await readdir7(paths.lineage, { withFileTypes: true });
9926
+ const entries = await readdir8(paths.lineage, { withFileTypes: true });
9905
9927
  for (const entry of entries) {
9906
9928
  if (entry.isDirectory() && entry.name.startsWith("gen-")) {
9907
9929
  const meta = await readMeta(paths, entry.name);
9908
9930
  if (meta)
9909
9931
  metas.push(meta);
9910
9932
  } else if (entry.isFile() && entry.name.startsWith("gen-") && entry.name.endsWith(".md")) {
9911
- const content = await readTextFile(join8(paths.lineage, entry.name));
9933
+ const content = await readTextFile(join9(paths.lineage, entry.name));
9912
9934
  if (content) {
9913
9935
  const meta = parseFrontmatter(content);
9914
9936
  if (meta)
@@ -9921,21 +9943,25 @@ async function listMeta(paths) {
9921
9943
  }
9922
9944
  async function nextSeq(paths, currentId) {
9923
9945
  const genDirs = await listCompleted(paths);
9924
- if (genDirs.length === 0) {
9946
+ const epochGens = await listEpochGenerations(paths);
9947
+ const allIds = [...genDirs, ...epochGens];
9948
+ if (allIds.length === 0) {
9925
9949
  if (currentId) {
9926
9950
  return parseGenSeq(currentId) + 1;
9927
9951
  }
9928
9952
  return 1;
9929
9953
  }
9930
9954
  let maxSeq = 0;
9931
- for (const dir of genDirs) {
9932
- const seq = parseGenSeq(dir);
9955
+ for (const id of allIds) {
9956
+ const seq = parseGenSeq(id);
9933
9957
  if (seq > maxSeq)
9934
9958
  maxSeq = seq;
9935
9959
  }
9936
9960
  return maxSeq + 1;
9937
9961
  }
9938
9962
  function safeCompletedAtTime2(dateStr) {
9963
+ if (!ISO_DATE_RE.test(dateStr))
9964
+ return 0;
9939
9965
  const t = new Date(dateStr).getTime();
9940
9966
  return Number.isNaN(t) ? 0 : t;
9941
9967
  }
@@ -9954,27 +9980,28 @@ async function resolveParents(paths) {
9954
9980
  }
9955
9981
  return [];
9956
9982
  }
9957
- var import_yaml3;
9983
+ var import_yaml3, ISO_DATE_RE;
9958
9984
  var init_lineage = __esm(() => {
9959
9985
  init_fs();
9960
9986
  init_compression();
9961
9987
  init_generation();
9962
9988
  import_yaml3 = __toESM(require_dist(), 1);
9989
+ ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
9963
9990
  });
9964
9991
 
9965
9992
  // src/core/generation.ts
9966
9993
  import { createHash, randomBytes } from "crypto";
9967
9994
  import { hostname } from "os";
9968
- import { readdir as readdir8, mkdir as mkdir5, rename, unlink as unlink3 } from "fs/promises";
9969
- import { join as join9 } from "path";
9995
+ import { readdir as readdir9, mkdir as mkdir6, rename, unlink as unlink4 } from "fs/promises";
9996
+ import { join as join10 } from "path";
9970
9997
  function generateToken(genId, stage, phase) {
9971
9998
  const nonce = randomBytes(16).toString("hex");
9972
- const input = phase ? `${nonce}${genId}${stage}:${phase}` : `${nonce}${genId}${stage}`;
9999
+ const input = `${nonce}${genId}${stage}:${phase}`;
9973
10000
  const hash = createHash("sha256").update(input).digest("hex");
9974
10001
  return { nonce, hash };
9975
10002
  }
9976
10003
  function verifyToken(token, genId, stage, expectedHash, phase) {
9977
- const input = phase ? `${token}${genId}${stage}:${phase}` : `${token}${genId}${stage}`;
10004
+ const input = `${token}${genId}${stage}:${phase}`;
9978
10005
  return createHash("sha256").update(input).digest("hex") === expectedHash;
9979
10006
  }
9980
10007
  function generateGenHash(parents, goal, genomeHash, machineId, startedAt) {
@@ -9987,13 +10014,13 @@ function getMachineId() {
9987
10014
  async function computeGenomeHash(genomePath) {
9988
10015
  const hash = createHash("sha256");
9989
10016
  try {
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);
10017
+ const entries = (await readdir9(genomePath, { recursive: true, withFileTypes: true })).filter((e) => e.isFile()).sort((a, b) => {
10018
+ const pathA = join10(e2path(a), a.name);
10019
+ const pathB = join10(e2path(b), b.name);
9993
10020
  return pathA.localeCompare(pathB);
9994
10021
  });
9995
10022
  for (const entry of entries) {
9996
- const filePath = join9(e2path(entry), entry.name);
10023
+ const filePath = join10(e2path(entry), entry.name);
9997
10024
  const content = await readTextFile(filePath);
9998
10025
  if (content !== null) {
9999
10026
  hash.update(filePath.replace(genomePath, ""));
@@ -10106,7 +10133,7 @@ class GenerationManager {
10106
10133
  const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
10107
10134
  const genDirName = `${state.id}-${goalSlug}`;
10108
10135
  const genDir = this.paths.generationDir(genDirName);
10109
- await mkdir5(genDir, { recursive: true });
10136
+ await mkdir6(genDir, { recursive: true });
10110
10137
  const meta = {
10111
10138
  id: state.id,
10112
10139
  type: state.type ?? "normal",
@@ -10117,42 +10144,42 @@ class GenerationManager {
10117
10144
  completedAt: now,
10118
10145
  ...state.type === "recovery" && state.recovers ? { recovers: state.recovers } : {}
10119
10146
  };
10120
- await writeTextFile(join9(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
10121
- const lifeEntries = await readdir8(this.paths.life);
10147
+ await writeTextFile(join10(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
10148
+ const lifeEntries = await readdir9(this.paths.life);
10122
10149
  for (const entry of lifeEntries) {
10123
10150
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
10124
- const srcPath = join9(this.paths.life, entry);
10125
- const destPath = join9(genDir, entry);
10151
+ const srcPath = join10(this.paths.life, entry);
10152
+ const destPath = join10(genDir, entry);
10126
10153
  let content = await readTextFile(srcPath);
10127
10154
  if (content && content.startsWith("# REAP MANAGED")) {
10128
10155
  content = content.replace(/^# REAP MANAGED[^\n]*\n/, "");
10129
10156
  }
10130
10157
  await writeTextFile(destPath, content ?? "");
10131
- await unlink3(srcPath);
10158
+ await unlink4(srcPath);
10132
10159
  }
10133
10160
  }
10134
- const backlogDir = join9(genDir, "backlog");
10135
- await mkdir5(backlogDir, { recursive: true });
10161
+ const backlogDir = join10(genDir, "backlog");
10162
+ await mkdir6(backlogDir, { recursive: true });
10136
10163
  try {
10137
- const backlogEntries = await readdir8(this.paths.backlog);
10164
+ const backlogEntries = await readdir9(this.paths.backlog);
10138
10165
  for (const entry of backlogEntries) {
10139
- const content = await readTextFile(join9(this.paths.backlog, entry));
10166
+ const content = await readTextFile(join10(this.paths.backlog, entry));
10140
10167
  if (!content)
10141
10168
  continue;
10142
10169
  const isConsumed = /status:\s*consumed/i.test(content) || /consumed:\s*true/i.test(content);
10143
- await writeTextFile(join9(backlogDir, entry), content);
10170
+ await writeTextFile(join10(backlogDir, entry), content);
10144
10171
  if (isConsumed) {
10145
- await unlink3(join9(this.paths.backlog, entry));
10172
+ await unlink4(join10(this.paths.backlog, entry));
10146
10173
  }
10147
10174
  }
10148
10175
  } catch {}
10149
10176
  try {
10150
- const mutEntries = await readdir8(this.paths.mutations);
10177
+ const mutEntries = await readdir9(this.paths.mutations);
10151
10178
  if (mutEntries.length > 0) {
10152
- const mutDir = join9(genDir, "mutations");
10153
- await mkdir5(mutDir, { recursive: true });
10179
+ const mutDir = join10(genDir, "mutations");
10180
+ await mkdir6(mutDir, { recursive: true });
10154
10181
  for (const entry of mutEntries) {
10155
- await rename(join9(this.paths.mutations, entry), join9(mutDir, entry));
10182
+ await rename(join10(this.paths.mutations, entry), join10(mutDir, entry));
10156
10183
  }
10157
10184
  }
10158
10185
  } catch {}
@@ -10166,6 +10193,9 @@ class GenerationManager {
10166
10193
  async listCompleted() {
10167
10194
  return listCompleted(this.paths);
10168
10195
  }
10196
+ async countAllCompleted() {
10197
+ return countAllCompleted(this.paths);
10198
+ }
10169
10199
  async readMeta(lineageDirName) {
10170
10200
  return readMeta(this.paths, lineageDirName);
10171
10201
  }
@@ -10195,10 +10225,10 @@ var init_generation = __esm(() => {
10195
10225
  });
10196
10226
 
10197
10227
  // src/core/version.ts
10198
- import { execSync as execSync3 } from "child_process";
10228
+ import { execSync as execSync4 } from "child_process";
10199
10229
  function checkLatestVersion() {
10200
10230
  try {
10201
- const result = execSync3("npm view @c-d-cc/reap version", {
10231
+ const result = execSync4("npm view @c-d-cc/reap version", {
10202
10232
  timeout: 2000,
10203
10233
  encoding: "utf-8",
10204
10234
  stdio: ["pipe", "pipe", "pipe"]
@@ -10323,13 +10353,13 @@ var init_merge_lifecycle = __esm(() => {
10323
10353
 
10324
10354
  // src/core/hook-engine.ts
10325
10355
  import { readdir as readdir14 } from "fs/promises";
10326
- import { join as join16 } from "path";
10327
- import { execSync as execSync4 } from "child_process";
10356
+ import { join as join17 } from "path";
10357
+ import { execSync as execSync5 } from "child_process";
10328
10358
  async function executeHooks(hooksDir, event, projectRoot) {
10329
10359
  const hooks = await scanHooks(hooksDir, event);
10330
10360
  if (hooks.length === 0)
10331
10361
  return [];
10332
- const conditionsDir = join16(hooksDir, "conditions");
10362
+ const conditionsDir = join17(hooksDir, "conditions");
10333
10363
  const results = [];
10334
10364
  for (const hook of hooks) {
10335
10365
  const conditionMet = await evaluateCondition(conditionsDir, hook.condition, projectRoot);
@@ -10364,7 +10394,7 @@ async function scanHooks(hooksDir, event) {
10364
10394
  const match = filename.match(pattern);
10365
10395
  if (!match)
10366
10396
  continue;
10367
- const meta = await parseHookMeta(join16(hooksDir, filename), match[2]);
10397
+ const meta = await parseHookMeta(join17(hooksDir, filename), match[2]);
10368
10398
  hooks.push({
10369
10399
  filename,
10370
10400
  name: match[1],
@@ -10385,7 +10415,7 @@ async function parseHookMeta(filePath, ext) {
10385
10415
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
10386
10416
  if (fmMatch) {
10387
10417
  try {
10388
- const fm = import_yaml9.default.parse(fmMatch[1]) ?? {};
10418
+ const fm = import_yaml8.default.parse(fmMatch[1]) ?? {};
10389
10419
  return {
10390
10420
  condition: String(fm.condition ?? "always"),
10391
10421
  order: Number(fm.order ?? 50)
@@ -10411,12 +10441,12 @@ async function parseHookMeta(filePath, ext) {
10411
10441
  async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
10412
10442
  if (conditionName === "always")
10413
10443
  return { met: true };
10414
- const scriptPath = join16(conditionsDir, `${conditionName}.sh`);
10444
+ const scriptPath = join17(conditionsDir, `${conditionName}.sh`);
10415
10445
  if (!await fileExists(scriptPath)) {
10416
10446
  return { met: false, reason: `condition script not found: ${conditionName}.sh` };
10417
10447
  }
10418
10448
  try {
10419
- execSync4(`bash "${scriptPath}"`, { cwd: projectRoot, timeout: 1e4, stdio: "pipe" });
10449
+ execSync5(`bash "${scriptPath}"`, { cwd: projectRoot, timeout: 1e4, stdio: "pipe" });
10420
10450
  return { met: true };
10421
10451
  } catch {
10422
10452
  return { met: false, reason: `condition '${conditionName}' returned non-zero` };
@@ -10424,7 +10454,7 @@ async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
10424
10454
  }
10425
10455
  async function executeShHook(hook, event, projectRoot, hooksDir) {
10426
10456
  try {
10427
- const stdout = execSync4(`bash "${join16(hooksDir, hook.filename)}"`, {
10457
+ const stdout = execSync5(`bash "${join17(hooksDir, hook.filename)}"`, {
10428
10458
  cwd: projectRoot,
10429
10459
  timeout: 60000,
10430
10460
  stdio: "pipe"
@@ -10450,7 +10480,7 @@ async function executeShHook(hook, event, projectRoot, hooksDir) {
10450
10480
  }
10451
10481
  }
10452
10482
  async function executeMdHook(hook, event, hooksDir) {
10453
- const content = await readTextFile(join16(hooksDir, hook.filename));
10483
+ const content = await readTextFile(join17(hooksDir, hook.filename));
10454
10484
  const body = content?.replace(/^---\n[\s\S]*?\n---\n?/, "").trim() ?? "";
10455
10485
  return {
10456
10486
  name: hook.name,
@@ -10460,10 +10490,10 @@ async function executeMdHook(hook, event, hooksDir) {
10460
10490
  content: body
10461
10491
  };
10462
10492
  }
10463
- var import_yaml9;
10493
+ var import_yaml8;
10464
10494
  var init_hook_engine = __esm(() => {
10465
10495
  init_fs();
10466
- import_yaml9 = __toESM(require_dist(), 1);
10496
+ import_yaml8 = __toESM(require_dist(), 1);
10467
10497
  });
10468
10498
 
10469
10499
  // src/cli/commands/run/back.ts
@@ -10568,9 +10598,104 @@ var init_back = __esm(() => {
10568
10598
  init_hook_engine();
10569
10599
  });
10570
10600
 
10601
+ // src/core/stage-transition.ts
10602
+ import { join as join18 } from "path";
10603
+ function verifyNonce(command, state, stage, phase) {
10604
+ if (!state.lastNonce) {
10605
+ return;
10606
+ }
10607
+ if (!state.expectedHash) {
10608
+ emitError(command, "Nonce transition error: lastNonce exists but expectedHash is missing. State may be corrupted.");
10609
+ }
10610
+ if (!verifyToken(state.lastNonce, state.id, stage, state.expectedHash, phase)) {
10611
+ emitError(command, `Nonce verification failed for ${stage}:${phase}. Re-run the previous phase to get a valid token.`);
10612
+ }
10613
+ state.lastNonce = undefined;
10614
+ state.expectedHash = undefined;
10615
+ state.phase = undefined;
10616
+ }
10617
+ function setNonce(state, stage, phase) {
10618
+ const { nonce, hash } = generateToken(state.id, stage, phase);
10619
+ state.lastNonce = nonce;
10620
+ state.expectedHash = hash;
10621
+ state.phase = phase;
10622
+ }
10623
+ async function performTransition(paths, state, saveFn) {
10624
+ const isMerge = state.type === "merge";
10625
+ let nextStage;
10626
+ if (isMerge) {
10627
+ nextStage = MergeLifeCycle.next(state.stage);
10628
+ } else {
10629
+ nextStage = LifeCycle.next(state.stage);
10630
+ }
10631
+ if (!nextStage) {
10632
+ throw new Error(`Cannot advance from '${state.stage}' — already at the last stage.`);
10633
+ }
10634
+ state.stage = nextStage;
10635
+ if (!state.timeline)
10636
+ state.timeline = [];
10637
+ state.timeline.push({ stage: nextStage, at: new Date().toISOString() });
10638
+ await saveFn(state);
10639
+ const artifactFile = isMerge ? MERGE_ARTIFACT[nextStage] : NORMAL_ARTIFACT[nextStage];
10640
+ if (artifactFile) {
10641
+ const templateDir = join18(__require("os").homedir(), ".reap", "templates");
10642
+ const templatePath = join18(templateDir, artifactFile);
10643
+ const destPath = paths.artifact(artifactFile);
10644
+ if (await fileExists(templatePath) && !await fileExists(destPath)) {
10645
+ const templateContent = await readTextFile(templatePath);
10646
+ if (templateContent) {
10647
+ await writeTextFile(destPath, templateContent);
10648
+ }
10649
+ }
10650
+ }
10651
+ const stageKey = isMerge && (nextStage === "validation" || nextStage === "completion") ? `${nextStage}:merge` : nextStage;
10652
+ const stageHookEvent = STAGE_HOOK[stageKey];
10653
+ const stageHookResults = stageHookEvent ? await executeHooks(paths.hooks, stageHookEvent, paths.projectRoot) : [];
10654
+ const transitionEvent = isMerge ? "onMergeTransited" : "onLifeTransited";
10655
+ const transitionHookResults = await executeHooks(paths.hooks, transitionEvent, paths.projectRoot);
10656
+ return {
10657
+ nextStage,
10658
+ artifactFile,
10659
+ stageHookResults,
10660
+ transitionHookResults
10661
+ };
10662
+ }
10663
+ var NORMAL_ARTIFACT, MERGE_ARTIFACT, STAGE_HOOK;
10664
+ var init_stage_transition = __esm(() => {
10665
+ init_lifecycle();
10666
+ init_merge_lifecycle();
10667
+ init_generation();
10668
+ init_fs();
10669
+ init_hook_engine();
10670
+ NORMAL_ARTIFACT = {
10671
+ planning: "02-planning.md",
10672
+ implementation: "03-implementation.md",
10673
+ validation: "04-validation.md",
10674
+ completion: "05-completion.md"
10675
+ };
10676
+ MERGE_ARTIFACT = {
10677
+ mate: "02-mate.md",
10678
+ merge: "03-merge.md",
10679
+ sync: "04-sync.md",
10680
+ validation: "05-validation.md",
10681
+ completion: "06-completion.md"
10682
+ };
10683
+ STAGE_HOOK = {
10684
+ planning: "onLifeObjected",
10685
+ implementation: "onLifePlanned",
10686
+ validation: "onLifeImplemented",
10687
+ completion: "onLifeValidated",
10688
+ mate: "onMergeDetected",
10689
+ merge: "onMergeMated",
10690
+ sync: "onMergeMerged",
10691
+ "validation:merge": "onMergeSynced",
10692
+ "completion:merge": "onMergeValidated"
10693
+ };
10694
+ });
10695
+
10571
10696
  // src/core/backlog.ts
10572
10697
  import { readdir as readdir15 } from "fs/promises";
10573
- import { join as join17 } from "path";
10698
+ import { join as join19 } from "path";
10574
10699
  async function scanBacklog(backlogDir) {
10575
10700
  let entries;
10576
10701
  try {
@@ -10582,7 +10707,7 @@ async function scanBacklog(backlogDir) {
10582
10707
  for (const filename of entries) {
10583
10708
  if (!filename.endsWith(".md"))
10584
10709
  continue;
10585
- const content = await readTextFile(join17(backlogDir, filename));
10710
+ const content = await readTextFile(join19(backlogDir, filename));
10586
10711
  if (!content)
10587
10712
  continue;
10588
10713
  const { frontmatter, body } = parseFrontmatter2(content);
@@ -10600,7 +10725,7 @@ async function scanBacklog(backlogDir) {
10600
10725
  return items;
10601
10726
  }
10602
10727
  async function markBacklogConsumed(backlogDir, filename, genId) {
10603
- const filePath = join17(backlogDir, filename);
10728
+ const filePath = join19(backlogDir, filename);
10604
10729
  const content = await readTextFile(filePath);
10605
10730
  if (!content)
10606
10731
  return;
@@ -10609,7 +10734,7 @@ async function markBacklogConsumed(backlogDir, filename, genId) {
10609
10734
  frontmatter.consumedBy = genId;
10610
10735
  delete frontmatter.consumed;
10611
10736
  const newContent = `---
10612
- ${import_yaml10.default.stringify(frontmatter).trim()}
10737
+ ${import_yaml9.default.stringify(frontmatter).trim()}
10613
10738
  ---
10614
10739
  ${body}`;
10615
10740
  await writeTextFile(filePath, newContent);
@@ -10624,7 +10749,7 @@ async function revertBacklogConsumed(backlogDir, genId) {
10624
10749
  for (const filename of entries) {
10625
10750
  if (!filename.endsWith(".md"))
10626
10751
  continue;
10627
- const filePath = join17(backlogDir, filename);
10752
+ const filePath = join19(backlogDir, filename);
10628
10753
  const content = await readTextFile(filePath);
10629
10754
  if (!content)
10630
10755
  continue;
@@ -10633,7 +10758,7 @@ async function revertBacklogConsumed(backlogDir, genId) {
10633
10758
  frontmatter.status = "pending";
10634
10759
  delete frontmatter.consumedBy;
10635
10760
  const newContent = `---
10636
- ${import_yaml10.default.stringify(frontmatter).trim()}
10761
+ ${import_yaml9.default.stringify(frontmatter).trim()}
10637
10762
  ---
10638
10763
  ${body}`;
10639
10764
  await writeTextFile(filePath, newContent);
@@ -10645,15 +10770,15 @@ function parseFrontmatter2(content) {
10645
10770
  if (!match)
10646
10771
  return { frontmatter: {}, body: content };
10647
10772
  try {
10648
- return { frontmatter: import_yaml10.default.parse(match[1]) ?? {}, body: match[2] };
10773
+ return { frontmatter: import_yaml9.default.parse(match[1]) ?? {}, body: match[2] };
10649
10774
  } catch {
10650
10775
  return { frontmatter: {}, body: content };
10651
10776
  }
10652
10777
  }
10653
- var import_yaml10;
10778
+ var import_yaml9;
10654
10779
  var init_backlog = __esm(() => {
10655
10780
  init_fs();
10656
- import_yaml10 = __toESM(require_dist(), 1);
10781
+ import_yaml9 = __toESM(require_dist(), 1);
10657
10782
  });
10658
10783
 
10659
10784
  // src/cli/commands/run/start.ts
@@ -10661,7 +10786,7 @@ var exports_start = {};
10661
10786
  __export(exports_start, {
10662
10787
  execute: () => execute3
10663
10788
  });
10664
- import { join as join18 } from "path";
10789
+ import { join as join20 } from "path";
10665
10790
  import { readdir as readdir16 } from "fs/promises";
10666
10791
  function getFlag2(args, name) {
10667
10792
  const idx = args.indexOf(`--${name}`);
@@ -10715,14 +10840,13 @@ async function execute3(paths, phase, argv = []) {
10715
10840
  genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length + 1;
10716
10841
  } catch {}
10717
10842
  const state = await gm.create(goal, genomeVersion);
10718
- const { nonce, hash } = generateToken(state.id, state.stage);
10719
- state.expectedHash = hash;
10843
+ setNonce(state, "objective", "entry");
10720
10844
  await gm.save(state);
10721
10845
  if (backlogFile) {
10722
10846
  await markBacklogConsumed(paths.backlog, backlogFile, state.id);
10723
10847
  }
10724
- const templateDir = join18(__require("os").homedir(), ".reap", "templates");
10725
- const templatePath = join18(templateDir, "01-objective.md");
10848
+ const templateDir = join20(__require("os").homedir(), ".reap", "templates");
10849
+ const templatePath = join20(templateDir, "01-objective.md");
10726
10850
  const destPath = paths.artifact("01-objective.md");
10727
10851
  if (await fileExists(templatePath)) {
10728
10852
  let template = await readTextFile(templatePath);
@@ -10752,16 +10876,17 @@ async function execute3(paths, phase, argv = []) {
10752
10876
  }
10753
10877
  var init_start = __esm(() => {
10754
10878
  init_generation();
10879
+ init_stage_transition();
10755
10880
  init_fs();
10756
10881
  init_backlog();
10757
10882
  init_hook_engine();
10758
10883
  });
10759
10884
 
10760
10885
  // src/core/commit.ts
10761
- import { execSync as execSync5 } from "child_process";
10886
+ import { execSync as execSync6 } from "child_process";
10762
10887
  function checkSubmodules(projectRoot) {
10763
10888
  try {
10764
- const output = execSync5("git submodule status", { cwd: projectRoot, stdio: "pipe" }).toString();
10889
+ const output = execSync6("git submodule status", { cwd: projectRoot, stdio: "pipe" }).toString();
10765
10890
  if (!output.trim())
10766
10891
  return [];
10767
10892
  return output.trim().split(`
@@ -10781,134 +10906,13 @@ function checkSubmodules(projectRoot) {
10781
10906
  }
10782
10907
  var init_commit = () => {};
10783
10908
 
10784
- // src/core/stage-transition.ts
10785
- import { join as join19 } from "path";
10786
- function verifyStageEntry(command, state) {
10787
- if (!state.lastNonce) {
10788
- return;
10789
- }
10790
- if (state.phase) {
10791
- return;
10792
- }
10793
- if (!state.expectedHash) {
10794
- emitError(command, "Stage transition error: lastNonce exists but expectedHash is missing. State may be corrupted.");
10795
- }
10796
- const isMerge = state.type === "merge";
10797
- let prevStage;
10798
- if (isMerge) {
10799
- prevStage = MergeLifeCycle.prev(state.stage);
10800
- } else {
10801
- prevStage = LifeCycle.prev(state.stage);
10802
- }
10803
- if (!prevStage) {
10804
- emitError(command, "Stage transition error: cannot determine previous stage for token verification.");
10805
- }
10806
- if (!verifyToken(state.lastNonce, state.id, prevStage, state.expectedHash)) {
10807
- emitError(command, `Token verification failed. The stage chain token does not match. Re-run the previous stage command to get a valid token.`);
10808
- }
10809
- state.lastNonce = undefined;
10810
- state.expectedHash = undefined;
10811
- }
10812
- function setPhaseNonce(state, stage, phase) {
10813
- const { nonce, hash } = generateToken(state.id, stage, phase);
10814
- state.lastNonce = nonce;
10815
- state.expectedHash = hash;
10816
- state.phase = phase;
10817
- }
10818
- function verifyPhaseEntry(command, state, stage, phase) {
10819
- if (!state.lastNonce) {
10820
- emitError(command, `Phase nonce missing. Complete the previous phase before running --phase ${phase}.`);
10821
- }
10822
- if (!state.expectedHash) {
10823
- emitError(command, "Phase transition error: lastNonce exists but expectedHash is missing. State may be corrupted.");
10824
- }
10825
- if (!verifyToken(state.lastNonce, state.id, stage, state.expectedHash, phase)) {
10826
- emitError(command, `Phase token verification failed. Re-run the previous phase to get a valid token.`);
10827
- }
10828
- state.lastNonce = undefined;
10829
- state.expectedHash = undefined;
10830
- state.phase = undefined;
10831
- }
10832
- async function performTransition(paths, state, saveFn) {
10833
- const isMerge = state.type === "merge";
10834
- let nextStage;
10835
- if (isMerge) {
10836
- nextStage = MergeLifeCycle.next(state.stage);
10837
- } else {
10838
- nextStage = LifeCycle.next(state.stage);
10839
- }
10840
- if (!nextStage) {
10841
- throw new Error(`Cannot advance from '${state.stage}' — already at the last stage.`);
10842
- }
10843
- state.stage = nextStage;
10844
- if (!state.timeline)
10845
- state.timeline = [];
10846
- state.timeline.push({ stage: nextStage, at: new Date().toISOString() });
10847
- await saveFn(state);
10848
- const artifactFile = isMerge ? MERGE_ARTIFACT[nextStage] : NORMAL_ARTIFACT[nextStage];
10849
- if (artifactFile) {
10850
- const templateDir = join19(__require("os").homedir(), ".reap", "templates");
10851
- const templatePath = join19(templateDir, artifactFile);
10852
- const destPath = paths.artifact(artifactFile);
10853
- if (await fileExists(templatePath) && !await fileExists(destPath)) {
10854
- const templateContent = await readTextFile(templatePath);
10855
- if (templateContent) {
10856
- await writeTextFile(destPath, templateContent);
10857
- }
10858
- }
10859
- }
10860
- const stageKey = isMerge && (nextStage === "validation" || nextStage === "completion") ? `${nextStage}:merge` : nextStage;
10861
- const stageHookEvent = STAGE_HOOK[stageKey];
10862
- const stageHookResults = stageHookEvent ? await executeHooks(paths.hooks, stageHookEvent, paths.projectRoot) : [];
10863
- const transitionEvent = isMerge ? "onMergeTransited" : "onLifeTransited";
10864
- const transitionHookResults = await executeHooks(paths.hooks, transitionEvent, paths.projectRoot);
10865
- return {
10866
- nextStage,
10867
- artifactFile,
10868
- stageHookResults,
10869
- transitionHookResults
10870
- };
10871
- }
10872
- var NORMAL_ARTIFACT, MERGE_ARTIFACT, STAGE_HOOK;
10873
- var init_stage_transition = __esm(() => {
10874
- init_lifecycle();
10875
- init_merge_lifecycle();
10876
- init_generation();
10877
- init_fs();
10878
- init_hook_engine();
10879
- NORMAL_ARTIFACT = {
10880
- planning: "02-planning.md",
10881
- implementation: "03-implementation.md",
10882
- validation: "04-validation.md",
10883
- completion: "05-completion.md"
10884
- };
10885
- MERGE_ARTIFACT = {
10886
- mate: "02-mate.md",
10887
- merge: "03-merge.md",
10888
- sync: "04-sync.md",
10889
- validation: "05-validation.md",
10890
- completion: "06-completion.md"
10891
- };
10892
- STAGE_HOOK = {
10893
- planning: "onLifeObjected",
10894
- implementation: "onLifePlanned",
10895
- validation: "onLifeImplemented",
10896
- completion: "onLifeValidated",
10897
- mate: "onMergeDetected",
10898
- merge: "onMergeMated",
10899
- sync: "onMergeMerged",
10900
- "validation:merge": "onMergeSynced",
10901
- "completion:merge": "onMergeValidated"
10902
- };
10903
- });
10904
-
10905
10909
  // src/cli/commands/run/completion.ts
10906
10910
  var exports_completion = {};
10907
10911
  __export(exports_completion, {
10908
10912
  execute: () => execute4
10909
10913
  });
10910
- import { join as join20 } from "path";
10911
- import { execSync as execSync6 } from "child_process";
10914
+ import { join as join21 } from "path";
10915
+ import { execSync as execSync7 } from "child_process";
10912
10916
  function detectGenomeImpact(projectRoot) {
10913
10917
  const impact = {
10914
10918
  newCommands: [],
@@ -10917,7 +10921,7 @@ function detectGenomeImpact(projectRoot) {
10917
10921
  };
10918
10922
  let changedFiles;
10919
10923
  try {
10920
- const output = execSync6("git diff --name-only HEAD~1", {
10924
+ const output = execSync7("git diff --name-only HEAD~1", {
10921
10925
  cwd: projectRoot,
10922
10926
  encoding: "utf-8",
10923
10927
  timeout: 5000
@@ -10988,20 +10992,20 @@ async function execute4(paths, phase) {
10988
10992
  if (state.stage !== "completion") {
10989
10993
  emitError("completion", `Stage is '${state.stage}', expected 'completion'.`);
10990
10994
  }
10991
- verifyStageEntry("completion", state);
10992
- await gm.save(state);
10993
10995
  const validationArtifact = paths.artifact("04-validation.md");
10994
10996
  if (!await fileExists(validationArtifact)) {
10995
10997
  emitError("completion", "04-validation.md does not exist. Complete validation first.");
10996
10998
  }
10997
10999
  if (!phase || phase === "retrospective") {
11000
+ verifyNonce("completion", state, "completion", "entry");
11001
+ await gm.save(state);
10998
11002
  const backlogItems = await scanBacklog(paths.backlog);
10999
11003
  const validationContent = await readTextFile(validationArtifact);
11000
11004
  const implContent = await readTextFile(paths.artifact("03-implementation.md"));
11001
11005
  const destPath = paths.artifact("05-completion.md");
11002
11006
  if (!await fileExists(destPath)) {
11003
- const templateDir = join20(__require("os").homedir(), ".reap", "templates");
11004
- const templatePath = join20(templateDir, "05-completion.md");
11007
+ const templateDir = join21(__require("os").homedir(), ".reap", "templates");
11008
+ const templatePath = join21(templateDir, "05-completion.md");
11005
11009
  if (await fileExists(templatePath)) {
11006
11010
  const { writeTextFile: writeTextFile2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
11007
11011
  const template = await readTextFile(templatePath);
@@ -11009,7 +11013,7 @@ async function execute4(paths, phase) {
11009
11013
  await writeTextFile2(destPath, template);
11010
11014
  }
11011
11015
  }
11012
- setPhaseNonce(state, "completion", "retrospective");
11016
+ setNonce(state, "completion", "feedKnowledge");
11013
11017
  await gm.save(state);
11014
11018
  emitOutput({
11015
11019
  status: "prompt",
@@ -11030,7 +11034,7 @@ async function execute4(paths, phase) {
11030
11034
  });
11031
11035
  }
11032
11036
  if (phase === "feedKnowledge") {
11033
- verifyPhaseEntry("completion", state, "completion", "retrospective");
11037
+ verifyNonce("completion", state, "completion", "feedKnowledge");
11034
11038
  await gm.save(state);
11035
11039
  const backlogItems = await scanBacklog(paths.backlog);
11036
11040
  const genomeChanges = backlogItems.filter((b) => b.type === "genome-change" && b.status !== "consumed");
@@ -11077,43 +11081,6 @@ async function execute4(paths, phase) {
11077
11081
  message: `Generation ${state.id} archived. ${hasChanges ? "Genome/env changes applied." : "No genome/environment changes."} ${toConsume.length} backlog item(s) consumed.`
11078
11082
  });
11079
11083
  }
11080
- if (phase === "consume") {
11081
- const backlogItems = await scanBacklog(paths.backlog);
11082
- const toConsume = backlogItems.filter((b) => (b.type === "genome-change" || b.type === "environment-change") && b.status !== "consumed");
11083
- for (const item of toConsume) {
11084
- await markBacklogConsumed(paths.backlog, item.filename, state.id);
11085
- }
11086
- emitOutput({
11087
- status: "prompt",
11088
- command: "completion",
11089
- phase: "hook-suggest",
11090
- completed: ["gate", "artifact-create", "context-scan", "retrospective", "genome-apply", "backlog-consume"],
11091
- context: { id: state.id, consumedCount: toConsume.length },
11092
- prompt: "Check the last 3 generations in .reap/lineage/ for repeated manual patterns. If found, suggest hooks (max 2). Then run: reap run completion --phase archive",
11093
- nextCommand: "reap run completion --phase archive"
11094
- });
11095
- }
11096
- if (phase === "archive") {
11097
- const hookResults = await executeHooks(paths.hooks, "onLifeCompleted", paths.projectRoot);
11098
- const submodules = checkSubmodules(paths.projectRoot);
11099
- const dirtySubmodules = submodules.filter((s) => s.dirty);
11100
- const compression = await gm.complete();
11101
- emitOutput({
11102
- status: "prompt",
11103
- command: "completion",
11104
- phase: "commit",
11105
- completed: ["gate", "artifact-create", "context-scan", "retrospective", "genome", "hook-suggest", "hooks", "archive", "compress"],
11106
- context: {
11107
- id: state.id,
11108
- goal: state.goal,
11109
- compression: { level1: compression.level1.length, level2: compression.level2.length },
11110
- hookResults,
11111
- dirtySubmodules
11112
- },
11113
- prompt: (dirtySubmodules.length > 0 ? `Dirty submodules detected: ${dirtySubmodules.map((s) => s.path).join(", ")}. Commit and push inside each submodule first, then commit the parent repo. Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.` : `Commit all changes (code + .reap/ artifacts). Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.`) + buildMdHookPrompt(hookResults),
11114
- message: `Generation ${state.id} archived.`
11115
- });
11116
- }
11117
11084
  }
11118
11085
  var init_completion = __esm(() => {
11119
11086
  init_generation();
@@ -11129,7 +11096,7 @@ var exports_abort = {};
11129
11096
  __export(exports_abort, {
11130
11097
  execute: () => execute5
11131
11098
  });
11132
- import { join as join21 } from "path";
11099
+ import { join as join22 } from "path";
11133
11100
  import { readdir as readdir17, unlink as unlink6 } from "fs/promises";
11134
11101
  function getFlag3(args, name) {
11135
11102
  const idx = args.indexOf(`--${name}`);
@@ -11205,7 +11172,7 @@ async function execute5(paths, phase, argv = []) {
11205
11172
  sourceAction === "stash" ? "git stash pop으로 코드 복구" : sourceAction === "hold" ? "코드 변경이 working tree에 유지됨" : sourceAction === "rollback" ? "코드 변경이 revert됨. objective부터 재시작 필요" : "소스 코드 변경 없음"
11206
11173
  ].filter((line) => line !== null).join(`
11207
11174
  `);
11208
- await writeTextFile(join21(paths.backlog, `aborted-${state.id}.md`), backlogContent);
11175
+ await writeTextFile(join22(paths.backlog, `aborted-${state.id}.md`), backlogContent);
11209
11176
  backlogSaved = true;
11210
11177
  }
11211
11178
  await revertBacklogConsumed(paths.backlog, state.id);
@@ -11213,7 +11180,7 @@ async function execute5(paths, phase, argv = []) {
11213
11180
  const lifeEntries = await readdir17(paths.life);
11214
11181
  for (const entry of lifeEntries) {
11215
11182
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
11216
- await unlink6(join21(paths.life, entry));
11183
+ await unlink6(join22(paths.life, entry));
11217
11184
  }
11218
11185
  }
11219
11186
  } catch {}
@@ -11272,7 +11239,7 @@ var exports_objective = {};
11272
11239
  __export(exports_objective, {
11273
11240
  execute: () => execute7
11274
11241
  });
11275
- import { join as join22 } from "path";
11242
+ import { join as join23 } from "path";
11276
11243
  import { readdir as readdir18 } from "fs/promises";
11277
11244
  async function execute7(paths, phase) {
11278
11245
  const gm = new GenerationManager(paths);
@@ -11284,6 +11251,8 @@ async function execute7(paths, phase) {
11284
11251
  emitError("objective", `Current stage is '${state.stage}', not 'objective'. Start a new Generation with /reap.start or check the current state with 'reap status'.`);
11285
11252
  }
11286
11253
  if (!phase || phase === "work") {
11254
+ verifyNonce("objective", state, "objective", "entry");
11255
+ await gm.save(state);
11287
11256
  const artifactPath = paths.artifact("01-objective.md");
11288
11257
  const existingArtifact = await readTextFile(artifactPath);
11289
11258
  const isReentry = !!existingArtifact && existingArtifact.length > 100;
@@ -11294,9 +11263,9 @@ async function execute7(paths, phase) {
11294
11263
  const genDirs = lineageEntries.filter((e) => e.startsWith("gen-")).sort();
11295
11264
  if (genDirs.length > 0) {
11296
11265
  const lastGen = genDirs[genDirs.length - 1];
11297
- prevCompletion = await readTextFile(join22(paths.lineage, lastGen, "05-completion.md"));
11266
+ prevCompletion = await readTextFile(join23(paths.lineage, lastGen, "05-completion.md"));
11298
11267
  if (!prevCompletion) {
11299
- const compressed = await readTextFile(join22(paths.lineage, `${lastGen}.md`));
11268
+ const compressed = await readTextFile(join23(paths.lineage, `${lastGen}.md`));
11300
11269
  if (compressed)
11301
11270
  prevCompletion = compressed.slice(0, 2000);
11302
11271
  }
@@ -11313,8 +11282,8 @@ async function execute7(paths, phase) {
11313
11282
  lineageCount = entries.filter((e) => e.startsWith("gen-")).length;
11314
11283
  } catch {}
11315
11284
  if (!isReentry) {
11316
- const templateDir = join22(__require("os").homedir(), ".reap", "templates");
11317
- const templatePath = join22(templateDir, "01-objective.md");
11285
+ const templateDir = join23(__require("os").homedir(), ".reap", "templates");
11286
+ const templatePath = join23(templateDir, "01-objective.md");
11318
11287
  if (await fileExists(templatePath)) {
11319
11288
  let template = await readTextFile(templatePath);
11320
11289
  if (template) {
@@ -11323,7 +11292,7 @@ async function execute7(paths, phase) {
11323
11292
  }
11324
11293
  }
11325
11294
  }
11326
- setPhaseNonce(state, "objective", "work");
11295
+ setNonce(state, "objective", "complete");
11327
11296
  await gm.save(state);
11328
11297
  emitOutput({
11329
11298
  status: "prompt",
@@ -11386,8 +11355,7 @@ async function execute7(paths, phase) {
11386
11355
  });
11387
11356
  }
11388
11357
  if (phase === "complete") {
11389
- verifyPhaseEntry("objective", state, "objective", "work");
11390
- await gm.save(state);
11358
+ verifyNonce("objective", state, "objective", "complete");
11391
11359
  const artifactPath = paths.artifact("01-objective.md");
11392
11360
  if (!await fileExists(artifactPath)) {
11393
11361
  emitError("objective", "01-objective.md does not exist. Complete the objective work first.");
@@ -11396,9 +11364,8 @@ async function execute7(paths, phase) {
11396
11364
  if (!content || content.length < 50) {
11397
11365
  emitError("objective", "01-objective.md appears incomplete (too short). Fill in the objective before completing.");
11398
11366
  }
11399
- const { nonce, hash } = generateToken(state.id, state.stage);
11400
- state.expectedHash = hash;
11401
- state.lastNonce = nonce;
11367
+ setNonce(state, "planning", "entry");
11368
+ await gm.save(state);
11402
11369
  const hookResults = await executeHooks(paths.hooks, "onLifeObjected", paths.projectRoot);
11403
11370
  const transition = await performTransition(paths, state, (s) => gm.save(s));
11404
11371
  const nextCommand = `reap run ${transition.nextStage}`;
@@ -11432,7 +11399,7 @@ var exports_planning = {};
11432
11399
  __export(exports_planning, {
11433
11400
  execute: () => execute8
11434
11401
  });
11435
- import { join as join23 } from "path";
11402
+ import { join as join24 } from "path";
11436
11403
  async function execute8(paths, phase) {
11437
11404
  const gm = new GenerationManager(paths);
11438
11405
  const state = await gm.current();
@@ -11442,13 +11409,13 @@ async function execute8(paths, phase) {
11442
11409
  if (state.stage !== "planning") {
11443
11410
  emitError("planning", `Current stage is '${state.stage}', not 'planning'.`);
11444
11411
  }
11445
- verifyStageEntry("planning", state);
11446
- await gm.save(state);
11447
11412
  const objectiveArtifact = paths.artifact("01-objective.md");
11448
11413
  if (!await fileExists(objectiveArtifact)) {
11449
11414
  emitError("planning", "01-objective.md does not exist. Complete the objective stage first.");
11450
11415
  }
11451
11416
  if (!phase || phase === "work") {
11417
+ verifyNonce("planning", state, "planning", "entry");
11418
+ await gm.save(state);
11452
11419
  const artifactPath = paths.artifact("02-planning.md");
11453
11420
  const existingArtifact = await readTextFile(artifactPath);
11454
11421
  const isReentry = !!existingArtifact && existingArtifact.length > 100;
@@ -11458,15 +11425,15 @@ async function execute8(paths, phase) {
11458
11425
  const principlesContent = await readTextFile(paths.principles);
11459
11426
  const implContent = await readTextFile(paths.artifact("03-implementation.md"));
11460
11427
  if (!isReentry) {
11461
- const templateDir = join23(__require("os").homedir(), ".reap", "templates");
11462
- const templatePath = join23(templateDir, "02-planning.md");
11428
+ const templateDir = join24(__require("os").homedir(), ".reap", "templates");
11429
+ const templatePath = join24(templateDir, "02-planning.md");
11463
11430
  if (await fileExists(templatePath)) {
11464
11431
  const template = await readTextFile(templatePath);
11465
11432
  if (template)
11466
11433
  await writeTextFile(artifactPath, template);
11467
11434
  }
11468
11435
  }
11469
- setPhaseNonce(state, "planning", "work");
11436
+ setNonce(state, "planning", "complete");
11470
11437
  await gm.save(state);
11471
11438
  emitOutput({
11472
11439
  status: "prompt",
@@ -11524,8 +11491,7 @@ async function execute8(paths, phase) {
11524
11491
  });
11525
11492
  }
11526
11493
  if (phase === "complete") {
11527
- verifyPhaseEntry("planning", state, "planning", "work");
11528
- await gm.save(state);
11494
+ verifyNonce("planning", state, "planning", "complete");
11529
11495
  const artifactPath = paths.artifact("02-planning.md");
11530
11496
  if (!await fileExists(artifactPath)) {
11531
11497
  emitError("planning", "02-planning.md does not exist. Complete the planning work first.");
@@ -11534,9 +11500,8 @@ async function execute8(paths, phase) {
11534
11500
  if (!content || content.length < 50) {
11535
11501
  emitError("planning", "02-planning.md appears incomplete. Fill in the plan before completing.");
11536
11502
  }
11537
- const { nonce, hash } = generateToken(state.id, state.stage);
11538
- state.expectedHash = hash;
11539
- state.lastNonce = nonce;
11503
+ setNonce(state, "implementation", "entry");
11504
+ await gm.save(state);
11540
11505
  const hookResults = await executeHooks(paths.hooks, "onLifePlanned", paths.projectRoot);
11541
11506
  const transition = await performTransition(paths, state, (s) => gm.save(s));
11542
11507
  const nextCommand = `reap run ${transition.nextStage}`;
@@ -11569,7 +11534,7 @@ var exports_implementation = {};
11569
11534
  __export(exports_implementation, {
11570
11535
  execute: () => execute9
11571
11536
  });
11572
- import { join as join24 } from "path";
11537
+ import { join as join25 } from "path";
11573
11538
  async function execute9(paths, phase) {
11574
11539
  const gm = new GenerationManager(paths);
11575
11540
  const state = await gm.current();
@@ -11579,13 +11544,13 @@ async function execute9(paths, phase) {
11579
11544
  if (state.stage !== "implementation") {
11580
11545
  emitError("implementation", `Current stage is '${state.stage}', not 'implementation'.`);
11581
11546
  }
11582
- verifyStageEntry("implementation", state);
11583
- await gm.save(state);
11584
11547
  const planningArtifact = paths.artifact("02-planning.md");
11585
11548
  if (!await fileExists(planningArtifact)) {
11586
11549
  emitError("implementation", "02-planning.md does not exist. Complete the planning stage first.");
11587
11550
  }
11588
11551
  if (!phase || phase === "work") {
11552
+ verifyNonce("implementation", state, "implementation", "entry");
11553
+ await gm.save(state);
11589
11554
  const artifactPath = paths.artifact("03-implementation.md");
11590
11555
  const existingArtifact = await readTextFile(artifactPath);
11591
11556
  const isReentry = !!existingArtifact && existingArtifact.length > 100;
@@ -11593,15 +11558,15 @@ async function execute9(paths, phase) {
11593
11558
  const conventionsContent = await readTextFile(paths.conventions);
11594
11559
  const constraintsContent = await readTextFile(paths.constraints);
11595
11560
  if (!isReentry) {
11596
- const templateDir = join24(__require("os").homedir(), ".reap", "templates");
11597
- const templatePath = join24(templateDir, "03-implementation.md");
11561
+ const templateDir = join25(__require("os").homedir(), ".reap", "templates");
11562
+ const templatePath = join25(templateDir, "03-implementation.md");
11598
11563
  if (await fileExists(templatePath)) {
11599
11564
  const template = await readTextFile(templatePath);
11600
11565
  if (template)
11601
11566
  await writeTextFile(artifactPath, template);
11602
11567
  }
11603
11568
  }
11604
- setPhaseNonce(state, "implementation", "work");
11569
+ setNonce(state, "implementation", "complete");
11605
11570
  await gm.save(state);
11606
11571
  emitOutput({
11607
11572
  status: "prompt",
@@ -11662,15 +11627,13 @@ async function execute9(paths, phase) {
11662
11627
  });
11663
11628
  }
11664
11629
  if (phase === "complete") {
11665
- verifyPhaseEntry("implementation", state, "implementation", "work");
11666
- await gm.save(state);
11630
+ verifyNonce("implementation", state, "implementation", "complete");
11667
11631
  const artifactPath = paths.artifact("03-implementation.md");
11668
11632
  if (!await fileExists(artifactPath)) {
11669
11633
  emitError("implementation", "03-implementation.md does not exist. Complete the implementation work first.");
11670
11634
  }
11671
- const { nonce, hash } = generateToken(state.id, state.stage);
11672
- state.expectedHash = hash;
11673
- state.lastNonce = nonce;
11635
+ setNonce(state, "validation", "entry");
11636
+ await gm.save(state);
11674
11637
  const hookResults = await executeHooks(paths.hooks, "onLifeImplemented", paths.projectRoot);
11675
11638
  const transition = await performTransition(paths, state, (s) => gm.save(s));
11676
11639
  const nextCommand = `reap run ${transition.nextStage}`;
@@ -11703,7 +11666,7 @@ var exports_validation = {};
11703
11666
  __export(exports_validation, {
11704
11667
  execute: () => execute10
11705
11668
  });
11706
- import { join as join25 } from "path";
11669
+ import { join as join26 } from "path";
11707
11670
  async function execute10(paths, phase) {
11708
11671
  const gm = new GenerationManager(paths);
11709
11672
  const state = await gm.current();
@@ -11713,13 +11676,13 @@ async function execute10(paths, phase) {
11713
11676
  if (state.stage !== "validation") {
11714
11677
  emitError("validation", `Current stage is '${state.stage}', not 'validation'.`);
11715
11678
  }
11716
- verifyStageEntry("validation", state);
11717
- await gm.save(state);
11718
11679
  const implArtifact = paths.artifact("03-implementation.md");
11719
11680
  if (!await fileExists(implArtifact)) {
11720
11681
  emitError("validation", "03-implementation.md does not exist. Complete the implementation stage first.");
11721
11682
  }
11722
11683
  if (!phase || phase === "work") {
11684
+ verifyNonce("validation", state, "validation", "entry");
11685
+ await gm.save(state);
11723
11686
  const artifactPath = paths.artifact("04-validation.md");
11724
11687
  const existingArtifact = await readTextFile(artifactPath);
11725
11688
  const isReentry = !!existingArtifact && existingArtifact.length > 100;
@@ -11728,15 +11691,15 @@ async function execute10(paths, phase) {
11728
11691
  const implContent = await readTextFile(implArtifact);
11729
11692
  const objectiveContent = await readTextFile(paths.artifact("01-objective.md"));
11730
11693
  if (!isReentry) {
11731
- const templateDir = join25(__require("os").homedir(), ".reap", "templates");
11732
- const templatePath = join25(templateDir, "04-validation.md");
11694
+ const templateDir = join26(__require("os").homedir(), ".reap", "templates");
11695
+ const templatePath = join26(templateDir, "04-validation.md");
11733
11696
  if (await fileExists(templatePath)) {
11734
11697
  const template = await readTextFile(templatePath);
11735
11698
  if (template)
11736
11699
  await writeTextFile(artifactPath, template);
11737
11700
  }
11738
11701
  }
11739
- setPhaseNonce(state, "validation", "work");
11702
+ setNonce(state, "validation", "complete");
11740
11703
  await gm.save(state);
11741
11704
  emitOutput({
11742
11705
  status: "prompt",
@@ -11807,15 +11770,13 @@ async function execute10(paths, phase) {
11807
11770
  });
11808
11771
  }
11809
11772
  if (phase === "complete") {
11810
- verifyPhaseEntry("validation", state, "validation", "work");
11811
- await gm.save(state);
11773
+ verifyNonce("validation", state, "validation", "complete");
11812
11774
  const artifactPath = paths.artifact("04-validation.md");
11813
11775
  if (!await fileExists(artifactPath)) {
11814
11776
  emitError("validation", "04-validation.md does not exist. Complete the validation work first.");
11815
11777
  }
11816
- const { nonce, hash } = generateToken(state.id, state.stage);
11817
- state.expectedHash = hash;
11818
- state.lastNonce = nonce;
11778
+ setNonce(state, "completion", "entry");
11779
+ await gm.save(state);
11819
11780
  const hookResults = await executeHooks(paths.hooks, "onLifeValidated", paths.projectRoot);
11820
11781
  const transition = await performTransition(paths, state, (s) => gm.save(s));
11821
11782
  const nextCommand = transition.nextStage !== "completion" ? `reap run ${transition.nextStage}` : "reap run completion";
@@ -12129,7 +12090,7 @@ __export(exports_sync_genome, {
12129
12090
  execute: () => execute13
12130
12091
  });
12131
12092
  import { readdir as readdir19 } from "fs/promises";
12132
- import { join as join26 } from "path";
12093
+ import { join as join27 } from "path";
12133
12094
  async function execute13(paths, phase) {
12134
12095
  const gm = new GenerationManager(paths);
12135
12096
  const state = await gm.current();
@@ -12144,7 +12105,7 @@ async function execute13(paths, phase) {
12144
12105
  const entries = await readdir19(paths.domain);
12145
12106
  for (const entry of entries) {
12146
12107
  if (entry.endsWith(".md")) {
12147
- const content = await readTextFile(join26(paths.domain, entry));
12108
+ const content = await readTextFile(join27(paths.domain, entry));
12148
12109
  if (content)
12149
12110
  domainFiles[entry] = content.slice(0, 1000);
12150
12111
  }
@@ -12232,7 +12193,7 @@ __export(exports_sync_environment, {
12232
12193
  execute: () => execute14
12233
12194
  });
12234
12195
  import { readdir as readdir20 } from "fs/promises";
12235
- import { join as join27 } from "path";
12196
+ import { join as join28 } from "path";
12236
12197
  async function execute14(paths, phase) {
12237
12198
  const gm = new GenerationManager(paths);
12238
12199
  const state = await gm.current();
@@ -12244,7 +12205,7 @@ async function execute14(paths, phase) {
12244
12205
  const docsEntries = await readdir20(paths.environmentDocs);
12245
12206
  for (const entry of docsEntries) {
12246
12207
  if (entry.endsWith(".md")) {
12247
- const content = await readTextFile(join27(paths.environmentDocs, entry));
12208
+ const content = await readTextFile(join28(paths.environmentDocs, entry));
12248
12209
  if (content)
12249
12210
  envDocs[entry] = content.slice(0, 1000);
12250
12211
  }
@@ -12252,7 +12213,7 @@ async function execute14(paths, phase) {
12252
12213
  } catch {}
12253
12214
  let linksContent = null;
12254
12215
  try {
12255
- linksContent = await readTextFile(join27(paths.environmentResources, "links.md"));
12216
+ linksContent = await readTextFile(join28(paths.environmentResources, "links.md"));
12256
12217
  } catch {}
12257
12218
  emitOutput({
12258
12219
  status: "prompt",
@@ -12324,7 +12285,7 @@ var exports_help = {};
12324
12285
  __export(exports_help, {
12325
12286
  execute: () => execute15
12326
12287
  });
12327
- import { join as join28 } from "path";
12288
+ import { join as join29 } from "path";
12328
12289
  function detectLanguage(configContent) {
12329
12290
  const raw = configContent?.match(/language:\s*(\S+)/)?.[1] ?? null;
12330
12291
  if (raw && raw in LANGUAGE_ALIASES)
@@ -12359,7 +12320,7 @@ async function execute15(paths) {
12359
12320
  const gm = new GenerationManager(paths);
12360
12321
  const state = await gm.current();
12361
12322
  const configContent = await readTextFile(paths.config);
12362
- const installedVersion = "0.15.2";
12323
+ const installedVersion = "0.15.3";
12363
12324
  const autoUpdate = configContent?.match(/autoUpdate:\s*(true|false)/)?.[1] === "true";
12364
12325
  const versionDisplay = formatVersionLine(installedVersion, !autoUpdate);
12365
12326
  const rawLang = detectLanguage(configContent);
@@ -12370,7 +12331,7 @@ async function execute15(paths) {
12370
12331
  const stateDisplay = state?.id ? `Active: **${state.id}** — ${state.goal} (Stage: ${state.stage})` : "No active Generation → `/reap.start` or `/reap.evolve`";
12371
12332
  const lines = buildLines(versionDisplay, lang, stateDisplay);
12372
12333
  if (topic) {
12373
- const guidePath = join28(ReapPaths.packageHooksDir, "reap-guide.md");
12334
+ const guidePath = join29(ReapPaths.packageHooksDir, "reap-guide.md");
12374
12335
  const reapGuide = await readTextFile(guidePath) ?? "";
12375
12336
  emitOutput({
12376
12337
  status: "prompt",
@@ -12675,8 +12636,8 @@ var init_merge = __esm(() => {
12675
12636
  });
12676
12637
 
12677
12638
  // src/core/merge-generation.ts
12678
- import { readdir as readdir21, mkdir as mkdir10, rename as rename3 } from "fs/promises";
12679
- import { join as join29 } from "path";
12639
+ import { readdir as readdir21, mkdir as mkdir10, unlink as unlink7 } from "fs/promises";
12640
+ import { join as join30 } from "path";
12680
12641
 
12681
12642
  class MergeGenerationManager {
12682
12643
  paths;
@@ -12687,7 +12648,7 @@ class MergeGenerationManager {
12687
12648
  const content = await readTextFile(this.paths.currentYml);
12688
12649
  if (content === null || !content.trim())
12689
12650
  return null;
12690
- const state = import_yaml11.default.parse(content);
12651
+ const state = import_yaml10.default.parse(content);
12691
12652
  if (!state.type)
12692
12653
  state.type = "normal";
12693
12654
  if (!state.parents)
@@ -12720,7 +12681,7 @@ class MergeGenerationManager {
12720
12681
  genomeHash,
12721
12682
  commonAncestor: commonAncestor ?? undefined
12722
12683
  };
12723
- await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12684
+ await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12724
12685
  return state;
12725
12686
  }
12726
12687
  async createFromBranch(targetBranch, projectRoot) {
@@ -12764,10 +12725,10 @@ class MergeGenerationManager {
12764
12725
  genomeHash,
12765
12726
  commonAncestor: commonAncestor ?? undefined
12766
12727
  };
12767
- await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12728
+ await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12768
12729
  return { state, report };
12769
12730
  }
12770
- async resolveLatestGenId(branch, cwd) {
12731
+ async resolveLatestGenId(_branch, _cwd) {
12771
12732
  const metas = await listMeta(this.paths);
12772
12733
  if (metas.length === 0)
12773
12734
  return null;
@@ -12789,7 +12750,7 @@ class MergeGenerationManager {
12789
12750
  const content = gitShow(ref, metaFile, cwd);
12790
12751
  if (content) {
12791
12752
  try {
12792
- const meta = import_yaml11.default.parse(content);
12753
+ const meta = import_yaml10.default.parse(content);
12793
12754
  if (meta?.id)
12794
12755
  metas.push(meta);
12795
12756
  } catch {}
@@ -12836,7 +12797,7 @@ class MergeGenerationManager {
12836
12797
  if (!state.timeline)
12837
12798
  state.timeline = [];
12838
12799
  state.timeline.push({ stage: next, at: new Date().toISOString() });
12839
- await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12800
+ await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12840
12801
  return state;
12841
12802
  }
12842
12803
  async complete() {
@@ -12862,19 +12823,33 @@ class MergeGenerationManager {
12862
12823
  startedAt: state.startedAt,
12863
12824
  completedAt: now
12864
12825
  };
12865
- await writeTextFile(join29(genDir, "meta.yml"), import_yaml11.default.stringify(meta));
12826
+ await writeTextFile(join30(genDir, "meta.yml"), import_yaml10.default.stringify(meta));
12866
12827
  const lifeEntries = await readdir21(this.paths.life);
12867
12828
  for (const entry of lifeEntries) {
12868
12829
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
12869
- await rename3(join29(this.paths.life, entry), join29(genDir, entry));
12830
+ const srcPath = join30(this.paths.life, entry);
12831
+ const destPath = join30(genDir, entry);
12832
+ let content = await readTextFile(srcPath);
12833
+ if (content && content.startsWith("# REAP MANAGED")) {
12834
+ content = content.replace(/^# REAP MANAGED[^\n]*\n/, "");
12835
+ }
12836
+ await writeTextFile(destPath, content ?? "");
12837
+ await unlink7(srcPath);
12870
12838
  }
12871
12839
  }
12872
- const backlogDir = join29(genDir, "backlog");
12840
+ const backlogDir = join30(genDir, "backlog");
12873
12841
  await mkdir10(backlogDir, { recursive: true });
12874
12842
  try {
12875
12843
  const backlogEntries = await readdir21(this.paths.backlog);
12876
12844
  for (const entry of backlogEntries) {
12877
- await rename3(join29(this.paths.backlog, entry), join29(backlogDir, entry));
12845
+ const content = await readTextFile(join30(this.paths.backlog, entry));
12846
+ if (!content)
12847
+ continue;
12848
+ const isConsumed = /status:\s*consumed/i.test(content) || /consumed:\s*true/i.test(content);
12849
+ await writeTextFile(join30(backlogDir, entry), content);
12850
+ if (isConsumed) {
12851
+ await unlink7(join30(this.paths.backlog, entry));
12852
+ }
12878
12853
  }
12879
12854
  } catch {}
12880
12855
  await writeTextFile(this.paths.currentYml, "");
@@ -12882,7 +12857,7 @@ class MergeGenerationManager {
12882
12857
  return compression;
12883
12858
  }
12884
12859
  async save(state) {
12885
- await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
12860
+ await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
12886
12861
  }
12887
12862
  }
12888
12863
  function canFastForward(localLatestId, remoteLatestId, allMetas) {
@@ -12944,7 +12919,7 @@ function findCommonAncestor(idA, idB, metas) {
12944
12919
  }
12945
12920
  return null;
12946
12921
  }
12947
- var import_yaml11;
12922
+ var import_yaml10;
12948
12923
  var init_merge_generation = __esm(() => {
12949
12924
  init_merge_lifecycle();
12950
12925
  init_compression();
@@ -12954,7 +12929,7 @@ var init_merge_generation = __esm(() => {
12954
12929
  init_merge();
12955
12930
  init_lineage();
12956
12931
  init_compression();
12957
- import_yaml11 = __toESM(require_dist(), 1);
12932
+ import_yaml10 = __toESM(require_dist(), 1);
12958
12933
  });
12959
12934
 
12960
12935
  // src/cli/commands/run/merge-start.ts
@@ -13071,7 +13046,7 @@ async function execute18(paths, phase) {
13071
13046
  emitError("merge-detect", "01-detect.md does not exist. Run /reap.merge.start first.");
13072
13047
  }
13073
13048
  const detectContent = await readTextFile(detectArtifact);
13074
- setPhaseNonce(state, "detect", "review");
13049
+ setNonce(state, "detect", "complete");
13075
13050
  await mgm.save(state);
13076
13051
  emitOutput({
13077
13052
  status: "prompt",
@@ -13101,11 +13076,9 @@ async function execute18(paths, phase) {
13101
13076
  });
13102
13077
  }
13103
13078
  if (phase === "complete") {
13104
- verifyPhaseEntry("merge-detect", state, "detect", "review");
13079
+ verifyNonce("merge-detect", state, "detect", "complete");
13080
+ setNonce(state, "mate", "entry");
13105
13081
  await mgm.save(state);
13106
- const { nonce, hash } = generateToken(state.id, state.stage);
13107
- state.expectedHash = hash;
13108
- state.lastNonce = nonce;
13109
13082
  const hookResults = await executeHooks(paths.hooks, "onMergeDetected", paths.projectRoot);
13110
13083
  const transition = await performTransition(paths, state, (s) => mgm.save(s));
13111
13084
  const nextCommand = `reap run merge-${transition.nextStage}`;
@@ -13128,7 +13101,6 @@ async function execute18(paths, phase) {
13128
13101
  }
13129
13102
  var init_merge_detect = __esm(() => {
13130
13103
  init_merge_generation();
13131
- init_generation();
13132
13104
  init_fs();
13133
13105
  init_hook_engine();
13134
13106
  init_stage_transition();
@@ -13151,15 +13123,15 @@ async function execute19(paths, phase) {
13151
13123
  if (state.stage !== "mate") {
13152
13124
  emitError("merge-mate", `Stage is '${state.stage}', expected 'mate'.`);
13153
13125
  }
13154
- verifyStageEntry("merge-mate", state);
13155
- await mgm.save(state);
13156
13126
  const detectArtifact = paths.artifact("01-detect.md");
13157
13127
  if (!await fileExists(detectArtifact)) {
13158
13128
  emitError("merge-mate", "01-detect.md does not exist. Complete detect stage first.");
13159
13129
  }
13160
13130
  if (!phase || phase === "resolve") {
13131
+ verifyNonce("merge-mate", state, "mate", "entry");
13132
+ await mgm.save(state);
13161
13133
  const detectContent = await readTextFile(detectArtifact);
13162
- setPhaseNonce(state, "mate", "resolve");
13134
+ setNonce(state, "mate", "complete");
13163
13135
  await mgm.save(state);
13164
13136
  emitOutput({
13165
13137
  status: "prompt",
@@ -13200,11 +13172,13 @@ async function execute19(paths, phase) {
13200
13172
  });
13201
13173
  }
13202
13174
  if (phase === "complete") {
13203
- verifyPhaseEntry("merge-mate", state, "mate", "resolve");
13175
+ verifyNonce("merge-mate", state, "mate", "complete");
13176
+ const mateArtifact = paths.artifact("02-mate.md");
13177
+ if (!await fileExists(mateArtifact)) {
13178
+ emitError("merge-mate", "02-mate.md does not exist. Write the mate artifact before completing.");
13179
+ }
13180
+ setNonce(state, "merge", "entry");
13204
13181
  await mgm.save(state);
13205
- const { nonce, hash } = generateToken(state.id, state.stage);
13206
- state.expectedHash = hash;
13207
- state.lastNonce = nonce;
13208
13182
  const hookResults = await executeHooks(paths.hooks, "onMergeMated", paths.projectRoot);
13209
13183
  const transition = await performTransition(paths, state, (s) => mgm.save(s));
13210
13184
  const nextCommand = `reap run merge-${transition.nextStage}`;
@@ -13227,7 +13201,6 @@ async function execute19(paths, phase) {
13227
13201
  }
13228
13202
  var init_merge_mate = __esm(() => {
13229
13203
  init_merge_generation();
13230
- init_generation();
13231
13204
  init_fs();
13232
13205
  init_hook_engine();
13233
13206
  init_stage_transition();
@@ -13250,17 +13223,17 @@ async function execute20(paths, phase) {
13250
13223
  if (state.stage !== "merge") {
13251
13224
  emitError("merge-merge", `Stage is '${state.stage}', expected 'merge'.`);
13252
13225
  }
13253
- verifyStageEntry("merge-merge", state);
13254
- await mgm.save(state);
13255
13226
  const mateArtifact = paths.artifact("02-mate.md");
13256
13227
  if (!await fileExists(mateArtifact)) {
13257
13228
  emitError("merge-merge", "02-mate.md does not exist. Complete mate stage first.");
13258
13229
  }
13259
13230
  if (!phase || phase === "work") {
13231
+ verifyNonce("merge-merge", state, "merge", "entry");
13232
+ await mgm.save(state);
13260
13233
  const mateContent = await readTextFile(mateArtifact);
13261
13234
  const detectContent = await readTextFile(paths.artifact("01-detect.md"));
13262
13235
  const targetBranch = state.goal.split(" + ").pop() ?? "";
13263
- setPhaseNonce(state, "merge", "work");
13236
+ setNonce(state, "merge", "complete");
13264
13237
  await mgm.save(state);
13265
13238
  emitOutput({
13266
13239
  status: "prompt",
@@ -13296,11 +13269,13 @@ async function execute20(paths, phase) {
13296
13269
  });
13297
13270
  }
13298
13271
  if (phase === "complete") {
13299
- verifyPhaseEntry("merge-merge", state, "merge", "work");
13272
+ verifyNonce("merge-merge", state, "merge", "complete");
13273
+ const mergeArtifactCheck = paths.artifact("03-merge.md");
13274
+ if (!await fileExists(mergeArtifactCheck)) {
13275
+ emitError("merge-merge", "03-merge.md does not exist. Write the merge artifact before completing.");
13276
+ }
13277
+ setNonce(state, "sync", "entry");
13300
13278
  await mgm.save(state);
13301
- const { nonce, hash } = generateToken(state.id, state.stage);
13302
- state.expectedHash = hash;
13303
- state.lastNonce = nonce;
13304
13279
  const hookResults = await executeHooks(paths.hooks, "onMergeMerged", paths.projectRoot);
13305
13280
  const transition = await performTransition(paths, state, (s) => mgm.save(s));
13306
13281
  const nextCommand = `reap run merge-${transition.nextStage}`;
@@ -13323,7 +13298,6 @@ async function execute20(paths, phase) {
13323
13298
  }
13324
13299
  var init_merge_merge = __esm(() => {
13325
13300
  init_merge_generation();
13326
- init_generation();
13327
13301
  init_fs();
13328
13302
  init_hook_engine();
13329
13303
  init_stage_transition();
@@ -13346,18 +13320,18 @@ async function execute21(paths, phase) {
13346
13320
  if (state.stage !== "sync") {
13347
13321
  emitError("merge-sync", `Stage is '${state.stage}', expected 'sync'.`);
13348
13322
  }
13349
- verifyStageEntry("merge-sync", state);
13350
- await mgm.save(state);
13351
13323
  const mergeArtifact = paths.artifact("03-merge.md");
13352
13324
  if (!await fileExists(mergeArtifact)) {
13353
13325
  emitError("merge-sync", "03-merge.md does not exist. Complete merge stage first.");
13354
13326
  }
13355
13327
  if (!phase || phase === "verify") {
13328
+ verifyNonce("merge-sync", state, "sync", "entry");
13329
+ await mgm.save(state);
13356
13330
  const genomeConventions = await readTextFile(paths.conventions);
13357
13331
  const genomeConstraints = await readTextFile(paths.constraints);
13358
13332
  const genomePrinciples = await readTextFile(paths.principles);
13359
13333
  const mergeContent = await readTextFile(mergeArtifact);
13360
- setPhaseNonce(state, "sync", "verify");
13334
+ setNonce(state, "sync", "complete");
13361
13335
  await mgm.save(state);
13362
13336
  emitOutput({
13363
13337
  status: "prompt",
@@ -13403,11 +13377,13 @@ async function execute21(paths, phase) {
13403
13377
  });
13404
13378
  }
13405
13379
  if (phase === "complete") {
13406
- verifyPhaseEntry("merge-sync", state, "sync", "verify");
13380
+ verifyNonce("merge-sync", state, "sync", "complete");
13381
+ const syncArtifactCheck = paths.artifact("04-sync.md");
13382
+ if (!await fileExists(syncArtifactCheck)) {
13383
+ emitError("merge-sync", "04-sync.md does not exist. Write the sync artifact before completing.");
13384
+ }
13385
+ setNonce(state, "validation", "entry");
13407
13386
  await mgm.save(state);
13408
- const { nonce, hash } = generateToken(state.id, state.stage);
13409
- state.expectedHash = hash;
13410
- state.lastNonce = nonce;
13411
13387
  const hookResults = await executeHooks(paths.hooks, "onMergeSynced", paths.projectRoot);
13412
13388
  const transition = await performTransition(paths, state, (s) => mgm.save(s));
13413
13389
  const nextCommand = `reap run merge-${transition.nextStage}`;
@@ -13430,7 +13406,6 @@ async function execute21(paths, phase) {
13430
13406
  }
13431
13407
  var init_merge_sync = __esm(() => {
13432
13408
  init_merge_generation();
13433
- init_generation();
13434
13409
  init_fs();
13435
13410
  init_hook_engine();
13436
13411
  init_stage_transition();
@@ -13453,15 +13428,15 @@ async function execute22(paths, phase) {
13453
13428
  if (state.stage !== "validation") {
13454
13429
  emitError("merge-validation", `Stage is '${state.stage}', expected 'validation'.`);
13455
13430
  }
13456
- verifyStageEntry("merge-validation", state);
13457
- await mgm.save(state);
13458
13431
  const syncArtifact = paths.artifact("04-sync.md");
13459
13432
  if (!await fileExists(syncArtifact)) {
13460
13433
  emitError("merge-validation", "04-sync.md does not exist. Complete sync stage first.");
13461
13434
  }
13462
13435
  if (!phase || phase === "work") {
13436
+ verifyNonce("merge-validation", state, "validation", "entry");
13437
+ await mgm.save(state);
13463
13438
  const constraintsContent = await readTextFile(paths.constraints);
13464
- setPhaseNonce(state, "validation", "work");
13439
+ setNonce(state, "validation", "complete");
13465
13440
  await mgm.save(state);
13466
13441
  emitOutput({
13467
13442
  status: "prompt",
@@ -13499,15 +13474,13 @@ async function execute22(paths, phase) {
13499
13474
  });
13500
13475
  }
13501
13476
  if (phase === "complete") {
13502
- verifyPhaseEntry("merge-validation", state, "validation", "work");
13503
- await mgm.save(state);
13477
+ verifyNonce("merge-validation", state, "validation", "complete");
13504
13478
  const validationArtifact = paths.artifact("05-validation.md");
13505
13479
  if (!await fileExists(validationArtifact)) {
13506
13480
  emitError("merge-validation", "05-validation.md does not exist. Complete validation work first.");
13507
13481
  }
13508
- const { nonce, hash } = generateToken(state.id, state.stage);
13509
- state.expectedHash = hash;
13510
- state.lastNonce = nonce;
13482
+ setNonce(state, "completion", "entry");
13483
+ await mgm.save(state);
13511
13484
  const hookResults = await executeHooks(paths.hooks, "onMergeValidated", paths.projectRoot);
13512
13485
  const transition = await performTransition(paths, state, (s) => mgm.save(s));
13513
13486
  const nextCommand = `reap run merge-${transition.nextStage}`;
@@ -13530,7 +13503,6 @@ async function execute22(paths, phase) {
13530
13503
  }
13531
13504
  var init_merge_validation = __esm(() => {
13532
13505
  init_merge_generation();
13533
- init_generation();
13534
13506
  init_fs();
13535
13507
  init_hook_engine();
13536
13508
  init_stage_transition();
@@ -13553,18 +13525,18 @@ async function execute23(paths, phase) {
13553
13525
  if (state.stage !== "completion") {
13554
13526
  emitError("merge-completion", `Stage is '${state.stage}', expected 'completion'.`);
13555
13527
  }
13556
- verifyStageEntry("merge-completion", state);
13557
- await mgm.save(state);
13558
13528
  const validationArtifact = paths.artifact("05-validation.md");
13559
13529
  if (!await fileExists(validationArtifact)) {
13560
13530
  emitError("merge-completion", "05-validation.md does not exist. Complete validation first.");
13561
13531
  }
13562
13532
  if (!phase || phase === "retrospective") {
13533
+ verifyNonce("merge-completion", state, "completion", "entry");
13534
+ await mgm.save(state);
13563
13535
  const detectContent = await readTextFile(paths.artifact("01-detect.md"));
13564
13536
  const mateContent = await readTextFile(paths.artifact("02-mate.md"));
13565
13537
  const mergeContent = await readTextFile(paths.artifact("03-merge.md"));
13566
13538
  const validationContent = await readTextFile(validationArtifact);
13567
- setPhaseNonce(state, "completion", "retrospective");
13539
+ setNonce(state, "completion", "archive");
13568
13540
  await mgm.save(state);
13569
13541
  emitOutput({
13570
13542
  status: "prompt",
@@ -13596,7 +13568,7 @@ async function execute23(paths, phase) {
13596
13568
  });
13597
13569
  }
13598
13570
  if (phase === "archive") {
13599
- verifyPhaseEntry("merge-completion", state, "completion", "retrospective");
13571
+ verifyNonce("merge-completion", state, "completion", "archive");
13600
13572
  await mgm.save(state);
13601
13573
  const hookResults = await executeHooks(paths.hooks, "onMergeCompleted", paths.projectRoot);
13602
13574
  const submodules = checkSubmodules(paths.projectRoot);
@@ -13807,7 +13779,7 @@ var exports_evolve_recovery = {};
13807
13779
  __export(exports_evolve_recovery, {
13808
13780
  execute: () => execute26
13809
13781
  });
13810
- import { join as join30 } from "path";
13782
+ import { join as join31 } from "path";
13811
13783
  import { readdir as readdir22 } from "fs/promises";
13812
13784
  function getFlag4(args, name) {
13813
13785
  const idx = args.indexOf(`--${name}`);
@@ -13830,12 +13802,12 @@ async function loadLineageArtifacts(paths, genId) {
13830
13802
  const completed = await listCompleted(paths);
13831
13803
  const genDir = completed.find((d) => d.startsWith(genId));
13832
13804
  if (genDir) {
13833
- const dirPath = join30(paths.lineage, genDir);
13805
+ const dirPath = join31(paths.lineage, genDir);
13834
13806
  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"))
13807
+ readTextFile(join31(dirPath, "01-objective.md")),
13808
+ readTextFile(join31(dirPath, "02-planning.md")),
13809
+ readTextFile(join31(dirPath, "03-implementation.md")),
13810
+ readTextFile(join31(dirPath, "05-completion.md"))
13839
13811
  ]);
13840
13812
  return {
13841
13813
  objective: objective ?? "(not found)",
@@ -13848,7 +13820,7 @@ async function loadLineageArtifacts(paths, genId) {
13848
13820
  const entries = await readdir22(paths.lineage);
13849
13821
  const compressedFile = entries.find((e) => e.startsWith(genId) && e.endsWith(".md"));
13850
13822
  if (compressedFile) {
13851
- const content = await readTextFile(join30(paths.lineage, compressedFile));
13823
+ const content = await readTextFile(join31(paths.lineage, compressedFile));
13852
13824
  if (content) {
13853
13825
  return {
13854
13826
  objective: content,
@@ -13937,8 +13909,7 @@ async function execute26(paths, phase, argv = []) {
13937
13909
  } catch {}
13938
13910
  const goal = reason ? `Recovery: ${reason} (corrects ${targetGenIds.join(", ")})` : `Recovery for ${targetGenIds.join(", ")}`;
13939
13911
  const state = await gm.createRecoveryGeneration(goal, genomeVersion, targetGenIds);
13940
- const { nonce, hash } = generateToken(state.id, state.stage);
13941
- state.expectedHash = hash;
13912
+ setNonce(state, "objective", "entry");
13942
13913
  await gm.save(state);
13943
13914
  emitOutput({
13944
13915
  status: "prompt",
@@ -13960,6 +13931,7 @@ async function execute26(paths, phase, argv = []) {
13960
13931
  }
13961
13932
  var init_evolve_recovery = __esm(() => {
13962
13933
  init_generation();
13934
+ init_stage_transition();
13963
13935
  init_fs();
13964
13936
  init_lineage();
13965
13937
  });
@@ -14028,9 +14000,9 @@ async function execute27(paths, phase, argv = []) {
14028
14000
  const localLatest = localMetas.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())[0];
14029
14001
  const remoteLatest = remoteMetasRaw.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())[0];
14030
14002
  const allMetas = [...localMetas];
14031
- for (const rm5 of remoteMetasRaw) {
14032
- if (!allMetas.find((m) => m.id === rm5.id))
14033
- allMetas.push(rm5);
14003
+ for (const rm6 of remoteMetasRaw) {
14004
+ if (!allMetas.find((m) => m.id === rm6.id))
14005
+ allMetas.push(rm6);
14034
14006
  }
14035
14007
  const ffResult = canFastForward(localLatest.id, remoteLatest.id, allMetas);
14036
14008
  if (ffResult.fastForward) {
@@ -14220,20 +14192,20 @@ var exports_refresh_knowledge = {};
14220
14192
  __export(exports_refresh_knowledge, {
14221
14193
  execute: () => execute30
14222
14194
  });
14223
- import { join as join31 } from "path";
14195
+ import { join as join32 } from "path";
14224
14196
  import { readdir as readdir23 } from "fs/promises";
14225
14197
  async function loadGenome(genomeDir) {
14226
14198
  let content = "";
14227
14199
  let l1Lines = 0;
14228
14200
  let smLimit = null;
14229
- const smContent = await readTextFile(join31(genomeDir, "source-map.md"));
14201
+ const smContent = await readTextFile(join32(genomeDir, "source-map.md"));
14230
14202
  if (smContent) {
14231
14203
  const limitMatch = smContent.match(/줄 수 한도:\s*~?(\d+)줄/);
14232
14204
  if (limitMatch)
14233
14205
  smLimit = parseInt(limitMatch[1], 10);
14234
14206
  }
14235
14207
  for (const file of L1_FILES) {
14236
- const fileContent = await readTextFile(join31(genomeDir, file));
14208
+ const fileContent = await readTextFile(join32(genomeDir, file));
14237
14209
  if (!fileContent)
14238
14210
  continue;
14239
14211
  const lines = fileContent.split(`
@@ -14255,14 +14227,14 @@ ${fileContent.split(`
14255
14227
  `;
14256
14228
  }
14257
14229
  }
14258
- const domainDir = join31(genomeDir, "domain");
14230
+ const domainDir = join32(genomeDir, "domain");
14259
14231
  if (await fileExists(domainDir)) {
14260
14232
  let l2Lines = 0;
14261
14233
  let l2Overflow = false;
14262
14234
  try {
14263
14235
  const domainFiles = (await readdir23(domainDir)).filter((f) => f.endsWith(".md")).sort();
14264
14236
  for (const file of domainFiles) {
14265
- const fileContent = await readTextFile(join31(domainDir, file));
14237
+ const fileContent = await readTextFile(join32(domainDir, file));
14266
14238
  if (!fileContent)
14267
14239
  continue;
14268
14240
  const lines = fileContent.split(`
@@ -14313,7 +14285,7 @@ function buildStrictSection(strict, genStage) {
14313
14285
  return sections;
14314
14286
  }
14315
14287
  async function execute30(paths) {
14316
- const guidePath = join31(ReapPaths.packageHooksDir, "reap-guide.md");
14288
+ const guidePath = join32(ReapPaths.packageHooksDir, "reap-guide.md");
14317
14289
  const reapGuide = await readTextFile(guidePath) || "";
14318
14290
  const { content: genomeContent } = await loadGenome(paths.genome);
14319
14291
  const envSummary = await readTextFile(paths.environmentSummary) || "";
@@ -14385,7 +14357,7 @@ var exports_run = {};
14385
14357
  __export(exports_run, {
14386
14358
  runCommand: () => runCommand
14387
14359
  });
14388
- import { execSync as execSync7 } from "child_process";
14360
+ import { execSync as execSync8 } from "child_process";
14389
14361
  async function runCommand(command, phase, argv = []) {
14390
14362
  const cwd = process.cwd();
14391
14363
  const paths = new ReapPaths(cwd);
@@ -14403,7 +14375,7 @@ async function runCommand(command, phase, argv = []) {
14403
14375
  try {
14404
14376
  const config = await ConfigManager.read(paths);
14405
14377
  if (config.autoIssueReport) {
14406
- const version = "0.15.2";
14378
+ const version = "0.15.3";
14407
14379
  const errMsg = err instanceof Error ? err.message : String(err);
14408
14380
  const title = `[auto] reap run ${command}: ${errMsg.slice(0, 80)}`;
14409
14381
  const body = [
@@ -14413,7 +14385,7 @@ async function runCommand(command, phase, argv = []) {
14413
14385
  `**OS**: ${process.platform} ${process.arch}`,
14414
14386
  `**Node**: ${process.version}`
14415
14387
  ].join("\\n");
14416
- execSync7(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,bug" --body "${body}"`, { stdio: "ignore", timeout: 1e4 });
14388
+ execSync8(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,bug" --body "${body}"`, { stdio: "ignore", timeout: 1e4 });
14417
14389
  }
14418
14390
  } catch {}
14419
14391
  emitError(command, err instanceof Error ? err.message : String(err));
@@ -14479,15 +14451,15 @@ import { createInterface } from "readline";
14479
14451
  // src/cli/commands/init.ts
14480
14452
  init_paths();
14481
14453
  init_config();
14482
- import { mkdir as mkdir4, readdir as readdir5, chmod } from "fs/promises";
14483
- import { join as join6 } from "path";
14454
+ import { mkdir as mkdir5, readdir as readdir6, chmod } from "fs/promises";
14455
+ import { join as join7 } from "path";
14484
14456
 
14485
14457
  // src/core/agents/claude-code.ts
14486
14458
  init_fs();
14487
14459
  init_paths();
14488
14460
  import { join as join2 } from "path";
14489
14461
  import { homedir as homedir2 } from "os";
14490
- import { mkdir, readdir, unlink } from "fs/promises";
14462
+ import { mkdir, readdir, rm, unlink } from "fs/promises";
14491
14463
 
14492
14464
  class ClaudeCodeAdapter {
14493
14465
  name = "claude-code";
@@ -14674,6 +14646,55 @@ class ClaudeCodeAdapter {
14674
14646
  } catch {}
14675
14647
  return removed;
14676
14648
  }
14649
+ async setupAgentMd(projectRoot) {
14650
+ return this.setupClaudeMd(projectRoot);
14651
+ }
14652
+ async cleanupProjectFiles(projectRoot) {
14653
+ const removed = [];
14654
+ const skipped = [];
14655
+ const claudeCommandsDir = join2(projectRoot, ".claude", "commands");
14656
+ try {
14657
+ const files = await readdir(claudeCommandsDir);
14658
+ const matched = files.filter((f) => f.startsWith("reap."));
14659
+ for (const file of matched) {
14660
+ await unlink(join2(claudeCommandsDir, file));
14661
+ removed.push(`.claude/commands/${file}`);
14662
+ }
14663
+ if (matched.length === 0)
14664
+ skipped.push(".claude/commands/reap.* (none found)");
14665
+ } catch {
14666
+ skipped.push(".claude/commands/reap.* (directory not found)");
14667
+ }
14668
+ const claudeSkillsDir = join2(projectRoot, ".claude", "skills");
14669
+ try {
14670
+ const entries = await readdir(claudeSkillsDir);
14671
+ const matched = entries.filter((e) => e.startsWith("reap."));
14672
+ for (const entry of matched) {
14673
+ await rm(join2(claudeSkillsDir, entry), { recursive: true, force: true });
14674
+ removed.push(`.claude/skills/${entry}`);
14675
+ }
14676
+ if (matched.length === 0)
14677
+ skipped.push(".claude/skills/reap.* (none found)");
14678
+ } catch {
14679
+ skipped.push(".claude/skills/reap.* (directory not found)");
14680
+ }
14681
+ const claudeMdPath = join2(projectRoot, ".claude", "CLAUDE.md");
14682
+ const content = await readTextFile(claudeMdPath);
14683
+ if (content !== null && content.includes("# REAP Project")) {
14684
+ const cleaned = content.replace(/# REAP Project[\s\S]*?(?=\n# |\s*$)/, "").trim();
14685
+ if (cleaned.length === 0) {
14686
+ await unlink(claudeMdPath);
14687
+ removed.push(".claude/CLAUDE.md (deleted, was REAP-only)");
14688
+ } else {
14689
+ await writeTextFile(claudeMdPath, cleaned + `
14690
+ `);
14691
+ removed.push(".claude/CLAUDE.md (REAP section removed)");
14692
+ }
14693
+ } else {
14694
+ skipped.push(".claude/CLAUDE.md (no REAP section)");
14695
+ }
14696
+ return { removed, skipped };
14697
+ }
14677
14698
  async setupClaudeMd(projectRoot) {
14678
14699
  const claudeMdPath = join2(projectRoot, ".claude", "CLAUDE.md");
14679
14700
  const marker = "# REAP Project";
@@ -14737,31 +14758,232 @@ If context was compacted and REAP knowledge is lost, re-run the session-start ho
14737
14758
  }
14738
14759
  }
14739
14760
 
14740
- // src/core/agents/opencode.ts
14761
+ // src/core/agents/codex.ts
14741
14762
  init_fs();
14742
14763
  init_paths();
14743
14764
  import { join as join3 } from "path";
14744
14765
  import { homedir as homedir3 } from "os";
14745
14766
  import { mkdir as mkdir2, readdir as readdir2, unlink as unlink2 } from "fs/promises";
14746
- var OPENCODE_STATE_DIR = join3(homedir3(), ".local", "state", "opencode");
14767
+
14768
+ class CodexAdapter {
14769
+ name = "codex";
14770
+ displayName = "Codex CLI";
14771
+ get userDir() {
14772
+ return join3(homedir3(), ".codex");
14773
+ }
14774
+ get commandsDir() {
14775
+ return join3(this.userDir, "commands");
14776
+ }
14777
+ get hooksJsonPath() {
14778
+ return join3(this.userDir, "hooks.json");
14779
+ }
14780
+ get configTomlPath() {
14781
+ return join3(this.userDir, "config.toml");
14782
+ }
14783
+ async detect() {
14784
+ try {
14785
+ const { execSync } = await import("child_process");
14786
+ execSync("which codex", { stdio: "ignore" });
14787
+ return true;
14788
+ } catch {
14789
+ return false;
14790
+ }
14791
+ }
14792
+ getCommandsDir() {
14793
+ return this.commandsDir;
14794
+ }
14795
+ async installCommands(commandNames, sourceDir) {
14796
+ await mkdir2(ReapPaths.userReapCommands, { recursive: true });
14797
+ for (const cmd of commandNames) {
14798
+ const src = join3(sourceDir, `${cmd}.md`);
14799
+ const dest = join3(ReapPaths.userReapCommands, `${cmd}.md`);
14800
+ await writeTextFile(dest, await readTextFileOrThrow(src));
14801
+ }
14802
+ }
14803
+ async removeStaleCommands(validNames) {
14804
+ try {
14805
+ const existing = await readdir2(this.commandsDir);
14806
+ for (const file of existing) {
14807
+ if (!file.startsWith("reap.") || !file.endsWith(".md"))
14808
+ continue;
14809
+ if (!validNames.has(file)) {
14810
+ await unlink2(join3(this.commandsDir, file));
14811
+ }
14812
+ }
14813
+ } catch {}
14814
+ }
14815
+ async registerSessionHook(dryRun = false) {
14816
+ await mkdir2(this.userDir, { recursive: true });
14817
+ const hooksFile = await this.readHooksJson();
14818
+ const hooks = hooksFile["hooks"] ?? {};
14819
+ const sessionStart = hooks["SessionStart"] ?? [];
14820
+ if (Array.isArray(sessionStart) && this.hasReapHook(sessionStart)) {
14821
+ return { action: "skipped" };
14822
+ }
14823
+ const hadHooksSection = "hooks" in hooksFile && "SessionStart" in hooks;
14824
+ hooksFile["hooks"] = {
14825
+ ...hooks,
14826
+ SessionStart: [
14827
+ ...Array.isArray(sessionStart) ? sessionStart : [],
14828
+ this.getHookEntry()
14829
+ ]
14830
+ };
14831
+ if (!dryRun) {
14832
+ await this.writeHooksJson(hooksFile);
14833
+ }
14834
+ return { action: hadHooksSection ? "updated" : "created" };
14835
+ }
14836
+ async syncSessionHook(dryRun = false) {
14837
+ const hooksFile = await this.readHooksJson();
14838
+ const hooks = hooksFile["hooks"] ?? {};
14839
+ const sessionStart = hooks["SessionStart"] ?? [];
14840
+ if (!Array.isArray(sessionStart) || !this.hasReapHook(sessionStart)) {
14841
+ await this.registerSessionHook(dryRun);
14842
+ return { action: "updated" };
14843
+ }
14844
+ const expectedEntry = this.getHookEntry();
14845
+ let changed = false;
14846
+ const updated = sessionStart.map((entry) => {
14847
+ if (typeof entry !== "object" || entry === null)
14848
+ return entry;
14849
+ const entryHooks = entry["hooks"];
14850
+ if (!Array.isArray(entryHooks))
14851
+ return entry;
14852
+ const isReap = entryHooks.some((h) => {
14853
+ if (typeof h !== "object" || h === null)
14854
+ return false;
14855
+ const cmd = h["command"];
14856
+ return typeof cmd === "string" && cmd.includes("session-start");
14857
+ });
14858
+ if (isReap) {
14859
+ if (JSON.stringify(entry) !== JSON.stringify(expectedEntry)) {
14860
+ changed = true;
14861
+ return expectedEntry;
14862
+ }
14863
+ }
14864
+ return entry;
14865
+ });
14866
+ if (changed && !dryRun) {
14867
+ hooksFile["hooks"] = { ...hooks, SessionStart: updated };
14868
+ await this.writeHooksJson(hooksFile);
14869
+ }
14870
+ return { action: changed ? "updated" : "skipped" };
14871
+ }
14872
+ async readLanguage() {
14873
+ const content = await readTextFile(this.configTomlPath);
14874
+ if (!content)
14875
+ return null;
14876
+ const match = content.match(/^language\s*=\s*"([^"]+)"/m);
14877
+ return match ? match[1] : null;
14878
+ }
14879
+ async setupAgentMd(projectRoot) {
14880
+ const agentsMdPath = join3(projectRoot, ".codex", "AGENTS.md");
14881
+ const marker = "# REAP Project";
14882
+ const reapSection = `# REAP Project
14883
+ This project uses REAP. Session-start hook loads project knowledge on session start.
14884
+ If context was compacted and REAP knowledge is lost, re-run the session-start hook.
14885
+ `;
14886
+ await mkdir2(join3(projectRoot, ".codex"), { recursive: true });
14887
+ const existing = await readTextFile(agentsMdPath);
14888
+ if (existing === null) {
14889
+ await writeTextFile(agentsMdPath, reapSection);
14890
+ return { action: "created" };
14891
+ }
14892
+ if (existing.includes(marker)) {
14893
+ const updated = existing.replace(/# REAP Project[\s\S]*?(?=\n# |\n*$)/, reapSection.trim());
14894
+ if (updated === existing)
14895
+ return { action: "skipped" };
14896
+ await writeTextFile(agentsMdPath, updated);
14897
+ return { action: "updated" };
14898
+ }
14899
+ await writeTextFile(agentsMdPath, reapSection + `
14900
+ ` + existing);
14901
+ return { action: "created" };
14902
+ }
14903
+ async cleanupProjectFiles(projectRoot) {
14904
+ const removed = [];
14905
+ const skipped = [];
14906
+ const agentsMdPath = join3(projectRoot, ".codex", "AGENTS.md");
14907
+ const content = await readTextFile(agentsMdPath);
14908
+ if (content !== null && content.includes("# REAP Project")) {
14909
+ const cleaned = content.replace(/# REAP Project[\s\S]*?(?=\n# |\s*$)/, "").trim();
14910
+ if (cleaned.length === 0) {
14911
+ await unlink2(agentsMdPath);
14912
+ removed.push(".codex/AGENTS.md (deleted, was REAP-only)");
14913
+ } else {
14914
+ await writeTextFile(agentsMdPath, cleaned + `
14915
+ `);
14916
+ removed.push(".codex/AGENTS.md (REAP section removed)");
14917
+ }
14918
+ } else {
14919
+ skipped.push(".codex/AGENTS.md (no REAP section)");
14920
+ }
14921
+ return { removed, skipped };
14922
+ }
14923
+ getHookEntry() {
14924
+ const sessionStartPath = join3(ReapPaths.packageHooksDir, "session-start.cjs");
14925
+ return {
14926
+ matcher: "",
14927
+ hooks: [{ type: "command", command: `node "${sessionStartPath}"` }]
14928
+ };
14929
+ }
14930
+ hasReapHook(sessionStartHooks) {
14931
+ return sessionStartHooks.some((entry) => {
14932
+ if (typeof entry !== "object" || entry === null)
14933
+ return false;
14934
+ const hooks = entry["hooks"];
14935
+ if (!Array.isArray(hooks))
14936
+ return false;
14937
+ return hooks.some((h) => {
14938
+ if (typeof h !== "object" || h === null)
14939
+ return false;
14940
+ const cmd = h["command"];
14941
+ return typeof cmd === "string" && cmd.includes("session-start");
14942
+ });
14943
+ });
14944
+ }
14945
+ async readHooksJson() {
14946
+ const content = await readTextFile(this.hooksJsonPath);
14947
+ if (content === null)
14948
+ return {};
14949
+ try {
14950
+ return JSON.parse(content);
14951
+ } catch {
14952
+ return {};
14953
+ }
14954
+ }
14955
+ async writeHooksJson(data) {
14956
+ await mkdir2(this.userDir, { recursive: true });
14957
+ await writeTextFile(this.hooksJsonPath, JSON.stringify(data, null, 2) + `
14958
+ `);
14959
+ }
14960
+ }
14961
+
14962
+ // src/core/agents/opencode.ts
14963
+ init_fs();
14964
+ init_paths();
14965
+ import { join as join4 } from "path";
14966
+ import { homedir as homedir4 } from "os";
14967
+ import { mkdir as mkdir3, readdir as readdir3, unlink as unlink3 } from "fs/promises";
14968
+ var OPENCODE_STATE_DIR = join4(homedir4(), ".local", "state", "opencode");
14747
14969
 
14748
14970
  class OpenCodeAdapter {
14749
14971
  name = "opencode";
14750
14972
  displayName = "OpenCode";
14751
14973
  get configDir() {
14752
- return join3(homedir3(), ".config", "opencode");
14974
+ return join4(homedir4(), ".config", "opencode");
14753
14975
  }
14754
14976
  get commandsDir() {
14755
- return join3(this.configDir, "commands");
14977
+ return join4(this.configDir, "commands");
14756
14978
  }
14757
14979
  get pluginsDir() {
14758
- return join3(this.configDir, "plugins");
14980
+ return join4(this.configDir, "plugins");
14759
14981
  }
14760
14982
  get settingsPath() {
14761
- return join3(this.configDir, "opencode.json");
14983
+ return join4(this.configDir, "opencode.json");
14762
14984
  }
14763
14985
  get tuiConfigPath() {
14764
- return join3(this.configDir, "tui.json");
14986
+ return join4(this.configDir, "tui.json");
14765
14987
  }
14766
14988
  async detect() {
14767
14989
  try {
@@ -14776,27 +14998,27 @@ class OpenCodeAdapter {
14776
14998
  return this.commandsDir;
14777
14999
  }
14778
15000
  async installCommands(commandNames, sourceDir) {
14779
- await mkdir2(ReapPaths.userReapCommands, { recursive: true });
15001
+ await mkdir3(ReapPaths.userReapCommands, { recursive: true });
14780
15002
  for (const cmd of commandNames) {
14781
- const src = join3(sourceDir, `${cmd}.md`);
14782
- const dest = join3(ReapPaths.userReapCommands, `${cmd}.md`);
15003
+ const src = join4(sourceDir, `${cmd}.md`);
15004
+ const dest = join4(ReapPaths.userReapCommands, `${cmd}.md`);
14783
15005
  await writeTextFile(dest, await readTextFileOrThrow(src));
14784
15006
  }
14785
15007
  }
14786
15008
  async removeStaleCommands(validNames) {
14787
15009
  try {
14788
- const existing = await readdir2(this.commandsDir);
15010
+ const existing = await readdir3(this.commandsDir);
14789
15011
  for (const file of existing) {
14790
15012
  if (!file.startsWith("reap.") || !file.endsWith(".md"))
14791
15013
  continue;
14792
15014
  if (!validNames.has(file)) {
14793
- await unlink2(join3(this.commandsDir, file));
15015
+ await unlink3(join4(this.commandsDir, file));
14794
15016
  }
14795
15017
  }
14796
15018
  } catch {}
14797
15019
  }
14798
15020
  async registerSessionHook(dryRun = false) {
14799
- const pluginPath = join3(this.pluginsDir, "reap-session-start.js");
15021
+ const pluginPath = join4(this.pluginsDir, "reap-session-start.js");
14800
15022
  const exists = await fileExists(pluginPath);
14801
15023
  let pluginAction = "skipped";
14802
15024
  if (exists) {
@@ -14809,7 +15031,7 @@ class OpenCodeAdapter {
14809
15031
  }
14810
15032
  } else {
14811
15033
  if (!dryRun) {
14812
- await mkdir2(this.pluginsDir, { recursive: true });
15034
+ await mkdir3(this.pluginsDir, { recursive: true });
14813
15035
  await writeTextFile(pluginPath, await this.getPluginContent());
14814
15036
  }
14815
15037
  pluginAction = "created";
@@ -14836,12 +15058,12 @@ class OpenCodeAdapter {
14836
15058
  }
14837
15059
  }
14838
15060
  async getPluginContent() {
14839
- const templatePath = join3(ReapPaths.packageHooksDir, "opencode-session-start.js");
15061
+ const templatePath = join4(ReapPaths.packageHooksDir, "opencode-session-start.js");
14840
15062
  return readTextFileOrThrow(templatePath);
14841
15063
  }
14842
15064
  async ensureDefaultVisibility() {
14843
- const kvPath = join3(OPENCODE_STATE_DIR, "kv.json");
14844
- await mkdir2(OPENCODE_STATE_DIR, { recursive: true });
15065
+ const kvPath = join4(OPENCODE_STATE_DIR, "kv.json");
15066
+ await mkdir3(OPENCODE_STATE_DIR, { recursive: true });
14845
15067
  let kv = {};
14846
15068
  const existing = await readTextFile(kvPath);
14847
15069
  if (existing) {
@@ -14860,7 +15082,7 @@ class OpenCodeAdapter {
14860
15082
  }
14861
15083
  }
14862
15084
  async ensureTuiConfig() {
14863
- await mkdir2(this.configDir, { recursive: true });
15085
+ await mkdir3(this.configDir, { recursive: true });
14864
15086
  const reapKeybinds = {
14865
15087
  display_thinking: "<leader>t",
14866
15088
  tool_details: "<leader>d"
@@ -14894,7 +15116,8 @@ class OpenCodeAdapter {
14894
15116
  // src/core/agents/index.ts
14895
15117
  var ALL_ADAPTERS = [
14896
15118
  new ClaudeCodeAdapter,
14897
- new OpenCodeAdapter
15119
+ new OpenCodeAdapter,
15120
+ new CodexAdapter
14898
15121
  ];
14899
15122
 
14900
15123
  class AgentRegistry {
@@ -14934,17 +15157,17 @@ init_fs();
14934
15157
  // src/core/skills.ts
14935
15158
  init_paths();
14936
15159
  init_fs();
14937
- import { readdir as readdir3, mkdir as mkdir3 } from "fs/promises";
14938
- import { join as join4 } from "path";
15160
+ import { readdir as readdir4, mkdir as mkdir4 } from "fs/promises";
15161
+ import { join as join5 } from "path";
14939
15162
  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"));
15163
+ const projectClaudeSkills = join5(projectRoot, ".claude", "skills");
15164
+ const reapCmdFiles = (await readdir4(ReapPaths.userReapCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
14942
15165
  let installed = 0;
14943
15166
  for (const file of reapCmdFiles) {
14944
- const src = await readTextFileOrThrow(join4(ReapPaths.userReapCommands, file));
15167
+ const src = await readTextFileOrThrow(join5(ReapPaths.userReapCommands, file));
14945
15168
  const name = file.replace(/\.md$/, "");
14946
- const skillDir = join4(projectClaudeSkills, name);
14947
- const skillFile = join4(skillDir, "SKILL.md");
15169
+ const skillDir = join5(projectClaudeSkills, name);
15170
+ const skillFile = join5(skillDir, "SKILL.md");
14948
15171
  const fmMatch = src.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
14949
15172
  const description = fmMatch ? fmMatch[1].match(/^description:\s*"?([^"\n]*)"?/m)?.[1]?.trim() ?? "" : "";
14950
15173
  const body = fmMatch ? fmMatch[2] : src;
@@ -14957,7 +15180,7 @@ ${body}`;
14957
15180
  if (existing !== null && existing === skillContent)
14958
15181
  continue;
14959
15182
  if (!dryRun) {
14960
- await mkdir3(skillDir, { recursive: true });
15183
+ await mkdir4(skillDir, { recursive: true });
14961
15184
  await writeTextFile(skillFile, skillContent);
14962
15185
  }
14963
15186
  installed++;
@@ -15007,21 +15230,21 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
15007
15230
  throw new Error(".reap/ already exists. This is already a REAP project.");
15008
15231
  }
15009
15232
  if (preset) {
15010
- const presetDir = join6(ReapPaths.packageTemplatesDir, "presets", preset);
15011
- const presetExists = await fileExists(join6(presetDir, "principles.md"));
15233
+ const presetDir = join7(ReapPaths.packageTemplatesDir, "presets", preset);
15234
+ const presetExists = await fileExists(join7(presetDir, "principles.md"));
15012
15235
  if (!presetExists) {
15013
15236
  throw new Error(`Unknown preset: "${preset}". Available presets: bun-hono-react`);
15014
15237
  }
15015
15238
  }
15016
15239
  log("Creating .reap/ directory structure...");
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 });
15240
+ await mkdir5(paths.genome, { recursive: true });
15241
+ await mkdir5(paths.domain, { recursive: true });
15242
+ await mkdir5(paths.environment, { recursive: true });
15243
+ await mkdir5(join7(paths.environment, "docs"), { recursive: true });
15244
+ await mkdir5(join7(paths.environment, "resources"), { recursive: true });
15245
+ await mkdir5(paths.life, { recursive: true });
15246
+ await mkdir5(paths.backlog, { recursive: true });
15247
+ await mkdir5(paths.lineage, { recursive: true });
15025
15248
  log("Writing config.yml...");
15026
15249
  let hasGhCli = false;
15027
15250
  try {
@@ -15034,7 +15257,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
15034
15257
  }
15035
15258
  const detectedLanguage = await AgentRegistry.readLanguage();
15036
15259
  const config = {
15037
- version: "0.15.2",
15260
+ version: "0.15.3",
15038
15261
  project: projectName,
15039
15262
  entryMode,
15040
15263
  strict: false,
@@ -15047,10 +15270,10 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
15047
15270
  await ConfigManager.write(paths, config);
15048
15271
  log("Setting up Genome templates...");
15049
15272
  const genomeTemplates = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
15050
- const genomeSourceDir = preset ? join6(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
15273
+ const genomeSourceDir = preset ? join7(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
15051
15274
  for (const file of genomeTemplates) {
15052
- const src = join6(genomeSourceDir, file);
15053
- const dest = join6(paths.genome, file);
15275
+ const src = join7(genomeSourceDir, file);
15276
+ const dest = join7(paths.genome, file);
15054
15277
  await writeTextFile(dest, await readTextFileOrThrow(src));
15055
15278
  }
15056
15279
  if ((entryMode === "adoption" || entryMode === "migration") && !preset) {
@@ -15059,47 +15282,47 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
15059
15282
  await syncGenomeFromProject2(projectRoot, paths.genome, log);
15060
15283
  }
15061
15284
  log("Installing artifact templates...");
15062
- await mkdir4(ReapPaths.userReapTemplates, { recursive: true });
15285
+ await mkdir5(ReapPaths.userReapTemplates, { recursive: true });
15063
15286
  const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
15064
15287
  for (const file of artifactFiles) {
15065
- const src = join6(ReapPaths.packageArtifactsDir, file);
15066
- const dest = join6(ReapPaths.userReapTemplates, file);
15288
+ const src = join7(ReapPaths.packageArtifactsDir, file);
15289
+ const dest = join7(ReapPaths.userReapTemplates, file);
15067
15290
  await writeTextFile(dest, await readTextFileOrThrow(src));
15068
15291
  }
15069
- const domainGuideSrc = join6(ReapPaths.packageGenomeDir, "domain/README.md");
15070
- const domainGuideDest = join6(ReapPaths.userReapTemplates, "domain-guide.md");
15292
+ const domainGuideSrc = join7(ReapPaths.packageGenomeDir, "domain/README.md");
15293
+ const domainGuideDest = join7(ReapPaths.userReapTemplates, "domain-guide.md");
15071
15294
  await writeTextFile(domainGuideDest, await readTextFileOrThrow(domainGuideSrc));
15072
- const mergeTemplatesDir = join6(ReapPaths.userReapTemplates, "merge");
15073
- await mkdir4(mergeTemplatesDir, { recursive: true });
15295
+ const mergeTemplatesDir = join7(ReapPaths.userReapTemplates, "merge");
15296
+ await mkdir5(mergeTemplatesDir, { recursive: true });
15074
15297
  const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
15075
- const mergeSourceDir = join6(ReapPaths.packageArtifactsDir, "merge");
15298
+ const mergeSourceDir = join7(ReapPaths.packageArtifactsDir, "merge");
15076
15299
  for (const file of mergeArtifactFiles) {
15077
- const src = join6(mergeSourceDir, file);
15078
- const dest = join6(mergeTemplatesDir, file);
15300
+ const src = join7(mergeSourceDir, file);
15301
+ const dest = join7(mergeTemplatesDir, file);
15079
15302
  await writeTextFile(dest, await readTextFileOrThrow(src));
15080
15303
  }
15081
15304
  log("Installing hook conditions...");
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);
15305
+ const conditionsSourceDir = join7(ReapPaths.packageTemplatesDir, "conditions");
15306
+ const conditionsDestDir = join7(paths.hooks, "conditions");
15307
+ await mkdir5(conditionsDestDir, { recursive: true });
15308
+ const conditionFiles = await readdir6(conditionsSourceDir);
15086
15309
  for (const file of conditionFiles) {
15087
15310
  if (!file.endsWith(".sh"))
15088
15311
  continue;
15089
- const src = join6(conditionsSourceDir, file);
15090
- const dest = join6(conditionsDestDir, file);
15312
+ const src = join7(conditionsSourceDir, file);
15313
+ const dest = join7(conditionsDestDir, file);
15091
15314
  await writeTextFile(dest, await readTextFileOrThrow(src));
15092
15315
  await chmod(dest, 493);
15093
15316
  }
15094
15317
  log("Installing hook scripts...");
15095
- const hooksSourceDir = join6(ReapPaths.packageTemplatesDir, "hooks");
15318
+ const hooksSourceDir = join7(ReapPaths.packageTemplatesDir, "hooks");
15096
15319
  const { readdir: readdirAsync } = await import("fs/promises");
15097
15320
  const hookFiles = await readdirAsync(hooksSourceDir);
15098
15321
  for (const file of hookFiles) {
15099
15322
  if (!file.endsWith(".sh") || !file.startsWith("on"))
15100
15323
  continue;
15101
- const src = join6(hooksSourceDir, file);
15102
- const dest = join6(paths.hooks, file);
15324
+ const src = join7(hooksSourceDir, file);
15325
+ const dest = join7(paths.hooks, file);
15103
15326
  await writeTextFile(dest, await readTextFileOrThrow(src));
15104
15327
  await chmod(dest, 493);
15105
15328
  }
@@ -15111,9 +15334,9 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
15111
15334
  await adapter.installCommands(COMMAND_NAMES, sourceDir);
15112
15335
  log(` Registering session hook for ${adapter.displayName}...`);
15113
15336
  await adapter.registerSessionHook();
15114
- if (typeof adapter.setupClaudeMd === "function") {
15115
- const mdResult = await adapter.setupClaudeMd(projectRoot);
15116
- log(` .claude/CLAUDE.md: ${mdResult.action}`);
15337
+ if (typeof adapter.setupAgentMd === "function") {
15338
+ const mdResult = await adapter.setupAgentMd(projectRoot);
15339
+ log(` [${adapter.displayName}] agent md: ${mdResult.action}`);
15117
15340
  }
15118
15341
  }
15119
15342
  if (detectedAgents.length === 0) {
@@ -15134,9 +15357,9 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
15134
15357
 
15135
15358
  // src/cli/commands/update.ts
15136
15359
  init_paths();
15137
- import { readdir as readdir10, unlink as unlink4, rm as rm2, mkdir as mkdir7 } from "fs/promises";
15138
- import { join as join11 } from "path";
15139
- import { execSync as execSync2 } from "child_process";
15360
+ import { readdir as readdir12, unlink as unlink5, rm as rm3, mkdir as mkdir7 } from "fs/promises";
15361
+ import { join as join13 } from "path";
15362
+ import { execSync as execSync3 } from "child_process";
15140
15363
 
15141
15364
  // src/core/hooks.ts
15142
15365
  async function migrateHooks(dryRun = false) {
@@ -15162,15 +15385,30 @@ init_config();
15162
15385
  init_fs();
15163
15386
  init_generation();
15164
15387
  var import_yaml5 = __toESM(require_dist(), 1);
15165
- import { readdir as readdir9, rename as rename2 } from "fs/promises";
15166
- import { join as join10 } from "path";
15388
+ import { readdir as readdir10, rename as rename2 } from "fs/promises";
15389
+ import { join as join11 } from "path";
15390
+ import { execSync as execSync2 } from "child_process";
15391
+ function estimateGenDates(lineagePath, dirName, fallbackDate) {
15392
+ const pattern = join11(lineagePath, `${dirName}*`);
15393
+ const fallback = fallbackDate ?? new Date().toISOString();
15394
+ try {
15395
+ const startedRaw = execSync2(`git log --format=%aI --diff-filter=A -- "${pattern}"`, { encoding: "utf-8", timeout: 5000 }).trim();
15396
+ const startedAt = startedRaw.split(`
15397
+ `).pop() || fallback;
15398
+ const completedRaw = execSync2(`git log -1 --format=%aI -- "${pattern}"`, { encoding: "utf-8", timeout: 5000 }).trim();
15399
+ const completedAt = completedRaw || startedAt;
15400
+ return { startedAt, completedAt };
15401
+ } catch {
15402
+ return { startedAt: fallback, completedAt: fallback };
15403
+ }
15404
+ }
15167
15405
  async function needsMigration(paths) {
15168
15406
  try {
15169
- const entries = await readdir9(paths.lineage, { withFileTypes: true });
15407
+ const entries = await readdir10(paths.lineage, { withFileTypes: true });
15170
15408
  for (const entry of entries) {
15171
15409
  if (!entry.isDirectory() || !entry.name.startsWith("gen-"))
15172
15410
  continue;
15173
- const metaPath = join10(paths.lineage, entry.name, "meta.yml");
15411
+ const metaPath = join11(paths.lineage, entry.name, "meta.yml");
15174
15412
  const content = await readTextFile(metaPath);
15175
15413
  if (content === null)
15176
15414
  return true;
@@ -15184,18 +15422,18 @@ async function migrateLineage(paths) {
15184
15422
  const result = { migrated: [], skipped: [], errors: [] };
15185
15423
  let entries;
15186
15424
  try {
15187
- const dirEntries = await readdir9(paths.lineage, { withFileTypes: true });
15425
+ const dirEntries = await readdir10(paths.lineage, { withFileTypes: true });
15188
15426
  entries = dirEntries.filter((e) => e.isDirectory() && e.name.startsWith("gen-")).map((e) => e.name).sort();
15189
15427
  } catch {
15190
15428
  return result;
15191
15429
  }
15192
15430
  const plan = [];
15193
15431
  for (const dirName of entries) {
15194
- const metaPath = join10(paths.lineage, dirName, "meta.yml");
15432
+ const metaPath = join11(paths.lineage, dirName, "meta.yml");
15195
15433
  const metaContent = await readTextFile(metaPath);
15196
15434
  const seq = parseGenSeq(dirName);
15197
15435
  let goal = "";
15198
- const objContent = await readTextFile(join10(paths.lineage, dirName, "01-objective.md"));
15436
+ const objContent = await readTextFile(join11(paths.lineage, dirName, "01-objective.md"));
15199
15437
  if (objContent) {
15200
15438
  const goalMatch = objContent.match(/## Goal\n+([\s\S]*?)(?=\n##)/);
15201
15439
  if (goalMatch)
@@ -15210,7 +15448,7 @@ async function migrateLineage(paths) {
15210
15448
  let prevId = null;
15211
15449
  for (const entry of plan) {
15212
15450
  if (entry.hasMeta) {
15213
- const metaContent = await readTextFile(join10(paths.lineage, entry.dirName, "meta.yml"));
15451
+ const metaContent = await readTextFile(join11(paths.lineage, entry.dirName, "meta.yml"));
15214
15452
  if (metaContent) {
15215
15453
  const meta = import_yaml5.default.parse(metaContent);
15216
15454
  prevId = meta.id;
@@ -15222,20 +15460,21 @@ async function migrateLineage(paths) {
15222
15460
  const parents = prevId ? [prevId] : [];
15223
15461
  const hash = generateGenHash(parents, entry.goal, "legacy", "migration", `legacy-${entry.seq}`);
15224
15462
  const newId = formatGenId(entry.seq, hash);
15463
+ const dates = estimateGenDates(paths.lineage, entry.dirName);
15225
15464
  const meta = {
15226
15465
  id: newId,
15227
15466
  type: "normal",
15228
15467
  parents,
15229
15468
  goal: entry.goal,
15230
15469
  genomeHash: "legacy",
15231
- startedAt: `legacy-${entry.seq}`,
15232
- completedAt: `legacy-${entry.seq}`
15470
+ startedAt: dates.startedAt,
15471
+ completedAt: dates.completedAt
15233
15472
  };
15234
- await writeTextFile(join10(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
15473
+ await writeTextFile(join11(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
15235
15474
  const oldSlug = entry.dirName.replace(/^gen-\d{3}/, "");
15236
15475
  const newDirName = `${newId}${oldSlug}`;
15237
15476
  if (newDirName !== entry.dirName) {
15238
- await rename2(join10(paths.lineage, entry.dirName), join10(paths.lineage, newDirName));
15477
+ await rename2(join11(paths.lineage, entry.dirName), join11(paths.lineage, newDirName));
15239
15478
  }
15240
15479
  prevId = newId;
15241
15480
  result.migrated.push(`${entry.dirName} → ${newDirName}`);
@@ -15355,752 +15594,753 @@ class MigrationRunner {
15355
15594
  }
15356
15595
  }
15357
15596
 
15358
- // src/core/migration-spec.ts
15597
+ // src/core/integrity.ts
15359
15598
  init_fs();
15599
+ init_lifecycle();
15600
+ init_types();
15360
15601
  var import_yaml6 = __toESM(require_dist(), 1);
15361
- import { existsSync as existsSync2 } from "fs";
15362
- function buildMigrationSpec(paths) {
15363
- const sections = [];
15364
- sections.push(`## Config fields (config.yml)
15365
-
15366
- | Field | Type | Required | Default |
15367
- |-------|------|----------|---------|
15368
- | version | string | yes | — |
15369
- | project | string | yes | — |
15370
- | entryMode | greenfield \\| migration \\| adoption | yes | — |
15371
- | stack | string | no | — |
15372
- | preset | string | no | — |
15373
- | agents | AgentName[] | no | auto-detect |
15374
- | language | string | no | — |
15375
- | autoUpdate | boolean | no | true |
15376
- | autoSubagent | boolean | no | true |
15377
- | autoIssueReport | boolean | no | false |
15378
- | strict | boolean \\| { edit?: boolean; merge?: boolean } | no | false |`);
15379
- sections.push(`## Expected directory structure
15380
-
15381
- .reap/
15382
- ├── config.yml
15383
- ├── genome/
15384
- │ ├── principles.md
15385
- │ ├── conventions.md
15386
- │ ├── constraints.md
15387
- │ ├── source-map.md
15388
- │ └── domain/
15389
- ├── environment/
15390
- │ ├── summary.md
15391
- │ ├── docs/
15392
- │ └── resources/
15393
- ├── life/
15394
- │ ├── current.yml
15395
- │ ├── backlog/
15396
- │ ├── 01-objective.md
15397
- │ ├── 02-planning.md
15398
- │ ├── 03-implementation.md
15399
- │ ├── 04-validation.md
15400
- │ └── 05-completion.md
15401
- ├── lineage/
15402
- │ └── gen-NNN-hash/ or gen-NNN-hash.md
15403
- └── hooks/
15404
- ├── conditions/
15405
- └── {event}.{name}.{sh|md}`);
15406
- sections.push(`## Slash commands (29)
15407
-
15408
- reap.objective, reap.planning, reap.implementation,
15409
- reap.validation, reap.completion, reap.evolve,
15410
- reap.start, reap.next, reap.back, reap.abort,
15411
- reap.status, reap.sync, reap.sync.genome, reap.sync.environment,
15412
- reap.help, reap.update, reap.report,
15413
- reap.merge.start, reap.merge.detect, reap.merge.mate,
15414
- reap.merge.merge, reap.merge.sync, reap.merge.validation,
15415
- reap.merge.completion, reap.merge.evolve,
15416
- reap.merge,
15417
- reap.pull, reap.push,
15418
- reap.config`);
15419
- sections.push(`## Hooks format
15420
-
15421
- File naming: {event}.{name}.{md|sh}
15422
- Location: .reap/hooks/
15423
-
15424
- Frontmatter (md hooks):
15425
- ---
15426
- event: onLifeStarted
15427
- name: my-hook
15428
- description: What this hook does
15429
- ---
15430
-
15431
- Condition files: .reap/hooks/conditions/{name}.yml`);
15432
- sections.push(`## Project root: ${paths.projectRoot}`);
15433
- return sections.join(`
15434
-
15435
- `);
15602
+ import { readdir as readdir11, stat as stat3 } from "fs/promises";
15603
+ import { join as join12 } from "path";
15604
+ import { homedir as homedir5 } from "os";
15605
+ var VALID_BACKLOG_TYPES = ["genome-change", "environment-change", "task"];
15606
+ var VALID_BACKLOG_STATUSES = ["pending", "consumed"];
15607
+ var VALID_GENERATION_TYPES = ["normal", "merge", "recovery"];
15608
+ var GENOME_LINE_WARNING_THRESHOLD = 100;
15609
+ var ISO_DATE_RE2 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/;
15610
+ function isISODate(s) {
15611
+ return ISO_DATE_RE2.test(s) && !Number.isNaN(new Date(s).getTime());
15436
15612
  }
15437
- async function detectMigrationGaps(paths) {
15438
- const gaps = [];
15439
- const configContent = await readTextFile(paths.config);
15440
- if (configContent === null) {
15441
- gaps.push("config.yml missing");
15442
- } else {
15613
+ async function checkIntegrity(paths) {
15614
+ const errors = [];
15615
+ const warnings = [];
15616
+ await checkDirectoryStructure(paths, errors);
15617
+ await checkConfig(paths, errors, warnings);
15618
+ const state = await checkCurrentYml(paths, errors, warnings);
15619
+ await checkLineage(paths, errors, warnings);
15620
+ await checkGenome(paths, errors, warnings);
15621
+ await checkBacklog(paths, errors, warnings);
15622
+ if (state) {
15623
+ await checkArtifacts(paths, state, errors, warnings);
15624
+ }
15625
+ return { errors, warnings };
15626
+ }
15627
+ async function checkDirectoryStructure(paths, errors) {
15628
+ const requiredDirs = [
15629
+ { path: paths.genome, name: "genome/" },
15630
+ { path: paths.environment, name: "environment/" },
15631
+ { path: paths.life, name: "life/" },
15632
+ { path: paths.lineage, name: "lineage/" },
15633
+ { path: paths.backlog, name: "life/backlog/" },
15634
+ { path: paths.hooks, name: "hooks/" },
15635
+ { path: paths.hookConditions, name: "hooks/conditions/" }
15636
+ ];
15637
+ for (const dir of requiredDirs) {
15443
15638
  try {
15444
- const config = import_yaml6.default.parse(configContent);
15445
- if (!config?.version)
15446
- gaps.push("config.yml: missing required field 'version'");
15447
- if (!config?.project)
15448
- gaps.push("config.yml: missing required field 'project'");
15449
- if (!config?.entryMode)
15450
- gaps.push("config.yml: missing required field 'entryMode'");
15639
+ const s = await stat3(dir.path);
15640
+ if (!s.isDirectory()) {
15641
+ errors.push(`${dir.name} directory missing`);
15642
+ }
15451
15643
  } catch {
15452
- gaps.push("config.yml: invalid YAML");
15644
+ errors.push(`${dir.name} directory missing`);
15453
15645
  }
15454
15646
  }
15455
- if (!existsSync2(paths.genome)) {
15456
- gaps.push("genome/ directory missing");
15457
- } else {
15458
- const genomeFiles = [
15459
- { path: paths.principles, label: "genome/principles.md" },
15460
- { path: paths.conventions, label: "genome/conventions.md" },
15461
- { path: paths.constraints, label: "genome/constraints.md" },
15462
- { path: paths.sourceMap, label: "genome/source-map.md" }
15463
- ];
15464
- for (const { path, label } of genomeFiles) {
15465
- if (!existsSync2(path)) {
15466
- gaps.push(`${label} missing`);
15467
- }
15468
- }
15647
+ }
15648
+ async function checkConfig(paths, errors, warnings) {
15649
+ const content = await readTextFile(paths.config);
15650
+ if (content === null) {
15651
+ errors.push("config.yml does not exist");
15652
+ return;
15653
+ }
15654
+ let config;
15655
+ try {
15656
+ config = import_yaml6.default.parse(content) ?? {};
15657
+ } catch {
15658
+ errors.push("config.yml is not valid YAML");
15659
+ return;
15469
15660
  }
15470
- if (!existsSync2(paths.environment)) {
15471
- gaps.push("environment/ directory missing");
15661
+ if (typeof config !== "object" || Array.isArray(config)) {
15662
+ errors.push("config.yml root must be a YAML mapping");
15663
+ return;
15664
+ }
15665
+ if (!config.project || typeof config.project !== "string") {
15666
+ errors.push("config.yml: missing or invalid 'project' field (string required)");
15472
15667
  }
15473
- if (!existsSync2(paths.life)) {
15474
- gaps.push("life/ directory missing");
15668
+ if (!config.entryMode || typeof config.entryMode !== "string") {
15669
+ errors.push("config.yml: missing or invalid 'entryMode' field (string required)");
15475
15670
  } else {
15476
- if (!existsSync2(paths.backlog)) {
15477
- gaps.push("life/backlog/ directory missing");
15671
+ const validModes = ["greenfield", "migration", "adoption"];
15672
+ if (!validModes.includes(config.entryMode)) {
15673
+ errors.push(`config.yml: invalid entryMode "${config.entryMode}" (valid: ${validModes.join(", ")})`);
15478
15674
  }
15479
15675
  }
15480
- if (!existsSync2(paths.lineage)) {
15481
- gaps.push("lineage/ directory missing");
15676
+ if (config.version !== undefined && typeof config.version !== "string") {
15677
+ warnings.push("config.yml: 'version' should be a string");
15678
+ }
15679
+ if (config.strict !== undefined && typeof config.strict !== "boolean" && typeof config.strict !== "object") {
15680
+ warnings.push("config.yml: 'strict' should be boolean or object");
15482
15681
  }
15483
- if (!existsSync2(paths.hooks)) {
15484
- gaps.push("hooks/ directory missing");
15682
+ if (config.autoUpdate !== undefined && typeof config.autoUpdate !== "boolean") {
15683
+ warnings.push("config.yml: 'autoUpdate' should be boolean");
15485
15684
  }
15486
- if (!existsSync2(paths.hookConditions)) {
15487
- gaps.push("hooks/conditions/ directory missing");
15685
+ if (config.autoSubagent !== undefined && typeof config.autoSubagent !== "boolean") {
15686
+ warnings.push("config.yml: 'autoSubagent' should be boolean");
15488
15687
  }
15489
- return gaps;
15490
15688
  }
15491
-
15492
- // src/cli/commands/update.ts
15493
- function selfUpgrade() {
15689
+ async function checkCurrentYml(paths, errors, warnings) {
15690
+ const content = await readTextFile(paths.currentYml);
15691
+ if (content === null || !content.trim()) {
15692
+ return null;
15693
+ }
15694
+ let state;
15494
15695
  try {
15495
- const installed = execSync2("reap --version", { encoding: "utf-8", timeout: 5000 }).trim();
15496
- if (installed.includes("+dev")) {
15497
- return { upgraded: false };
15498
- }
15499
- const latest = execSync2("npm view @c-d-cc/reap version", { encoding: "utf-8", timeout: 1e4 }).trim();
15500
- if (installed === latest) {
15501
- return { upgraded: false };
15502
- }
15503
- execSync2("npm update -g @c-d-cc/reap", { encoding: "utf-8", timeout: 60000, stdio: "pipe" });
15504
- return { upgraded: true, from: installed, to: latest };
15696
+ state = import_yaml6.default.parse(content) ?? {};
15505
15697
  } catch {
15506
- return { upgraded: false };
15698
+ errors.push("current.yml is not valid YAML");
15699
+ return null;
15507
15700
  }
15508
- }
15509
- async function updateProject(projectRoot, dryRun = false) {
15510
- const paths = new ReapPaths(projectRoot);
15511
- const result = { updated: [], skipped: [], removed: [] };
15512
- const config = await paths.isReapProject() ? await ConfigManager.read(paths) : null;
15513
- const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
15514
- const commandsDir = ReapPaths.packageCommandsDir;
15515
- const commandFiles = await readdir10(commandsDir);
15516
- await mkdir7(ReapPaths.userReapCommands, { recursive: true });
15517
- for (const file of commandFiles) {
15518
- if (!file.endsWith(".md"))
15519
- continue;
15520
- const src = await readTextFileOrThrow(join11(commandsDir, file));
15521
- const dest = join11(ReapPaths.userReapCommands, file);
15522
- const existing = await readTextFile(dest);
15523
- if (existing !== null && existing === src) {
15524
- result.skipped.push(`~/.reap/commands/${file}`);
15525
- } else {
15526
- if (!dryRun)
15527
- await writeTextFile(dest, src);
15528
- result.updated.push(`~/.reap/commands/${file}`);
15701
+ if (typeof state !== "object" || Array.isArray(state)) {
15702
+ errors.push("current.yml root must be a YAML mapping");
15703
+ return null;
15704
+ }
15705
+ const requiredStrings = ["id", "goal", "stage", "startedAt"];
15706
+ for (const field of requiredStrings) {
15707
+ if (!state[field] || typeof state[field] !== "string") {
15708
+ errors.push(`current.yml: missing or invalid '${field}' field (string required)`);
15529
15709
  }
15530
15710
  }
15531
- for (const adapter of adapters) {
15532
- const agentCmdDir = adapter.getCommandsDir();
15533
- const label = adapter.displayName;
15534
- try {
15535
- const existing = await readdir10(agentCmdDir);
15536
- for (const file of existing) {
15537
- if (!file.startsWith("reap.") || !file.endsWith(".md"))
15538
- continue;
15539
- const filePath = join11(agentCmdDir, file);
15540
- const content = await readTextFile(filePath);
15541
- if (content !== null && content.includes("redirected to ~/.reap/commands/")) {
15542
- if (!dryRun)
15543
- await unlink4(filePath);
15544
- result.removed.push(`[${label}] commands/${file} (Phase 2: redirect removed)`);
15545
- }
15546
- }
15547
- } catch {}
15711
+ if (state.genomeVersion === undefined || typeof state.genomeVersion !== "number") {
15712
+ errors.push("current.yml: missing or invalid 'genomeVersion' field (number required)");
15548
15713
  }
15549
- for (const adapter of adapters) {
15550
- if (typeof adapter.cleanupLegacyCommands === "function") {
15551
- const removed = await adapter.cleanupLegacyCommands();
15552
- for (const file of removed) {
15553
- result.removed.push(`[${adapter.displayName}] ~commands/${file} (legacy user-level)`);
15554
- }
15555
- }
15714
+ if (!Array.isArray(state.timeline)) {
15715
+ errors.push("current.yml: missing or invalid 'timeline' field (array required)");
15556
15716
  }
15557
- await mkdir7(ReapPaths.userReapTemplates, { recursive: true });
15558
- const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
15559
- for (const file of artifactFiles) {
15560
- const src = await readTextFileOrThrow(join11(ReapPaths.packageArtifactsDir, file));
15561
- const dest = join11(ReapPaths.userReapTemplates, file);
15562
- const existingContent = await readTextFile(dest);
15563
- if (existingContent !== null && existingContent === src) {
15564
- result.skipped.push(`~/.reap/templates/${file}`);
15565
- } else {
15566
- if (!dryRun)
15567
- await writeTextFile(dest, src);
15568
- result.updated.push(`~/.reap/templates/${file}`);
15717
+ if (state.type !== undefined) {
15718
+ if (!VALID_GENERATION_TYPES.includes(state.type)) {
15719
+ errors.push(`current.yml: invalid type "${state.type}" (valid: ${VALID_GENERATION_TYPES.join(", ")})`);
15569
15720
  }
15570
15721
  }
15571
- const domainGuideSrc = await readTextFileOrThrow(join11(ReapPaths.packageGenomeDir, "domain/README.md"));
15572
- const domainGuideDest = join11(ReapPaths.userReapTemplates, "domain-guide.md");
15573
- const domainExistingContent = await readTextFile(domainGuideDest);
15574
- if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
15575
- result.skipped.push(`~/.reap/templates/domain-guide.md`);
15576
- } else {
15577
- if (!dryRun)
15578
- await writeTextFile(domainGuideDest, domainGuideSrc);
15579
- result.updated.push(`~/.reap/templates/domain-guide.md`);
15722
+ if (state.parents !== undefined && !Array.isArray(state.parents)) {
15723
+ errors.push("current.yml: 'parents' must be an array");
15580
15724
  }
15581
- const mergeTemplatesDir = join11(ReapPaths.userReapTemplates, "merge");
15582
- await mkdir7(mergeTemplatesDir, { recursive: true });
15583
- const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
15584
- const mergeSourceDir = join11(ReapPaths.packageArtifactsDir, "merge");
15585
- for (const file of mergeArtifactFiles) {
15586
- const src = await readTextFileOrThrow(join11(mergeSourceDir, file));
15587
- const dest = join11(mergeTemplatesDir, file);
15588
- const existing = await readTextFile(dest);
15589
- if (existing !== null && existing === src) {
15590
- result.skipped.push(`~/.reap/templates/merge/${file}`);
15591
- } else {
15592
- if (!dryRun)
15593
- await writeTextFile(dest, src);
15594
- result.updated.push(`~/.reap/templates/merge/${file}`);
15725
+ if (typeof state.stage === "string" && !LifeCycle.isValid(state.stage)) {
15726
+ const mergeStages = ["detect", "mate", "merge", "sync", "validation", "completion"];
15727
+ if (!mergeStages.includes(state.stage)) {
15728
+ errors.push(`current.yml: invalid stage "${state.stage}"`);
15595
15729
  }
15596
15730
  }
15597
- const migrations = await migrateHooks(dryRun);
15598
- for (const m of migrations.results) {
15599
- if (m.action === "migrated") {
15600
- result.updated.push(`[${m.agent}] hooks (migrated)`);
15731
+ if (state.type === "recovery") {
15732
+ if (!state.recovers || !Array.isArray(state.recovers) || state.recovers.length === 0) {
15733
+ errors.push("current.yml: recovery generation must have non-empty 'recovers' array");
15601
15734
  }
15602
15735
  }
15603
- for (const adapter of adapters) {
15604
- const hookResult = await adapter.syncSessionHook(dryRun);
15605
- if (hookResult.action === "updated") {
15606
- result.updated.push(`[${adapter.displayName}] session hook`);
15607
- } else {
15608
- result.skipped.push(`[${adapter.displayName}] session hook`);
15609
- }
15736
+ return state;
15737
+ }
15738
+ async function checkLineage(paths, errors, warnings) {
15739
+ let entries;
15740
+ try {
15741
+ const items2 = await readdir11(paths.lineage, { withFileTypes: true });
15742
+ entries = items2.map((i) => ({ name: i.name, isDir: i.isDirectory(), isFile: i.isFile() }));
15743
+ } catch {
15744
+ return;
15610
15745
  }
15611
- for (const adapter of adapters) {
15612
- if (typeof adapter.setupClaudeMd === "function") {
15613
- const mdResult = await adapter.setupClaudeMd(projectRoot);
15614
- if (mdResult.action !== "skipped") {
15615
- result.updated.push(`[${adapter.displayName}] .claude/CLAUDE.md (${mdResult.action})`);
15616
- } else {
15617
- result.skipped.push(`[${adapter.displayName}] .claude/CLAUDE.md`);
15746
+ const items = await readdir11(paths.lineage, { withFileTypes: true });
15747
+ const allMetaIds = new Set;
15748
+ for (const item of items) {
15749
+ if (item.isDirectory() && item.name.startsWith("gen-")) {
15750
+ const metaPath = join12(paths.lineage, item.name, "meta.yml");
15751
+ const metaContent = await readTextFile(metaPath);
15752
+ if (metaContent === null) {
15753
+ errors.push(`lineage/${item.name}: missing meta.yml`);
15754
+ continue;
15618
15755
  }
15619
- }
15620
- }
15621
- if (await paths.isReapProject()) {
15622
- if (!dryRun) {
15623
- const backfillResult = await ConfigManager.backfill(paths);
15624
- if (backfillResult.added.length > 0) {
15625
- result.updated.push(`Config: added ${backfillResult.added.join(", ")}`);
15756
+ let meta;
15757
+ try {
15758
+ meta = import_yaml6.default.parse(metaContent) ?? {};
15759
+ } catch {
15760
+ errors.push(`lineage/${item.name}/meta.yml: invalid YAML`);
15761
+ continue;
15626
15762
  }
15627
- }
15628
- const skillsResult = await syncSkillsToProject(paths.projectRoot, dryRun);
15629
- if (skillsResult.installed > 0) {
15630
- result.updated.push(`.claude/skills/ (${skillsResult.installed} synced)`);
15631
- } else {
15632
- result.skipped.push(`.claude/skills/ (${skillsResult.total} unchanged)`);
15633
- }
15634
- const projectClaudeCommands = join11(paths.projectRoot, ".claude", "commands");
15635
- try {
15636
- const legacyFiles = (await readdir10(projectClaudeCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
15637
- for (const file of legacyFiles) {
15638
- if (!dryRun)
15639
- await unlink4(join11(projectClaudeCommands, file));
15640
- result.removed.push(`.claude/commands/${file} (legacy)`);
15763
+ validateLineageMeta(meta, `lineage/${item.name}/meta.yml`, errors, warnings);
15764
+ if (meta.id)
15765
+ allMetaIds.add(meta.id);
15766
+ } else if (item.isFile() && item.name.startsWith("gen-") && item.name.endsWith(".md")) {
15767
+ const content = await readTextFile(join12(paths.lineage, item.name));
15768
+ if (!content)
15769
+ continue;
15770
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
15771
+ if (!fmMatch) {
15772
+ warnings.push(`lineage/${item.name}: compressed file missing frontmatter`);
15773
+ continue;
15641
15774
  }
15642
- } catch {}
15643
- await migrateLegacyFiles(paths, dryRun, result);
15644
- const currentVersion = "0.15.2";
15645
- const migrationResult = await MigrationRunner.run(paths, currentVersion, dryRun);
15646
- for (const m of migrationResult.migrated) {
15647
- result.updated.push(`[migration] ${m}`);
15648
- }
15649
- for (const s of migrationResult.skipped) {
15650
- result.skipped.push(`[migration] ${s}`);
15651
- }
15652
- for (const e of migrationResult.errors) {
15653
- result.removed.push(`[migration error] ${e}`);
15654
- }
15655
- const gaps = await detectMigrationGaps(paths);
15656
- if (gaps.length > 0) {
15657
- const spec = buildMigrationSpec(paths);
15658
- console.log(JSON.stringify({
15659
- status: "prompt",
15660
- command: "migrate",
15661
- gaps,
15662
- spec,
15663
- prompt: "Analyze the gaps between current .reap/ structure and expected structure. Fix each gap after user confirmation."
15664
- }, null, 2));
15665
- }
15666
- if (migrationResult.errors.length > 0 && config?.autoIssueReport) {
15775
+ let meta;
15667
15776
  try {
15668
- const { execSync: execSync3 } = await import("child_process");
15669
- const errorSummary = migrationResult.errors.join("\\n");
15670
- const title = `Migration failure: ${migrationResult.fromVersion} ${migrationResult.toVersion}`;
15671
- const body = `## Migration Error\\n\\nFrom: ${migrationResult.fromVersion}\\nTo: ${migrationResult.toVersion}\\n\\n### Errors\\n\\n${errorSummary}`;
15672
- execSync3(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,migration" --body "${body}"`, { encoding: "utf-8", timeout: 15000, stdio: "pipe" });
15673
- } catch {}
15777
+ meta = import_yaml6.default.parse(fmMatch[1]) ?? {};
15778
+ } catch {
15779
+ errors.push(`lineage/${item.name}: invalid frontmatter YAML`);
15780
+ continue;
15781
+ }
15782
+ validateLineageMeta(meta, `lineage/${item.name}`, errors, warnings);
15783
+ if (meta.id)
15784
+ allMetaIds.add(meta.id);
15674
15785
  }
15675
15786
  }
15676
- return result;
15677
- }
15678
- async function migrateLegacyFiles(paths, dryRun, result) {
15679
- await removeDirIfExists(paths.legacyCommands, ".reap/commands/", dryRun, result);
15680
- await removeDirIfExists(paths.legacyTemplates, ".reap/templates/", dryRun, result);
15681
- try {
15682
- const legacyHooksJson = paths.legacyClaudeHooksJson;
15683
- const fileContent = await readTextFile(legacyHooksJson);
15684
- if (fileContent !== null) {
15685
- const content = JSON.parse(fileContent);
15686
- const sessionStart = content["SessionStart"];
15687
- if (Array.isArray(sessionStart)) {
15688
- const filtered = sessionStart.filter((entry) => {
15689
- if (typeof entry !== "object" || entry === null)
15690
- return true;
15691
- const hooks = entry["hooks"];
15692
- if (!Array.isArray(hooks))
15693
- return true;
15694
- return !hooks.some((h) => {
15695
- if (typeof h !== "object" || h === null)
15696
- return false;
15697
- const cmd = h["command"];
15698
- return typeof cmd === "string" && cmd.includes(".reap/hooks/");
15699
- });
15700
- });
15701
- if (filtered.length !== sessionStart.length) {
15702
- if (!dryRun) {
15703
- if (filtered.length === 0 && Object.keys(content).length === 1) {
15704
- await unlink4(legacyHooksJson);
15705
- result.removed.push(`.claude/hooks.json (legacy)`);
15706
- } else {
15707
- content["SessionStart"] = filtered;
15708
- await writeTextFile(legacyHooksJson, JSON.stringify(content, null, 2) + `
15709
- `);
15710
- result.removed.push(`.claude/hooks.json (legacy REAP hook entry)`);
15711
- }
15712
- }
15787
+ for (const item of items) {
15788
+ let parents = [];
15789
+ if (item.isDirectory() && item.name.startsWith("gen-")) {
15790
+ const metaContent = await readTextFile(join12(paths.lineage, item.name, "meta.yml"));
15791
+ if (metaContent) {
15792
+ try {
15793
+ const meta = import_yaml6.default.parse(metaContent);
15794
+ parents = meta?.parents ?? [];
15795
+ } catch {}
15796
+ }
15797
+ } else if (item.isFile() && item.name.startsWith("gen-") && item.name.endsWith(".md")) {
15798
+ const content = await readTextFile(join12(paths.lineage, item.name));
15799
+ if (content) {
15800
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
15801
+ if (fmMatch) {
15802
+ try {
15803
+ const meta = import_yaml6.default.parse(fmMatch[1]);
15804
+ parents = meta?.parents ?? [];
15805
+ } catch {}
15713
15806
  }
15714
15807
  }
15715
15808
  }
15716
- } catch {}
15717
- }
15718
- async function removeDirIfExists(dirPath, label, dryRun, result) {
15719
- try {
15720
- const entries = await readdir10(dirPath);
15721
- if (entries.length > 0 || true) {
15722
- if (!dryRun)
15723
- await rm2(dirPath, { recursive: true });
15724
- result.removed.push(label);
15809
+ for (const parent of parents) {
15810
+ if (parent && !allMetaIds.has(parent)) {
15811
+ const epochContent = await readTextFile(join12(paths.lineage, "epoch.md"));
15812
+ let inEpoch = false;
15813
+ if (epochContent) {
15814
+ inEpoch = epochContent.includes(parent);
15815
+ }
15816
+ if (!inEpoch) {
15817
+ warnings.push(`lineage/${item.name}: parent "${parent}" not found in lineage (may be in a compressed epoch)`);
15818
+ }
15819
+ }
15725
15820
  }
15726
- } catch {}
15727
- }
15728
-
15729
- // src/cli/commands/status.ts
15730
- init_paths();
15731
- init_generation();
15732
- init_config();
15733
- async function getStatus(projectRoot) {
15734
- const paths = new ReapPaths(projectRoot);
15735
- const config = await ConfigManager.read(paths);
15736
- const mgr = new GenerationManager(paths);
15737
- const current = await mgr.current();
15738
- const completedGens = await mgr.listCompleted();
15739
- return {
15740
- version: config.version,
15741
- project: config.project,
15742
- entryMode: config.entryMode,
15743
- lastSyncedGeneration: config.lastSyncedGeneration,
15744
- generation: current ? {
15745
- id: current.id,
15746
- goal: current.goal,
15747
- stage: current.stage,
15748
- genomeVersion: current.genomeVersion,
15749
- startedAt: current.startedAt,
15750
- type: current.type,
15751
- parents: current.parents,
15752
- genomeHash: current.genomeHash
15753
- } : null,
15754
- totalGenerations: completedGens.length
15755
- };
15756
- }
15757
-
15758
- // src/cli/commands/fix.ts
15759
- init_paths();
15760
- init_lifecycle();
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();
15770
- var import_yaml7 = __toESM(require_dist(), 1);
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
15821
  }
15792
- return { errors, warnings };
15793
15822
  }
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)");
15823
+ function validateLineageMeta(meta, location, errors, warnings) {
15824
+ const requiredStrings = ["id", "goal", "startedAt", "completedAt"];
15825
+ for (const field of requiredStrings) {
15826
+ if (!meta[field] || typeof meta[field] !== "string") {
15827
+ errors.push(`${location}: missing or invalid '${field}' field`);
15828
+ }
15813
15829
  }
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(", ")})`);
15830
+ if (typeof meta.completedAt === "string") {
15831
+ if (!isISODate(meta.completedAt)) {
15832
+ errors.push(`${location}: completedAt "${meta.completedAt}" is not a valid ISO date`);
15820
15833
  }
15821
15834
  }
15822
- if (config.version !== undefined && typeof config.version !== "string") {
15823
- warnings.push("config.yml: 'version' should be a string");
15835
+ if (typeof meta.startedAt === "string") {
15836
+ if (!isISODate(meta.startedAt)) {
15837
+ warnings.push(`${location}: startedAt "${meta.startedAt}" is not a valid ISO date`);
15838
+ }
15824
15839
  }
15825
- if (config.strict !== undefined && typeof config.strict !== "boolean" && typeof config.strict !== "object") {
15826
- warnings.push("config.yml: 'strict' should be boolean or object");
15840
+ if (!Array.isArray(meta.parents)) {
15841
+ errors.push(`${location}: missing or invalid 'parents' field (array required)`);
15827
15842
  }
15828
- if (config.autoUpdate !== undefined && typeof config.autoUpdate !== "boolean") {
15829
- warnings.push("config.yml: 'autoUpdate' should be boolean");
15843
+ if (!meta.type || typeof meta.type !== "string") {
15844
+ errors.push(`${location}: missing or invalid 'type' field`);
15845
+ } else if (!VALID_GENERATION_TYPES.includes(meta.type)) {
15846
+ errors.push(`${location}: invalid type "${meta.type}"`);
15830
15847
  }
15831
- if (config.autoSubagent !== undefined && typeof config.autoSubagent !== "boolean") {
15832
- warnings.push("config.yml: 'autoSubagent' should be boolean");
15848
+ if (meta.genomeHash !== undefined && typeof meta.genomeHash !== "string") {
15849
+ warnings.push(`${location}: genomeHash should be a string`);
15833
15850
  }
15834
15851
  }
15835
- async function checkCurrentYml(paths, errors, warnings) {
15836
- const content = await readTextFile(paths.currentYml);
15837
- if (content === null || !content.trim()) {
15838
- return null;
15852
+ async function checkGenome(paths, errors, warnings) {
15853
+ const l1Files = [
15854
+ { path: paths.principles, name: "principles.md" },
15855
+ { path: paths.conventions, name: "conventions.md" },
15856
+ { path: paths.constraints, name: "constraints.md" },
15857
+ { path: paths.sourceMap, name: "source-map.md" }
15858
+ ];
15859
+ for (const gf of l1Files) {
15860
+ if (!await fileExists(gf.path)) {
15861
+ errors.push(`genome/${gf.name} does not exist`);
15862
+ continue;
15863
+ }
15864
+ const content = await readTextFile(gf.path);
15865
+ if (content === null)
15866
+ continue;
15867
+ const lines = content.split(`
15868
+ `).length;
15869
+ if (lines > GENOME_LINE_WARNING_THRESHOLD) {
15870
+ warnings.push(`genome/${gf.name}: ${lines} lines (exceeds ~${GENOME_LINE_WARNING_THRESHOLD} line guideline)`);
15871
+ }
15872
+ const stripped = content.split(`
15873
+ `).filter((l) => !l.startsWith("#") && !l.startsWith(">") && !l.startsWith("-") && l.trim() !== "").join("").trim();
15874
+ if (stripped.length === 0) {
15875
+ warnings.push(`genome/${gf.name}: appears to be placeholder-only (no substantive content)`);
15876
+ }
15839
15877
  }
15840
- let state;
15878
+ }
15879
+ async function checkBacklog(paths, errors, warnings) {
15880
+ let entries;
15841
15881
  try {
15842
- state = import_yaml7.default.parse(content) ?? {};
15882
+ entries = await readdir11(paths.backlog);
15843
15883
  } 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;
15884
+ return;
15850
15885
  }
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)`);
15886
+ for (const filename of entries) {
15887
+ if (!filename.endsWith(".md"))
15888
+ continue;
15889
+ const content = await readTextFile(join12(paths.backlog, filename));
15890
+ if (!content)
15891
+ continue;
15892
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
15893
+ if (!fmMatch) {
15894
+ errors.push(`backlog/${filename}: missing frontmatter`);
15895
+ continue;
15855
15896
  }
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(", ")})`);
15897
+ let fm;
15898
+ try {
15899
+ fm = import_yaml6.default.parse(fmMatch[1]) ?? {};
15900
+ } catch {
15901
+ errors.push(`backlog/${filename}: invalid frontmatter YAML`);
15902
+ continue;
15903
+ }
15904
+ if (!fm.type || typeof fm.type !== "string") {
15905
+ errors.push(`backlog/${filename}: missing or invalid 'type' field in frontmatter`);
15906
+ } else if (!VALID_BACKLOG_TYPES.includes(fm.type)) {
15907
+ errors.push(`backlog/${filename}: invalid type "${fm.type}" (valid: ${VALID_BACKLOG_TYPES.join(", ")})`);
15908
+ }
15909
+ if (!fm.status || typeof fm.status !== "string") {
15910
+ errors.push(`backlog/${filename}: missing or invalid 'status' field in frontmatter`);
15911
+ } else if (!VALID_BACKLOG_STATUSES.includes(fm.status)) {
15912
+ errors.push(`backlog/${filename}: invalid status "${fm.status}" (valid: ${VALID_BACKLOG_STATUSES.join(", ")})`);
15913
+ }
15914
+ if (fm.status === "consumed" && !fm.consumedBy) {
15915
+ errors.push(`backlog/${filename}: status is 'consumed' but missing 'consumedBy' field`);
15866
15916
  }
15867
15917
  }
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}"`);
15918
+ }
15919
+ var STAGE_ARTIFACT_MAP = {
15920
+ objective: "01-objective.md",
15921
+ planning: "02-planning.md",
15922
+ implementation: "03-implementation.md",
15923
+ validation: "04-validation.md",
15924
+ completion: "05-completion.md"
15925
+ };
15926
+ async function checkArtifacts(paths, state, errors, warnings) {
15927
+ const currentStageIdx = LIFECYCLE_ORDER.indexOf(state.stage);
15928
+ if (currentStageIdx < 0)
15929
+ return;
15930
+ for (let i = 0;i < currentStageIdx; i++) {
15931
+ const stage = LIFECYCLE_ORDER[i];
15932
+ const artifactName = STAGE_ARTIFACT_MAP[stage];
15933
+ if (!artifactName)
15934
+ continue;
15935
+ const artifactPath = paths.artifact(artifactName);
15936
+ if (!await fileExists(artifactPath)) {
15937
+ errors.push(`artifact ${artifactName} missing (expected for completed stage '${stage}')`);
15938
+ continue;
15939
+ }
15940
+ const content = await readTextFile(artifactPath);
15941
+ if (content && !content.startsWith("# REAP MANAGED")) {
15942
+ warnings.push(`artifact ${artifactName}: missing 'REAP MANAGED' header`);
15875
15943
  }
15876
15944
  }
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");
15945
+ const currentArtifact = STAGE_ARTIFACT_MAP[state.stage];
15946
+ if (currentArtifact) {
15947
+ const artifactPath = paths.artifact(currentArtifact);
15948
+ if (!await fileExists(artifactPath)) {
15949
+ warnings.push(`artifact ${currentArtifact} not yet created for current stage '${state.stage}'`);
15880
15950
  }
15881
15951
  }
15882
- return state;
15883
15952
  }
15884
- async function checkLineage(paths, errors, warnings) {
15953
+ async function checkUserLevelArtifacts(projectRoot) {
15954
+ const errors = [];
15955
+ const warnings = [];
15956
+ const home = homedir5();
15957
+ await checkGlobPattern(join12(home, ".claude", "skills"), /^reap\./, "~/.claude/skills/", "user-level reap skill found (should only be project-level)", errors);
15958
+ await checkGlobPattern(join12(home, ".claude", "commands"), /^reap\./, "~/.claude/commands/", "legacy reap command at user level (Phase 2 remnant)", warnings);
15959
+ await checkGlobPattern(join12(home, ".config", "opencode", "commands"), /^reap\./, "~/.config/opencode/commands/", "legacy reap command at user level (Phase 2 remnant)", warnings);
15960
+ await checkGlobPattern(join12(projectRoot, ".claude", "commands"), /^reap\./, ".claude/commands/", "legacy project-level reap command (should be migrated to skills/)", warnings);
15961
+ return { errors, warnings };
15962
+ }
15963
+ async function checkGlobPattern(dir, pattern, displayDir, message, target) {
15885
15964
  let entries;
15886
15965
  try {
15887
- const items2 = await readdir11(paths.lineage, { withFileTypes: true });
15888
- entries = items2.map((i) => ({ name: i.name, isDir: i.isDirectory(), isFile: i.isFile() }));
15966
+ entries = await readdir11(dir);
15889
15967
  } catch {
15890
15968
  return;
15891
15969
  }
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);
15970
+ for (const entry of entries) {
15971
+ if (pattern.test(entry)) {
15972
+ target.push(`${displayDir}${entry}: ${message}`);
15931
15973
  }
15932
15974
  }
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
- }
15975
+ }
15976
+
15977
+ // src/core/migration-spec.ts
15978
+ function buildMigrationSpec(paths) {
15979
+ const sections = [];
15980
+ sections.push(`## Config fields (config.yml)
15981
+
15982
+ | Field | Type | Required | Default |
15983
+ |-------|------|----------|---------|
15984
+ | version | string | yes | — |
15985
+ | project | string | yes | |
15986
+ | entryMode | greenfield \\| migration \\| adoption | yes | — |
15987
+ | stack | string | no | — |
15988
+ | preset | string | no | — |
15989
+ | agents | AgentName[] | no | auto-detect |
15990
+ | language | string | no | — |
15991
+ | autoUpdate | boolean | no | true |
15992
+ | autoSubagent | boolean | no | true |
15993
+ | autoIssueReport | boolean | no | false |
15994
+ | strict | boolean \\| { edit?: boolean; merge?: boolean } | no | false |`);
15995
+ sections.push(`## Expected directory structure
15996
+
15997
+ .reap/
15998
+ ├── config.yml
15999
+ ├── genome/
16000
+ │ ├── principles.md
16001
+ │ ├── conventions.md
16002
+ │ ├── constraints.md
16003
+ │ ├── source-map.md
16004
+ │ └── domain/
16005
+ ├── environment/
16006
+ │ ├── summary.md
16007
+ │ ├── docs/
16008
+ │ └── resources/
16009
+ ├── life/
16010
+ │ ├── current.yml
16011
+ │ ├── backlog/
16012
+ │ ├── 01-objective.md
16013
+ │ ├── 02-planning.md
16014
+ │ ├── 03-implementation.md
16015
+ │ ├── 04-validation.md
16016
+ │ └── 05-completion.md
16017
+ ├── lineage/
16018
+ │ └── gen-NNN-hash/ or gen-NNN-hash.md
16019
+ └── hooks/
16020
+ ├── conditions/
16021
+ └── {event}.{name}.{sh|md}`);
16022
+ sections.push(`## Slash commands (32)
16023
+
16024
+ reap.objective, reap.planning, reap.implementation,
16025
+ reap.validation, reap.completion, reap.evolve,
16026
+ reap.evolve.recovery, reap.start, reap.next, reap.back, reap.abort,
16027
+ reap.status, reap.sync, reap.sync.genome, reap.sync.environment,
16028
+ reap.help, reap.update, reap.update-genome, reap.report,
16029
+ reap.refreshKnowledge,
16030
+ reap.merge.start, reap.merge.detect, reap.merge.mate,
16031
+ reap.merge.merge, reap.merge.sync, reap.merge.validation,
16032
+ reap.merge.completion, reap.merge.evolve,
16033
+ reap.merge,
16034
+ reap.pull, reap.push,
16035
+ reap.config`);
16036
+ sections.push(`## Hooks format
16037
+
16038
+ File naming: {event}.{name}.{md|sh}
16039
+ Location: .reap/hooks/
16040
+
16041
+ Frontmatter (md hooks):
16042
+ ---
16043
+ event: onLifeStarted
16044
+ name: my-hook
16045
+ description: What this hook does
16046
+ ---
16047
+
16048
+ Condition files: .reap/hooks/conditions/{name}.yml`);
16049
+ sections.push(`## Project root: ${paths.projectRoot}`);
16050
+ return sections.join(`
16051
+
16052
+ `);
16053
+ }
16054
+ async function detectMigrationGaps(paths) {
16055
+ const result = await checkIntegrity(paths);
16056
+ return result.errors;
16057
+ }
16058
+
16059
+ // src/cli/commands/update.ts
16060
+ function selfUpgrade() {
16061
+ try {
16062
+ const installed = execSync3("reap --version", { encoding: "utf-8", timeout: 5000 }).trim();
16063
+ if (installed.includes("+dev")) {
16064
+ return { upgraded: false };
15954
16065
  }
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
- }
16066
+ const latest = execSync3("npm view @c-d-cc/reap version", { encoding: "utf-8", timeout: 1e4 }).trim();
16067
+ if (installed === latest) {
16068
+ return { upgraded: false };
15966
16069
  }
16070
+ execSync3("npm update -g @c-d-cc/reap", { encoding: "utf-8", timeout: 60000, stdio: "pipe" });
16071
+ return { upgraded: true, from: installed, to: latest };
16072
+ } catch {
16073
+ return { upgraded: false };
15967
16074
  }
15968
16075
  }
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`);
16076
+ async function updateProject(projectRoot, dryRun = false) {
16077
+ const paths = new ReapPaths(projectRoot);
16078
+ const result = { updated: [], skipped: [], removed: [] };
16079
+ const config = await paths.isReapProject() ? await ConfigManager.read(paths) : null;
16080
+ const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
16081
+ const commandsDir = ReapPaths.packageCommandsDir;
16082
+ const commandFiles = await readdir12(commandsDir);
16083
+ await mkdir7(ReapPaths.userReapCommands, { recursive: true });
16084
+ for (const file of commandFiles) {
16085
+ if (!file.endsWith(".md"))
16086
+ continue;
16087
+ const src = await readTextFileOrThrow(join13(commandsDir, file));
16088
+ const dest = join13(ReapPaths.userReapCommands, file);
16089
+ const existing = await readTextFile(dest);
16090
+ if (existing !== null && existing === src) {
16091
+ result.skipped.push(`~/.reap/commands/${file}`);
16092
+ } else {
16093
+ if (!dryRun)
16094
+ await writeTextFile(dest, src);
16095
+ result.updated.push(`~/.reap/commands/${file}`);
15974
16096
  }
15975
16097
  }
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
- }
16098
+ for (const adapter of adapters) {
16099
+ const agentCmdDir = adapter.getCommandsDir();
16100
+ const label = adapter.displayName;
16101
+ try {
16102
+ const existing = await readdir12(agentCmdDir);
16103
+ for (const file of existing) {
16104
+ if (!file.startsWith("reap.") || !file.endsWith(".md"))
16105
+ continue;
16106
+ const filePath = join13(agentCmdDir, file);
16107
+ const content = await readTextFile(filePath);
16108
+ if (content !== null && content.includes("redirected to ~/.reap/commands/")) {
16109
+ if (!dryRun)
16110
+ await unlink5(filePath);
16111
+ result.removed.push(`[${label}] commands/${file} (Phase 2: redirect removed)`);
16112
+ }
16113
+ }
16114
+ } catch {}
15980
16115
  }
15981
- if (typeof meta.startedAt === "string") {
15982
- if (!isISODate(meta.startedAt)) {
15983
- warnings.push(`${location}: startedAt "${meta.startedAt}" is not a valid ISO date`);
16116
+ for (const adapter of adapters) {
16117
+ if (typeof adapter.cleanupLegacyCommands === "function") {
16118
+ const removed = await adapter.cleanupLegacyCommands();
16119
+ for (const file of removed) {
16120
+ result.removed.push(`[${adapter.displayName}] ~commands/${file} (legacy user-level)`);
16121
+ }
15984
16122
  }
15985
16123
  }
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}"`);
16124
+ await mkdir7(ReapPaths.userReapTemplates, { recursive: true });
16125
+ const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
16126
+ for (const file of artifactFiles) {
16127
+ const src = await readTextFileOrThrow(join13(ReapPaths.packageArtifactsDir, file));
16128
+ const dest = join13(ReapPaths.userReapTemplates, file);
16129
+ const existingContent = await readTextFile(dest);
16130
+ if (existingContent !== null && existingContent === src) {
16131
+ result.skipped.push(`~/.reap/templates/${file}`);
16132
+ } else {
16133
+ if (!dryRun)
16134
+ await writeTextFile(dest, src);
16135
+ result.updated.push(`~/.reap/templates/${file}`);
16136
+ }
15993
16137
  }
15994
- if (meta.genomeHash !== undefined && typeof meta.genomeHash !== "string") {
15995
- warnings.push(`${location}: genomeHash should be a string`);
16138
+ const domainGuideSrc = await readTextFileOrThrow(join13(ReapPaths.packageGenomeDir, "domain/README.md"));
16139
+ const domainGuideDest = join13(ReapPaths.userReapTemplates, "domain-guide.md");
16140
+ const domainExistingContent = await readTextFile(domainGuideDest);
16141
+ if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
16142
+ result.skipped.push(`~/.reap/templates/domain-guide.md`);
16143
+ } else {
16144
+ if (!dryRun)
16145
+ await writeTextFile(domainGuideDest, domainGuideSrc);
16146
+ result.updated.push(`~/.reap/templates/domain-guide.md`);
15996
16147
  }
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;
16148
+ const mergeTemplatesDir = join13(ReapPaths.userReapTemplates, "merge");
16149
+ await mkdir7(mergeTemplatesDir, { recursive: true });
16150
+ const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
16151
+ const mergeSourceDir = join13(ReapPaths.packageArtifactsDir, "merge");
16152
+ for (const file of mergeArtifactFiles) {
16153
+ const src = await readTextFileOrThrow(join13(mergeSourceDir, file));
16154
+ const dest = join13(mergeTemplatesDir, file);
16155
+ const existing = await readTextFile(dest);
16156
+ if (existing !== null && existing === src) {
16157
+ result.skipped.push(`~/.reap/templates/merge/${file}`);
16158
+ } else {
16159
+ if (!dryRun)
16160
+ await writeTextFile(dest, src);
16161
+ result.updated.push(`~/.reap/templates/merge/${file}`);
16009
16162
  }
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)`);
16163
+ }
16164
+ const migrations = await migrateHooks(dryRun);
16165
+ for (const m of migrations.results) {
16166
+ if (m.action === "migrated") {
16167
+ result.updated.push(`[${m.agent}] hooks (migrated)`);
16017
16168
  }
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)`);
16169
+ }
16170
+ for (const adapter of adapters) {
16171
+ const hookResult = await adapter.syncSessionHook(dryRun);
16172
+ if (hookResult.action === "updated") {
16173
+ result.updated.push(`[${adapter.displayName}] session hook`);
16174
+ } else {
16175
+ result.skipped.push(`[${adapter.displayName}] session hook`);
16022
16176
  }
16023
16177
  }
16024
- }
16025
- async function checkBacklog(paths, errors, warnings) {
16026
- let entries;
16027
- try {
16028
- entries = await readdir11(paths.backlog);
16029
- } catch {
16030
- return;
16178
+ for (const adapter of adapters) {
16179
+ if (typeof adapter.setupAgentMd === "function") {
16180
+ const mdResult = await adapter.setupAgentMd(projectRoot);
16181
+ if (mdResult.action !== "skipped") {
16182
+ result.updated.push(`[${adapter.displayName}] agent md (${mdResult.action})`);
16183
+ } else {
16184
+ result.skipped.push(`[${adapter.displayName}] agent md`);
16185
+ }
16186
+ }
16031
16187
  }
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;
16188
+ if (await paths.isReapProject()) {
16189
+ if (!dryRun) {
16190
+ const backfillResult = await ConfigManager.backfill(paths);
16191
+ if (backfillResult.added.length > 0) {
16192
+ result.updated.push(`Config: added ${backfillResult.added.join(", ")}`);
16193
+ }
16042
16194
  }
16043
- let fm;
16195
+ const skillsResult = await syncSkillsToProject(paths.projectRoot, dryRun);
16196
+ if (skillsResult.installed > 0) {
16197
+ result.updated.push(`.claude/skills/ (${skillsResult.installed} synced)`);
16198
+ } else {
16199
+ result.skipped.push(`.claude/skills/ (${skillsResult.total} unchanged)`);
16200
+ }
16201
+ const projectClaudeCommands = join13(paths.projectRoot, ".claude", "commands");
16044
16202
  try {
16045
- fm = import_yaml7.default.parse(fmMatch[1]) ?? {};
16046
- } catch {
16047
- errors.push(`backlog/${filename}: invalid frontmatter YAML`);
16048
- continue;
16203
+ const legacyFiles = (await readdir12(projectClaudeCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
16204
+ for (const file of legacyFiles) {
16205
+ if (!dryRun)
16206
+ await unlink5(join13(projectClaudeCommands, file));
16207
+ result.removed.push(`.claude/commands/${file} (legacy)`);
16208
+ }
16209
+ } catch {}
16210
+ await migrateLegacyFiles(paths, dryRun, result);
16211
+ const currentVersion = "0.15.3";
16212
+ const migrationResult = await MigrationRunner.run(paths, currentVersion, dryRun);
16213
+ for (const m of migrationResult.migrated) {
16214
+ result.updated.push(`[migration] ${m}`);
16049
16215
  }
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(", ")})`);
16216
+ for (const s of migrationResult.skipped) {
16217
+ result.skipped.push(`[migration] ${s}`);
16054
16218
  }
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(", ")})`);
16219
+ for (const e of migrationResult.errors) {
16220
+ result.removed.push(`[migration error] ${e}`);
16059
16221
  }
16060
- if (fm.status === "consumed" && !fm.consumedBy) {
16061
- errors.push(`backlog/${filename}: status is 'consumed' but missing 'consumedBy' field`);
16222
+ const gaps = await detectMigrationGaps(paths);
16223
+ if (gaps.length > 0) {
16224
+ const spec = buildMigrationSpec(paths);
16225
+ console.log(JSON.stringify({
16226
+ status: "prompt",
16227
+ command: "migrate",
16228
+ gaps,
16229
+ spec,
16230
+ prompt: "Analyze the gaps between current .reap/ structure and expected structure. Fix each gap after user confirmation."
16231
+ }, null, 2));
16232
+ }
16233
+ if (migrationResult.errors.length > 0 && config?.autoIssueReport) {
16234
+ try {
16235
+ const { execSync: execSync4 } = await import("child_process");
16236
+ const errorSummary = migrationResult.errors.join("\\n");
16237
+ const title = `Migration failure: ${migrationResult.fromVersion} → ${migrationResult.toVersion}`;
16238
+ const body = `## Migration Error\\n\\nFrom: ${migrationResult.fromVersion}\\nTo: ${migrationResult.toVersion}\\n\\n### Errors\\n\\n${errorSummary}`;
16239
+ execSync4(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,migration" --body "${body}"`, { encoding: "utf-8", timeout: 15000, stdio: "pipe" });
16240
+ } catch {}
16062
16241
  }
16063
16242
  }
16243
+ return result;
16064
16244
  }
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`);
16245
+ async function migrateLegacyFiles(paths, dryRun, result) {
16246
+ await removeDirIfExists(paths.legacyCommands, ".reap/commands/", dryRun, result);
16247
+ await removeDirIfExists(paths.legacyTemplates, ".reap/templates/", dryRun, result);
16248
+ try {
16249
+ const legacyHooksJson = paths.legacyClaudeHooksJson;
16250
+ const fileContent = await readTextFile(legacyHooksJson);
16251
+ if (fileContent !== null) {
16252
+ const content = JSON.parse(fileContent);
16253
+ const sessionStart = content["SessionStart"];
16254
+ if (Array.isArray(sessionStart)) {
16255
+ const filtered = sessionStart.filter((entry) => {
16256
+ if (typeof entry !== "object" || entry === null)
16257
+ return true;
16258
+ const hooks = entry["hooks"];
16259
+ if (!Array.isArray(hooks))
16260
+ return true;
16261
+ return !hooks.some((h) => {
16262
+ if (typeof h !== "object" || h === null)
16263
+ return false;
16264
+ const cmd = h["command"];
16265
+ return typeof cmd === "string" && cmd.includes(".reap/hooks/");
16266
+ });
16267
+ });
16268
+ if (filtered.length !== sessionStart.length) {
16269
+ if (!dryRun) {
16270
+ if (filtered.length === 0 && Object.keys(content).length === 1) {
16271
+ await unlink5(legacyHooksJson);
16272
+ result.removed.push(`.claude/hooks.json (legacy)`);
16273
+ } else {
16274
+ content["SessionStart"] = filtered;
16275
+ await writeTextFile(legacyHooksJson, JSON.stringify(content, null, 2) + `
16276
+ `);
16277
+ result.removed.push(`.claude/hooks.json (legacy REAP hook entry)`);
16278
+ }
16279
+ }
16280
+ }
16281
+ }
16089
16282
  }
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}'`);
16283
+ } catch {}
16284
+ }
16285
+ async function removeDirIfExists(dirPath, label, dryRun, result) {
16286
+ try {
16287
+ const entries = await readdir12(dirPath);
16288
+ if (entries.length > 0 || true) {
16289
+ if (!dryRun)
16290
+ await rm3(dirPath, { recursive: true });
16291
+ result.removed.push(label);
16096
16292
  }
16097
- }
16293
+ } catch {}
16294
+ }
16295
+
16296
+ // src/cli/commands/status.ts
16297
+ init_paths();
16298
+ init_generation();
16299
+ init_config();
16300
+ async function getStatus(projectRoot) {
16301
+ const paths = new ReapPaths(projectRoot);
16302
+ const config = await ConfigManager.read(paths);
16303
+ const mgr = new GenerationManager(paths);
16304
+ const current = await mgr.current();
16305
+ const totalCompleted = await mgr.countAllCompleted();
16306
+ const integrityResult = await checkIntegrity(paths);
16307
+ return {
16308
+ version: config.version,
16309
+ project: config.project,
16310
+ entryMode: config.entryMode,
16311
+ lastSyncedGeneration: config.lastSyncedGeneration,
16312
+ generation: current ? {
16313
+ id: current.id,
16314
+ goal: current.goal,
16315
+ stage: current.stage,
16316
+ genomeVersion: current.genomeVersion,
16317
+ startedAt: current.startedAt,
16318
+ type: current.type,
16319
+ parents: current.parents,
16320
+ genomeHash: current.genomeHash
16321
+ } : null,
16322
+ totalGenerations: totalCompleted,
16323
+ integrity: { errors: integrityResult.errors.length, warnings: integrityResult.warnings.length }
16324
+ };
16098
16325
  }
16099
16326
 
16100
16327
  // src/cli/commands/fix.ts
16328
+ init_paths();
16329
+ init_lifecycle();
16330
+ init_fs();
16331
+ var import_yaml7 = __toESM(require_dist(), 1);
16332
+ import { mkdir as mkdir8, stat as stat4, copyFile } from "fs/promises";
16333
+ import { join as join14 } from "path";
16101
16334
  async function checkProject(projectRoot) {
16102
16335
  const paths = new ReapPaths(projectRoot);
16103
- return checkIntegrity(paths);
16336
+ const [structureResult, userLevelResult] = await Promise.all([
16337
+ checkIntegrity(paths),
16338
+ checkUserLevelArtifacts(projectRoot)
16339
+ ]);
16340
+ return {
16341
+ errors: [...structureResult.errors, ...userLevelResult.errors],
16342
+ warnings: [...structureResult.warnings, ...userLevelResult.warnings]
16343
+ };
16104
16344
  }
16105
16345
  async function dirExists(path) {
16106
16346
  try {
@@ -16136,7 +16376,7 @@ async function fixProject(projectRoot) {
16136
16376
  ];
16137
16377
  for (const gf of genomeFiles) {
16138
16378
  if (!await fileExists(gf.path)) {
16139
- const templateSrc = join13(ReapPaths.packageGenomeDir, gf.name);
16379
+ const templateSrc = join14(ReapPaths.packageGenomeDir, gf.name);
16140
16380
  if (await fileExists(templateSrc)) {
16141
16381
  await copyFile(templateSrc, gf.path);
16142
16382
  fixed.push(`Restored missing genome/${gf.name} from template`);
@@ -16152,7 +16392,7 @@ async function fixProject(projectRoot) {
16152
16392
  if (currentContent !== null) {
16153
16393
  if (currentContent.trim()) {
16154
16394
  try {
16155
- const state = import_yaml8.default.parse(currentContent);
16395
+ const state = import_yaml7.default.parse(currentContent);
16156
16396
  if (!state.stage || !LifeCycle.isValid(state.stage)) {
16157
16397
  issues.push(`Invalid stage "${state.stage}" in current.yml. Valid stages: ${LifeCycle.stages().join(", ")}. Manual correction required.`);
16158
16398
  }
@@ -16177,8 +16417,8 @@ async function fixProject(projectRoot) {
16177
16417
  init_fs();
16178
16418
  init_paths();
16179
16419
  init_config();
16180
- import { rm as rm3, readdir as readdir12, unlink as unlink5 } from "fs/promises";
16181
- import { join as join14 } from "path";
16420
+ import { rm as rm4 } from "fs/promises";
16421
+ import { join as join15 } from "path";
16182
16422
  async function getProjectName(projectRoot) {
16183
16423
  try {
16184
16424
  const paths = new ReapPaths(projectRoot);
@@ -16191,77 +16431,26 @@ async function getProjectName(projectRoot) {
16191
16431
  async function destroyProject(projectRoot) {
16192
16432
  const removed = [];
16193
16433
  const skipped = [];
16194
- const reapDir = join14(projectRoot, ".reap");
16434
+ const reapDir = join15(projectRoot, ".reap");
16195
16435
  if (await fileExists(reapDir)) {
16196
- await rm3(reapDir, { recursive: true, force: true });
16436
+ await rm4(reapDir, { recursive: true, force: true });
16197
16437
  removed.push(".reap/");
16198
16438
  } else {
16199
16439
  skipped.push(".reap/ (not found)");
16200
16440
  }
16201
- const claudeCommandsDir = join14(projectRoot, ".claude", "commands");
16202
- await removeGlobFiles(claudeCommandsDir, "reap.", removed, skipped, ".claude/commands/");
16203
- const claudeSkillsDir = join14(projectRoot, ".claude", "skills");
16204
- await removeGlobDirs(claudeSkillsDir, "reap.", removed, skipped, ".claude/skills/");
16205
- await cleanClaudeMd(projectRoot, removed, skipped);
16206
- await cleanGitignore(projectRoot, removed, skipped);
16207
- return { removed, skipped };
16208
- }
16209
- async function removeGlobFiles(dir, prefix, removed, skipped, displayPrefix) {
16210
- try {
16211
- const files = await readdir12(dir);
16212
- const matched = files.filter((f) => f.startsWith(prefix));
16213
- if (matched.length === 0) {
16214
- skipped.push(`${displayPrefix}${prefix}* (none found)`);
16215
- return;
16216
- }
16217
- for (const file of matched) {
16218
- await unlink5(join14(dir, file));
16219
- removed.push(`${displayPrefix}${file}`);
16220
- }
16221
- } catch {
16222
- skipped.push(`${displayPrefix}${prefix}* (directory not found)`);
16223
- }
16224
- }
16225
- async function removeGlobDirs(dir, prefix, removed, skipped, displayPrefix) {
16226
- try {
16227
- const entries = await readdir12(dir);
16228
- const matched = entries.filter((e) => e.startsWith(prefix));
16229
- if (matched.length === 0) {
16230
- skipped.push(`${displayPrefix}${prefix}* (none found)`);
16231
- return;
16232
- }
16233
- for (const entry of matched) {
16234
- await rm3(join14(dir, entry), { recursive: true, force: true });
16235
- removed.push(`${displayPrefix}${entry}`);
16441
+ const adapters = AgentRegistry.allAdapters();
16442
+ for (const adapter of adapters) {
16443
+ if (typeof adapter.cleanupProjectFiles === "function") {
16444
+ const result = await adapter.cleanupProjectFiles(projectRoot);
16445
+ removed.push(...result.removed);
16446
+ skipped.push(...result.skipped);
16236
16447
  }
16237
- } catch {
16238
- skipped.push(`${displayPrefix}${prefix}* (directory not found)`);
16239
- }
16240
- }
16241
- async function cleanClaudeMd(projectRoot, removed, skipped) {
16242
- const claudeMdPath = join14(projectRoot, ".claude", "CLAUDE.md");
16243
- const content = await readTextFile(claudeMdPath);
16244
- if (content === null) {
16245
- skipped.push(".claude/CLAUDE.md (not found)");
16246
- return;
16247
- }
16248
- const marker = "# REAP Project";
16249
- if (!content.includes(marker)) {
16250
- skipped.push(".claude/CLAUDE.md (no REAP section)");
16251
- return;
16252
- }
16253
- const cleaned = content.replace(/# REAP Project[\s\S]*?(?=\n# |\s*$)/, "").trim();
16254
- if (cleaned.length === 0) {
16255
- await unlink5(claudeMdPath);
16256
- removed.push(".claude/CLAUDE.md (deleted, was REAP-only)");
16257
- } else {
16258
- await writeTextFile(claudeMdPath, cleaned + `
16259
- `);
16260
- removed.push(".claude/CLAUDE.md (REAP section removed)");
16261
16448
  }
16449
+ await cleanGitignore(projectRoot, removed, skipped);
16450
+ return { removed, skipped };
16262
16451
  }
16263
16452
  async function cleanGitignore(projectRoot, removed, skipped) {
16264
- const gitignorePath = join14(projectRoot, ".gitignore");
16453
+ const gitignorePath = join15(projectRoot, ".gitignore");
16265
16454
  const content = await readTextFile(gitignorePath);
16266
16455
  if (content === null) {
16267
16456
  skipped.push(".gitignore (not found)");
@@ -16297,8 +16486,8 @@ async function cleanGitignore(projectRoot, removed, skipped) {
16297
16486
  init_paths();
16298
16487
  init_fs();
16299
16488
  init_generation();
16300
- import { rm as rm4, readdir as readdir13, mkdir as mkdir9 } from "fs/promises";
16301
- import { join as join15 } from "path";
16489
+ import { rm as rm5, readdir as readdir13, mkdir as mkdir9 } from "fs/promises";
16490
+ import { join as join16 } from "path";
16302
16491
  async function hasActiveGeneration(projectRoot) {
16303
16492
  const paths = new ReapPaths(projectRoot);
16304
16493
  try {
@@ -16314,7 +16503,7 @@ async function cleanProject(projectRoot, options) {
16314
16503
  const actions = [];
16315
16504
  const warnings = [];
16316
16505
  if (options.lineage === "delete") {
16317
- await rm4(paths.lineage, { recursive: true, force: true });
16506
+ await rm5(paths.lineage, { recursive: true, force: true });
16318
16507
  await mkdir9(paths.lineage, { recursive: true });
16319
16508
  actions.push("Lineage: 전체 삭제됨");
16320
16509
  } else {
@@ -16324,7 +16513,7 @@ async function cleanProject(projectRoot, options) {
16324
16513
  if (options.hooks === "reset") {
16325
16514
  const hooksDir = paths.hooks;
16326
16515
  if (await fileExists(hooksDir)) {
16327
- await rm4(hooksDir, { recursive: true, force: true });
16516
+ await rm5(hooksDir, { recursive: true, force: true });
16328
16517
  await mkdir9(hooksDir, { recursive: true });
16329
16518
  actions.push("Hooks: 초기화됨");
16330
16519
  } else {
@@ -16343,7 +16532,7 @@ async function cleanProject(projectRoot, options) {
16343
16532
  const backlogDir = paths.backlog;
16344
16533
  if (options.backlog === "delete") {
16345
16534
  if (await fileExists(backlogDir)) {
16346
- await rm4(backlogDir, { recursive: true, force: true });
16535
+ await rm5(backlogDir, { recursive: true, force: true });
16347
16536
  await mkdir9(backlogDir, { recursive: true });
16348
16537
  actions.push("Backlog: 삭제됨");
16349
16538
  } else {
@@ -16384,9 +16573,9 @@ async function compressLineage(paths, actions) {
16384
16573
  ].join(`
16385
16574
  `);
16386
16575
  for (const dir of genDirs) {
16387
- await rm4(join15(lineageDir, dir), { recursive: true, force: true });
16576
+ await rm5(join16(lineageDir, dir), { recursive: true, force: true });
16388
16577
  }
16389
- await writeTextFile(join15(lineageDir, `${epochId}.md`), summary);
16578
+ await writeTextFile(join16(lineageDir, `${epochId}.md`), summary);
16390
16579
  actions.push(`Lineage: ${genDirs.length}개 세대를 ${epochId}로 압축`);
16391
16580
  }
16392
16581
  async function cleanLife(paths, actions) {
@@ -16406,8 +16595,8 @@ async function cleanLife(paths, actions) {
16406
16595
  for (const entry of entries) {
16407
16596
  if (entry === "backlog")
16408
16597
  continue;
16409
- const entryPath = join15(lifeDir, entry);
16410
- await rm4(entryPath, { recursive: true, force: true });
16598
+ const entryPath = join16(lifeDir, entry);
16599
+ await rm5(entryPath, { recursive: true, force: true });
16411
16600
  removedCount++;
16412
16601
  }
16413
16602
  actions.push(`Life: ${removedCount}개 파일/디렉토리 정리됨`);
@@ -16415,14 +16604,14 @@ async function cleanLife(paths, actions) {
16415
16604
  async function resetGenomeToTemplate(paths, actions) {
16416
16605
  const genomeFiles = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
16417
16606
  for (const file of genomeFiles) {
16418
- const templatePath = join15(ReapPaths.packageGenomeDir, file);
16419
- const destPath = join15(paths.genome, file);
16607
+ const templatePath = join16(ReapPaths.packageGenomeDir, file);
16608
+ const destPath = join16(paths.genome, file);
16420
16609
  try {
16421
16610
  const templateContent = await readTextFileOrThrow(templatePath);
16422
16611
  await writeTextFile(destPath, templateContent);
16423
16612
  } catch {}
16424
16613
  }
16425
- const envSummaryTemplate = join15(ReapPaths.packageTemplatesDir, "environment", "summary.md");
16614
+ const envSummaryTemplate = join16(ReapPaths.packageTemplatesDir, "environment", "summary.md");
16426
16615
  if (await fileExists(envSummaryTemplate)) {
16427
16616
  try {
16428
16617
  const content = await readTextFileOrThrow(envSummaryTemplate);
@@ -16438,8 +16627,8 @@ init_paths();
16438
16627
  init_fs();
16439
16628
  init_version();
16440
16629
  init_config();
16441
- import { join as join32 } from "path";
16442
- program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.2");
16630
+ import { join as join33 } from "path";
16631
+ program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.3");
16443
16632
  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) => {
16444
16633
  try {
16445
16634
  const cwd = process.cwd();
@@ -16447,9 +16636,9 @@ program.command("init").description("Initialize a new REAP project (Genesis)").a
16447
16636
  let mode = options.mode;
16448
16637
  const modeExplicit = process.argv.some((a) => a === "-m" || a === "--mode");
16449
16638
  if (!modeExplicit && mode === "greenfield") {
16450
- const { existsSync: existsSync3 } = __require("fs");
16639
+ const { existsSync: existsSync2 } = __require("fs");
16451
16640
  const signals = ["package.json", "go.mod", "Cargo.toml", "pom.xml", "pyproject.toml", "Makefile", "CMakeLists.txt"];
16452
- const hasExistingProject = signals.some((f) => existsSync3(__require("path").join(cwd, f)));
16641
+ const hasExistingProject = signals.some((f) => existsSync2(__require("path").join(cwd, f)));
16453
16642
  if (hasExistingProject) {
16454
16643
  mode = "adoption";
16455
16644
  console.log(`Existing project detected. Automatically using adoption mode.`);
@@ -16496,12 +16685,22 @@ program.command("status").description("Show current project and Generation statu
16496
16685
  const paths = new ReapPaths(cwd);
16497
16686
  const config = await ConfigManager.read(paths);
16498
16687
  const skipCheck = config.autoUpdate === false;
16499
- const installedVersion = "0.15.2";
16688
+ const installedVersion = "0.15.3";
16500
16689
  const versionLine = formatVersionLine(installedVersion, skipCheck);
16501
16690
  console.log(`${versionLine} | Project: ${status.project} (${status.entryMode})`);
16502
16691
  console.log(`Completed Generations: ${status.totalGenerations}`);
16503
16692
  const syncLabel = status.lastSyncedGeneration ? `synced (${status.lastSyncedGeneration})` : "never synced";
16504
16693
  console.log(`Genome Sync: ${syncLabel}`);
16694
+ if (status.integrity.errors > 0 || status.integrity.warnings > 0) {
16695
+ const parts = [];
16696
+ if (status.integrity.errors > 0)
16697
+ parts.push(`${status.integrity.errors} error${status.integrity.errors > 1 ? "s" : ""}`);
16698
+ if (status.integrity.warnings > 0)
16699
+ parts.push(`${status.integrity.warnings} warning${status.integrity.warnings > 1 ? "s" : ""}`);
16700
+ console.log(`Integrity: ${parts.join(", ")} (run 'reap fix --check' for details)`);
16701
+ } else {
16702
+ console.log(`Integrity: ✓ OK`);
16703
+ }
16505
16704
  if (status.generation) {
16506
16705
  console.log(`
16507
16706
  Active Generation: ${status.generation.id}`);
@@ -16583,6 +16782,21 @@ program.command("update").description("Upgrade REAP package and sync slash comma
16583
16782
  if (result.skipped.length > 0) {
16584
16783
  console.log(`Unchanged: ${result.skipped.length} files`);
16585
16784
  }
16785
+ try {
16786
+ const integrityResult = await checkProject(process.cwd());
16787
+ if (integrityResult.errors.length > 0 || integrityResult.warnings.length > 0) {
16788
+ const parts = [];
16789
+ if (integrityResult.errors.length > 0)
16790
+ parts.push(`${integrityResult.errors.length} error${integrityResult.errors.length > 1 ? "s" : ""}`);
16791
+ if (integrityResult.warnings.length > 0)
16792
+ parts.push(`${integrityResult.warnings.length} warning${integrityResult.warnings.length > 1 ? "s" : ""}`);
16793
+ console.log(`
16794
+ Integrity: ${parts.join(", ")} (run 'reap fix --check' for details)`);
16795
+ } else {
16796
+ console.log(`
16797
+ Integrity: ✓ OK`);
16798
+ }
16799
+ } catch {}
16586
16800
  } catch (e) {
16587
16801
  console.error(`Error: ${e.message}`);
16588
16802
  process.exit(1);
@@ -16596,10 +16810,10 @@ program.command("help").description("Show REAP commands, slash commands, and wor
16596
16810
  if (l === "korean" || l === "ko")
16597
16811
  lang = "ko";
16598
16812
  }
16599
- const helpDir = join32(ReapPaths.packageTemplatesDir, "help");
16600
- let helpText = await readTextFile(join32(helpDir, `${lang}.txt`));
16813
+ const helpDir = join33(ReapPaths.packageTemplatesDir, "help");
16814
+ let helpText = await readTextFile(join33(helpDir, `${lang}.txt`));
16601
16815
  if (!helpText)
16602
- helpText = await readTextFile(join32(helpDir, "en.txt"));
16816
+ helpText = await readTextFile(join33(helpDir, "en.txt"));
16603
16817
  if (!helpText) {
16604
16818
  console.log("Help file not found. Run 'reap update' to install templates.");
16605
16819
  return;