@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 +1431 -1217
- package/dist/templates/hooks/session-start.cjs +23 -1
- package/package.json +1 -1
- package/scripts/postinstall.cjs +1 -0
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
|
|
9077
|
-
import { readdir as
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
9164
|
-
const pyproject = await readTextFile(
|
|
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(
|
|
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(
|
|
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(
|
|
9188
|
+
scan.hasDocker = await fileExists(join6(projectRoot, "Dockerfile")) || await fileExists(join6(projectRoot, "docker-compose.yml"));
|
|
9189
9189
|
try {
|
|
9190
|
-
const entries = await
|
|
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(
|
|
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(
|
|
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(
|
|
9332
|
+
await writeTextFile2(join6(genomePath, "constraints.md"), generateConstraints(scan));
|
|
9333
9333
|
log("Generating conventions.md...");
|
|
9334
|
-
await writeTextFile2(
|
|
9334
|
+
await writeTextFile2(join6(genomePath, "conventions.md"), generateConventions(scan));
|
|
9335
9335
|
log("Generating principles.md...");
|
|
9336
|
-
await writeTextFile2(
|
|
9336
|
+
await writeTextFile2(join6(genomePath, "principles.md"), generatePrinciples(scan));
|
|
9337
9337
|
log("Generating source-map.md...");
|
|
9338
|
-
await writeTextFile2(
|
|
9339
|
-
const { mkdir:
|
|
9340
|
-
const domainDir =
|
|
9341
|
-
await
|
|
9342
|
-
const domainReadme =
|
|
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
|
|
9486
|
-
import { join as
|
|
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
|
|
9521
|
+
const entries = await readdir7(dirPath, { withFileTypes: true });
|
|
9522
9522
|
for (const entry of entries) {
|
|
9523
|
-
const fullPath =
|
|
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(
|
|
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
|
|
9552
|
+
const items = await readdir7(paths.lineage, { withFileTypes: true });
|
|
9553
9553
|
for (const item of items) {
|
|
9554
|
-
const fullPath =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
9846
|
+
const outPath = join8(paths.lineage, `${genId}.md`);
|
|
9847
9847
|
await writeTextFile(outPath, compressed);
|
|
9848
|
-
await
|
|
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:
|
|
9868
|
-
meta: await readFileMeta(
|
|
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
|
|
9885
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
9932
|
-
const seq = parseGenSeq(
|
|
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
|
|
9969
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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
|
|
9991
|
-
const pathA =
|
|
9992
|
-
const pathB =
|
|
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 =
|
|
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
|
|
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(
|
|
10121
|
-
const lifeEntries = await
|
|
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 =
|
|
10125
|
-
const destPath =
|
|
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
|
|
10158
|
+
await unlink4(srcPath);
|
|
10132
10159
|
}
|
|
10133
10160
|
}
|
|
10134
|
-
const backlogDir =
|
|
10135
|
-
await
|
|
10161
|
+
const backlogDir = join10(genDir, "backlog");
|
|
10162
|
+
await mkdir6(backlogDir, { recursive: true });
|
|
10136
10163
|
try {
|
|
10137
|
-
const backlogEntries = await
|
|
10164
|
+
const backlogEntries = await readdir9(this.paths.backlog);
|
|
10138
10165
|
for (const entry of backlogEntries) {
|
|
10139
|
-
const content = await readTextFile(
|
|
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(
|
|
10170
|
+
await writeTextFile(join10(backlogDir, entry), content);
|
|
10144
10171
|
if (isConsumed) {
|
|
10145
|
-
await
|
|
10172
|
+
await unlink4(join10(this.paths.backlog, entry));
|
|
10146
10173
|
}
|
|
10147
10174
|
}
|
|
10148
10175
|
} catch {}
|
|
10149
10176
|
try {
|
|
10150
|
-
const mutEntries = await
|
|
10177
|
+
const mutEntries = await readdir9(this.paths.mutations);
|
|
10151
10178
|
if (mutEntries.length > 0) {
|
|
10152
|
-
const mutDir =
|
|
10153
|
-
await
|
|
10179
|
+
const mutDir = join10(genDir, "mutations");
|
|
10180
|
+
await mkdir6(mutDir, { recursive: true });
|
|
10154
10181
|
for (const entry of mutEntries) {
|
|
10155
|
-
await rename(
|
|
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
|
|
10228
|
+
import { execSync as execSync4 } from "child_process";
|
|
10199
10229
|
function checkLatestVersion() {
|
|
10200
10230
|
try {
|
|
10201
|
-
const result =
|
|
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
|
|
10327
|
-
import { execSync as
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
10493
|
+
var import_yaml8;
|
|
10464
10494
|
var init_hook_engine = __esm(() => {
|
|
10465
10495
|
init_fs();
|
|
10466
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
-
${
|
|
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 =
|
|
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
|
-
${
|
|
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:
|
|
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
|
|
10778
|
+
var import_yaml9;
|
|
10654
10779
|
var init_backlog = __esm(() => {
|
|
10655
10780
|
init_fs();
|
|
10656
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
10725
|
-
const templatePath =
|
|
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
|
|
10886
|
+
import { execSync as execSync6 } from "child_process";
|
|
10762
10887
|
function checkSubmodules(projectRoot) {
|
|
10763
10888
|
try {
|
|
10764
|
-
const output =
|
|
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
|
|
10911
|
-
import { execSync as
|
|
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 =
|
|
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 =
|
|
11004
|
-
const templatePath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
11266
|
+
prevCompletion = await readTextFile(join23(paths.lineage, lastGen, "05-completion.md"));
|
|
11298
11267
|
if (!prevCompletion) {
|
|
11299
|
-
const compressed = await readTextFile(
|
|
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 =
|
|
11317
|
-
const templatePath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11400
|
-
state
|
|
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
|
|
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 =
|
|
11462
|
-
const templatePath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11538
|
-
state
|
|
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
|
|
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 =
|
|
11597
|
-
const templatePath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11672
|
-
state
|
|
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
|
|
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 =
|
|
11732
|
-
const templatePath =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11817
|
-
state
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
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 =
|
|
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,
|
|
12679
|
-
import { join as
|
|
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 =
|
|
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,
|
|
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,
|
|
12728
|
+
await writeTextFile(this.paths.currentYml, import_yaml10.default.stringify(state));
|
|
12768
12729
|
return { state, report };
|
|
12769
12730
|
}
|
|
12770
|
-
async resolveLatestGenId(
|
|
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 =
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13509
|
-
state
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
13805
|
+
const dirPath = join31(paths.lineage, genDir);
|
|
13834
13806
|
const [objective, planning, implementation, completion] = await Promise.all([
|
|
13835
|
-
readTextFile(
|
|
13836
|
-
readTextFile(
|
|
13837
|
-
readTextFile(
|
|
13838
|
-
readTextFile(
|
|
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(
|
|
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
|
-
|
|
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
|
|
14032
|
-
if (!allMetas.find((m) => m.id ===
|
|
14033
|
-
allMetas.push(
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
14483
|
-
import { join as
|
|
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/
|
|
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
|
-
|
|
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
|
|
14974
|
+
return join4(homedir4(), ".config", "opencode");
|
|
14753
14975
|
}
|
|
14754
14976
|
get commandsDir() {
|
|
14755
|
-
return
|
|
14977
|
+
return join4(this.configDir, "commands");
|
|
14756
14978
|
}
|
|
14757
14979
|
get pluginsDir() {
|
|
14758
|
-
return
|
|
14980
|
+
return join4(this.configDir, "plugins");
|
|
14759
14981
|
}
|
|
14760
14982
|
get settingsPath() {
|
|
14761
|
-
return
|
|
14983
|
+
return join4(this.configDir, "opencode.json");
|
|
14762
14984
|
}
|
|
14763
14985
|
get tuiConfigPath() {
|
|
14764
|
-
return
|
|
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
|
|
15001
|
+
await mkdir3(ReapPaths.userReapCommands, { recursive: true });
|
|
14780
15002
|
for (const cmd of commandNames) {
|
|
14781
|
-
const src =
|
|
14782
|
-
const dest =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
15061
|
+
const templatePath = join4(ReapPaths.packageHooksDir, "opencode-session-start.js");
|
|
14840
15062
|
return readTextFileOrThrow(templatePath);
|
|
14841
15063
|
}
|
|
14842
15064
|
async ensureDefaultVisibility() {
|
|
14843
|
-
const kvPath =
|
|
14844
|
-
await
|
|
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
|
|
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
|
|
14938
|
-
import { join as
|
|
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 =
|
|
14941
|
-
const reapCmdFiles = (await
|
|
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(
|
|
15167
|
+
const src = await readTextFileOrThrow(join5(ReapPaths.userReapCommands, file));
|
|
14945
15168
|
const name = file.replace(/\.md$/, "");
|
|
14946
|
-
const skillDir =
|
|
14947
|
-
const skillFile =
|
|
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
|
|
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 =
|
|
15011
|
-
const presetExists = await fileExists(
|
|
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
|
|
15018
|
-
await
|
|
15019
|
-
await
|
|
15020
|
-
await
|
|
15021
|
-
await
|
|
15022
|
-
await
|
|
15023
|
-
await
|
|
15024
|
-
await
|
|
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.
|
|
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 ?
|
|
15273
|
+
const genomeSourceDir = preset ? join7(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
|
|
15051
15274
|
for (const file of genomeTemplates) {
|
|
15052
|
-
const src =
|
|
15053
|
-
const dest =
|
|
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
|
|
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 =
|
|
15066
|
-
const dest =
|
|
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 =
|
|
15070
|
-
const domainGuideDest =
|
|
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 =
|
|
15073
|
-
await
|
|
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 =
|
|
15298
|
+
const mergeSourceDir = join7(ReapPaths.packageArtifactsDir, "merge");
|
|
15076
15299
|
for (const file of mergeArtifactFiles) {
|
|
15077
|
-
const src =
|
|
15078
|
-
const dest =
|
|
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 =
|
|
15083
|
-
const conditionsDestDir =
|
|
15084
|
-
await
|
|
15085
|
-
const conditionFiles = await
|
|
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 =
|
|
15090
|
-
const dest =
|
|
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 =
|
|
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 =
|
|
15102
|
-
const dest =
|
|
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.
|
|
15115
|
-
const mdResult = await adapter.
|
|
15116
|
-
log(` .
|
|
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
|
|
15138
|
-
import { join as
|
|
15139
|
-
import { execSync as
|
|
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
|
|
15166
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
15232
|
-
completedAt:
|
|
15470
|
+
startedAt: dates.startedAt,
|
|
15471
|
+
completedAt: dates.completedAt
|
|
15233
15472
|
};
|
|
15234
|
-
await writeTextFile(
|
|
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(
|
|
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/
|
|
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 {
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
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
|
|
15438
|
-
const
|
|
15439
|
-
const
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
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
|
|
15445
|
-
if (!
|
|
15446
|
-
|
|
15447
|
-
|
|
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
|
-
|
|
15644
|
+
errors.push(`${dir.name} directory missing`);
|
|
15453
15645
|
}
|
|
15454
15646
|
}
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
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 (
|
|
15471
|
-
|
|
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 (!
|
|
15474
|
-
|
|
15668
|
+
if (!config.entryMode || typeof config.entryMode !== "string") {
|
|
15669
|
+
errors.push("config.yml: missing or invalid 'entryMode' field (string required)");
|
|
15475
15670
|
} else {
|
|
15476
|
-
|
|
15477
|
-
|
|
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 (
|
|
15481
|
-
|
|
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 (
|
|
15484
|
-
|
|
15682
|
+
if (config.autoUpdate !== undefined && typeof config.autoUpdate !== "boolean") {
|
|
15683
|
+
warnings.push("config.yml: 'autoUpdate' should be boolean");
|
|
15485
15684
|
}
|
|
15486
|
-
if (
|
|
15487
|
-
|
|
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
|
-
|
|
15493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15698
|
+
errors.push("current.yml is not valid YAML");
|
|
15699
|
+
return null;
|
|
15507
15700
|
}
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
const
|
|
15513
|
-
const
|
|
15514
|
-
|
|
15515
|
-
|
|
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
|
-
|
|
15532
|
-
|
|
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
|
-
|
|
15550
|
-
|
|
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
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
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
|
-
|
|
15572
|
-
|
|
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
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
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
|
-
|
|
15598
|
-
|
|
15599
|
-
|
|
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
|
-
|
|
15604
|
-
|
|
15605
|
-
|
|
15606
|
-
|
|
15607
|
-
|
|
15608
|
-
|
|
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
|
-
|
|
15612
|
-
|
|
15613
|
-
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
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
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
|
|
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
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
}
|
|
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
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
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
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
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
|
-
|
|
15795
|
-
const
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
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 (
|
|
15815
|
-
|
|
15816
|
-
|
|
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 (
|
|
15823
|
-
|
|
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 (
|
|
15826
|
-
|
|
15840
|
+
if (!Array.isArray(meta.parents)) {
|
|
15841
|
+
errors.push(`${location}: missing or invalid 'parents' field (array required)`);
|
|
15827
15842
|
}
|
|
15828
|
-
if (
|
|
15829
|
-
|
|
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 (
|
|
15832
|
-
warnings.push(
|
|
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
|
|
15836
|
-
const
|
|
15837
|
-
|
|
15838
|
-
|
|
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
|
-
|
|
15878
|
+
}
|
|
15879
|
+
async function checkBacklog(paths, errors, warnings) {
|
|
15880
|
+
let entries;
|
|
15841
15881
|
try {
|
|
15842
|
-
|
|
15882
|
+
entries = await readdir11(paths.backlog);
|
|
15843
15883
|
} catch {
|
|
15844
|
-
|
|
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
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
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
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
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
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
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
|
-
|
|
15878
|
-
|
|
15879
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
15893
|
-
|
|
15894
|
-
|
|
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
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
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
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
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
|
|
15970
|
-
const
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
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
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
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
|
-
|
|
15982
|
-
if (
|
|
15983
|
-
|
|
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
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
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
|
-
|
|
15995
|
-
|
|
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
|
-
|
|
15999
|
-
const
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
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
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
`)
|
|
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
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
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
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
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
|
-
|
|
16033
|
-
if (!
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
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
|
-
|
|
16051
|
-
|
|
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
|
-
|
|
16056
|
-
|
|
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
|
-
|
|
16061
|
-
|
|
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
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
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
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
16181
|
-
import { join as
|
|
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 =
|
|
16434
|
+
const reapDir = join15(projectRoot, ".reap");
|
|
16195
16435
|
if (await fileExists(reapDir)) {
|
|
16196
|
-
await
|
|
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
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
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 =
|
|
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
|
|
16301
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
16576
|
+
await rm5(join16(lineageDir, dir), { recursive: true, force: true });
|
|
16388
16577
|
}
|
|
16389
|
-
await writeTextFile(
|
|
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 =
|
|
16410
|
-
await
|
|
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 =
|
|
16419
|
-
const destPath =
|
|
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 =
|
|
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
|
|
16442
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.
|
|
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:
|
|
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) =>
|
|
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.
|
|
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 =
|
|
16600
|
-
let helpText = await readTextFile(
|
|
16813
|
+
const helpDir = join33(ReapPaths.packageTemplatesDir, "help");
|
|
16814
|
+
let helpText = await readTextFile(join33(helpDir, `${lang}.txt`));
|
|
16601
16815
|
if (!helpText)
|
|
16602
|
-
helpText = await readTextFile(
|
|
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;
|