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