@c-d-cc/reap 0.14.0 → 0.15.1
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 +18 -13
- package/README.ko.md +18 -13
- package/README.md +19 -14
- package/README.zh-CN.md +18 -13
- package/dist/cli.js +1776 -481
- package/dist/templates/commands/reap.evolve.recovery.md +5 -0
- package/dist/templates/commands/reap.update-genome.md +5 -0
- package/dist/templates/help/en.txt +2 -0
- package/dist/templates/help/ko.txt +2 -0
- package/dist/templates/hooks/genome-loader.cjs +50 -7
- package/dist/templates/hooks/onLifeCompleted.integrity-check.sh +14 -0
- package/dist/templates/hooks/reap-guide.md +15 -7
- 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,16 +9965,17 @@ 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
|
|
9954
|
-
function
|
|
9968
|
+
import { readdir as readdir8, mkdir as mkdir5, rename, unlink as unlink3 } from "fs/promises";
|
|
9969
|
+
import { join as join9 } from "path";
|
|
9970
|
+
function generateToken(genId, stage, phase) {
|
|
9955
9971
|
const nonce = randomBytes(16).toString("hex");
|
|
9956
|
-
const
|
|
9972
|
+
const input = phase ? `${nonce}${genId}${stage}:${phase}` : `${nonce}${genId}${stage}`;
|
|
9973
|
+
const hash = createHash("sha256").update(input).digest("hex");
|
|
9957
9974
|
return { nonce, hash };
|
|
9958
9975
|
}
|
|
9959
|
-
function
|
|
9960
|
-
const
|
|
9961
|
-
return
|
|
9976
|
+
function verifyToken(token, genId, stage, expectedHash, phase) {
|
|
9977
|
+
const input = phase ? `${token}${genId}${stage}:${phase}` : `${token}${genId}${stage}`;
|
|
9978
|
+
return createHash("sha256").update(input).digest("hex") === expectedHash;
|
|
9962
9979
|
}
|
|
9963
9980
|
function generateGenHash(parents, goal, genomeHash, machineId, startedAt) {
|
|
9964
9981
|
const input = JSON.stringify({ parents, goal, genomeHash, machineId, startedAt });
|
|
@@ -9970,13 +9987,13 @@ function getMachineId() {
|
|
|
9970
9987
|
async function computeGenomeHash(genomePath) {
|
|
9971
9988
|
const hash = createHash("sha256");
|
|
9972
9989
|
try {
|
|
9973
|
-
const entries = (await
|
|
9974
|
-
const pathA =
|
|
9975
|
-
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);
|
|
9976
9993
|
return pathA.localeCompare(pathB);
|
|
9977
9994
|
});
|
|
9978
9995
|
for (const entry of entries) {
|
|
9979
|
-
const filePath =
|
|
9996
|
+
const filePath = join9(e2path(entry), entry.name);
|
|
9980
9997
|
const content = await readTextFile(filePath);
|
|
9981
9998
|
if (content !== null) {
|
|
9982
9999
|
hash.update(filePath.replace(genomePath, ""));
|
|
@@ -10038,6 +10055,32 @@ class GenerationManager {
|
|
|
10038
10055
|
await writeTextFile(this.paths.currentYml, CURRENT_YML_HEADER + import_yaml4.default.stringify(state));
|
|
10039
10056
|
return state;
|
|
10040
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
|
+
}
|
|
10041
10084
|
async advance() {
|
|
10042
10085
|
const state = await this.current();
|
|
10043
10086
|
if (!state)
|
|
@@ -10063,7 +10106,7 @@ class GenerationManager {
|
|
|
10063
10106
|
const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
|
|
10064
10107
|
const genDirName = `${state.id}-${goalSlug}`;
|
|
10065
10108
|
const genDir = this.paths.generationDir(genDirName);
|
|
10066
|
-
await
|
|
10109
|
+
await mkdir5(genDir, { recursive: true });
|
|
10067
10110
|
const meta = {
|
|
10068
10111
|
id: state.id,
|
|
10069
10112
|
type: state.type ?? "normal",
|
|
@@ -10071,14 +10114,15 @@ class GenerationManager {
|
|
|
10071
10114
|
goal: state.goal,
|
|
10072
10115
|
genomeHash: state.genomeHash ?? "unknown",
|
|
10073
10116
|
startedAt: state.startedAt,
|
|
10074
|
-
completedAt: now
|
|
10117
|
+
completedAt: now,
|
|
10118
|
+
...state.type === "recovery" && state.recovers ? { recovers: state.recovers } : {}
|
|
10075
10119
|
};
|
|
10076
|
-
await writeTextFile(
|
|
10077
|
-
const lifeEntries = await
|
|
10120
|
+
await writeTextFile(join9(genDir, "meta.yml"), import_yaml4.default.stringify(meta));
|
|
10121
|
+
const lifeEntries = await readdir8(this.paths.life);
|
|
10078
10122
|
for (const entry of lifeEntries) {
|
|
10079
10123
|
if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
|
|
10080
|
-
const srcPath =
|
|
10081
|
-
const destPath =
|
|
10124
|
+
const srcPath = join9(this.paths.life, entry);
|
|
10125
|
+
const destPath = join9(genDir, entry);
|
|
10082
10126
|
let content = await readTextFile(srcPath);
|
|
10083
10127
|
if (content && content.startsWith("# REAP MANAGED")) {
|
|
10084
10128
|
content = content.replace(/^# REAP MANAGED[^\n]*\n/, "");
|
|
@@ -10087,28 +10131,28 @@ class GenerationManager {
|
|
|
10087
10131
|
await unlink3(srcPath);
|
|
10088
10132
|
}
|
|
10089
10133
|
}
|
|
10090
|
-
const backlogDir =
|
|
10091
|
-
await
|
|
10134
|
+
const backlogDir = join9(genDir, "backlog");
|
|
10135
|
+
await mkdir5(backlogDir, { recursive: true });
|
|
10092
10136
|
try {
|
|
10093
|
-
const backlogEntries = await
|
|
10137
|
+
const backlogEntries = await readdir8(this.paths.backlog);
|
|
10094
10138
|
for (const entry of backlogEntries) {
|
|
10095
|
-
const content = await readTextFile(
|
|
10139
|
+
const content = await readTextFile(join9(this.paths.backlog, entry));
|
|
10096
10140
|
if (!content)
|
|
10097
10141
|
continue;
|
|
10098
10142
|
const isConsumed = /status:\s*consumed/i.test(content) || /consumed:\s*true/i.test(content);
|
|
10099
|
-
await writeTextFile(
|
|
10143
|
+
await writeTextFile(join9(backlogDir, entry), content);
|
|
10100
10144
|
if (isConsumed) {
|
|
10101
|
-
await unlink3(
|
|
10145
|
+
await unlink3(join9(this.paths.backlog, entry));
|
|
10102
10146
|
}
|
|
10103
10147
|
}
|
|
10104
10148
|
} catch {}
|
|
10105
10149
|
try {
|
|
10106
|
-
const mutEntries = await
|
|
10150
|
+
const mutEntries = await readdir8(this.paths.mutations);
|
|
10107
10151
|
if (mutEntries.length > 0) {
|
|
10108
|
-
const mutDir =
|
|
10109
|
-
await
|
|
10152
|
+
const mutDir = join9(genDir, "mutations");
|
|
10153
|
+
await mkdir5(mutDir, { recursive: true });
|
|
10110
10154
|
for (const entry of mutEntries) {
|
|
10111
|
-
await rename(
|
|
10155
|
+
await rename(join9(this.paths.mutations, entry), join9(mutDir, entry));
|
|
10112
10156
|
}
|
|
10113
10157
|
}
|
|
10114
10158
|
} catch {}
|
|
@@ -10197,6 +10241,40 @@ function emitError(command, message, details) {
|
|
|
10197
10241
|
process.exit(1);
|
|
10198
10242
|
}
|
|
10199
10243
|
|
|
10244
|
+
// src/cli/commands/run/next.ts
|
|
10245
|
+
var exports_next = {};
|
|
10246
|
+
__export(exports_next, {
|
|
10247
|
+
execute: () => execute
|
|
10248
|
+
});
|
|
10249
|
+
async function execute(paths, _phase) {
|
|
10250
|
+
const gm = new GenerationManager(paths);
|
|
10251
|
+
const state = await gm.current();
|
|
10252
|
+
if (!state) {
|
|
10253
|
+
emitError("next", "No active Generation. Run /reap.start first.");
|
|
10254
|
+
}
|
|
10255
|
+
const isMerge = state.type === "merge";
|
|
10256
|
+
if (state.lastNonce) {
|
|
10257
|
+
const nextCommand = isMerge ? `reap run merge-${state.stage}` : `reap run ${state.stage}`;
|
|
10258
|
+
emitOutput({
|
|
10259
|
+
status: "ok",
|
|
10260
|
+
command: "next",
|
|
10261
|
+
phase: "done",
|
|
10262
|
+
completed: ["gate", "auto-transition-detected"],
|
|
10263
|
+
context: {
|
|
10264
|
+
generationId: state.id,
|
|
10265
|
+
stage: state.stage,
|
|
10266
|
+
type: state.type
|
|
10267
|
+
},
|
|
10268
|
+
message: `Stage already advanced to ${state.stage} by auto-transition. Run: ${nextCommand}`,
|
|
10269
|
+
nextCommand
|
|
10270
|
+
});
|
|
10271
|
+
}
|
|
10272
|
+
emitError("next", `Stage transition not available. The current stage '${state.stage}' has not been completed yet. Run the stage command with --phase complete first, which will auto-advance to the next stage.`);
|
|
10273
|
+
}
|
|
10274
|
+
var init_next = __esm(() => {
|
|
10275
|
+
init_generation();
|
|
10276
|
+
});
|
|
10277
|
+
|
|
10200
10278
|
// src/core/merge-lifecycle.ts
|
|
10201
10279
|
class MergeLifeCycle {
|
|
10202
10280
|
static next(stage) {
|
|
@@ -10244,14 +10322,14 @@ var init_merge_lifecycle = __esm(() => {
|
|
|
10244
10322
|
});
|
|
10245
10323
|
|
|
10246
10324
|
// src/core/hook-engine.ts
|
|
10247
|
-
import { readdir as
|
|
10248
|
-
import { join as
|
|
10325
|
+
import { readdir as readdir14 } from "fs/promises";
|
|
10326
|
+
import { join as join16 } from "path";
|
|
10249
10327
|
import { execSync as execSync4 } from "child_process";
|
|
10250
10328
|
async function executeHooks(hooksDir, event, projectRoot) {
|
|
10251
10329
|
const hooks = await scanHooks(hooksDir, event);
|
|
10252
10330
|
if (hooks.length === 0)
|
|
10253
10331
|
return [];
|
|
10254
|
-
const conditionsDir =
|
|
10332
|
+
const conditionsDir = join16(hooksDir, "conditions");
|
|
10255
10333
|
const results = [];
|
|
10256
10334
|
for (const hook of hooks) {
|
|
10257
10335
|
const conditionMet = await evaluateCondition(conditionsDir, hook.condition, projectRoot);
|
|
@@ -10276,7 +10354,7 @@ async function executeHooks(hooksDir, event, projectRoot) {
|
|
|
10276
10354
|
async function scanHooks(hooksDir, event) {
|
|
10277
10355
|
let entries;
|
|
10278
10356
|
try {
|
|
10279
|
-
entries = await
|
|
10357
|
+
entries = await readdir14(hooksDir);
|
|
10280
10358
|
} catch {
|
|
10281
10359
|
return [];
|
|
10282
10360
|
}
|
|
@@ -10286,7 +10364,7 @@ async function scanHooks(hooksDir, event) {
|
|
|
10286
10364
|
const match = filename.match(pattern);
|
|
10287
10365
|
if (!match)
|
|
10288
10366
|
continue;
|
|
10289
|
-
const meta = await parseHookMeta(
|
|
10367
|
+
const meta = await parseHookMeta(join16(hooksDir, filename), match[2]);
|
|
10290
10368
|
hooks.push({
|
|
10291
10369
|
filename,
|
|
10292
10370
|
name: match[1],
|
|
@@ -10307,7 +10385,7 @@ async function parseHookMeta(filePath, ext) {
|
|
|
10307
10385
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
10308
10386
|
if (fmMatch) {
|
|
10309
10387
|
try {
|
|
10310
|
-
const fm =
|
|
10388
|
+
const fm = import_yaml9.default.parse(fmMatch[1]) ?? {};
|
|
10311
10389
|
return {
|
|
10312
10390
|
condition: String(fm.condition ?? "always"),
|
|
10313
10391
|
order: Number(fm.order ?? 50)
|
|
@@ -10333,7 +10411,7 @@ async function parseHookMeta(filePath, ext) {
|
|
|
10333
10411
|
async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
|
|
10334
10412
|
if (conditionName === "always")
|
|
10335
10413
|
return { met: true };
|
|
10336
|
-
const scriptPath =
|
|
10414
|
+
const scriptPath = join16(conditionsDir, `${conditionName}.sh`);
|
|
10337
10415
|
if (!await fileExists(scriptPath)) {
|
|
10338
10416
|
return { met: false, reason: `condition script not found: ${conditionName}.sh` };
|
|
10339
10417
|
}
|
|
@@ -10346,7 +10424,7 @@ async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
|
|
|
10346
10424
|
}
|
|
10347
10425
|
async function executeShHook(hook, event, projectRoot, hooksDir) {
|
|
10348
10426
|
try {
|
|
10349
|
-
const stdout = execSync4(`bash "${
|
|
10427
|
+
const stdout = execSync4(`bash "${join16(hooksDir, hook.filename)}"`, {
|
|
10350
10428
|
cwd: projectRoot,
|
|
10351
10429
|
timeout: 60000,
|
|
10352
10430
|
stdio: "pipe"
|
|
@@ -10372,7 +10450,7 @@ async function executeShHook(hook, event, projectRoot, hooksDir) {
|
|
|
10372
10450
|
}
|
|
10373
10451
|
}
|
|
10374
10452
|
async function executeMdHook(hook, event, hooksDir) {
|
|
10375
|
-
const content = await readTextFile(
|
|
10453
|
+
const content = await readTextFile(join16(hooksDir, hook.filename));
|
|
10376
10454
|
const body = content?.replace(/^---\n[\s\S]*?\n---\n?/, "").trim() ?? "";
|
|
10377
10455
|
return {
|
|
10378
10456
|
name: hook.name,
|
|
@@ -10382,115 +10460,10 @@ async function executeMdHook(hook, event, hooksDir) {
|
|
|
10382
10460
|
content: body
|
|
10383
10461
|
};
|
|
10384
10462
|
}
|
|
10385
|
-
var
|
|
10463
|
+
var import_yaml9;
|
|
10386
10464
|
var init_hook_engine = __esm(() => {
|
|
10387
10465
|
init_fs();
|
|
10388
|
-
|
|
10389
|
-
});
|
|
10390
|
-
|
|
10391
|
-
// src/cli/commands/run/next.ts
|
|
10392
|
-
var exports_next = {};
|
|
10393
|
-
__export(exports_next, {
|
|
10394
|
-
execute: () => execute
|
|
10395
|
-
});
|
|
10396
|
-
import { join as join13 } from "path";
|
|
10397
|
-
async function execute(paths, _phase) {
|
|
10398
|
-
const gm = new GenerationManager(paths);
|
|
10399
|
-
const state = await gm.current();
|
|
10400
|
-
if (!state) {
|
|
10401
|
-
emitError("next", "No active Generation. Run /reap.start first.");
|
|
10402
|
-
}
|
|
10403
|
-
if (state.expectedTokenHash) {
|
|
10404
|
-
const args = process.argv.slice(2);
|
|
10405
|
-
const nonce = args.find((a) => !a.startsWith("-") && a !== "run" && a !== "next");
|
|
10406
|
-
if (!nonce) {
|
|
10407
|
-
emitError("next", `Stage transition blocked: no token provided. The stage command outputs a nonce that must be passed to /reap.next. Example: /reap.next <nonce>. This ensures the stage command was actually executed — you cannot skip stages.`);
|
|
10408
|
-
}
|
|
10409
|
-
if (!verifyStageToken(nonce, state.id, state.stage, state.expectedTokenHash)) {
|
|
10410
|
-
emitError("next", `Token verification failed. The provided nonce does not match. Re-run the current stage command (reap run ${state.stage}) to get a valid token. You cannot forge or guess the token.`);
|
|
10411
|
-
}
|
|
10412
|
-
}
|
|
10413
|
-
const isMerge = state.type === "merge";
|
|
10414
|
-
let nextStage;
|
|
10415
|
-
if (isMerge) {
|
|
10416
|
-
nextStage = MergeLifeCycle.next(state.stage);
|
|
10417
|
-
} else {
|
|
10418
|
-
nextStage = LifeCycle.next(state.stage);
|
|
10419
|
-
}
|
|
10420
|
-
if (!nextStage) {
|
|
10421
|
-
emitError("next", `Cannot advance from '${state.stage}' — already at the last stage.`);
|
|
10422
|
-
}
|
|
10423
|
-
state.stage = nextStage;
|
|
10424
|
-
if (!state.timeline)
|
|
10425
|
-
state.timeline = [];
|
|
10426
|
-
state.timeline.push({ stage: nextStage, at: new Date().toISOString() });
|
|
10427
|
-
state.expectedTokenHash = undefined;
|
|
10428
|
-
await gm.save(state);
|
|
10429
|
-
const artifactFile = isMerge ? MERGE_ARTIFACT[nextStage] : NORMAL_ARTIFACT[nextStage];
|
|
10430
|
-
if (artifactFile) {
|
|
10431
|
-
const templateDir = join13(__require("os").homedir(), ".reap", "templates");
|
|
10432
|
-
const templatePath = join13(templateDir, artifactFile);
|
|
10433
|
-
const destPath = paths.artifact(artifactFile);
|
|
10434
|
-
if (await fileExists(templatePath) && !await fileExists(destPath)) {
|
|
10435
|
-
const templateContent = await readTextFile(templatePath);
|
|
10436
|
-
if (templateContent) {
|
|
10437
|
-
await writeTextFile(destPath, templateContent);
|
|
10438
|
-
}
|
|
10439
|
-
}
|
|
10440
|
-
}
|
|
10441
|
-
const previousStage = state.timeline[state.timeline.length - 2]?.stage;
|
|
10442
|
-
const STAGE_HOOK = {
|
|
10443
|
-
planning: "onLifeObjected",
|
|
10444
|
-
implementation: "onLifePlanned",
|
|
10445
|
-
validation: "onLifeImplemented",
|
|
10446
|
-
completion: "onLifeValidated",
|
|
10447
|
-
mate: "onMergeDetected",
|
|
10448
|
-
merge: "onMergeMated",
|
|
10449
|
-
sync: "onMergeMerged",
|
|
10450
|
-
"validation:merge": "onMergeSynced",
|
|
10451
|
-
"completion:merge": "onMergeValidated"
|
|
10452
|
-
};
|
|
10453
|
-
const stageKey = isMerge && (nextStage === "validation" || nextStage === "completion") ? `${nextStage}:merge` : nextStage;
|
|
10454
|
-
const stageHookEvent = STAGE_HOOK[stageKey];
|
|
10455
|
-
const stageHookResults = stageHookEvent ? await executeHooks(paths.hooks, stageHookEvent, paths.projectRoot) : [];
|
|
10456
|
-
const transitionEvent = isMerge ? "onMergeTransited" : "onLifeTransited";
|
|
10457
|
-
const transitionHookResults = await executeHooks(paths.hooks, transitionEvent, paths.projectRoot);
|
|
10458
|
-
emitOutput({
|
|
10459
|
-
status: "ok",
|
|
10460
|
-
command: "next",
|
|
10461
|
-
phase: "done",
|
|
10462
|
-
completed: ["gate", "nonce-verify", "advance-stage", "create-artifact", "hooks"],
|
|
10463
|
-
context: {
|
|
10464
|
-
generationId: state.id,
|
|
10465
|
-
previousStage,
|
|
10466
|
-
stage: nextStage,
|
|
10467
|
-
type: state.type,
|
|
10468
|
-
artifactFile,
|
|
10469
|
-
hookResults: [...stageHookResults, ...transitionHookResults]
|
|
10470
|
-
},
|
|
10471
|
-
message: `Advanced to ${nextStage}. Proceed with /reap.${nextStage}.`
|
|
10472
|
-
});
|
|
10473
|
-
}
|
|
10474
|
-
var NORMAL_ARTIFACT, MERGE_ARTIFACT;
|
|
10475
|
-
var init_next = __esm(() => {
|
|
10476
|
-
init_generation();
|
|
10477
|
-
init_lifecycle();
|
|
10478
|
-
init_merge_lifecycle();
|
|
10479
|
-
init_fs();
|
|
10480
|
-
init_hook_engine();
|
|
10481
|
-
NORMAL_ARTIFACT = {
|
|
10482
|
-
planning: "02-planning.md",
|
|
10483
|
-
implementation: "03-implementation.md",
|
|
10484
|
-
validation: "04-validation.md",
|
|
10485
|
-
completion: "05-completion.md"
|
|
10486
|
-
};
|
|
10487
|
-
MERGE_ARTIFACT = {
|
|
10488
|
-
mate: "02-mate.md",
|
|
10489
|
-
merge: "03-merge.md",
|
|
10490
|
-
sync: "04-sync.md",
|
|
10491
|
-
validation: "05-validation.md",
|
|
10492
|
-
completion: "06-completion.md"
|
|
10493
|
-
};
|
|
10466
|
+
import_yaml9 = __toESM(require_dist(), 1);
|
|
10494
10467
|
});
|
|
10495
10468
|
|
|
10496
10469
|
// src/cli/commands/run/back.ts
|
|
@@ -10596,12 +10569,12 @@ var init_back = __esm(() => {
|
|
|
10596
10569
|
});
|
|
10597
10570
|
|
|
10598
10571
|
// src/core/backlog.ts
|
|
10599
|
-
import { readdir as
|
|
10600
|
-
import { join as
|
|
10572
|
+
import { readdir as readdir15 } from "fs/promises";
|
|
10573
|
+
import { join as join17 } from "path";
|
|
10601
10574
|
async function scanBacklog(backlogDir) {
|
|
10602
10575
|
let entries;
|
|
10603
10576
|
try {
|
|
10604
|
-
entries = await
|
|
10577
|
+
entries = await readdir15(backlogDir);
|
|
10605
10578
|
} catch {
|
|
10606
10579
|
return [];
|
|
10607
10580
|
}
|
|
@@ -10609,7 +10582,7 @@ async function scanBacklog(backlogDir) {
|
|
|
10609
10582
|
for (const filename of entries) {
|
|
10610
10583
|
if (!filename.endsWith(".md"))
|
|
10611
10584
|
continue;
|
|
10612
|
-
const content = await readTextFile(
|
|
10585
|
+
const content = await readTextFile(join17(backlogDir, filename));
|
|
10613
10586
|
if (!content)
|
|
10614
10587
|
continue;
|
|
10615
10588
|
const { frontmatter, body } = parseFrontmatter2(content);
|
|
@@ -10627,7 +10600,7 @@ async function scanBacklog(backlogDir) {
|
|
|
10627
10600
|
return items;
|
|
10628
10601
|
}
|
|
10629
10602
|
async function markBacklogConsumed(backlogDir, filename, genId) {
|
|
10630
|
-
const filePath =
|
|
10603
|
+
const filePath = join17(backlogDir, filename);
|
|
10631
10604
|
const content = await readTextFile(filePath);
|
|
10632
10605
|
if (!content)
|
|
10633
10606
|
return;
|
|
@@ -10636,7 +10609,7 @@ async function markBacklogConsumed(backlogDir, filename, genId) {
|
|
|
10636
10609
|
frontmatter.consumedBy = genId;
|
|
10637
10610
|
delete frontmatter.consumed;
|
|
10638
10611
|
const newContent = `---
|
|
10639
|
-
${
|
|
10612
|
+
${import_yaml10.default.stringify(frontmatter).trim()}
|
|
10640
10613
|
---
|
|
10641
10614
|
${body}`;
|
|
10642
10615
|
await writeTextFile(filePath, newContent);
|
|
@@ -10644,14 +10617,14 @@ ${body}`;
|
|
|
10644
10617
|
async function revertBacklogConsumed(backlogDir, genId) {
|
|
10645
10618
|
let entries;
|
|
10646
10619
|
try {
|
|
10647
|
-
entries = await
|
|
10620
|
+
entries = await readdir15(backlogDir);
|
|
10648
10621
|
} catch {
|
|
10649
10622
|
return;
|
|
10650
10623
|
}
|
|
10651
10624
|
for (const filename of entries) {
|
|
10652
10625
|
if (!filename.endsWith(".md"))
|
|
10653
10626
|
continue;
|
|
10654
|
-
const filePath =
|
|
10627
|
+
const filePath = join17(backlogDir, filename);
|
|
10655
10628
|
const content = await readTextFile(filePath);
|
|
10656
10629
|
if (!content)
|
|
10657
10630
|
continue;
|
|
@@ -10660,7 +10633,7 @@ async function revertBacklogConsumed(backlogDir, genId) {
|
|
|
10660
10633
|
frontmatter.status = "pending";
|
|
10661
10634
|
delete frontmatter.consumedBy;
|
|
10662
10635
|
const newContent = `---
|
|
10663
|
-
${
|
|
10636
|
+
${import_yaml10.default.stringify(frontmatter).trim()}
|
|
10664
10637
|
---
|
|
10665
10638
|
${body}`;
|
|
10666
10639
|
await writeTextFile(filePath, newContent);
|
|
@@ -10672,15 +10645,15 @@ function parseFrontmatter2(content) {
|
|
|
10672
10645
|
if (!match)
|
|
10673
10646
|
return { frontmatter: {}, body: content };
|
|
10674
10647
|
try {
|
|
10675
|
-
return { frontmatter:
|
|
10648
|
+
return { frontmatter: import_yaml10.default.parse(match[1]) ?? {}, body: match[2] };
|
|
10676
10649
|
} catch {
|
|
10677
10650
|
return { frontmatter: {}, body: content };
|
|
10678
10651
|
}
|
|
10679
10652
|
}
|
|
10680
|
-
var
|
|
10653
|
+
var import_yaml10;
|
|
10681
10654
|
var init_backlog = __esm(() => {
|
|
10682
10655
|
init_fs();
|
|
10683
|
-
|
|
10656
|
+
import_yaml10 = __toESM(require_dist(), 1);
|
|
10684
10657
|
});
|
|
10685
10658
|
|
|
10686
10659
|
// src/cli/commands/run/start.ts
|
|
@@ -10688,8 +10661,8 @@ var exports_start = {};
|
|
|
10688
10661
|
__export(exports_start, {
|
|
10689
10662
|
execute: () => execute3
|
|
10690
10663
|
});
|
|
10691
|
-
import { join as
|
|
10692
|
-
import { readdir as
|
|
10664
|
+
import { join as join18 } from "path";
|
|
10665
|
+
import { readdir as readdir16 } from "fs/promises";
|
|
10693
10666
|
function getFlag2(args, name) {
|
|
10694
10667
|
const idx = args.indexOf(`--${name}`);
|
|
10695
10668
|
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
@@ -10738,18 +10711,18 @@ async function execute3(paths, phase, argv = []) {
|
|
|
10738
10711
|
}
|
|
10739
10712
|
let genomeVersion = 1;
|
|
10740
10713
|
try {
|
|
10741
|
-
const lineageEntries = await
|
|
10714
|
+
const lineageEntries = await readdir16(paths.lineage);
|
|
10742
10715
|
genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length + 1;
|
|
10743
10716
|
} catch {}
|
|
10744
10717
|
const state = await gm.create(goal, genomeVersion);
|
|
10745
|
-
const { nonce, hash } =
|
|
10746
|
-
state.
|
|
10718
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
10719
|
+
state.expectedHash = hash;
|
|
10747
10720
|
await gm.save(state);
|
|
10748
10721
|
if (backlogFile) {
|
|
10749
10722
|
await markBacklogConsumed(paths.backlog, backlogFile, state.id);
|
|
10750
10723
|
}
|
|
10751
|
-
const templateDir =
|
|
10752
|
-
const templatePath =
|
|
10724
|
+
const templateDir = join18(__require("os").homedir(), ".reap", "templates");
|
|
10725
|
+
const templatePath = join18(templateDir, "01-objective.md");
|
|
10753
10726
|
const destPath = paths.artifact("01-objective.md");
|
|
10754
10727
|
if (await fileExists(templatePath)) {
|
|
10755
10728
|
let template = await readTextFile(templatePath);
|
|
@@ -10808,12 +10781,133 @@ function checkSubmodules(projectRoot) {
|
|
|
10808
10781
|
}
|
|
10809
10782
|
var init_commit = () => {};
|
|
10810
10783
|
|
|
10784
|
+
// src/core/stage-transition.ts
|
|
10785
|
+
import { join as join19 } from "path";
|
|
10786
|
+
function verifyStageEntry(command, state) {
|
|
10787
|
+
if (!state.lastNonce) {
|
|
10788
|
+
return;
|
|
10789
|
+
}
|
|
10790
|
+
if (state.phase) {
|
|
10791
|
+
return;
|
|
10792
|
+
}
|
|
10793
|
+
if (!state.expectedHash) {
|
|
10794
|
+
emitError(command, "Stage transition error: lastNonce exists but expectedHash is missing. State may be corrupted.");
|
|
10795
|
+
}
|
|
10796
|
+
const isMerge = state.type === "merge";
|
|
10797
|
+
let prevStage;
|
|
10798
|
+
if (isMerge) {
|
|
10799
|
+
prevStage = MergeLifeCycle.prev(state.stage);
|
|
10800
|
+
} else {
|
|
10801
|
+
prevStage = LifeCycle.prev(state.stage);
|
|
10802
|
+
}
|
|
10803
|
+
if (!prevStage) {
|
|
10804
|
+
emitError(command, "Stage transition error: cannot determine previous stage for token verification.");
|
|
10805
|
+
}
|
|
10806
|
+
if (!verifyToken(state.lastNonce, state.id, prevStage, state.expectedHash)) {
|
|
10807
|
+
emitError(command, `Token verification failed. The stage chain token does not match. Re-run the previous stage command to get a valid token.`);
|
|
10808
|
+
}
|
|
10809
|
+
state.lastNonce = undefined;
|
|
10810
|
+
state.expectedHash = undefined;
|
|
10811
|
+
}
|
|
10812
|
+
function setPhaseNonce(state, stage, phase) {
|
|
10813
|
+
const { nonce, hash } = generateToken(state.id, stage, phase);
|
|
10814
|
+
state.lastNonce = nonce;
|
|
10815
|
+
state.expectedHash = hash;
|
|
10816
|
+
state.phase = phase;
|
|
10817
|
+
}
|
|
10818
|
+
function verifyPhaseEntry(command, state, stage, phase) {
|
|
10819
|
+
if (!state.lastNonce) {
|
|
10820
|
+
emitError(command, `Phase nonce missing. Complete the previous phase before running --phase ${phase}.`);
|
|
10821
|
+
}
|
|
10822
|
+
if (!state.expectedHash) {
|
|
10823
|
+
emitError(command, "Phase transition error: lastNonce exists but expectedHash is missing. State may be corrupted.");
|
|
10824
|
+
}
|
|
10825
|
+
if (!verifyToken(state.lastNonce, state.id, stage, state.expectedHash, phase)) {
|
|
10826
|
+
emitError(command, `Phase token verification failed. Re-run the previous phase to get a valid token.`);
|
|
10827
|
+
}
|
|
10828
|
+
state.lastNonce = undefined;
|
|
10829
|
+
state.expectedHash = undefined;
|
|
10830
|
+
state.phase = undefined;
|
|
10831
|
+
}
|
|
10832
|
+
async function performTransition(paths, state, saveFn) {
|
|
10833
|
+
const isMerge = state.type === "merge";
|
|
10834
|
+
let nextStage;
|
|
10835
|
+
if (isMerge) {
|
|
10836
|
+
nextStage = MergeLifeCycle.next(state.stage);
|
|
10837
|
+
} else {
|
|
10838
|
+
nextStage = LifeCycle.next(state.stage);
|
|
10839
|
+
}
|
|
10840
|
+
if (!nextStage) {
|
|
10841
|
+
throw new Error(`Cannot advance from '${state.stage}' — already at the last stage.`);
|
|
10842
|
+
}
|
|
10843
|
+
state.stage = nextStage;
|
|
10844
|
+
if (!state.timeline)
|
|
10845
|
+
state.timeline = [];
|
|
10846
|
+
state.timeline.push({ stage: nextStage, at: new Date().toISOString() });
|
|
10847
|
+
await saveFn(state);
|
|
10848
|
+
const artifactFile = isMerge ? MERGE_ARTIFACT[nextStage] : NORMAL_ARTIFACT[nextStage];
|
|
10849
|
+
if (artifactFile) {
|
|
10850
|
+
const templateDir = join19(__require("os").homedir(), ".reap", "templates");
|
|
10851
|
+
const templatePath = join19(templateDir, artifactFile);
|
|
10852
|
+
const destPath = paths.artifact(artifactFile);
|
|
10853
|
+
if (await fileExists(templatePath) && !await fileExists(destPath)) {
|
|
10854
|
+
const templateContent = await readTextFile(templatePath);
|
|
10855
|
+
if (templateContent) {
|
|
10856
|
+
await writeTextFile(destPath, templateContent);
|
|
10857
|
+
}
|
|
10858
|
+
}
|
|
10859
|
+
}
|
|
10860
|
+
const stageKey = isMerge && (nextStage === "validation" || nextStage === "completion") ? `${nextStage}:merge` : nextStage;
|
|
10861
|
+
const stageHookEvent = STAGE_HOOK[stageKey];
|
|
10862
|
+
const stageHookResults = stageHookEvent ? await executeHooks(paths.hooks, stageHookEvent, paths.projectRoot) : [];
|
|
10863
|
+
const transitionEvent = isMerge ? "onMergeTransited" : "onLifeTransited";
|
|
10864
|
+
const transitionHookResults = await executeHooks(paths.hooks, transitionEvent, paths.projectRoot);
|
|
10865
|
+
return {
|
|
10866
|
+
nextStage,
|
|
10867
|
+
artifactFile,
|
|
10868
|
+
stageHookResults,
|
|
10869
|
+
transitionHookResults
|
|
10870
|
+
};
|
|
10871
|
+
}
|
|
10872
|
+
var NORMAL_ARTIFACT, MERGE_ARTIFACT, STAGE_HOOK;
|
|
10873
|
+
var init_stage_transition = __esm(() => {
|
|
10874
|
+
init_lifecycle();
|
|
10875
|
+
init_merge_lifecycle();
|
|
10876
|
+
init_generation();
|
|
10877
|
+
init_fs();
|
|
10878
|
+
init_hook_engine();
|
|
10879
|
+
NORMAL_ARTIFACT = {
|
|
10880
|
+
planning: "02-planning.md",
|
|
10881
|
+
implementation: "03-implementation.md",
|
|
10882
|
+
validation: "04-validation.md",
|
|
10883
|
+
completion: "05-completion.md"
|
|
10884
|
+
};
|
|
10885
|
+
MERGE_ARTIFACT = {
|
|
10886
|
+
mate: "02-mate.md",
|
|
10887
|
+
merge: "03-merge.md",
|
|
10888
|
+
sync: "04-sync.md",
|
|
10889
|
+
validation: "05-validation.md",
|
|
10890
|
+
completion: "06-completion.md"
|
|
10891
|
+
};
|
|
10892
|
+
STAGE_HOOK = {
|
|
10893
|
+
planning: "onLifeObjected",
|
|
10894
|
+
implementation: "onLifePlanned",
|
|
10895
|
+
validation: "onLifeImplemented",
|
|
10896
|
+
completion: "onLifeValidated",
|
|
10897
|
+
mate: "onMergeDetected",
|
|
10898
|
+
merge: "onMergeMated",
|
|
10899
|
+
sync: "onMergeMerged",
|
|
10900
|
+
"validation:merge": "onMergeSynced",
|
|
10901
|
+
"completion:merge": "onMergeValidated"
|
|
10902
|
+
};
|
|
10903
|
+
});
|
|
10904
|
+
|
|
10811
10905
|
// src/cli/commands/run/completion.ts
|
|
10812
10906
|
var exports_completion = {};
|
|
10813
10907
|
__export(exports_completion, {
|
|
10814
10908
|
execute: () => execute4
|
|
10815
10909
|
});
|
|
10816
|
-
import { join as
|
|
10910
|
+
import { join as join20 } from "path";
|
|
10817
10911
|
import { execSync as execSync6 } from "child_process";
|
|
10818
10912
|
function detectGenomeImpact(projectRoot) {
|
|
10819
10913
|
const impact = {
|
|
@@ -10894,6 +10988,8 @@ async function execute4(paths, phase) {
|
|
|
10894
10988
|
if (state.stage !== "completion") {
|
|
10895
10989
|
emitError("completion", `Stage is '${state.stage}', expected 'completion'.`);
|
|
10896
10990
|
}
|
|
10991
|
+
verifyStageEntry("completion", state);
|
|
10992
|
+
await gm.save(state);
|
|
10897
10993
|
const validationArtifact = paths.artifact("04-validation.md");
|
|
10898
10994
|
if (!await fileExists(validationArtifact)) {
|
|
10899
10995
|
emitError("completion", "04-validation.md does not exist. Complete validation first.");
|
|
@@ -10904,8 +11000,8 @@ async function execute4(paths, phase) {
|
|
|
10904
11000
|
const implContent = await readTextFile(paths.artifact("03-implementation.md"));
|
|
10905
11001
|
const destPath = paths.artifact("05-completion.md");
|
|
10906
11002
|
if (!await fileExists(destPath)) {
|
|
10907
|
-
const templateDir =
|
|
10908
|
-
const templatePath =
|
|
11003
|
+
const templateDir = join20(__require("os").homedir(), ".reap", "templates");
|
|
11004
|
+
const templatePath = join20(templateDir, "05-completion.md");
|
|
10909
11005
|
if (await fileExists(templatePath)) {
|
|
10910
11006
|
const { writeTextFile: writeTextFile2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
|
|
10911
11007
|
const template = await readTextFile(templatePath);
|
|
@@ -10913,6 +11009,8 @@ async function execute4(paths, phase) {
|
|
|
10913
11009
|
await writeTextFile2(destPath, template);
|
|
10914
11010
|
}
|
|
10915
11011
|
}
|
|
11012
|
+
setPhaseNonce(state, "completion", "retrospective");
|
|
11013
|
+
await gm.save(state);
|
|
10916
11014
|
emitOutput({
|
|
10917
11015
|
status: "prompt",
|
|
10918
11016
|
command: "completion",
|
|
@@ -10932,6 +11030,8 @@ async function execute4(paths, phase) {
|
|
|
10932
11030
|
});
|
|
10933
11031
|
}
|
|
10934
11032
|
if (phase === "feedKnowledge") {
|
|
11033
|
+
verifyPhaseEntry("completion", state, "completion", "retrospective");
|
|
11034
|
+
await gm.save(state);
|
|
10935
11035
|
const backlogItems = await scanBacklog(paths.backlog);
|
|
10936
11036
|
const genomeChanges = backlogItems.filter((b) => b.type === "genome-change" && b.status !== "consumed");
|
|
10937
11037
|
const envChanges = backlogItems.filter((b) => b.type === "environment-change" && b.status !== "consumed");
|
|
@@ -11021,6 +11121,7 @@ var init_completion = __esm(() => {
|
|
|
11021
11121
|
init_backlog();
|
|
11022
11122
|
init_hook_engine();
|
|
11023
11123
|
init_commit();
|
|
11124
|
+
init_stage_transition();
|
|
11024
11125
|
});
|
|
11025
11126
|
|
|
11026
11127
|
// src/cli/commands/run/abort.ts
|
|
@@ -11028,8 +11129,8 @@ var exports_abort = {};
|
|
|
11028
11129
|
__export(exports_abort, {
|
|
11029
11130
|
execute: () => execute5
|
|
11030
11131
|
});
|
|
11031
|
-
import { join as
|
|
11032
|
-
import { readdir as
|
|
11132
|
+
import { join as join21 } from "path";
|
|
11133
|
+
import { readdir as readdir17, unlink as unlink6 } from "fs/promises";
|
|
11033
11134
|
function getFlag3(args, name) {
|
|
11034
11135
|
const idx = args.indexOf(`--${name}`);
|
|
11035
11136
|
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
@@ -11104,15 +11205,15 @@ async function execute5(paths, phase, argv = []) {
|
|
|
11104
11205
|
sourceAction === "stash" ? "git stash pop으로 코드 복구" : sourceAction === "hold" ? "코드 변경이 working tree에 유지됨" : sourceAction === "rollback" ? "코드 변경이 revert됨. objective부터 재시작 필요" : "소스 코드 변경 없음"
|
|
11105
11206
|
].filter((line) => line !== null).join(`
|
|
11106
11207
|
`);
|
|
11107
|
-
await writeTextFile(
|
|
11208
|
+
await writeTextFile(join21(paths.backlog, `aborted-${state.id}.md`), backlogContent);
|
|
11108
11209
|
backlogSaved = true;
|
|
11109
11210
|
}
|
|
11110
11211
|
await revertBacklogConsumed(paths.backlog, state.id);
|
|
11111
11212
|
try {
|
|
11112
|
-
const lifeEntries = await
|
|
11213
|
+
const lifeEntries = await readdir17(paths.life);
|
|
11113
11214
|
for (const entry of lifeEntries) {
|
|
11114
11215
|
if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
|
|
11115
|
-
await
|
|
11216
|
+
await unlink6(join21(paths.life, entry));
|
|
11116
11217
|
}
|
|
11117
11218
|
}
|
|
11118
11219
|
} catch {}
|
|
@@ -11171,8 +11272,8 @@ var exports_objective = {};
|
|
|
11171
11272
|
__export(exports_objective, {
|
|
11172
11273
|
execute: () => execute7
|
|
11173
11274
|
});
|
|
11174
|
-
import { join as
|
|
11175
|
-
import { readdir as
|
|
11275
|
+
import { join as join22 } from "path";
|
|
11276
|
+
import { readdir as readdir18 } from "fs/promises";
|
|
11176
11277
|
async function execute7(paths, phase) {
|
|
11177
11278
|
const gm = new GenerationManager(paths);
|
|
11178
11279
|
const state = await gm.current();
|
|
@@ -11189,13 +11290,13 @@ async function execute7(paths, phase) {
|
|
|
11189
11290
|
const envSummary = await readTextFile(paths.environmentSummary);
|
|
11190
11291
|
let prevCompletion = null;
|
|
11191
11292
|
try {
|
|
11192
|
-
const lineageEntries = await
|
|
11293
|
+
const lineageEntries = await readdir18(paths.lineage);
|
|
11193
11294
|
const genDirs = lineageEntries.filter((e) => e.startsWith("gen-")).sort();
|
|
11194
11295
|
if (genDirs.length > 0) {
|
|
11195
11296
|
const lastGen = genDirs[genDirs.length - 1];
|
|
11196
|
-
prevCompletion = await readTextFile(
|
|
11297
|
+
prevCompletion = await readTextFile(join22(paths.lineage, lastGen, "05-completion.md"));
|
|
11197
11298
|
if (!prevCompletion) {
|
|
11198
|
-
const compressed = await readTextFile(
|
|
11299
|
+
const compressed = await readTextFile(join22(paths.lineage, `${lastGen}.md`));
|
|
11199
11300
|
if (compressed)
|
|
11200
11301
|
prevCompletion = compressed.slice(0, 2000);
|
|
11201
11302
|
}
|
|
@@ -11208,12 +11309,12 @@ async function execute7(paths, phase) {
|
|
|
11208
11309
|
const configContent = await readTextFile(paths.config);
|
|
11209
11310
|
let lineageCount = 0;
|
|
11210
11311
|
try {
|
|
11211
|
-
const entries = await
|
|
11312
|
+
const entries = await readdir18(paths.lineage);
|
|
11212
11313
|
lineageCount = entries.filter((e) => e.startsWith("gen-")).length;
|
|
11213
11314
|
} catch {}
|
|
11214
11315
|
if (!isReentry) {
|
|
11215
|
-
const templateDir =
|
|
11216
|
-
const templatePath =
|
|
11316
|
+
const templateDir = join22(__require("os").homedir(), ".reap", "templates");
|
|
11317
|
+
const templatePath = join22(templateDir, "01-objective.md");
|
|
11217
11318
|
if (await fileExists(templatePath)) {
|
|
11218
11319
|
let template = await readTextFile(templatePath);
|
|
11219
11320
|
if (template) {
|
|
@@ -11222,6 +11323,8 @@ async function execute7(paths, phase) {
|
|
|
11222
11323
|
}
|
|
11223
11324
|
}
|
|
11224
11325
|
}
|
|
11326
|
+
setPhaseNonce(state, "objective", "work");
|
|
11327
|
+
await gm.save(state);
|
|
11225
11328
|
emitOutput({
|
|
11226
11329
|
status: "prompt",
|
|
11227
11330
|
command: "objective",
|
|
@@ -11283,6 +11386,8 @@ async function execute7(paths, phase) {
|
|
|
11283
11386
|
});
|
|
11284
11387
|
}
|
|
11285
11388
|
if (phase === "complete") {
|
|
11389
|
+
verifyPhaseEntry("objective", state, "objective", "work");
|
|
11390
|
+
await gm.save(state);
|
|
11286
11391
|
const artifactPath = paths.artifact("01-objective.md");
|
|
11287
11392
|
if (!await fileExists(artifactPath)) {
|
|
11288
11393
|
emitError("objective", "01-objective.md does not exist. Complete the objective work first.");
|
|
@@ -11291,20 +11396,26 @@ async function execute7(paths, phase) {
|
|
|
11291
11396
|
if (!content || content.length < 50) {
|
|
11292
11397
|
emitError("objective", "01-objective.md appears incomplete (too short). Fill in the objective before completing.");
|
|
11293
11398
|
}
|
|
11294
|
-
const { nonce, hash } =
|
|
11295
|
-
state.
|
|
11296
|
-
|
|
11399
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11400
|
+
state.expectedHash = hash;
|
|
11401
|
+
state.lastNonce = nonce;
|
|
11297
11402
|
const hookResults = await executeHooks(paths.hooks, "onLifeObjected", paths.projectRoot);
|
|
11403
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11404
|
+
const nextCommand = `reap run ${transition.nextStage}`;
|
|
11298
11405
|
emitOutput({
|
|
11299
11406
|
status: "ok",
|
|
11300
11407
|
command: "objective",
|
|
11301
11408
|
phase: "complete",
|
|
11302
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks"],
|
|
11409
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks", "auto-transition"],
|
|
11303
11410
|
context: {
|
|
11304
11411
|
id: state.id,
|
|
11305
|
-
hookResults
|
|
11412
|
+
hookResults,
|
|
11413
|
+
nextStage: transition.nextStage,
|
|
11414
|
+
artifactFile: transition.artifactFile,
|
|
11415
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11306
11416
|
},
|
|
11307
|
-
message: `Objective stage complete.
|
|
11417
|
+
message: `Objective stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11418
|
+
nextCommand
|
|
11308
11419
|
});
|
|
11309
11420
|
}
|
|
11310
11421
|
}
|
|
@@ -11313,6 +11424,7 @@ var init_objective = __esm(() => {
|
|
|
11313
11424
|
init_fs();
|
|
11314
11425
|
init_backlog();
|
|
11315
11426
|
init_hook_engine();
|
|
11427
|
+
init_stage_transition();
|
|
11316
11428
|
});
|
|
11317
11429
|
|
|
11318
11430
|
// src/cli/commands/run/planning.ts
|
|
@@ -11320,7 +11432,7 @@ var exports_planning = {};
|
|
|
11320
11432
|
__export(exports_planning, {
|
|
11321
11433
|
execute: () => execute8
|
|
11322
11434
|
});
|
|
11323
|
-
import { join as
|
|
11435
|
+
import { join as join23 } from "path";
|
|
11324
11436
|
async function execute8(paths, phase) {
|
|
11325
11437
|
const gm = new GenerationManager(paths);
|
|
11326
11438
|
const state = await gm.current();
|
|
@@ -11330,6 +11442,8 @@ async function execute8(paths, phase) {
|
|
|
11330
11442
|
if (state.stage !== "planning") {
|
|
11331
11443
|
emitError("planning", `Current stage is '${state.stage}', not 'planning'.`);
|
|
11332
11444
|
}
|
|
11445
|
+
verifyStageEntry("planning", state);
|
|
11446
|
+
await gm.save(state);
|
|
11333
11447
|
const objectiveArtifact = paths.artifact("01-objective.md");
|
|
11334
11448
|
if (!await fileExists(objectiveArtifact)) {
|
|
11335
11449
|
emitError("planning", "01-objective.md does not exist. Complete the objective stage first.");
|
|
@@ -11344,14 +11458,16 @@ async function execute8(paths, phase) {
|
|
|
11344
11458
|
const principlesContent = await readTextFile(paths.principles);
|
|
11345
11459
|
const implContent = await readTextFile(paths.artifact("03-implementation.md"));
|
|
11346
11460
|
if (!isReentry) {
|
|
11347
|
-
const templateDir =
|
|
11348
|
-
const templatePath =
|
|
11461
|
+
const templateDir = join23(__require("os").homedir(), ".reap", "templates");
|
|
11462
|
+
const templatePath = join23(templateDir, "02-planning.md");
|
|
11349
11463
|
if (await fileExists(templatePath)) {
|
|
11350
11464
|
const template = await readTextFile(templatePath);
|
|
11351
11465
|
if (template)
|
|
11352
11466
|
await writeTextFile(artifactPath, template);
|
|
11353
11467
|
}
|
|
11354
11468
|
}
|
|
11469
|
+
setPhaseNonce(state, "planning", "work");
|
|
11470
|
+
await gm.save(state);
|
|
11355
11471
|
emitOutput({
|
|
11356
11472
|
status: "prompt",
|
|
11357
11473
|
command: "planning",
|
|
@@ -11408,6 +11524,8 @@ async function execute8(paths, phase) {
|
|
|
11408
11524
|
});
|
|
11409
11525
|
}
|
|
11410
11526
|
if (phase === "complete") {
|
|
11527
|
+
verifyPhaseEntry("planning", state, "planning", "work");
|
|
11528
|
+
await gm.save(state);
|
|
11411
11529
|
const artifactPath = paths.artifact("02-planning.md");
|
|
11412
11530
|
if (!await fileExists(artifactPath)) {
|
|
11413
11531
|
emitError("planning", "02-planning.md does not exist. Complete the planning work first.");
|
|
@@ -11416,20 +11534,26 @@ async function execute8(paths, phase) {
|
|
|
11416
11534
|
if (!content || content.length < 50) {
|
|
11417
11535
|
emitError("planning", "02-planning.md appears incomplete. Fill in the plan before completing.");
|
|
11418
11536
|
}
|
|
11419
|
-
const { nonce, hash } =
|
|
11420
|
-
state.
|
|
11421
|
-
|
|
11537
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11538
|
+
state.expectedHash = hash;
|
|
11539
|
+
state.lastNonce = nonce;
|
|
11422
11540
|
const hookResults = await executeHooks(paths.hooks, "onLifePlanned", paths.projectRoot);
|
|
11541
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11542
|
+
const nextCommand = `reap run ${transition.nextStage}`;
|
|
11423
11543
|
emitOutput({
|
|
11424
11544
|
status: "ok",
|
|
11425
11545
|
command: "planning",
|
|
11426
11546
|
phase: "complete",
|
|
11427
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks"],
|
|
11547
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks", "auto-transition"],
|
|
11428
11548
|
context: {
|
|
11429
11549
|
id: state.id,
|
|
11430
|
-
hookResults
|
|
11550
|
+
hookResults,
|
|
11551
|
+
nextStage: transition.nextStage,
|
|
11552
|
+
artifactFile: transition.artifactFile,
|
|
11553
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11431
11554
|
},
|
|
11432
|
-
message: `Planning stage complete.
|
|
11555
|
+
message: `Planning stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11556
|
+
nextCommand
|
|
11433
11557
|
});
|
|
11434
11558
|
}
|
|
11435
11559
|
}
|
|
@@ -11437,6 +11561,7 @@ var init_planning = __esm(() => {
|
|
|
11437
11561
|
init_generation();
|
|
11438
11562
|
init_fs();
|
|
11439
11563
|
init_hook_engine();
|
|
11564
|
+
init_stage_transition();
|
|
11440
11565
|
});
|
|
11441
11566
|
|
|
11442
11567
|
// src/cli/commands/run/implementation.ts
|
|
@@ -11444,7 +11569,7 @@ var exports_implementation = {};
|
|
|
11444
11569
|
__export(exports_implementation, {
|
|
11445
11570
|
execute: () => execute9
|
|
11446
11571
|
});
|
|
11447
|
-
import { join as
|
|
11572
|
+
import { join as join24 } from "path";
|
|
11448
11573
|
async function execute9(paths, phase) {
|
|
11449
11574
|
const gm = new GenerationManager(paths);
|
|
11450
11575
|
const state = await gm.current();
|
|
@@ -11454,6 +11579,8 @@ async function execute9(paths, phase) {
|
|
|
11454
11579
|
if (state.stage !== "implementation") {
|
|
11455
11580
|
emitError("implementation", `Current stage is '${state.stage}', not 'implementation'.`);
|
|
11456
11581
|
}
|
|
11582
|
+
verifyStageEntry("implementation", state);
|
|
11583
|
+
await gm.save(state);
|
|
11457
11584
|
const planningArtifact = paths.artifact("02-planning.md");
|
|
11458
11585
|
if (!await fileExists(planningArtifact)) {
|
|
11459
11586
|
emitError("implementation", "02-planning.md does not exist. Complete the planning stage first.");
|
|
@@ -11466,14 +11593,16 @@ async function execute9(paths, phase) {
|
|
|
11466
11593
|
const conventionsContent = await readTextFile(paths.conventions);
|
|
11467
11594
|
const constraintsContent = await readTextFile(paths.constraints);
|
|
11468
11595
|
if (!isReentry) {
|
|
11469
|
-
const templateDir =
|
|
11470
|
-
const templatePath =
|
|
11596
|
+
const templateDir = join24(__require("os").homedir(), ".reap", "templates");
|
|
11597
|
+
const templatePath = join24(templateDir, "03-implementation.md");
|
|
11471
11598
|
if (await fileExists(templatePath)) {
|
|
11472
11599
|
const template = await readTextFile(templatePath);
|
|
11473
11600
|
if (template)
|
|
11474
11601
|
await writeTextFile(artifactPath, template);
|
|
11475
11602
|
}
|
|
11476
11603
|
}
|
|
11604
|
+
setPhaseNonce(state, "implementation", "work");
|
|
11605
|
+
await gm.save(state);
|
|
11477
11606
|
emitOutput({
|
|
11478
11607
|
status: "prompt",
|
|
11479
11608
|
command: "implementation",
|
|
@@ -11533,24 +11662,32 @@ async function execute9(paths, phase) {
|
|
|
11533
11662
|
});
|
|
11534
11663
|
}
|
|
11535
11664
|
if (phase === "complete") {
|
|
11665
|
+
verifyPhaseEntry("implementation", state, "implementation", "work");
|
|
11666
|
+
await gm.save(state);
|
|
11536
11667
|
const artifactPath = paths.artifact("03-implementation.md");
|
|
11537
11668
|
if (!await fileExists(artifactPath)) {
|
|
11538
11669
|
emitError("implementation", "03-implementation.md does not exist. Complete the implementation work first.");
|
|
11539
11670
|
}
|
|
11540
|
-
const { nonce, hash } =
|
|
11541
|
-
state.
|
|
11542
|
-
|
|
11671
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11672
|
+
state.expectedHash = hash;
|
|
11673
|
+
state.lastNonce = nonce;
|
|
11543
11674
|
const hookResults = await executeHooks(paths.hooks, "onLifeImplemented", paths.projectRoot);
|
|
11675
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11676
|
+
const nextCommand = `reap run ${transition.nextStage}`;
|
|
11544
11677
|
emitOutput({
|
|
11545
11678
|
status: "ok",
|
|
11546
11679
|
command: "implementation",
|
|
11547
11680
|
phase: "complete",
|
|
11548
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "hooks"],
|
|
11681
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "hooks", "auto-transition"],
|
|
11549
11682
|
context: {
|
|
11550
11683
|
id: state.id,
|
|
11551
|
-
hookResults
|
|
11684
|
+
hookResults,
|
|
11685
|
+
nextStage: transition.nextStage,
|
|
11686
|
+
artifactFile: transition.artifactFile,
|
|
11687
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11552
11688
|
},
|
|
11553
|
-
message: `Implementation stage complete.
|
|
11689
|
+
message: `Implementation stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11690
|
+
nextCommand
|
|
11554
11691
|
});
|
|
11555
11692
|
}
|
|
11556
11693
|
}
|
|
@@ -11558,6 +11695,7 @@ var init_implementation = __esm(() => {
|
|
|
11558
11695
|
init_generation();
|
|
11559
11696
|
init_fs();
|
|
11560
11697
|
init_hook_engine();
|
|
11698
|
+
init_stage_transition();
|
|
11561
11699
|
});
|
|
11562
11700
|
|
|
11563
11701
|
// src/cli/commands/run/validation.ts
|
|
@@ -11565,7 +11703,7 @@ var exports_validation = {};
|
|
|
11565
11703
|
__export(exports_validation, {
|
|
11566
11704
|
execute: () => execute10
|
|
11567
11705
|
});
|
|
11568
|
-
import { join as
|
|
11706
|
+
import { join as join25 } from "path";
|
|
11569
11707
|
async function execute10(paths, phase) {
|
|
11570
11708
|
const gm = new GenerationManager(paths);
|
|
11571
11709
|
const state = await gm.current();
|
|
@@ -11575,6 +11713,8 @@ async function execute10(paths, phase) {
|
|
|
11575
11713
|
if (state.stage !== "validation") {
|
|
11576
11714
|
emitError("validation", `Current stage is '${state.stage}', not 'validation'.`);
|
|
11577
11715
|
}
|
|
11716
|
+
verifyStageEntry("validation", state);
|
|
11717
|
+
await gm.save(state);
|
|
11578
11718
|
const implArtifact = paths.artifact("03-implementation.md");
|
|
11579
11719
|
if (!await fileExists(implArtifact)) {
|
|
11580
11720
|
emitError("validation", "03-implementation.md does not exist. Complete the implementation stage first.");
|
|
@@ -11588,14 +11728,16 @@ async function execute10(paths, phase) {
|
|
|
11588
11728
|
const implContent = await readTextFile(implArtifact);
|
|
11589
11729
|
const objectiveContent = await readTextFile(paths.artifact("01-objective.md"));
|
|
11590
11730
|
if (!isReentry) {
|
|
11591
|
-
const templateDir =
|
|
11592
|
-
const templatePath =
|
|
11731
|
+
const templateDir = join25(__require("os").homedir(), ".reap", "templates");
|
|
11732
|
+
const templatePath = join25(templateDir, "04-validation.md");
|
|
11593
11733
|
if (await fileExists(templatePath)) {
|
|
11594
11734
|
const template = await readTextFile(templatePath);
|
|
11595
11735
|
if (template)
|
|
11596
11736
|
await writeTextFile(artifactPath, template);
|
|
11597
11737
|
}
|
|
11598
11738
|
}
|
|
11739
|
+
setPhaseNonce(state, "validation", "work");
|
|
11740
|
+
await gm.save(state);
|
|
11599
11741
|
emitOutput({
|
|
11600
11742
|
status: "prompt",
|
|
11601
11743
|
command: "validation",
|
|
@@ -11665,24 +11807,32 @@ async function execute10(paths, phase) {
|
|
|
11665
11807
|
});
|
|
11666
11808
|
}
|
|
11667
11809
|
if (phase === "complete") {
|
|
11810
|
+
verifyPhaseEntry("validation", state, "validation", "work");
|
|
11811
|
+
await gm.save(state);
|
|
11668
11812
|
const artifactPath = paths.artifact("04-validation.md");
|
|
11669
11813
|
if (!await fileExists(artifactPath)) {
|
|
11670
11814
|
emitError("validation", "04-validation.md does not exist. Complete the validation work first.");
|
|
11671
11815
|
}
|
|
11672
|
-
const { nonce, hash } =
|
|
11673
|
-
state.
|
|
11674
|
-
|
|
11816
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11817
|
+
state.expectedHash = hash;
|
|
11818
|
+
state.lastNonce = nonce;
|
|
11675
11819
|
const hookResults = await executeHooks(paths.hooks, "onLifeValidated", paths.projectRoot);
|
|
11820
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11821
|
+
const nextCommand = transition.nextStage !== "completion" ? `reap run ${transition.nextStage}` : "reap run completion";
|
|
11676
11822
|
emitOutput({
|
|
11677
11823
|
status: "ok",
|
|
11678
11824
|
command: "validation",
|
|
11679
11825
|
phase: "complete",
|
|
11680
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks"],
|
|
11826
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks", "auto-transition"],
|
|
11681
11827
|
context: {
|
|
11682
11828
|
id: state.id,
|
|
11683
|
-
hookResults
|
|
11829
|
+
hookResults,
|
|
11830
|
+
nextStage: transition.nextStage,
|
|
11831
|
+
artifactFile: transition.artifactFile,
|
|
11832
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11684
11833
|
},
|
|
11685
|
-
message: `Validation stage complete.
|
|
11834
|
+
message: `Validation stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11835
|
+
nextCommand
|
|
11686
11836
|
});
|
|
11687
11837
|
}
|
|
11688
11838
|
}
|
|
@@ -11690,6 +11840,7 @@ var init_validation = __esm(() => {
|
|
|
11690
11840
|
init_generation();
|
|
11691
11841
|
init_fs();
|
|
11692
11842
|
init_hook_engine();
|
|
11843
|
+
init_stage_transition();
|
|
11693
11844
|
});
|
|
11694
11845
|
|
|
11695
11846
|
// src/cli/commands/run/evolve.ts
|
|
@@ -11707,8 +11858,9 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
|
|
|
11707
11858
|
lines.push("");
|
|
11708
11859
|
lines.push("## Rules");
|
|
11709
11860
|
lines.push("- ALWAYS use `reap run <cmd>` commands to drive lifecycle. NEVER modify `current.yml` directly.");
|
|
11710
|
-
lines.push("-
|
|
11711
|
-
lines.push("-
|
|
11861
|
+
lines.push("- Each `--phase complete` auto-transitions to the next stage. No explicit `/reap.next` needed.");
|
|
11862
|
+
lines.push("- Use `/reap.back` to regress to a previous stage.");
|
|
11863
|
+
lines.push("- Each stage command verifies the stage chain token at entry (auto-verified from lastNonce).");
|
|
11712
11864
|
lines.push("- `/reap.completion` handles archiving and the final commit.");
|
|
11713
11865
|
lines.push("");
|
|
11714
11866
|
lines.push("## Current State");
|
|
@@ -11743,7 +11895,7 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
|
|
|
11743
11895
|
lines.push(`Resume from stage: **${state.stage}**`);
|
|
11744
11896
|
}
|
|
11745
11897
|
lines.push("");
|
|
11746
|
-
lines.push("### Stage Loop");
|
|
11898
|
+
lines.push("### Stage Loop (Auto-Transition)");
|
|
11747
11899
|
lines.push("1. Read `current.yml` to confirm the current stage.");
|
|
11748
11900
|
lines.push("2. Execute the stage command:");
|
|
11749
11901
|
lines.push(" - `objective` -> `/reap.objective`");
|
|
@@ -11752,31 +11904,48 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
|
|
|
11752
11904
|
lines.push(" - `validation` -> `/reap.validation`");
|
|
11753
11905
|
lines.push(" - `completion` -> `/reap.completion`");
|
|
11754
11906
|
lines.push("3. Write the required artifact BEFORE completing the stage.");
|
|
11755
|
-
lines.push("4. Run
|
|
11756
|
-
lines.push("5. If current stage
|
|
11757
|
-
lines.push("6.
|
|
11907
|
+
lines.push("4. Run `--phase complete` — this auto-transitions to the next stage.");
|
|
11908
|
+
lines.push("5. If current stage IS `completion`: `/reap.completion` auto-archives after feedKnowledge phase. Done.");
|
|
11909
|
+
lines.push("6. Otherwise: the output tells you the next command — go to step 1.");
|
|
11758
11910
|
lines.push("");
|
|
11759
|
-
lines.push("Note: `/reap.next` is a
|
|
11911
|
+
lines.push("Note: `--phase complete` auto-transitions. `/reap.next` is a fallback, not required.");
|
|
11760
11912
|
lines.push("");
|
|
11761
11913
|
lines.push("## Project");
|
|
11762
11914
|
lines.push(`- Path: ${paths.projectRoot}`);
|
|
11763
11915
|
lines.push("");
|
|
11764
|
-
lines.push("## Stage Chain Token");
|
|
11765
|
-
lines.push("- Each
|
|
11766
|
-
lines.push("-
|
|
11767
|
-
lines.push("-
|
|
11768
|
-
lines.push("- If token is missing or mismatched, re-run the current stage command to obtain a new token.");
|
|
11916
|
+
lines.push("## Stage Chain Token (Auto-Transition)");
|
|
11917
|
+
lines.push("- Each `--phase complete` generates a stage chain token and auto-transitions to the next stage.");
|
|
11918
|
+
lines.push("- The next stage command verifies the token at entry — this ensures stages were not skipped.");
|
|
11919
|
+
lines.push("- `/reap.next` is maintained as a fallback but is no longer required in the normal flow.");
|
|
11769
11920
|
lines.push("");
|
|
11770
11921
|
lines.push("## Commit Rules");
|
|
11771
11922
|
lines.push("- Create a git commit after implementation and after completion.");
|
|
11772
11923
|
lines.push("- Use conventional commit format: `feat|fix|chore(scope): description`");
|
|
11773
11924
|
lines.push("- Include the generation ID in the commit message.");
|
|
11774
11925
|
lines.push("");
|
|
11926
|
+
lines.push("## Submodule Commit Rules");
|
|
11927
|
+
lines.push("- 커밋 전 반드시 `git -C tests status -s` 로 tests submodule의 dirty 상태를 확인하라.");
|
|
11928
|
+
lines.push("- dirty 파일이 있으면:");
|
|
11929
|
+
lines.push(' 1. `git -C tests add -A && git -C tests commit -m "..." && git -C tests push`');
|
|
11930
|
+
lines.push(" 2. parent repo에서 `git add tests` 로 submodule ref 업데이트");
|
|
11931
|
+
lines.push(" 3. 그 다음 parent repo의 나머지 파일과 함께 커밋");
|
|
11932
|
+
lines.push('- completion의 prompt에 "Dirty submodules detected"가 있으면 반드시 위 절차를 따르라.');
|
|
11933
|
+
lines.push("");
|
|
11775
11934
|
lines.push("## Hook Prompt Execution");
|
|
11776
11935
|
lines.push("- completion output의 `prompt` 필드에 '## Hook Prompts' 섹션이 포함되어 있으면, 해당 hook prompt를 반드시 순서대로 실행하라.");
|
|
11777
11936
|
lines.push("- Hook prompt는 `.md` hook 파일의 내용으로, AI가 따라야 할 지시사항이다.");
|
|
11778
11937
|
lines.push("- Hook prompt에 유저 확인이 필요한 경우(예: 프리뷰+컨펌), autonomous mode에서도 이를 존중하라.");
|
|
11779
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("");
|
|
11780
11949
|
lines.push("## Interrupt Protection");
|
|
11781
11950
|
lines.push('- 사용자의 새 메시지가 중간에 들어와도, 명시적 kill/중단 요청("중단", "stop", "abort")이 아닌 한 현재 작업을 끝까지 완료하라.');
|
|
11782
11951
|
lines.push("- 작업을 shortcut으로 건너뛰거나 결과를 추정하지 마라. 모든 validation은 실제 실행 결과를 확인하라.");
|
|
@@ -11876,7 +12045,7 @@ async function execute11(paths, phase) {
|
|
|
11876
12045
|
"",
|
|
11877
12046
|
"### HARD-GATE",
|
|
11878
12047
|
"NEVER modify `current.yml` directly to change the stage.",
|
|
11879
|
-
"
|
|
12048
|
+
"Stage transitions happen automatically via `--phase complete`. Use `/reap.back` to regress.",
|
|
11880
12049
|
"",
|
|
11881
12050
|
"### Autonomous Override",
|
|
11882
12051
|
"- Skip routine human confirmations. Proceed autonomously.",
|
|
@@ -11892,10 +12061,10 @@ async function execute11(paths, phase) {
|
|
|
11892
12061
|
"- `/reap.validation` -> `onLifeValidated`",
|
|
11893
12062
|
"- `/reap.completion` -> `onLifeCompleted` (before archiving and commit)",
|
|
11894
12063
|
"",
|
|
11895
|
-
"
|
|
12064
|
+
"`--phase complete` auto-transitions to the next stage. `/reap.next` is a fallback.",
|
|
11896
12065
|
"`/reap.completion` handles archiving and the final commit.",
|
|
11897
12066
|
"",
|
|
11898
|
-
"### Lifecycle Loop",
|
|
12067
|
+
"### Lifecycle Loop (Auto-Transition)",
|
|
11899
12068
|
"Execute the following loop until the generation is complete:",
|
|
11900
12069
|
"1. Read `current.yml` to determine the current stage",
|
|
11901
12070
|
"2. Execute the corresponding stage command:",
|
|
@@ -11904,14 +12073,16 @@ async function execute11(paths, phase) {
|
|
|
11904
12073
|
" - `implementation` -> `/reap.implementation`",
|
|
11905
12074
|
" - `validation` -> `/reap.validation`",
|
|
11906
12075
|
" - `completion` -> `/reap.completion`",
|
|
11907
|
-
"3.
|
|
11908
|
-
" - If the
|
|
11909
|
-
" - Otherwise:
|
|
12076
|
+
"3. `--phase complete` auto-transitions to the next stage.",
|
|
12077
|
+
" - If the stage is `completion`: the loop ends.",
|
|
12078
|
+
" - Otherwise: follow the `nextCommand` in the output to run the next stage.",
|
|
11910
12079
|
"",
|
|
11911
12080
|
"### Handling Issues",
|
|
11912
12081
|
"- If validation fails: `/reap.back` to return to implementation (or earlier), then resume the loop",
|
|
11913
|
-
"- If the
|
|
11914
|
-
"- If
|
|
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",
|
|
12085
|
+
"- If the human wants to pause: stop the loop"
|
|
11915
12086
|
].join(`
|
|
11916
12087
|
`)
|
|
11917
12088
|
});
|
|
@@ -11957,8 +12128,8 @@ var exports_sync_genome = {};
|
|
|
11957
12128
|
__export(exports_sync_genome, {
|
|
11958
12129
|
execute: () => execute13
|
|
11959
12130
|
});
|
|
11960
|
-
import { readdir as
|
|
11961
|
-
import { join as
|
|
12131
|
+
import { readdir as readdir19 } from "fs/promises";
|
|
12132
|
+
import { join as join26 } from "path";
|
|
11962
12133
|
async function execute13(paths, phase) {
|
|
11963
12134
|
const gm = new GenerationManager(paths);
|
|
11964
12135
|
const state = await gm.current();
|
|
@@ -11970,10 +12141,10 @@ async function execute13(paths, phase) {
|
|
|
11970
12141
|
const sourceMapContent = await readTextFile(paths.sourceMap);
|
|
11971
12142
|
const domainFiles = {};
|
|
11972
12143
|
try {
|
|
11973
|
-
const entries = await
|
|
12144
|
+
const entries = await readdir19(paths.domain);
|
|
11974
12145
|
for (const entry of entries) {
|
|
11975
12146
|
if (entry.endsWith(".md")) {
|
|
11976
|
-
const content = await readTextFile(
|
|
12147
|
+
const content = await readTextFile(join26(paths.domain, entry));
|
|
11977
12148
|
if (content)
|
|
11978
12149
|
domainFiles[entry] = content.slice(0, 1000);
|
|
11979
12150
|
}
|
|
@@ -11981,7 +12152,7 @@ async function execute13(paths, phase) {
|
|
|
11981
12152
|
} catch {}
|
|
11982
12153
|
let genomeVersion = 0;
|
|
11983
12154
|
try {
|
|
11984
|
-
const lineageEntries = await
|
|
12155
|
+
const lineageEntries = await readdir19(paths.lineage);
|
|
11985
12156
|
genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length;
|
|
11986
12157
|
} catch {}
|
|
11987
12158
|
emitOutput({
|
|
@@ -12032,20 +12203,26 @@ async function execute13(paths, phase) {
|
|
|
12032
12203
|
});
|
|
12033
12204
|
}
|
|
12034
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);
|
|
12035
12210
|
emitOutput({
|
|
12036
12211
|
status: "ok",
|
|
12037
12212
|
command: "sync-genome",
|
|
12038
12213
|
phase: "complete",
|
|
12039
|
-
completed: ["gate", "context-collect", "analyze", "apply"],
|
|
12214
|
+
completed: ["gate", "context-collect", "analyze", "apply", "update-sync-state"],
|
|
12040
12215
|
context: {
|
|
12041
|
-
hasActiveGeneration: hasActiveGen
|
|
12216
|
+
hasActiveGeneration: hasActiveGen,
|
|
12217
|
+
lastSyncedGeneration: genId
|
|
12042
12218
|
},
|
|
12043
|
-
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}`
|
|
12044
12220
|
});
|
|
12045
12221
|
}
|
|
12046
12222
|
}
|
|
12047
12223
|
var init_sync_genome = __esm(() => {
|
|
12048
12224
|
init_generation();
|
|
12225
|
+
init_config();
|
|
12049
12226
|
init_fs();
|
|
12050
12227
|
});
|
|
12051
12228
|
|
|
@@ -12054,8 +12231,8 @@ var exports_sync_environment = {};
|
|
|
12054
12231
|
__export(exports_sync_environment, {
|
|
12055
12232
|
execute: () => execute14
|
|
12056
12233
|
});
|
|
12057
|
-
import { readdir as
|
|
12058
|
-
import { join as
|
|
12234
|
+
import { readdir as readdir20 } from "fs/promises";
|
|
12235
|
+
import { join as join27 } from "path";
|
|
12059
12236
|
async function execute14(paths, phase) {
|
|
12060
12237
|
const gm = new GenerationManager(paths);
|
|
12061
12238
|
const state = await gm.current();
|
|
@@ -12064,10 +12241,10 @@ async function execute14(paths, phase) {
|
|
|
12064
12241
|
const envSummary = await readTextFile(paths.environmentSummary);
|
|
12065
12242
|
const envDocs = {};
|
|
12066
12243
|
try {
|
|
12067
|
-
const docsEntries = await
|
|
12244
|
+
const docsEntries = await readdir20(paths.environmentDocs);
|
|
12068
12245
|
for (const entry of docsEntries) {
|
|
12069
12246
|
if (entry.endsWith(".md")) {
|
|
12070
|
-
const content = await readTextFile(
|
|
12247
|
+
const content = await readTextFile(join27(paths.environmentDocs, entry));
|
|
12071
12248
|
if (content)
|
|
12072
12249
|
envDocs[entry] = content.slice(0, 1000);
|
|
12073
12250
|
}
|
|
@@ -12075,7 +12252,7 @@ async function execute14(paths, phase) {
|
|
|
12075
12252
|
} catch {}
|
|
12076
12253
|
let linksContent = null;
|
|
12077
12254
|
try {
|
|
12078
|
-
linksContent = await readTextFile(
|
|
12255
|
+
linksContent = await readTextFile(join27(paths.environmentResources, "links.md"));
|
|
12079
12256
|
} catch {}
|
|
12080
12257
|
emitOutput({
|
|
12081
12258
|
status: "prompt",
|
|
@@ -12147,7 +12324,7 @@ var exports_help = {};
|
|
|
12147
12324
|
__export(exports_help, {
|
|
12148
12325
|
execute: () => execute15
|
|
12149
12326
|
});
|
|
12150
|
-
import { join as
|
|
12327
|
+
import { join as join28 } from "path";
|
|
12151
12328
|
function detectLanguage(configContent) {
|
|
12152
12329
|
const raw = configContent?.match(/language:\s*(\S+)/)?.[1] ?? null;
|
|
12153
12330
|
if (raw && raw in LANGUAGE_ALIASES)
|
|
@@ -12182,7 +12359,7 @@ async function execute15(paths) {
|
|
|
12182
12359
|
const gm = new GenerationManager(paths);
|
|
12183
12360
|
const state = await gm.current();
|
|
12184
12361
|
const configContent = await readTextFile(paths.config);
|
|
12185
|
-
const installedVersion = "0.
|
|
12362
|
+
const installedVersion = "0.15.1";
|
|
12186
12363
|
const autoUpdate = configContent?.match(/autoUpdate:\s*(true|false)/)?.[1] === "true";
|
|
12187
12364
|
const versionDisplay = formatVersionLine(installedVersion, !autoUpdate);
|
|
12188
12365
|
const rawLang = detectLanguage(configContent);
|
|
@@ -12193,7 +12370,7 @@ async function execute15(paths) {
|
|
|
12193
12370
|
const stateDisplay = state?.id ? `Active: **${state.id}** — ${state.goal} (Stage: ${state.stage})` : "No active Generation → `/reap.start` or `/reap.evolve`";
|
|
12194
12371
|
const lines = buildLines(versionDisplay, lang, stateDisplay);
|
|
12195
12372
|
if (topic) {
|
|
12196
|
-
const guidePath =
|
|
12373
|
+
const guidePath = join28(ReapPaths.packageHooksDir, "reap-guide.md");
|
|
12197
12374
|
const reapGuide = await readTextFile(guidePath) ?? "";
|
|
12198
12375
|
emitOutput({
|
|
12199
12376
|
status: "prompt",
|
|
@@ -12498,8 +12675,8 @@ var init_merge = __esm(() => {
|
|
|
12498
12675
|
});
|
|
12499
12676
|
|
|
12500
12677
|
// src/core/merge-generation.ts
|
|
12501
|
-
import { readdir as
|
|
12502
|
-
import { join as
|
|
12678
|
+
import { readdir as readdir21, mkdir as mkdir10, rename as rename3 } from "fs/promises";
|
|
12679
|
+
import { join as join29 } from "path";
|
|
12503
12680
|
|
|
12504
12681
|
class MergeGenerationManager {
|
|
12505
12682
|
paths;
|
|
@@ -12510,7 +12687,7 @@ class MergeGenerationManager {
|
|
|
12510
12687
|
const content = await readTextFile(this.paths.currentYml);
|
|
12511
12688
|
if (content === null || !content.trim())
|
|
12512
12689
|
return null;
|
|
12513
|
-
const state =
|
|
12690
|
+
const state = import_yaml11.default.parse(content);
|
|
12514
12691
|
if (!state.type)
|
|
12515
12692
|
state.type = "normal";
|
|
12516
12693
|
if (!state.parents)
|
|
@@ -12543,7 +12720,7 @@ class MergeGenerationManager {
|
|
|
12543
12720
|
genomeHash,
|
|
12544
12721
|
commonAncestor: commonAncestor ?? undefined
|
|
12545
12722
|
};
|
|
12546
|
-
await writeTextFile(this.paths.currentYml,
|
|
12723
|
+
await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
|
|
12547
12724
|
return state;
|
|
12548
12725
|
}
|
|
12549
12726
|
async createFromBranch(targetBranch, projectRoot) {
|
|
@@ -12587,7 +12764,7 @@ class MergeGenerationManager {
|
|
|
12587
12764
|
genomeHash,
|
|
12588
12765
|
commonAncestor: commonAncestor ?? undefined
|
|
12589
12766
|
};
|
|
12590
|
-
await writeTextFile(this.paths.currentYml,
|
|
12767
|
+
await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
|
|
12591
12768
|
return { state, report };
|
|
12592
12769
|
}
|
|
12593
12770
|
async resolveLatestGenId(branch, cwd) {
|
|
@@ -12612,7 +12789,7 @@ class MergeGenerationManager {
|
|
|
12612
12789
|
const content = gitShow(ref, metaFile, cwd);
|
|
12613
12790
|
if (content) {
|
|
12614
12791
|
try {
|
|
12615
|
-
const meta =
|
|
12792
|
+
const meta = import_yaml11.default.parse(content);
|
|
12616
12793
|
if (meta?.id)
|
|
12617
12794
|
metas.push(meta);
|
|
12618
12795
|
} catch {}
|
|
@@ -12659,7 +12836,7 @@ class MergeGenerationManager {
|
|
|
12659
12836
|
if (!state.timeline)
|
|
12660
12837
|
state.timeline = [];
|
|
12661
12838
|
state.timeline.push({ stage: next, at: new Date().toISOString() });
|
|
12662
|
-
await writeTextFile(this.paths.currentYml,
|
|
12839
|
+
await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
|
|
12663
12840
|
return state;
|
|
12664
12841
|
}
|
|
12665
12842
|
async complete() {
|
|
@@ -12675,7 +12852,7 @@ class MergeGenerationManager {
|
|
|
12675
12852
|
const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
|
|
12676
12853
|
const genDirName = `${state.id}-${goalSlug}`;
|
|
12677
12854
|
const genDir = this.paths.generationDir(genDirName);
|
|
12678
|
-
await
|
|
12855
|
+
await mkdir10(genDir, { recursive: true });
|
|
12679
12856
|
const meta = {
|
|
12680
12857
|
id: state.id,
|
|
12681
12858
|
type: "merge",
|
|
@@ -12685,19 +12862,19 @@ class MergeGenerationManager {
|
|
|
12685
12862
|
startedAt: state.startedAt,
|
|
12686
12863
|
completedAt: now
|
|
12687
12864
|
};
|
|
12688
|
-
await writeTextFile(
|
|
12689
|
-
const lifeEntries = await
|
|
12865
|
+
await writeTextFile(join29(genDir, "meta.yml"), import_yaml11.default.stringify(meta));
|
|
12866
|
+
const lifeEntries = await readdir21(this.paths.life);
|
|
12690
12867
|
for (const entry of lifeEntries) {
|
|
12691
12868
|
if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
|
|
12692
|
-
await rename3(
|
|
12869
|
+
await rename3(join29(this.paths.life, entry), join29(genDir, entry));
|
|
12693
12870
|
}
|
|
12694
12871
|
}
|
|
12695
|
-
const backlogDir =
|
|
12696
|
-
await
|
|
12872
|
+
const backlogDir = join29(genDir, "backlog");
|
|
12873
|
+
await mkdir10(backlogDir, { recursive: true });
|
|
12697
12874
|
try {
|
|
12698
|
-
const backlogEntries = await
|
|
12875
|
+
const backlogEntries = await readdir21(this.paths.backlog);
|
|
12699
12876
|
for (const entry of backlogEntries) {
|
|
12700
|
-
await rename3(
|
|
12877
|
+
await rename3(join29(this.paths.backlog, entry), join29(backlogDir, entry));
|
|
12701
12878
|
}
|
|
12702
12879
|
} catch {}
|
|
12703
12880
|
await writeTextFile(this.paths.currentYml, "");
|
|
@@ -12705,7 +12882,7 @@ class MergeGenerationManager {
|
|
|
12705
12882
|
return compression;
|
|
12706
12883
|
}
|
|
12707
12884
|
async save(state) {
|
|
12708
|
-
await writeTextFile(this.paths.currentYml,
|
|
12885
|
+
await writeTextFile(this.paths.currentYml, import_yaml11.default.stringify(state));
|
|
12709
12886
|
}
|
|
12710
12887
|
}
|
|
12711
12888
|
function canFastForward(localLatestId, remoteLatestId, allMetas) {
|
|
@@ -12767,7 +12944,7 @@ function findCommonAncestor(idA, idB, metas) {
|
|
|
12767
12944
|
}
|
|
12768
12945
|
return null;
|
|
12769
12946
|
}
|
|
12770
|
-
var
|
|
12947
|
+
var import_yaml11;
|
|
12771
12948
|
var init_merge_generation = __esm(() => {
|
|
12772
12949
|
init_merge_lifecycle();
|
|
12773
12950
|
init_compression();
|
|
@@ -12777,7 +12954,7 @@ var init_merge_generation = __esm(() => {
|
|
|
12777
12954
|
init_merge();
|
|
12778
12955
|
init_lineage();
|
|
12779
12956
|
init_compression();
|
|
12780
|
-
|
|
12957
|
+
import_yaml11 = __toESM(require_dist(), 1);
|
|
12781
12958
|
});
|
|
12782
12959
|
|
|
12783
12960
|
// src/cli/commands/run/merge-start.ts
|
|
@@ -12894,6 +13071,8 @@ async function execute18(paths, phase) {
|
|
|
12894
13071
|
emitError("merge-detect", "01-detect.md does not exist. Run /reap.merge.start first.");
|
|
12895
13072
|
}
|
|
12896
13073
|
const detectContent = await readTextFile(detectArtifact);
|
|
13074
|
+
setPhaseNonce(state, "detect", "review");
|
|
13075
|
+
await mgm.save(state);
|
|
12897
13076
|
emitOutput({
|
|
12898
13077
|
status: "prompt",
|
|
12899
13078
|
command: "merge-detect",
|
|
@@ -12922,24 +13101,37 @@ async function execute18(paths, phase) {
|
|
|
12922
13101
|
});
|
|
12923
13102
|
}
|
|
12924
13103
|
if (phase === "complete") {
|
|
13104
|
+
verifyPhaseEntry("merge-detect", state, "detect", "review");
|
|
13105
|
+
await mgm.save(state);
|
|
13106
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13107
|
+
state.expectedHash = hash;
|
|
13108
|
+
state.lastNonce = nonce;
|
|
12925
13109
|
const hookResults = await executeHooks(paths.hooks, "onMergeDetected", paths.projectRoot);
|
|
13110
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13111
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
12926
13112
|
emitOutput({
|
|
12927
13113
|
status: "ok",
|
|
12928
13114
|
command: "merge-detect",
|
|
12929
13115
|
phase: "complete",
|
|
12930
|
-
completed: ["gate", "artifact-read", "review", "hooks"],
|
|
13116
|
+
completed: ["gate", "artifact-read", "review", "hooks", "auto-transition"],
|
|
12931
13117
|
context: {
|
|
12932
13118
|
id: state.id,
|
|
12933
|
-
hookResults
|
|
13119
|
+
hookResults,
|
|
13120
|
+
nextStage: transition.nextStage,
|
|
13121
|
+
artifactFile: transition.artifactFile,
|
|
13122
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
12934
13123
|
},
|
|
12935
|
-
message:
|
|
13124
|
+
message: `Detect stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13125
|
+
nextCommand
|
|
12936
13126
|
});
|
|
12937
13127
|
}
|
|
12938
13128
|
}
|
|
12939
13129
|
var init_merge_detect = __esm(() => {
|
|
12940
13130
|
init_merge_generation();
|
|
13131
|
+
init_generation();
|
|
12941
13132
|
init_fs();
|
|
12942
13133
|
init_hook_engine();
|
|
13134
|
+
init_stage_transition();
|
|
12943
13135
|
});
|
|
12944
13136
|
|
|
12945
13137
|
// src/cli/commands/run/merge-mate.ts
|
|
@@ -12959,12 +13151,16 @@ async function execute19(paths, phase) {
|
|
|
12959
13151
|
if (state.stage !== "mate") {
|
|
12960
13152
|
emitError("merge-mate", `Stage is '${state.stage}', expected 'mate'.`);
|
|
12961
13153
|
}
|
|
13154
|
+
verifyStageEntry("merge-mate", state);
|
|
13155
|
+
await mgm.save(state);
|
|
12962
13156
|
const detectArtifact = paths.artifact("01-detect.md");
|
|
12963
13157
|
if (!await fileExists(detectArtifact)) {
|
|
12964
13158
|
emitError("merge-mate", "01-detect.md does not exist. Complete detect stage first.");
|
|
12965
13159
|
}
|
|
12966
13160
|
if (!phase || phase === "resolve") {
|
|
12967
13161
|
const detectContent = await readTextFile(detectArtifact);
|
|
13162
|
+
setPhaseNonce(state, "mate", "resolve");
|
|
13163
|
+
await mgm.save(state);
|
|
12968
13164
|
emitOutput({
|
|
12969
13165
|
status: "prompt",
|
|
12970
13166
|
command: "merge-mate",
|
|
@@ -13004,24 +13200,37 @@ async function execute19(paths, phase) {
|
|
|
13004
13200
|
});
|
|
13005
13201
|
}
|
|
13006
13202
|
if (phase === "complete") {
|
|
13203
|
+
verifyPhaseEntry("merge-mate", state, "mate", "resolve");
|
|
13204
|
+
await mgm.save(state);
|
|
13205
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13206
|
+
state.expectedHash = hash;
|
|
13207
|
+
state.lastNonce = nonce;
|
|
13007
13208
|
const hookResults = await executeHooks(paths.hooks, "onMergeMated", paths.projectRoot);
|
|
13209
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13210
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
13008
13211
|
emitOutput({
|
|
13009
13212
|
status: "ok",
|
|
13010
13213
|
command: "merge-mate",
|
|
13011
13214
|
phase: "complete",
|
|
13012
|
-
completed: ["gate", "artifact-read", "conflict-resolution", "hooks"],
|
|
13215
|
+
completed: ["gate", "artifact-read", "conflict-resolution", "hooks", "auto-transition"],
|
|
13013
13216
|
context: {
|
|
13014
13217
|
id: state.id,
|
|
13015
|
-
hookResults
|
|
13218
|
+
hookResults,
|
|
13219
|
+
nextStage: transition.nextStage,
|
|
13220
|
+
artifactFile: transition.artifactFile,
|
|
13221
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
13016
13222
|
},
|
|
13017
|
-
message:
|
|
13223
|
+
message: `Mate stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13224
|
+
nextCommand
|
|
13018
13225
|
});
|
|
13019
13226
|
}
|
|
13020
13227
|
}
|
|
13021
13228
|
var init_merge_mate = __esm(() => {
|
|
13022
13229
|
init_merge_generation();
|
|
13230
|
+
init_generation();
|
|
13023
13231
|
init_fs();
|
|
13024
13232
|
init_hook_engine();
|
|
13233
|
+
init_stage_transition();
|
|
13025
13234
|
});
|
|
13026
13235
|
|
|
13027
13236
|
// src/cli/commands/run/merge-merge.ts
|
|
@@ -13041,6 +13250,8 @@ async function execute20(paths, phase) {
|
|
|
13041
13250
|
if (state.stage !== "merge") {
|
|
13042
13251
|
emitError("merge-merge", `Stage is '${state.stage}', expected 'merge'.`);
|
|
13043
13252
|
}
|
|
13253
|
+
verifyStageEntry("merge-merge", state);
|
|
13254
|
+
await mgm.save(state);
|
|
13044
13255
|
const mateArtifact = paths.artifact("02-mate.md");
|
|
13045
13256
|
if (!await fileExists(mateArtifact)) {
|
|
13046
13257
|
emitError("merge-merge", "02-mate.md does not exist. Complete mate stage first.");
|
|
@@ -13049,6 +13260,8 @@ async function execute20(paths, phase) {
|
|
|
13049
13260
|
const mateContent = await readTextFile(mateArtifact);
|
|
13050
13261
|
const detectContent = await readTextFile(paths.artifact("01-detect.md"));
|
|
13051
13262
|
const targetBranch = state.goal.split(" + ").pop() ?? "";
|
|
13263
|
+
setPhaseNonce(state, "merge", "work");
|
|
13264
|
+
await mgm.save(state);
|
|
13052
13265
|
emitOutput({
|
|
13053
13266
|
status: "prompt",
|
|
13054
13267
|
command: "merge-merge",
|
|
@@ -13083,24 +13296,37 @@ async function execute20(paths, phase) {
|
|
|
13083
13296
|
});
|
|
13084
13297
|
}
|
|
13085
13298
|
if (phase === "complete") {
|
|
13299
|
+
verifyPhaseEntry("merge-merge", state, "merge", "work");
|
|
13300
|
+
await mgm.save(state);
|
|
13301
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13302
|
+
state.expectedHash = hash;
|
|
13303
|
+
state.lastNonce = nonce;
|
|
13086
13304
|
const hookResults = await executeHooks(paths.hooks, "onMergeMerged", paths.projectRoot);
|
|
13305
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13306
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
13087
13307
|
emitOutput({
|
|
13088
13308
|
status: "ok",
|
|
13089
13309
|
command: "merge-merge",
|
|
13090
13310
|
phase: "complete",
|
|
13091
|
-
completed: ["gate", "artifact-read", "source-merge", "hooks"],
|
|
13311
|
+
completed: ["gate", "artifact-read", "source-merge", "hooks", "auto-transition"],
|
|
13092
13312
|
context: {
|
|
13093
13313
|
id: state.id,
|
|
13094
|
-
hookResults
|
|
13314
|
+
hookResults,
|
|
13315
|
+
nextStage: transition.nextStage,
|
|
13316
|
+
artifactFile: transition.artifactFile,
|
|
13317
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
13095
13318
|
},
|
|
13096
|
-
message:
|
|
13319
|
+
message: `Merge stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13320
|
+
nextCommand
|
|
13097
13321
|
});
|
|
13098
13322
|
}
|
|
13099
13323
|
}
|
|
13100
13324
|
var init_merge_merge = __esm(() => {
|
|
13101
13325
|
init_merge_generation();
|
|
13326
|
+
init_generation();
|
|
13102
13327
|
init_fs();
|
|
13103
13328
|
init_hook_engine();
|
|
13329
|
+
init_stage_transition();
|
|
13104
13330
|
});
|
|
13105
13331
|
|
|
13106
13332
|
// src/cli/commands/run/merge-sync.ts
|
|
@@ -13120,6 +13346,8 @@ async function execute21(paths, phase) {
|
|
|
13120
13346
|
if (state.stage !== "sync") {
|
|
13121
13347
|
emitError("merge-sync", `Stage is '${state.stage}', expected 'sync'.`);
|
|
13122
13348
|
}
|
|
13349
|
+
verifyStageEntry("merge-sync", state);
|
|
13350
|
+
await mgm.save(state);
|
|
13123
13351
|
const mergeArtifact = paths.artifact("03-merge.md");
|
|
13124
13352
|
if (!await fileExists(mergeArtifact)) {
|
|
13125
13353
|
emitError("merge-sync", "03-merge.md does not exist. Complete merge stage first.");
|
|
@@ -13129,6 +13357,8 @@ async function execute21(paths, phase) {
|
|
|
13129
13357
|
const genomeConstraints = await readTextFile(paths.constraints);
|
|
13130
13358
|
const genomePrinciples = await readTextFile(paths.principles);
|
|
13131
13359
|
const mergeContent = await readTextFile(mergeArtifact);
|
|
13360
|
+
setPhaseNonce(state, "sync", "verify");
|
|
13361
|
+
await mgm.save(state);
|
|
13132
13362
|
emitOutput({
|
|
13133
13363
|
status: "prompt",
|
|
13134
13364
|
command: "merge-sync",
|
|
@@ -13173,24 +13403,37 @@ async function execute21(paths, phase) {
|
|
|
13173
13403
|
});
|
|
13174
13404
|
}
|
|
13175
13405
|
if (phase === "complete") {
|
|
13406
|
+
verifyPhaseEntry("merge-sync", state, "sync", "verify");
|
|
13407
|
+
await mgm.save(state);
|
|
13408
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13409
|
+
state.expectedHash = hash;
|
|
13410
|
+
state.lastNonce = nonce;
|
|
13176
13411
|
const hookResults = await executeHooks(paths.hooks, "onMergeSynced", paths.projectRoot);
|
|
13412
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13413
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
13177
13414
|
emitOutput({
|
|
13178
13415
|
status: "ok",
|
|
13179
13416
|
command: "merge-sync",
|
|
13180
13417
|
phase: "complete",
|
|
13181
|
-
completed: ["gate", "context-collect", "sync-verify", "hooks"],
|
|
13418
|
+
completed: ["gate", "context-collect", "sync-verify", "hooks", "auto-transition"],
|
|
13182
13419
|
context: {
|
|
13183
13420
|
id: state.id,
|
|
13184
|
-
hookResults
|
|
13421
|
+
hookResults,
|
|
13422
|
+
nextStage: transition.nextStage,
|
|
13423
|
+
artifactFile: transition.artifactFile,
|
|
13424
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
13185
13425
|
},
|
|
13186
|
-
message:
|
|
13426
|
+
message: `Sync stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13427
|
+
nextCommand
|
|
13187
13428
|
});
|
|
13188
13429
|
}
|
|
13189
13430
|
}
|
|
13190
13431
|
var init_merge_sync = __esm(() => {
|
|
13191
13432
|
init_merge_generation();
|
|
13433
|
+
init_generation();
|
|
13192
13434
|
init_fs();
|
|
13193
13435
|
init_hook_engine();
|
|
13436
|
+
init_stage_transition();
|
|
13194
13437
|
});
|
|
13195
13438
|
|
|
13196
13439
|
// src/cli/commands/run/merge-validation.ts
|
|
@@ -13210,12 +13453,16 @@ async function execute22(paths, phase) {
|
|
|
13210
13453
|
if (state.stage !== "validation") {
|
|
13211
13454
|
emitError("merge-validation", `Stage is '${state.stage}', expected 'validation'.`);
|
|
13212
13455
|
}
|
|
13456
|
+
verifyStageEntry("merge-validation", state);
|
|
13457
|
+
await mgm.save(state);
|
|
13213
13458
|
const syncArtifact = paths.artifact("04-sync.md");
|
|
13214
13459
|
if (!await fileExists(syncArtifact)) {
|
|
13215
13460
|
emitError("merge-validation", "04-sync.md does not exist. Complete sync stage first.");
|
|
13216
13461
|
}
|
|
13217
13462
|
if (!phase || phase === "work") {
|
|
13218
13463
|
const constraintsContent = await readTextFile(paths.constraints);
|
|
13464
|
+
setPhaseNonce(state, "validation", "work");
|
|
13465
|
+
await mgm.save(state);
|
|
13219
13466
|
emitOutput({
|
|
13220
13467
|
status: "prompt",
|
|
13221
13468
|
command: "merge-validation",
|
|
@@ -13252,28 +13499,41 @@ async function execute22(paths, phase) {
|
|
|
13252
13499
|
});
|
|
13253
13500
|
}
|
|
13254
13501
|
if (phase === "complete") {
|
|
13502
|
+
verifyPhaseEntry("merge-validation", state, "validation", "work");
|
|
13503
|
+
await mgm.save(state);
|
|
13255
13504
|
const validationArtifact = paths.artifact("05-validation.md");
|
|
13256
13505
|
if (!await fileExists(validationArtifact)) {
|
|
13257
13506
|
emitError("merge-validation", "05-validation.md does not exist. Complete validation work first.");
|
|
13258
13507
|
}
|
|
13508
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13509
|
+
state.expectedHash = hash;
|
|
13510
|
+
state.lastNonce = nonce;
|
|
13259
13511
|
const hookResults = await executeHooks(paths.hooks, "onMergeValidated", paths.projectRoot);
|
|
13512
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13513
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
13260
13514
|
emitOutput({
|
|
13261
13515
|
status: "ok",
|
|
13262
13516
|
command: "merge-validation",
|
|
13263
13517
|
phase: "complete",
|
|
13264
|
-
completed: ["gate", "context-collect", "validation-work", "hooks"],
|
|
13518
|
+
completed: ["gate", "context-collect", "validation-work", "hooks", "auto-transition"],
|
|
13265
13519
|
context: {
|
|
13266
13520
|
id: state.id,
|
|
13267
|
-
hookResults
|
|
13521
|
+
hookResults,
|
|
13522
|
+
nextStage: transition.nextStage,
|
|
13523
|
+
artifactFile: transition.artifactFile,
|
|
13524
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
13268
13525
|
},
|
|
13269
|
-
message:
|
|
13526
|
+
message: `Validation stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13527
|
+
nextCommand
|
|
13270
13528
|
});
|
|
13271
13529
|
}
|
|
13272
13530
|
}
|
|
13273
13531
|
var init_merge_validation = __esm(() => {
|
|
13274
13532
|
init_merge_generation();
|
|
13533
|
+
init_generation();
|
|
13275
13534
|
init_fs();
|
|
13276
13535
|
init_hook_engine();
|
|
13536
|
+
init_stage_transition();
|
|
13277
13537
|
});
|
|
13278
13538
|
|
|
13279
13539
|
// src/cli/commands/run/merge-completion.ts
|
|
@@ -13293,6 +13553,8 @@ async function execute23(paths, phase) {
|
|
|
13293
13553
|
if (state.stage !== "completion") {
|
|
13294
13554
|
emitError("merge-completion", `Stage is '${state.stage}', expected 'completion'.`);
|
|
13295
13555
|
}
|
|
13556
|
+
verifyStageEntry("merge-completion", state);
|
|
13557
|
+
await mgm.save(state);
|
|
13296
13558
|
const validationArtifact = paths.artifact("05-validation.md");
|
|
13297
13559
|
if (!await fileExists(validationArtifact)) {
|
|
13298
13560
|
emitError("merge-completion", "05-validation.md does not exist. Complete validation first.");
|
|
@@ -13302,6 +13564,8 @@ async function execute23(paths, phase) {
|
|
|
13302
13564
|
const mateContent = await readTextFile(paths.artifact("02-mate.md"));
|
|
13303
13565
|
const mergeContent = await readTextFile(paths.artifact("03-merge.md"));
|
|
13304
13566
|
const validationContent = await readTextFile(validationArtifact);
|
|
13567
|
+
setPhaseNonce(state, "completion", "retrospective");
|
|
13568
|
+
await mgm.save(state);
|
|
13305
13569
|
emitOutput({
|
|
13306
13570
|
status: "prompt",
|
|
13307
13571
|
command: "merge-completion",
|
|
@@ -13332,6 +13596,8 @@ async function execute23(paths, phase) {
|
|
|
13332
13596
|
});
|
|
13333
13597
|
}
|
|
13334
13598
|
if (phase === "archive") {
|
|
13599
|
+
verifyPhaseEntry("merge-completion", state, "completion", "retrospective");
|
|
13600
|
+
await mgm.save(state);
|
|
13335
13601
|
const hookResults = await executeHooks(paths.hooks, "onMergeCompleted", paths.projectRoot);
|
|
13336
13602
|
const submodules = checkSubmodules(paths.projectRoot);
|
|
13337
13603
|
const dirtySubmodules = submodules.filter((s) => s.dirty);
|
|
@@ -13358,6 +13624,7 @@ var init_merge_completion = __esm(() => {
|
|
|
13358
13624
|
init_fs();
|
|
13359
13625
|
init_hook_engine();
|
|
13360
13626
|
init_commit();
|
|
13627
|
+
init_stage_transition();
|
|
13361
13628
|
});
|
|
13362
13629
|
|
|
13363
13630
|
// src/cli/commands/run/merge-evolve.ts
|
|
@@ -13410,10 +13677,10 @@ async function execute24(paths, phase) {
|
|
|
13410
13677
|
"- `/reap.merge.validation` -> `onMergeValidated`",
|
|
13411
13678
|
"- `/reap.merge.completion` -> `onMergeCompleted` (before archiving and commit)",
|
|
13412
13679
|
"",
|
|
13413
|
-
"
|
|
13680
|
+
"`--phase complete` auto-transitions to the next stage. `/reap.next` is a fallback.",
|
|
13414
13681
|
"`/reap.merge.completion` handles archiving and the final commit.",
|
|
13415
13682
|
"",
|
|
13416
|
-
"### Merge Lifecycle Loop",
|
|
13683
|
+
"### Merge Lifecycle Loop (Auto-Transition)",
|
|
13417
13684
|
"Execute the following loop until the generation is complete:",
|
|
13418
13685
|
"1. Read `current.yml` to determine the current stage",
|
|
13419
13686
|
"2. Execute the corresponding merge stage command:",
|
|
@@ -13423,9 +13690,17 @@ async function execute24(paths, phase) {
|
|
|
13423
13690
|
" - `sync` -> `/reap.merge.sync`",
|
|
13424
13691
|
" - `validation` -> `/reap.merge.validation`",
|
|
13425
13692
|
" - `completion` -> `/reap.merge.completion`",
|
|
13426
|
-
"3.
|
|
13427
|
-
" - If the
|
|
13428
|
-
" - Otherwise:
|
|
13693
|
+
"3. `--phase complete` auto-transitions to the next stage.",
|
|
13694
|
+
" - If the stage is `completion`: the loop ends.",
|
|
13695
|
+
" - Otherwise: follow the `nextCommand` in the output to run the next stage.",
|
|
13696
|
+
"",
|
|
13697
|
+
"### Submodule Commit Rules",
|
|
13698
|
+
"- 커밋 전 반드시 `git -C tests status -s` 로 tests submodule의 dirty 상태를 확인하라.",
|
|
13699
|
+
"- dirty 파일이 있으면:",
|
|
13700
|
+
' 1. `git -C tests add -A && git -C tests commit -m "..." && git -C tests push`',
|
|
13701
|
+
" 2. parent repo에서 `git add tests` 로 submodule ref 업데이트",
|
|
13702
|
+
" 3. 그 다음 parent repo의 나머지 파일과 함께 커밋",
|
|
13703
|
+
'- completion의 prompt에 "Dirty submodules detected"가 있으면 반드시 위 절차를 따르라.',
|
|
13429
13704
|
"",
|
|
13430
13705
|
"### Handling Issues",
|
|
13431
13706
|
"- If validation fails: `/reap.back merge` or `/reap.back mate`, then resume the loop",
|
|
@@ -13527,12 +13802,174 @@ var init_merge2 = __esm(() => {
|
|
|
13527
13802
|
init_lineage();
|
|
13528
13803
|
});
|
|
13529
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
|
+
|
|
13530
13967
|
// src/cli/commands/run/pull.ts
|
|
13531
13968
|
var exports_pull = {};
|
|
13532
13969
|
__export(exports_pull, {
|
|
13533
|
-
execute: () =>
|
|
13970
|
+
execute: () => execute27
|
|
13534
13971
|
});
|
|
13535
|
-
async function
|
|
13972
|
+
async function execute27(paths, phase, argv = []) {
|
|
13536
13973
|
const positionals = argv.filter((a) => !a.startsWith("--"));
|
|
13537
13974
|
const targetBranchArg = positionals[0];
|
|
13538
13975
|
const gm = new GenerationManager(paths);
|
|
@@ -13591,9 +14028,9 @@ async function execute26(paths, phase, argv = []) {
|
|
|
13591
14028
|
const localLatest = localMetas.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())[0];
|
|
13592
14029
|
const remoteLatest = remoteMetasRaw.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())[0];
|
|
13593
14030
|
const allMetas = [...localMetas];
|
|
13594
|
-
for (const
|
|
13595
|
-
if (!allMetas.find((m) => m.id ===
|
|
13596
|
-
allMetas.push(
|
|
14031
|
+
for (const rm5 of remoteMetasRaw) {
|
|
14032
|
+
if (!allMetas.find((m) => m.id === rm5.id))
|
|
14033
|
+
allMetas.push(rm5);
|
|
13597
14034
|
}
|
|
13598
14035
|
const ffResult = canFastForward(localLatest.id, remoteLatest.id, allMetas);
|
|
13599
14036
|
if (ffResult.fastForward) {
|
|
@@ -13653,9 +14090,9 @@ var init_pull = __esm(() => {
|
|
|
13653
14090
|
// src/cli/commands/run/config.ts
|
|
13654
14091
|
var exports_config = {};
|
|
13655
14092
|
__export(exports_config, {
|
|
13656
|
-
execute: () =>
|
|
14093
|
+
execute: () => execute28
|
|
13657
14094
|
});
|
|
13658
|
-
async function
|
|
14095
|
+
async function execute28(paths) {
|
|
13659
14096
|
const config = await ConfigManager.read(paths);
|
|
13660
14097
|
const lines = [
|
|
13661
14098
|
`REAP Configuration (${paths.config})`,
|
|
@@ -13684,25 +14121,119 @@ var init_config2 = __esm(() => {
|
|
|
13684
14121
|
init_config();
|
|
13685
14122
|
});
|
|
13686
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
|
+
|
|
13687
14218
|
// src/cli/commands/run/refresh-knowledge.ts
|
|
13688
14219
|
var exports_refresh_knowledge = {};
|
|
13689
14220
|
__export(exports_refresh_knowledge, {
|
|
13690
|
-
execute: () =>
|
|
14221
|
+
execute: () => execute30
|
|
13691
14222
|
});
|
|
13692
|
-
import { join as
|
|
13693
|
-
import { readdir as
|
|
14223
|
+
import { join as join31 } from "path";
|
|
14224
|
+
import { readdir as readdir23 } from "fs/promises";
|
|
13694
14225
|
async function loadGenome(genomeDir) {
|
|
13695
14226
|
let content = "";
|
|
13696
14227
|
let l1Lines = 0;
|
|
13697
14228
|
let smLimit = null;
|
|
13698
|
-
const smContent = await readTextFile(
|
|
14229
|
+
const smContent = await readTextFile(join31(genomeDir, "source-map.md"));
|
|
13699
14230
|
if (smContent) {
|
|
13700
14231
|
const limitMatch = smContent.match(/줄 수 한도:\s*~?(\d+)줄/);
|
|
13701
14232
|
if (limitMatch)
|
|
13702
14233
|
smLimit = parseInt(limitMatch[1], 10);
|
|
13703
14234
|
}
|
|
13704
14235
|
for (const file of L1_FILES) {
|
|
13705
|
-
const fileContent = await readTextFile(
|
|
14236
|
+
const fileContent = await readTextFile(join31(genomeDir, file));
|
|
13706
14237
|
if (!fileContent)
|
|
13707
14238
|
continue;
|
|
13708
14239
|
const lines = fileContent.split(`
|
|
@@ -13724,14 +14255,14 @@ ${fileContent.split(`
|
|
|
13724
14255
|
`;
|
|
13725
14256
|
}
|
|
13726
14257
|
}
|
|
13727
|
-
const domainDir =
|
|
14258
|
+
const domainDir = join31(genomeDir, "domain");
|
|
13728
14259
|
if (await fileExists(domainDir)) {
|
|
13729
14260
|
let l2Lines = 0;
|
|
13730
14261
|
let l2Overflow = false;
|
|
13731
14262
|
try {
|
|
13732
|
-
const domainFiles = (await
|
|
14263
|
+
const domainFiles = (await readdir23(domainDir)).filter((f) => f.endsWith(".md")).sort();
|
|
13733
14264
|
for (const file of domainFiles) {
|
|
13734
|
-
const fileContent = await readTextFile(
|
|
14265
|
+
const fileContent = await readTextFile(join31(domainDir, file));
|
|
13735
14266
|
if (!fileContent)
|
|
13736
14267
|
continue;
|
|
13737
14268
|
const lines = fileContent.split(`
|
|
@@ -13781,8 +14312,8 @@ function buildStrictSection(strict, genStage) {
|
|
|
13781
14312
|
}
|
|
13782
14313
|
return sections;
|
|
13783
14314
|
}
|
|
13784
|
-
async function
|
|
13785
|
-
const guidePath =
|
|
14315
|
+
async function execute30(paths) {
|
|
14316
|
+
const guidePath = join31(ReapPaths.packageHooksDir, "reap-guide.md");
|
|
13786
14317
|
const reapGuide = await readTextFile(guidePath) || "";
|
|
13787
14318
|
const { content: genomeContent } = await loadGenome(paths.genome);
|
|
13788
14319
|
const envSummary = await readTextFile(paths.environmentSummary) || "";
|
|
@@ -13872,7 +14403,7 @@ async function runCommand(command, phase, argv = []) {
|
|
|
13872
14403
|
try {
|
|
13873
14404
|
const config = await ConfigManager.read(paths);
|
|
13874
14405
|
if (config.autoIssueReport) {
|
|
13875
|
-
const version = "0.
|
|
14406
|
+
const version = "0.15.1";
|
|
13876
14407
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13877
14408
|
const title = `[auto] reap run ${command}: ${errMsg.slice(0, 80)}`;
|
|
13878
14409
|
const body = [
|
|
@@ -13918,8 +14449,10 @@ var init_run = __esm(() => {
|
|
|
13918
14449
|
"merge-completion": () => Promise.resolve().then(() => (init_merge_completion(), exports_merge_completion)),
|
|
13919
14450
|
"merge-evolve": () => Promise.resolve().then(() => (init_merge_evolve(), exports_merge_evolve)),
|
|
13920
14451
|
merge: () => Promise.resolve().then(() => (init_merge2(), exports_merge)),
|
|
14452
|
+
"evolve-recovery": () => Promise.resolve().then(() => (init_evolve_recovery(), exports_evolve_recovery)),
|
|
13921
14453
|
pull: () => Promise.resolve().then(() => (init_pull(), exports_pull)),
|
|
13922
14454
|
config: () => Promise.resolve().then(() => (init_config2(), exports_config)),
|
|
14455
|
+
"update-genome": () => Promise.resolve().then(() => (init_update_genome(), exports_update_genome)),
|
|
13923
14456
|
refreshKnowledge: () => Promise.resolve().then(() => (init_refresh_knowledge(), exports_refresh_knowledge))
|
|
13924
14457
|
};
|
|
13925
14458
|
});
|
|
@@ -13940,11 +14473,14 @@ var {
|
|
|
13940
14473
|
Help
|
|
13941
14474
|
} = import__.default;
|
|
13942
14475
|
|
|
14476
|
+
// src/cli/index.ts
|
|
14477
|
+
import { createInterface } from "readline";
|
|
14478
|
+
|
|
13943
14479
|
// src/cli/commands/init.ts
|
|
13944
14480
|
init_paths();
|
|
13945
14481
|
init_config();
|
|
13946
|
-
import { mkdir as
|
|
13947
|
-
import { join as
|
|
14482
|
+
import { mkdir as mkdir4, readdir as readdir5, chmod } from "fs/promises";
|
|
14483
|
+
import { join as join6 } from "path";
|
|
13948
14484
|
|
|
13949
14485
|
// src/core/agents/claude-code.ts
|
|
13950
14486
|
init_fs();
|
|
@@ -14394,6 +14930,42 @@ class AgentRegistry {
|
|
|
14394
14930
|
|
|
14395
14931
|
// src/cli/commands/init.ts
|
|
14396
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
|
|
14397
14969
|
var COMMAND_NAMES = [
|
|
14398
14970
|
"reap.objective",
|
|
14399
14971
|
"reap.planning",
|
|
@@ -14420,10 +14992,12 @@ var COMMAND_NAMES = [
|
|
|
14420
14992
|
"reap.merge.validation",
|
|
14421
14993
|
"reap.merge.completion",
|
|
14422
14994
|
"reap.merge.evolve",
|
|
14995
|
+
"reap.evolve.recovery",
|
|
14423
14996
|
"reap.merge",
|
|
14424
14997
|
"reap.pull",
|
|
14425
14998
|
"reap.push",
|
|
14426
14999
|
"reap.config",
|
|
15000
|
+
"reap.update-genome",
|
|
14427
15001
|
"reap.refreshKnowledge"
|
|
14428
15002
|
];
|
|
14429
15003
|
async function initProject(projectRoot, projectName, entryMode, preset, onProgress) {
|
|
@@ -14433,21 +15007,21 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14433
15007
|
throw new Error(".reap/ already exists. This is already a REAP project.");
|
|
14434
15008
|
}
|
|
14435
15009
|
if (preset) {
|
|
14436
|
-
const presetDir =
|
|
14437
|
-
const presetExists = await fileExists(
|
|
15010
|
+
const presetDir = join6(ReapPaths.packageTemplatesDir, "presets", preset);
|
|
15011
|
+
const presetExists = await fileExists(join6(presetDir, "principles.md"));
|
|
14438
15012
|
if (!presetExists) {
|
|
14439
15013
|
throw new Error(`Unknown preset: "${preset}". Available presets: bun-hono-react`);
|
|
14440
15014
|
}
|
|
14441
15015
|
}
|
|
14442
15016
|
log("Creating .reap/ directory structure...");
|
|
14443
|
-
await
|
|
14444
|
-
await
|
|
14445
|
-
await
|
|
14446
|
-
await
|
|
14447
|
-
await
|
|
14448
|
-
await
|
|
14449
|
-
await
|
|
14450
|
-
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 });
|
|
14451
15025
|
log("Writing config.yml...");
|
|
14452
15026
|
let hasGhCli = false;
|
|
14453
15027
|
try {
|
|
@@ -14460,7 +15034,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14460
15034
|
}
|
|
14461
15035
|
const detectedLanguage = await AgentRegistry.readLanguage();
|
|
14462
15036
|
const config = {
|
|
14463
|
-
version: "0.
|
|
15037
|
+
version: "0.15.1",
|
|
14464
15038
|
project: projectName,
|
|
14465
15039
|
entryMode,
|
|
14466
15040
|
strict: false,
|
|
@@ -14473,10 +15047,10 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14473
15047
|
await ConfigManager.write(paths, config);
|
|
14474
15048
|
log("Setting up Genome templates...");
|
|
14475
15049
|
const genomeTemplates = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
|
|
14476
|
-
const genomeSourceDir = preset ?
|
|
15050
|
+
const genomeSourceDir = preset ? join6(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
|
|
14477
15051
|
for (const file of genomeTemplates) {
|
|
14478
|
-
const src =
|
|
14479
|
-
const dest =
|
|
15052
|
+
const src = join6(genomeSourceDir, file);
|
|
15053
|
+
const dest = join6(paths.genome, file);
|
|
14480
15054
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
14481
15055
|
}
|
|
14482
15056
|
if ((entryMode === "adoption" || entryMode === "migration") && !preset) {
|
|
@@ -14485,35 +15059,47 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14485
15059
|
await syncGenomeFromProject2(projectRoot, paths.genome, log);
|
|
14486
15060
|
}
|
|
14487
15061
|
log("Installing artifact templates...");
|
|
14488
|
-
await
|
|
15062
|
+
await mkdir4(ReapPaths.userReapTemplates, { recursive: true });
|
|
14489
15063
|
const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
|
|
14490
15064
|
for (const file of artifactFiles) {
|
|
14491
|
-
const src =
|
|
14492
|
-
const dest =
|
|
15065
|
+
const src = join6(ReapPaths.packageArtifactsDir, file);
|
|
15066
|
+
const dest = join6(ReapPaths.userReapTemplates, file);
|
|
14493
15067
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
14494
15068
|
}
|
|
14495
|
-
const domainGuideSrc =
|
|
14496
|
-
const domainGuideDest =
|
|
15069
|
+
const domainGuideSrc = join6(ReapPaths.packageGenomeDir, "domain/README.md");
|
|
15070
|
+
const domainGuideDest = join6(ReapPaths.userReapTemplates, "domain-guide.md");
|
|
14497
15071
|
await writeTextFile(domainGuideDest, await readTextFileOrThrow(domainGuideSrc));
|
|
14498
|
-
const mergeTemplatesDir =
|
|
14499
|
-
await
|
|
15072
|
+
const mergeTemplatesDir = join6(ReapPaths.userReapTemplates, "merge");
|
|
15073
|
+
await mkdir4(mergeTemplatesDir, { recursive: true });
|
|
14500
15074
|
const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
|
|
14501
|
-
const mergeSourceDir =
|
|
15075
|
+
const mergeSourceDir = join6(ReapPaths.packageArtifactsDir, "merge");
|
|
14502
15076
|
for (const file of mergeArtifactFiles) {
|
|
14503
|
-
const src =
|
|
14504
|
-
const dest =
|
|
15077
|
+
const src = join6(mergeSourceDir, file);
|
|
15078
|
+
const dest = join6(mergeTemplatesDir, file);
|
|
14505
15079
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
14506
15080
|
}
|
|
14507
15081
|
log("Installing hook conditions...");
|
|
14508
|
-
const conditionsSourceDir =
|
|
14509
|
-
const conditionsDestDir =
|
|
14510
|
-
await
|
|
14511
|
-
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);
|
|
14512
15086
|
for (const file of conditionFiles) {
|
|
14513
15087
|
if (!file.endsWith(".sh"))
|
|
14514
15088
|
continue;
|
|
14515
|
-
const src =
|
|
14516
|
-
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);
|
|
14517
15103
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
14518
15104
|
await chmod(dest, 493);
|
|
14519
15105
|
}
|
|
@@ -14533,6 +15119,11 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14533
15119
|
if (detectedAgents.length === 0) {
|
|
14534
15120
|
log(" No AI agents detected.");
|
|
14535
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
|
+
}
|
|
14536
15127
|
const autoSynced = (entryMode === "adoption" || entryMode === "migration") && !preset;
|
|
14537
15128
|
if (!autoSynced) {
|
|
14538
15129
|
log(`
|
|
@@ -14543,8 +15134,8 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14543
15134
|
|
|
14544
15135
|
// src/cli/commands/update.ts
|
|
14545
15136
|
init_paths();
|
|
14546
|
-
import { readdir as
|
|
14547
|
-
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";
|
|
14548
15139
|
import { execSync as execSync2 } from "child_process";
|
|
14549
15140
|
|
|
14550
15141
|
// src/core/hooks.ts
|
|
@@ -14571,15 +15162,15 @@ init_config();
|
|
|
14571
15162
|
init_fs();
|
|
14572
15163
|
init_generation();
|
|
14573
15164
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
14574
|
-
import { readdir as
|
|
14575
|
-
import { join as
|
|
15165
|
+
import { readdir as readdir9, rename as rename2 } from "fs/promises";
|
|
15166
|
+
import { join as join10 } from "path";
|
|
14576
15167
|
async function needsMigration(paths) {
|
|
14577
15168
|
try {
|
|
14578
|
-
const entries = await
|
|
15169
|
+
const entries = await readdir9(paths.lineage, { withFileTypes: true });
|
|
14579
15170
|
for (const entry of entries) {
|
|
14580
15171
|
if (!entry.isDirectory() || !entry.name.startsWith("gen-"))
|
|
14581
15172
|
continue;
|
|
14582
|
-
const metaPath =
|
|
15173
|
+
const metaPath = join10(paths.lineage, entry.name, "meta.yml");
|
|
14583
15174
|
const content = await readTextFile(metaPath);
|
|
14584
15175
|
if (content === null)
|
|
14585
15176
|
return true;
|
|
@@ -14593,18 +15184,18 @@ async function migrateLineage(paths) {
|
|
|
14593
15184
|
const result = { migrated: [], skipped: [], errors: [] };
|
|
14594
15185
|
let entries;
|
|
14595
15186
|
try {
|
|
14596
|
-
const dirEntries = await
|
|
15187
|
+
const dirEntries = await readdir9(paths.lineage, { withFileTypes: true });
|
|
14597
15188
|
entries = dirEntries.filter((e) => e.isDirectory() && e.name.startsWith("gen-")).map((e) => e.name).sort();
|
|
14598
15189
|
} catch {
|
|
14599
15190
|
return result;
|
|
14600
15191
|
}
|
|
14601
15192
|
const plan = [];
|
|
14602
15193
|
for (const dirName of entries) {
|
|
14603
|
-
const metaPath =
|
|
15194
|
+
const metaPath = join10(paths.lineage, dirName, "meta.yml");
|
|
14604
15195
|
const metaContent = await readTextFile(metaPath);
|
|
14605
15196
|
const seq = parseGenSeq(dirName);
|
|
14606
15197
|
let goal = "";
|
|
14607
|
-
const objContent = await readTextFile(
|
|
15198
|
+
const objContent = await readTextFile(join10(paths.lineage, dirName, "01-objective.md"));
|
|
14608
15199
|
if (objContent) {
|
|
14609
15200
|
const goalMatch = objContent.match(/## Goal\n+([\s\S]*?)(?=\n##)/);
|
|
14610
15201
|
if (goalMatch)
|
|
@@ -14619,7 +15210,7 @@ async function migrateLineage(paths) {
|
|
|
14619
15210
|
let prevId = null;
|
|
14620
15211
|
for (const entry of plan) {
|
|
14621
15212
|
if (entry.hasMeta) {
|
|
14622
|
-
const metaContent = await readTextFile(
|
|
15213
|
+
const metaContent = await readTextFile(join10(paths.lineage, entry.dirName, "meta.yml"));
|
|
14623
15214
|
if (metaContent) {
|
|
14624
15215
|
const meta = import_yaml5.default.parse(metaContent);
|
|
14625
15216
|
prevId = meta.id;
|
|
@@ -14640,11 +15231,11 @@ async function migrateLineage(paths) {
|
|
|
14640
15231
|
startedAt: `legacy-${entry.seq}`,
|
|
14641
15232
|
completedAt: `legacy-${entry.seq}`
|
|
14642
15233
|
};
|
|
14643
|
-
await writeTextFile(
|
|
15234
|
+
await writeTextFile(join10(paths.lineage, entry.dirName, "meta.yml"), import_yaml5.default.stringify(meta));
|
|
14644
15235
|
const oldSlug = entry.dirName.replace(/^gen-\d{3}/, "");
|
|
14645
15236
|
const newDirName = `${newId}${oldSlug}`;
|
|
14646
15237
|
if (newDirName !== entry.dirName) {
|
|
14647
|
-
await rename2(
|
|
15238
|
+
await rename2(join10(paths.lineage, entry.dirName), join10(paths.lineage, newDirName));
|
|
14648
15239
|
}
|
|
14649
15240
|
prevId = newId;
|
|
14650
15241
|
result.migrated.push(`${entry.dirName} → ${newDirName}`);
|
|
@@ -14921,13 +15512,13 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
14921
15512
|
const config = await paths.isReapProject() ? await ConfigManager.read(paths) : null;
|
|
14922
15513
|
const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
|
|
14923
15514
|
const commandsDir = ReapPaths.packageCommandsDir;
|
|
14924
|
-
const commandFiles = await
|
|
14925
|
-
await
|
|
15515
|
+
const commandFiles = await readdir10(commandsDir);
|
|
15516
|
+
await mkdir7(ReapPaths.userReapCommands, { recursive: true });
|
|
14926
15517
|
for (const file of commandFiles) {
|
|
14927
15518
|
if (!file.endsWith(".md"))
|
|
14928
15519
|
continue;
|
|
14929
|
-
const src = await readTextFileOrThrow(
|
|
14930
|
-
const dest =
|
|
15520
|
+
const src = await readTextFileOrThrow(join11(commandsDir, file));
|
|
15521
|
+
const dest = join11(ReapPaths.userReapCommands, file);
|
|
14931
15522
|
const existing = await readTextFile(dest);
|
|
14932
15523
|
if (existing !== null && existing === src) {
|
|
14933
15524
|
result.skipped.push(`~/.reap/commands/${file}`);
|
|
@@ -14941,11 +15532,11 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
14941
15532
|
const agentCmdDir = adapter.getCommandsDir();
|
|
14942
15533
|
const label = adapter.displayName;
|
|
14943
15534
|
try {
|
|
14944
|
-
const existing = await
|
|
15535
|
+
const existing = await readdir10(agentCmdDir);
|
|
14945
15536
|
for (const file of existing) {
|
|
14946
15537
|
if (!file.startsWith("reap.") || !file.endsWith(".md"))
|
|
14947
15538
|
continue;
|
|
14948
|
-
const filePath =
|
|
15539
|
+
const filePath = join11(agentCmdDir, file);
|
|
14949
15540
|
const content = await readTextFile(filePath);
|
|
14950
15541
|
if (content !== null && content.includes("redirected to ~/.reap/commands/")) {
|
|
14951
15542
|
if (!dryRun)
|
|
@@ -14963,11 +15554,11 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
14963
15554
|
}
|
|
14964
15555
|
}
|
|
14965
15556
|
}
|
|
14966
|
-
await
|
|
15557
|
+
await mkdir7(ReapPaths.userReapTemplates, { recursive: true });
|
|
14967
15558
|
const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
|
|
14968
15559
|
for (const file of artifactFiles) {
|
|
14969
|
-
const src = await readTextFileOrThrow(
|
|
14970
|
-
const dest =
|
|
15560
|
+
const src = await readTextFileOrThrow(join11(ReapPaths.packageArtifactsDir, file));
|
|
15561
|
+
const dest = join11(ReapPaths.userReapTemplates, file);
|
|
14971
15562
|
const existingContent = await readTextFile(dest);
|
|
14972
15563
|
if (existingContent !== null && existingContent === src) {
|
|
14973
15564
|
result.skipped.push(`~/.reap/templates/${file}`);
|
|
@@ -14977,8 +15568,8 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
14977
15568
|
result.updated.push(`~/.reap/templates/${file}`);
|
|
14978
15569
|
}
|
|
14979
15570
|
}
|
|
14980
|
-
const domainGuideSrc = await readTextFileOrThrow(
|
|
14981
|
-
const domainGuideDest =
|
|
15571
|
+
const domainGuideSrc = await readTextFileOrThrow(join11(ReapPaths.packageGenomeDir, "domain/README.md"));
|
|
15572
|
+
const domainGuideDest = join11(ReapPaths.userReapTemplates, "domain-guide.md");
|
|
14982
15573
|
const domainExistingContent = await readTextFile(domainGuideDest);
|
|
14983
15574
|
if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
|
|
14984
15575
|
result.skipped.push(`~/.reap/templates/domain-guide.md`);
|
|
@@ -14987,13 +15578,13 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
14987
15578
|
await writeTextFile(domainGuideDest, domainGuideSrc);
|
|
14988
15579
|
result.updated.push(`~/.reap/templates/domain-guide.md`);
|
|
14989
15580
|
}
|
|
14990
|
-
const mergeTemplatesDir =
|
|
14991
|
-
await
|
|
15581
|
+
const mergeTemplatesDir = join11(ReapPaths.userReapTemplates, "merge");
|
|
15582
|
+
await mkdir7(mergeTemplatesDir, { recursive: true });
|
|
14992
15583
|
const mergeArtifactFiles = ["01-detect.md", "02-mate.md", "03-merge.md", "04-sync.md", "05-validation.md", "06-completion.md"];
|
|
14993
|
-
const mergeSourceDir =
|
|
15584
|
+
const mergeSourceDir = join11(ReapPaths.packageArtifactsDir, "merge");
|
|
14994
15585
|
for (const file of mergeArtifactFiles) {
|
|
14995
|
-
const src = await readTextFileOrThrow(
|
|
14996
|
-
const dest =
|
|
15586
|
+
const src = await readTextFileOrThrow(join11(mergeSourceDir, file));
|
|
15587
|
+
const dest = join11(mergeTemplatesDir, file);
|
|
14997
15588
|
const existing = await readTextFile(dest);
|
|
14998
15589
|
if (existing !== null && existing === src) {
|
|
14999
15590
|
result.skipped.push(`~/.reap/templates/merge/${file}`);
|
|
@@ -15034,47 +15625,23 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
15034
15625
|
result.updated.push(`Config: added ${backfillResult.added.join(", ")}`);
|
|
15035
15626
|
}
|
|
15036
15627
|
}
|
|
15037
|
-
const
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
for (const file of reapCmdFiles) {
|
|
15041
|
-
const src = await readTextFileOrThrow(join10(ReapPaths.userReapCommands, file));
|
|
15042
|
-
const name = file.replace(/\.md$/, "");
|
|
15043
|
-
const skillDir = join10(projectClaudeSkills, name);
|
|
15044
|
-
const skillFile = join10(skillDir, "SKILL.md");
|
|
15045
|
-
const fmMatch = src.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
15046
|
-
const description = fmMatch ? fmMatch[1].match(/^description:\s*"?([^"\n]*)"?/m)?.[1]?.trim() ?? "" : "";
|
|
15047
|
-
const body = fmMatch ? fmMatch[2] : src;
|
|
15048
|
-
const skillContent = `---
|
|
15049
|
-
name: ${name}
|
|
15050
|
-
description: "${description}"
|
|
15051
|
-
---
|
|
15052
|
-
${body}`;
|
|
15053
|
-
const existing = await readTextFile(skillFile);
|
|
15054
|
-
if (existing !== null && existing === skillContent)
|
|
15055
|
-
continue;
|
|
15056
|
-
if (!dryRun) {
|
|
15057
|
-
await mkdir6(skillDir, { recursive: true });
|
|
15058
|
-
await writeTextFile(skillFile, skillContent);
|
|
15059
|
-
}
|
|
15060
|
-
cmdInstalled++;
|
|
15061
|
-
}
|
|
15062
|
-
if (cmdInstalled > 0) {
|
|
15063
|
-
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)`);
|
|
15064
15631
|
} else {
|
|
15065
|
-
result.skipped.push(`.claude/skills/ (${
|
|
15632
|
+
result.skipped.push(`.claude/skills/ (${skillsResult.total} unchanged)`);
|
|
15066
15633
|
}
|
|
15067
|
-
const projectClaudeCommands =
|
|
15634
|
+
const projectClaudeCommands = join11(paths.projectRoot, ".claude", "commands");
|
|
15068
15635
|
try {
|
|
15069
|
-
const legacyFiles = (await
|
|
15636
|
+
const legacyFiles = (await readdir10(projectClaudeCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
|
|
15070
15637
|
for (const file of legacyFiles) {
|
|
15071
15638
|
if (!dryRun)
|
|
15072
|
-
await unlink4(
|
|
15639
|
+
await unlink4(join11(projectClaudeCommands, file));
|
|
15073
15640
|
result.removed.push(`.claude/commands/${file} (legacy)`);
|
|
15074
15641
|
}
|
|
15075
15642
|
} catch {}
|
|
15076
15643
|
await migrateLegacyFiles(paths, dryRun, result);
|
|
15077
|
-
const currentVersion = "0.
|
|
15644
|
+
const currentVersion = "0.15.1";
|
|
15078
15645
|
const migrationResult = await MigrationRunner.run(paths, currentVersion, dryRun);
|
|
15079
15646
|
for (const m of migrationResult.migrated) {
|
|
15080
15647
|
result.updated.push(`[migration] ${m}`);
|
|
@@ -15150,7 +15717,7 @@ async function migrateLegacyFiles(paths, dryRun, result) {
|
|
|
15150
15717
|
}
|
|
15151
15718
|
async function removeDirIfExists(dirPath, label, dryRun, result) {
|
|
15152
15719
|
try {
|
|
15153
|
-
const entries = await
|
|
15720
|
+
const entries = await readdir10(dirPath);
|
|
15154
15721
|
if (entries.length > 0 || true) {
|
|
15155
15722
|
if (!dryRun)
|
|
15156
15723
|
await rm2(dirPath, { recursive: true });
|
|
@@ -15173,6 +15740,7 @@ async function getStatus(projectRoot) {
|
|
|
15173
15740
|
version: config.version,
|
|
15174
15741
|
project: config.project,
|
|
15175
15742
|
entryMode: config.entryMode,
|
|
15743
|
+
lastSyncedGeneration: config.lastSyncedGeneration,
|
|
15176
15744
|
generation: current ? {
|
|
15177
15745
|
id: current.id,
|
|
15178
15746
|
goal: current.goal,
|
|
@@ -15191,21 +15759,361 @@ async function getStatus(projectRoot) {
|
|
|
15191
15759
|
init_paths();
|
|
15192
15760
|
init_lifecycle();
|
|
15193
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();
|
|
15194
15770
|
var import_yaml7 = __toESM(require_dist(), 1);
|
|
15195
|
-
import {
|
|
15196
|
-
import { join as
|
|
15197
|
-
|
|
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;
|
|
15198
15801
|
try {
|
|
15199
|
-
|
|
15200
|
-
return s.isDirectory();
|
|
15802
|
+
config = import_yaml7.default.parse(content) ?? {};
|
|
15201
15803
|
} catch {
|
|
15202
|
-
|
|
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");
|
|
15203
15833
|
}
|
|
15204
15834
|
}
|
|
15205
|
-
async function
|
|
15206
|
-
const
|
|
15207
|
-
|
|
15208
|
-
|
|
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
|
+
}
|
|
16105
|
+
async function dirExists(path) {
|
|
16106
|
+
try {
|
|
16107
|
+
const s = await stat4(path);
|
|
16108
|
+
return s.isDirectory();
|
|
16109
|
+
} catch {
|
|
16110
|
+
return false;
|
|
16111
|
+
}
|
|
16112
|
+
}
|
|
16113
|
+
async function fixProject(projectRoot) {
|
|
16114
|
+
const paths = new ReapPaths(projectRoot);
|
|
16115
|
+
const issues = [];
|
|
16116
|
+
const fixed = [];
|
|
15209
16117
|
const requiredDirs = [
|
|
15210
16118
|
{ path: paths.genome, name: "genome" },
|
|
15211
16119
|
{ path: paths.domain, name: "genome/domain" },
|
|
@@ -15216,7 +16124,7 @@ async function fixProject(projectRoot) {
|
|
|
15216
16124
|
];
|
|
15217
16125
|
for (const dir of requiredDirs) {
|
|
15218
16126
|
if (!await dirExists(dir.path)) {
|
|
15219
|
-
await
|
|
16127
|
+
await mkdir8(dir.path, { recursive: true });
|
|
15220
16128
|
fixed.push(`Recreated missing directory: ${dir.name}/`);
|
|
15221
16129
|
}
|
|
15222
16130
|
}
|
|
@@ -15228,7 +16136,7 @@ async function fixProject(projectRoot) {
|
|
|
15228
16136
|
];
|
|
15229
16137
|
for (const gf of genomeFiles) {
|
|
15230
16138
|
if (!await fileExists(gf.path)) {
|
|
15231
|
-
const templateSrc =
|
|
16139
|
+
const templateSrc = join13(ReapPaths.packageGenomeDir, gf.name);
|
|
15232
16140
|
if (await fileExists(templateSrc)) {
|
|
15233
16141
|
await copyFile(templateSrc, gf.path);
|
|
15234
16142
|
fixed.push(`Restored missing genome/${gf.name} from template`);
|
|
@@ -15244,7 +16152,7 @@ async function fixProject(projectRoot) {
|
|
|
15244
16152
|
if (currentContent !== null) {
|
|
15245
16153
|
if (currentContent.trim()) {
|
|
15246
16154
|
try {
|
|
15247
|
-
const state =
|
|
16155
|
+
const state = import_yaml8.default.parse(currentContent);
|
|
15248
16156
|
if (!state.stage || !LifeCycle.isValid(state.stage)) {
|
|
15249
16157
|
issues.push(`Invalid stage "${state.stage}" in current.yml. Valid stages: ${LifeCycle.stages().join(", ")}. Manual correction required.`);
|
|
15250
16158
|
}
|
|
@@ -15253,7 +16161,7 @@ async function fixProject(projectRoot) {
|
|
|
15253
16161
|
if (!state.goal)
|
|
15254
16162
|
issues.push("current.yml is missing 'goal' field. Manual correction required.");
|
|
15255
16163
|
if (!await dirExists(paths.backlog)) {
|
|
15256
|
-
await
|
|
16164
|
+
await mkdir8(paths.backlog, { recursive: true });
|
|
15257
16165
|
fixed.push("Recreated missing backlog/ directory for active generation");
|
|
15258
16166
|
}
|
|
15259
16167
|
} catch {
|
|
@@ -15265,14 +16173,273 @@ async function fixProject(projectRoot) {
|
|
|
15265
16173
|
return { issues, fixed };
|
|
15266
16174
|
}
|
|
15267
16175
|
|
|
16176
|
+
// src/cli/commands/destroy.ts
|
|
16177
|
+
init_fs();
|
|
16178
|
+
init_paths();
|
|
16179
|
+
init_config();
|
|
16180
|
+
import { rm as rm3, readdir as readdir12, unlink as unlink5 } from "fs/promises";
|
|
16181
|
+
import { join as join14 } from "path";
|
|
16182
|
+
async function getProjectName(projectRoot) {
|
|
16183
|
+
try {
|
|
16184
|
+
const paths = new ReapPaths(projectRoot);
|
|
16185
|
+
const config = await ConfigManager.read(paths);
|
|
16186
|
+
return config.project;
|
|
16187
|
+
} catch {
|
|
16188
|
+
return null;
|
|
16189
|
+
}
|
|
16190
|
+
}
|
|
16191
|
+
async function destroyProject(projectRoot) {
|
|
16192
|
+
const removed = [];
|
|
16193
|
+
const skipped = [];
|
|
16194
|
+
const reapDir = join14(projectRoot, ".reap");
|
|
16195
|
+
if (await fileExists(reapDir)) {
|
|
16196
|
+
await rm3(reapDir, { recursive: true, force: true });
|
|
16197
|
+
removed.push(".reap/");
|
|
16198
|
+
} else {
|
|
16199
|
+
skipped.push(".reap/ (not found)");
|
|
16200
|
+
}
|
|
16201
|
+
const claudeCommandsDir = join14(projectRoot, ".claude", "commands");
|
|
16202
|
+
await removeGlobFiles(claudeCommandsDir, "reap.", removed, skipped, ".claude/commands/");
|
|
16203
|
+
const claudeSkillsDir = join14(projectRoot, ".claude", "skills");
|
|
16204
|
+
await removeGlobDirs(claudeSkillsDir, "reap.", removed, skipped, ".claude/skills/");
|
|
16205
|
+
await cleanClaudeMd(projectRoot, removed, skipped);
|
|
16206
|
+
await cleanGitignore(projectRoot, removed, skipped);
|
|
16207
|
+
return { removed, skipped };
|
|
16208
|
+
}
|
|
16209
|
+
async function removeGlobFiles(dir, prefix, removed, skipped, displayPrefix) {
|
|
16210
|
+
try {
|
|
16211
|
+
const files = await readdir12(dir);
|
|
16212
|
+
const matched = files.filter((f) => f.startsWith(prefix));
|
|
16213
|
+
if (matched.length === 0) {
|
|
16214
|
+
skipped.push(`${displayPrefix}${prefix}* (none found)`);
|
|
16215
|
+
return;
|
|
16216
|
+
}
|
|
16217
|
+
for (const file of matched) {
|
|
16218
|
+
await unlink5(join14(dir, file));
|
|
16219
|
+
removed.push(`${displayPrefix}${file}`);
|
|
16220
|
+
}
|
|
16221
|
+
} catch {
|
|
16222
|
+
skipped.push(`${displayPrefix}${prefix}* (directory not found)`);
|
|
16223
|
+
}
|
|
16224
|
+
}
|
|
16225
|
+
async function removeGlobDirs(dir, prefix, removed, skipped, displayPrefix) {
|
|
16226
|
+
try {
|
|
16227
|
+
const entries = await readdir12(dir);
|
|
16228
|
+
const matched = entries.filter((e) => e.startsWith(prefix));
|
|
16229
|
+
if (matched.length === 0) {
|
|
16230
|
+
skipped.push(`${displayPrefix}${prefix}* (none found)`);
|
|
16231
|
+
return;
|
|
16232
|
+
}
|
|
16233
|
+
for (const entry of matched) {
|
|
16234
|
+
await rm3(join14(dir, entry), { recursive: true, force: true });
|
|
16235
|
+
removed.push(`${displayPrefix}${entry}`);
|
|
16236
|
+
}
|
|
16237
|
+
} catch {
|
|
16238
|
+
skipped.push(`${displayPrefix}${prefix}* (directory not found)`);
|
|
16239
|
+
}
|
|
16240
|
+
}
|
|
16241
|
+
async function cleanClaudeMd(projectRoot, removed, skipped) {
|
|
16242
|
+
const claudeMdPath = join14(projectRoot, ".claude", "CLAUDE.md");
|
|
16243
|
+
const content = await readTextFile(claudeMdPath);
|
|
16244
|
+
if (content === null) {
|
|
16245
|
+
skipped.push(".claude/CLAUDE.md (not found)");
|
|
16246
|
+
return;
|
|
16247
|
+
}
|
|
16248
|
+
const marker = "# REAP Project";
|
|
16249
|
+
if (!content.includes(marker)) {
|
|
16250
|
+
skipped.push(".claude/CLAUDE.md (no REAP section)");
|
|
16251
|
+
return;
|
|
16252
|
+
}
|
|
16253
|
+
const cleaned = content.replace(/# REAP Project[\s\S]*?(?=\n# |\s*$)/, "").trim();
|
|
16254
|
+
if (cleaned.length === 0) {
|
|
16255
|
+
await unlink5(claudeMdPath);
|
|
16256
|
+
removed.push(".claude/CLAUDE.md (deleted, was REAP-only)");
|
|
16257
|
+
} else {
|
|
16258
|
+
await writeTextFile(claudeMdPath, cleaned + `
|
|
16259
|
+
`);
|
|
16260
|
+
removed.push(".claude/CLAUDE.md (REAP section removed)");
|
|
16261
|
+
}
|
|
16262
|
+
}
|
|
16263
|
+
async function cleanGitignore(projectRoot, removed, skipped) {
|
|
16264
|
+
const gitignorePath = join14(projectRoot, ".gitignore");
|
|
16265
|
+
const content = await readTextFile(gitignorePath);
|
|
16266
|
+
if (content === null) {
|
|
16267
|
+
skipped.push(".gitignore (not found)");
|
|
16268
|
+
return;
|
|
16269
|
+
}
|
|
16270
|
+
const reapPatterns = [
|
|
16271
|
+
/^# REAP.*$/m,
|
|
16272
|
+
/^\.claude\/skills\/reap\..*$/m,
|
|
16273
|
+
/^\.claude\/commands\/reap\..*$/m,
|
|
16274
|
+
/^\.reap\/.*$/m
|
|
16275
|
+
];
|
|
16276
|
+
let cleaned = content;
|
|
16277
|
+
let anyRemoved = false;
|
|
16278
|
+
for (const pattern of reapPatterns) {
|
|
16279
|
+
if (pattern.test(cleaned)) {
|
|
16280
|
+
cleaned = cleaned.replace(new RegExp(pattern.source, "gm"), "");
|
|
16281
|
+
anyRemoved = true;
|
|
16282
|
+
}
|
|
16283
|
+
}
|
|
16284
|
+
if (!anyRemoved) {
|
|
16285
|
+
skipped.push(".gitignore (no REAP entries)");
|
|
16286
|
+
return;
|
|
16287
|
+
}
|
|
16288
|
+
cleaned = cleaned.replace(/\n{3,}/g, `
|
|
16289
|
+
|
|
16290
|
+
`).trim() + `
|
|
16291
|
+
`;
|
|
16292
|
+
await writeTextFile(gitignorePath, cleaned);
|
|
16293
|
+
removed.push(".gitignore (REAP entries removed)");
|
|
16294
|
+
}
|
|
16295
|
+
|
|
16296
|
+
// src/cli/commands/clean.ts
|
|
16297
|
+
init_paths();
|
|
16298
|
+
init_fs();
|
|
16299
|
+
init_generation();
|
|
16300
|
+
import { rm as rm4, readdir as readdir13, mkdir as mkdir9 } from "fs/promises";
|
|
16301
|
+
import { join as join15 } from "path";
|
|
16302
|
+
async function hasActiveGeneration(projectRoot) {
|
|
16303
|
+
const paths = new ReapPaths(projectRoot);
|
|
16304
|
+
try {
|
|
16305
|
+
const mgr = new GenerationManager(paths);
|
|
16306
|
+
const current = await mgr.current();
|
|
16307
|
+
return current !== null;
|
|
16308
|
+
} catch {
|
|
16309
|
+
return false;
|
|
16310
|
+
}
|
|
16311
|
+
}
|
|
16312
|
+
async function cleanProject(projectRoot, options) {
|
|
16313
|
+
const paths = new ReapPaths(projectRoot);
|
|
16314
|
+
const actions = [];
|
|
16315
|
+
const warnings = [];
|
|
16316
|
+
if (options.lineage === "delete") {
|
|
16317
|
+
await rm4(paths.lineage, { recursive: true, force: true });
|
|
16318
|
+
await mkdir9(paths.lineage, { recursive: true });
|
|
16319
|
+
actions.push("Lineage: 전체 삭제됨");
|
|
16320
|
+
} else {
|
|
16321
|
+
await compressLineage(paths, actions);
|
|
16322
|
+
}
|
|
16323
|
+
await cleanLife(paths, actions);
|
|
16324
|
+
if (options.hooks === "reset") {
|
|
16325
|
+
const hooksDir = paths.hooks;
|
|
16326
|
+
if (await fileExists(hooksDir)) {
|
|
16327
|
+
await rm4(hooksDir, { recursive: true, force: true });
|
|
16328
|
+
await mkdir9(hooksDir, { recursive: true });
|
|
16329
|
+
actions.push("Hooks: 초기화됨");
|
|
16330
|
+
} else {
|
|
16331
|
+
actions.push("Hooks: 디렉토리 없음 (skip)");
|
|
16332
|
+
}
|
|
16333
|
+
} else {
|
|
16334
|
+
actions.push("Hooks: 기존 유지");
|
|
16335
|
+
}
|
|
16336
|
+
if (options.genome === "template") {
|
|
16337
|
+
await resetGenomeToTemplate(paths, actions);
|
|
16338
|
+
} else if (options.genome === "keep") {
|
|
16339
|
+
actions.push("Genome/Environment: 기존 유지");
|
|
16340
|
+
} else {
|
|
16341
|
+
actions.push("Genome/Environment: 수동 편집 모드 (변경 없음)");
|
|
16342
|
+
}
|
|
16343
|
+
const backlogDir = paths.backlog;
|
|
16344
|
+
if (options.backlog === "delete") {
|
|
16345
|
+
if (await fileExists(backlogDir)) {
|
|
16346
|
+
await rm4(backlogDir, { recursive: true, force: true });
|
|
16347
|
+
await mkdir9(backlogDir, { recursive: true });
|
|
16348
|
+
actions.push("Backlog: 삭제됨");
|
|
16349
|
+
} else {
|
|
16350
|
+
actions.push("Backlog: 디렉토리 없음 (skip)");
|
|
16351
|
+
}
|
|
16352
|
+
} else {
|
|
16353
|
+
actions.push("Backlog: 보존됨");
|
|
16354
|
+
}
|
|
16355
|
+
return { actions, warnings };
|
|
16356
|
+
}
|
|
16357
|
+
async function compressLineage(paths, actions) {
|
|
16358
|
+
const lineageDir = paths.lineage;
|
|
16359
|
+
if (!await fileExists(lineageDir)) {
|
|
16360
|
+
actions.push("Lineage: 디렉토리 없음 (skip)");
|
|
16361
|
+
return;
|
|
16362
|
+
}
|
|
16363
|
+
let entries;
|
|
16364
|
+
try {
|
|
16365
|
+
entries = await readdir13(lineageDir);
|
|
16366
|
+
} catch {
|
|
16367
|
+
actions.push("Lineage: 읽기 실패 (skip)");
|
|
16368
|
+
return;
|
|
16369
|
+
}
|
|
16370
|
+
const genDirs = entries.filter((e) => e.startsWith("gen-"));
|
|
16371
|
+
if (genDirs.length === 0) {
|
|
16372
|
+
actions.push("Lineage: 세대 기록 없음 (skip)");
|
|
16373
|
+
return;
|
|
16374
|
+
}
|
|
16375
|
+
const epochId = `epoch-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}`;
|
|
16376
|
+
const summary = [
|
|
16377
|
+
`# Epoch: ${epochId}`,
|
|
16378
|
+
`# Compressed ${genDirs.length} generations`,
|
|
16379
|
+
`# Date: ${new Date().toISOString()}`,
|
|
16380
|
+
"",
|
|
16381
|
+
"## Generations",
|
|
16382
|
+
...genDirs.map((d) => `- ${d}`),
|
|
16383
|
+
""
|
|
16384
|
+
].join(`
|
|
16385
|
+
`);
|
|
16386
|
+
for (const dir of genDirs) {
|
|
16387
|
+
await rm4(join15(lineageDir, dir), { recursive: true, force: true });
|
|
16388
|
+
}
|
|
16389
|
+
await writeTextFile(join15(lineageDir, `${epochId}.md`), summary);
|
|
16390
|
+
actions.push(`Lineage: ${genDirs.length}개 세대를 ${epochId}로 압축`);
|
|
16391
|
+
}
|
|
16392
|
+
async function cleanLife(paths, actions) {
|
|
16393
|
+
const lifeDir = paths.life;
|
|
16394
|
+
if (!await fileExists(lifeDir)) {
|
|
16395
|
+
actions.push("Life: 디렉토리 없음 (skip)");
|
|
16396
|
+
return;
|
|
16397
|
+
}
|
|
16398
|
+
let entries;
|
|
16399
|
+
try {
|
|
16400
|
+
entries = await readdir13(lifeDir);
|
|
16401
|
+
} catch {
|
|
16402
|
+
actions.push("Life: 읽기 실패 (skip)");
|
|
16403
|
+
return;
|
|
16404
|
+
}
|
|
16405
|
+
let removedCount = 0;
|
|
16406
|
+
for (const entry of entries) {
|
|
16407
|
+
if (entry === "backlog")
|
|
16408
|
+
continue;
|
|
16409
|
+
const entryPath = join15(lifeDir, entry);
|
|
16410
|
+
await rm4(entryPath, { recursive: true, force: true });
|
|
16411
|
+
removedCount++;
|
|
16412
|
+
}
|
|
16413
|
+
actions.push(`Life: ${removedCount}개 파일/디렉토리 정리됨`);
|
|
16414
|
+
}
|
|
16415
|
+
async function resetGenomeToTemplate(paths, actions) {
|
|
16416
|
+
const genomeFiles = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
|
|
16417
|
+
for (const file of genomeFiles) {
|
|
16418
|
+
const templatePath = join15(ReapPaths.packageGenomeDir, file);
|
|
16419
|
+
const destPath = join15(paths.genome, file);
|
|
16420
|
+
try {
|
|
16421
|
+
const templateContent = await readTextFileOrThrow(templatePath);
|
|
16422
|
+
await writeTextFile(destPath, templateContent);
|
|
16423
|
+
} catch {}
|
|
16424
|
+
}
|
|
16425
|
+
const envSummaryTemplate = join15(ReapPaths.packageTemplatesDir, "environment", "summary.md");
|
|
16426
|
+
if (await fileExists(envSummaryTemplate)) {
|
|
16427
|
+
try {
|
|
16428
|
+
const content = await readTextFileOrThrow(envSummaryTemplate);
|
|
16429
|
+
await writeTextFile(paths.environmentSummary, content);
|
|
16430
|
+
} catch {}
|
|
16431
|
+
}
|
|
16432
|
+
actions.push("Genome/Environment: 템플릿으로 초기화됨");
|
|
16433
|
+
}
|
|
16434
|
+
|
|
15268
16435
|
// src/cli/index.ts
|
|
15269
16436
|
init_lifecycle();
|
|
15270
16437
|
init_paths();
|
|
15271
16438
|
init_fs();
|
|
15272
16439
|
init_version();
|
|
15273
16440
|
init_config();
|
|
15274
|
-
import { join as
|
|
15275
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
16441
|
+
import { join as join32 } from "path";
|
|
16442
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.1");
|
|
15276
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) => {
|
|
15277
16444
|
try {
|
|
15278
16445
|
const cwd = process.cwd();
|
|
@@ -15310,8 +16477,9 @@ Initializing REAP project "${name}" (${mode} mode)...
|
|
|
15310
16477
|
console.log(`
|
|
15311
16478
|
Getting started:`);
|
|
15312
16479
|
console.log(` 1. Open your AI agent (${initResult.agents[0] || "Claude Code or OpenCode"})`);
|
|
15313
|
-
console.log(` 2. Run /reap.
|
|
15314
|
-
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`);
|
|
15315
16483
|
if (initResult.agents.length === 0) {
|
|
15316
16484
|
console.log(`
|
|
15317
16485
|
⚠ No AI agents detected. Install Claude Code or OpenCode, then run 'reap update'.`);
|
|
@@ -15328,10 +16496,12 @@ program.command("status").description("Show current project and Generation statu
|
|
|
15328
16496
|
const paths = new ReapPaths(cwd);
|
|
15329
16497
|
const config = await ConfigManager.read(paths);
|
|
15330
16498
|
const skipCheck = config.autoUpdate === false;
|
|
15331
|
-
const installedVersion = "0.
|
|
16499
|
+
const installedVersion = "0.15.1";
|
|
15332
16500
|
const versionLine = formatVersionLine(installedVersion, skipCheck);
|
|
15333
16501
|
console.log(`${versionLine} | Project: ${status.project} (${status.entryMode})`);
|
|
15334
16502
|
console.log(`Completed Generations: ${status.totalGenerations}`);
|
|
16503
|
+
const syncLabel = status.lastSyncedGeneration ? `synced (${status.lastSyncedGeneration})` : "never synced";
|
|
16504
|
+
console.log(`Genome Sync: ${syncLabel}`);
|
|
15335
16505
|
if (status.generation) {
|
|
15336
16506
|
console.log(`
|
|
15337
16507
|
Active Generation: ${status.generation.id}`);
|
|
@@ -15347,19 +16517,38 @@ No active Generation. Run '/reap.start' to start one.`);
|
|
|
15347
16517
|
process.exit(1);
|
|
15348
16518
|
}
|
|
15349
16519
|
});
|
|
15350
|
-
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) => {
|
|
15351
16521
|
try {
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
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
|
+
}
|
|
15359
16535
|
}
|
|
15360
|
-
if (result.
|
|
15361
|
-
|
|
15362
|
-
|
|
16536
|
+
if (result.errors.length > 0) {
|
|
16537
|
+
process.exit(1);
|
|
16538
|
+
}
|
|
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
|
+
}
|
|
15363
16552
|
}
|
|
15364
16553
|
}
|
|
15365
16554
|
} catch (e) {
|
|
@@ -15407,16 +16596,113 @@ program.command("help").description("Show REAP commands, slash commands, and wor
|
|
|
15407
16596
|
if (l === "korean" || l === "ko")
|
|
15408
16597
|
lang = "ko";
|
|
15409
16598
|
}
|
|
15410
|
-
const helpDir =
|
|
15411
|
-
let helpText = await readTextFile(
|
|
16599
|
+
const helpDir = join32(ReapPaths.packageTemplatesDir, "help");
|
|
16600
|
+
let helpText = await readTextFile(join32(helpDir, `${lang}.txt`));
|
|
15412
16601
|
if (!helpText)
|
|
15413
|
-
helpText = await readTextFile(
|
|
16602
|
+
helpText = await readTextFile(join32(helpDir, "en.txt"));
|
|
15414
16603
|
if (!helpText) {
|
|
15415
16604
|
console.log("Help file not found. Run 'reap update' to install templates.");
|
|
15416
16605
|
return;
|
|
15417
16606
|
}
|
|
15418
16607
|
console.log(helpText);
|
|
15419
16608
|
});
|
|
16609
|
+
program.command("destroy").description("Remove all REAP files from this project").action(async () => {
|
|
16610
|
+
try {
|
|
16611
|
+
const cwd = process.cwd();
|
|
16612
|
+
const projectName = await getProjectName(cwd);
|
|
16613
|
+
if (!projectName) {
|
|
16614
|
+
console.error("Error: Not a REAP project (cannot read .reap/config.yml).");
|
|
16615
|
+
process.exit(1);
|
|
16616
|
+
}
|
|
16617
|
+
const expectedInput = "yes destroy";
|
|
16618
|
+
console.log(`
|
|
16619
|
+
This will permanently remove all REAP files from this project.`);
|
|
16620
|
+
console.log(`To confirm, type '${expectedInput}':
|
|
16621
|
+
`);
|
|
16622
|
+
const answer = await prompt("> ");
|
|
16623
|
+
if (answer.trim() !== expectedInput) {
|
|
16624
|
+
console.log(`
|
|
16625
|
+
Confirmation mismatch. Destroy cancelled.`);
|
|
16626
|
+
process.exit(0);
|
|
16627
|
+
}
|
|
16628
|
+
console.log("");
|
|
16629
|
+
const result = await destroyProject(cwd);
|
|
16630
|
+
if (result.removed.length > 0) {
|
|
16631
|
+
console.log("Removed:");
|
|
16632
|
+
result.removed.forEach((f) => console.log(` - ${f}`));
|
|
16633
|
+
}
|
|
16634
|
+
if (result.skipped.length > 0) {
|
|
16635
|
+
console.log("Skipped:");
|
|
16636
|
+
result.skipped.forEach((f) => console.log(` - ${f}`));
|
|
16637
|
+
}
|
|
16638
|
+
console.log(`
|
|
16639
|
+
REAP has been removed from this project.`);
|
|
16640
|
+
} catch (e) {
|
|
16641
|
+
console.error(`Error: ${e.message}`);
|
|
16642
|
+
process.exit(1);
|
|
16643
|
+
}
|
|
16644
|
+
});
|
|
16645
|
+
program.command("clean").description("Reset REAP project with interactive options").action(async () => {
|
|
16646
|
+
try {
|
|
16647
|
+
const cwd = process.cwd();
|
|
16648
|
+
const paths = new ReapPaths(cwd);
|
|
16649
|
+
if (!await paths.isReapProject()) {
|
|
16650
|
+
console.error("Error: Not a REAP project (.reap/ not found).");
|
|
16651
|
+
process.exit(1);
|
|
16652
|
+
}
|
|
16653
|
+
if (await hasActiveGeneration(cwd)) {
|
|
16654
|
+
console.log(`
|
|
16655
|
+
⚠ Warning: There is an active generation in progress.`);
|
|
16656
|
+
const proceed = await prompt("Continue and discard it? (y/N): ");
|
|
16657
|
+
if (proceed.trim().toLowerCase() !== "y") {
|
|
16658
|
+
console.log("Clean cancelled.");
|
|
16659
|
+
process.exit(0);
|
|
16660
|
+
}
|
|
16661
|
+
}
|
|
16662
|
+
console.log(`
|
|
16663
|
+
--- REAP Clean ---
|
|
16664
|
+
`);
|
|
16665
|
+
console.log("1. Lineage (generation history):");
|
|
16666
|
+
console.log(" [1] Compress into epoch summary");
|
|
16667
|
+
console.log(" [2] Delete entirely");
|
|
16668
|
+
const lineageChoice = await prompt(" Choice (1/2): ");
|
|
16669
|
+
const lineage = lineageChoice.trim() === "2" ? "delete" : "compress";
|
|
16670
|
+
console.log(`
|
|
16671
|
+
2. Hooks:`);
|
|
16672
|
+
console.log(" [1] Keep existing hooks");
|
|
16673
|
+
console.log(" [2] Reset to defaults");
|
|
16674
|
+
const hooksChoice = await prompt(" Choice (1/2): ");
|
|
16675
|
+
const hooks = hooksChoice.trim() === "2" ? "reset" : "keep";
|
|
16676
|
+
console.log(`
|
|
16677
|
+
3. Genome / Environment:`);
|
|
16678
|
+
console.log(" [1] Override with templates (then run /reap.sync)");
|
|
16679
|
+
console.log(" [2] Keep current files");
|
|
16680
|
+
console.log(" [3] Manual editing (no changes)");
|
|
16681
|
+
const genomeChoice = await prompt(" Choice (1/2/3): ");
|
|
16682
|
+
const genome = genomeChoice.trim() === "1" ? "template" : genomeChoice.trim() === "3" ? "manual" : "keep";
|
|
16683
|
+
console.log(`
|
|
16684
|
+
4. Backlog:`);
|
|
16685
|
+
console.log(" [1] Keep existing backlog items");
|
|
16686
|
+
console.log(" [2] Delete all");
|
|
16687
|
+
const backlogChoice = await prompt(" Choice (1/2): ");
|
|
16688
|
+
const backlog = backlogChoice.trim() === "2" ? "delete" : "keep";
|
|
16689
|
+
console.log(`
|
|
16690
|
+
Applying...
|
|
16691
|
+
`);
|
|
16692
|
+
const result = await cleanProject(cwd, { lineage, hooks, genome, backlog });
|
|
16693
|
+
for (const action of result.actions) {
|
|
16694
|
+
console.log(` - ${action}`);
|
|
16695
|
+
}
|
|
16696
|
+
for (const warning of result.warnings) {
|
|
16697
|
+
console.log(` ⚠ ${warning}`);
|
|
16698
|
+
}
|
|
16699
|
+
console.log(`
|
|
16700
|
+
Clean complete. Run /reap.start to begin a new generation.`);
|
|
16701
|
+
} catch (e) {
|
|
16702
|
+
console.error(`Error: ${e.message}`);
|
|
16703
|
+
process.exit(1);
|
|
16704
|
+
}
|
|
16705
|
+
});
|
|
15420
16706
|
program.command("run <command>").description("Run a REAP command script (internal, used by slash commands)").option("--phase <phase>", "Start from a specific phase").allowUnknownOption().action(async (command, options, cmd) => {
|
|
15421
16707
|
const rawArgs = cmd.args.slice(1);
|
|
15422
16708
|
const passArgs = [];
|
|
@@ -15430,4 +16716,13 @@ program.command("run <command>").description("Run a REAP command script (interna
|
|
|
15430
16716
|
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_run(), exports_run));
|
|
15431
16717
|
await runCommand2(command, options.phase, passArgs);
|
|
15432
16718
|
});
|
|
16719
|
+
function prompt(question) {
|
|
16720
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
16721
|
+
return new Promise((resolve) => {
|
|
16722
|
+
rl.question(question, (answer) => {
|
|
16723
|
+
rl.close();
|
|
16724
|
+
resolve(answer ?? "");
|
|
16725
|
+
});
|
|
16726
|
+
});
|
|
16727
|
+
}
|
|
15433
16728
|
program.parse();
|