@c-d-cc/reap 0.13.4 → 0.15.0
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 +13 -10
- package/README.ko.md +13 -10
- package/README.md +14 -11
- package/README.zh-CN.md +13 -10
- package/dist/cli.js +1137 -286
- package/dist/templates/commands/reap.refreshKnowledge.md +6 -0
- package/dist/templates/help/en.txt +3 -0
- package/dist/templates/help/ko.txt +3 -0
- package/dist/templates/hooks/genome-loader.cjs +40 -0
- package/dist/templates/hooks/reap-guide.md +13 -11
- package/dist/templates/hooks/session-start.cjs +64 -22
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9951,14 +9951,15 @@ import { createHash, randomBytes } from "crypto";
|
|
|
9951
9951
|
import { hostname } from "os";
|
|
9952
9952
|
import { readdir as readdir7, mkdir as mkdir4, rename, unlink as unlink3 } from "fs/promises";
|
|
9953
9953
|
import { join as join8 } from "path";
|
|
9954
|
-
function
|
|
9954
|
+
function generateToken(genId, stage, phase) {
|
|
9955
9955
|
const nonce = randomBytes(16).toString("hex");
|
|
9956
|
-
const
|
|
9956
|
+
const input = phase ? `${nonce}${genId}${stage}:${phase}` : `${nonce}${genId}${stage}`;
|
|
9957
|
+
const hash = createHash("sha256").update(input).digest("hex");
|
|
9957
9958
|
return { nonce, hash };
|
|
9958
9959
|
}
|
|
9959
|
-
function
|
|
9960
|
-
const
|
|
9961
|
-
return
|
|
9960
|
+
function verifyToken(token, genId, stage, expectedHash, phase) {
|
|
9961
|
+
const input = phase ? `${token}${genId}${stage}:${phase}` : `${token}${genId}${stage}`;
|
|
9962
|
+
return createHash("sha256").update(input).digest("hex") === expectedHash;
|
|
9962
9963
|
}
|
|
9963
9964
|
function generateGenHash(parents, goal, genomeHash, machineId, startedAt) {
|
|
9964
9965
|
const input = JSON.stringify({ parents, goal, genomeHash, machineId, startedAt });
|
|
@@ -10197,6 +10198,40 @@ function emitError(command, message, details) {
|
|
|
10197
10198
|
process.exit(1);
|
|
10198
10199
|
}
|
|
10199
10200
|
|
|
10201
|
+
// src/cli/commands/run/next.ts
|
|
10202
|
+
var exports_next = {};
|
|
10203
|
+
__export(exports_next, {
|
|
10204
|
+
execute: () => execute
|
|
10205
|
+
});
|
|
10206
|
+
async function execute(paths, _phase) {
|
|
10207
|
+
const gm = new GenerationManager(paths);
|
|
10208
|
+
const state = await gm.current();
|
|
10209
|
+
if (!state) {
|
|
10210
|
+
emitError("next", "No active Generation. Run /reap.start first.");
|
|
10211
|
+
}
|
|
10212
|
+
const isMerge = state.type === "merge";
|
|
10213
|
+
if (state.lastNonce) {
|
|
10214
|
+
const nextCommand = isMerge ? `reap run merge-${state.stage}` : `reap run ${state.stage}`;
|
|
10215
|
+
emitOutput({
|
|
10216
|
+
status: "ok",
|
|
10217
|
+
command: "next",
|
|
10218
|
+
phase: "done",
|
|
10219
|
+
completed: ["gate", "auto-transition-detected"],
|
|
10220
|
+
context: {
|
|
10221
|
+
generationId: state.id,
|
|
10222
|
+
stage: state.stage,
|
|
10223
|
+
type: state.type
|
|
10224
|
+
},
|
|
10225
|
+
message: `Stage already advanced to ${state.stage} by auto-transition. Run: ${nextCommand}`,
|
|
10226
|
+
nextCommand
|
|
10227
|
+
});
|
|
10228
|
+
}
|
|
10229
|
+
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.`);
|
|
10230
|
+
}
|
|
10231
|
+
var init_next = __esm(() => {
|
|
10232
|
+
init_generation();
|
|
10233
|
+
});
|
|
10234
|
+
|
|
10200
10235
|
// src/core/merge-lifecycle.ts
|
|
10201
10236
|
class MergeLifeCycle {
|
|
10202
10237
|
static next(stage) {
|
|
@@ -10244,14 +10279,14 @@ var init_merge_lifecycle = __esm(() => {
|
|
|
10244
10279
|
});
|
|
10245
10280
|
|
|
10246
10281
|
// src/core/hook-engine.ts
|
|
10247
|
-
import { readdir as
|
|
10248
|
-
import { join as
|
|
10282
|
+
import { readdir as readdir12 } from "fs/promises";
|
|
10283
|
+
import { join as join14 } from "path";
|
|
10249
10284
|
import { execSync as execSync4 } from "child_process";
|
|
10250
10285
|
async function executeHooks(hooksDir, event, projectRoot) {
|
|
10251
10286
|
const hooks = await scanHooks(hooksDir, event);
|
|
10252
10287
|
if (hooks.length === 0)
|
|
10253
10288
|
return [];
|
|
10254
|
-
const conditionsDir =
|
|
10289
|
+
const conditionsDir = join14(hooksDir, "conditions");
|
|
10255
10290
|
const results = [];
|
|
10256
10291
|
for (const hook of hooks) {
|
|
10257
10292
|
const conditionMet = await evaluateCondition(conditionsDir, hook.condition, projectRoot);
|
|
@@ -10276,7 +10311,7 @@ async function executeHooks(hooksDir, event, projectRoot) {
|
|
|
10276
10311
|
async function scanHooks(hooksDir, event) {
|
|
10277
10312
|
let entries;
|
|
10278
10313
|
try {
|
|
10279
|
-
entries = await
|
|
10314
|
+
entries = await readdir12(hooksDir);
|
|
10280
10315
|
} catch {
|
|
10281
10316
|
return [];
|
|
10282
10317
|
}
|
|
@@ -10286,7 +10321,7 @@ async function scanHooks(hooksDir, event) {
|
|
|
10286
10321
|
const match = filename.match(pattern);
|
|
10287
10322
|
if (!match)
|
|
10288
10323
|
continue;
|
|
10289
|
-
const meta = await parseHookMeta(
|
|
10324
|
+
const meta = await parseHookMeta(join14(hooksDir, filename), match[2]);
|
|
10290
10325
|
hooks.push({
|
|
10291
10326
|
filename,
|
|
10292
10327
|
name: match[1],
|
|
@@ -10333,7 +10368,7 @@ async function parseHookMeta(filePath, ext) {
|
|
|
10333
10368
|
async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
|
|
10334
10369
|
if (conditionName === "always")
|
|
10335
10370
|
return { met: true };
|
|
10336
|
-
const scriptPath =
|
|
10371
|
+
const scriptPath = join14(conditionsDir, `${conditionName}.sh`);
|
|
10337
10372
|
if (!await fileExists(scriptPath)) {
|
|
10338
10373
|
return { met: false, reason: `condition script not found: ${conditionName}.sh` };
|
|
10339
10374
|
}
|
|
@@ -10346,7 +10381,7 @@ async function evaluateCondition(conditionsDir, conditionName, projectRoot) {
|
|
|
10346
10381
|
}
|
|
10347
10382
|
async function executeShHook(hook, event, projectRoot, hooksDir) {
|
|
10348
10383
|
try {
|
|
10349
|
-
const stdout = execSync4(`bash "${
|
|
10384
|
+
const stdout = execSync4(`bash "${join14(hooksDir, hook.filename)}"`, {
|
|
10350
10385
|
cwd: projectRoot,
|
|
10351
10386
|
timeout: 60000,
|
|
10352
10387
|
stdio: "pipe"
|
|
@@ -10372,7 +10407,7 @@ async function executeShHook(hook, event, projectRoot, hooksDir) {
|
|
|
10372
10407
|
}
|
|
10373
10408
|
}
|
|
10374
10409
|
async function executeMdHook(hook, event, hooksDir) {
|
|
10375
|
-
const content = await readTextFile(
|
|
10410
|
+
const content = await readTextFile(join14(hooksDir, hook.filename));
|
|
10376
10411
|
const body = content?.replace(/^---\n[\s\S]*?\n---\n?/, "").trim() ?? "";
|
|
10377
10412
|
return {
|
|
10378
10413
|
name: hook.name,
|
|
@@ -10388,111 +10423,6 @@ var init_hook_engine = __esm(() => {
|
|
|
10388
10423
|
import_yaml8 = __toESM(require_dist(), 1);
|
|
10389
10424
|
});
|
|
10390
10425
|
|
|
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
|
-
};
|
|
10494
|
-
});
|
|
10495
|
-
|
|
10496
10426
|
// src/cli/commands/run/back.ts
|
|
10497
10427
|
var exports_back = {};
|
|
10498
10428
|
__export(exports_back, {
|
|
@@ -10596,12 +10526,12 @@ var init_back = __esm(() => {
|
|
|
10596
10526
|
});
|
|
10597
10527
|
|
|
10598
10528
|
// src/core/backlog.ts
|
|
10599
|
-
import { readdir as
|
|
10600
|
-
import { join as
|
|
10529
|
+
import { readdir as readdir13 } from "fs/promises";
|
|
10530
|
+
import { join as join15 } from "path";
|
|
10601
10531
|
async function scanBacklog(backlogDir) {
|
|
10602
10532
|
let entries;
|
|
10603
10533
|
try {
|
|
10604
|
-
entries = await
|
|
10534
|
+
entries = await readdir13(backlogDir);
|
|
10605
10535
|
} catch {
|
|
10606
10536
|
return [];
|
|
10607
10537
|
}
|
|
@@ -10609,7 +10539,7 @@ async function scanBacklog(backlogDir) {
|
|
|
10609
10539
|
for (const filename of entries) {
|
|
10610
10540
|
if (!filename.endsWith(".md"))
|
|
10611
10541
|
continue;
|
|
10612
|
-
const content = await readTextFile(
|
|
10542
|
+
const content = await readTextFile(join15(backlogDir, filename));
|
|
10613
10543
|
if (!content)
|
|
10614
10544
|
continue;
|
|
10615
10545
|
const { frontmatter, body } = parseFrontmatter2(content);
|
|
@@ -10627,7 +10557,7 @@ async function scanBacklog(backlogDir) {
|
|
|
10627
10557
|
return items;
|
|
10628
10558
|
}
|
|
10629
10559
|
async function markBacklogConsumed(backlogDir, filename, genId) {
|
|
10630
|
-
const filePath =
|
|
10560
|
+
const filePath = join15(backlogDir, filename);
|
|
10631
10561
|
const content = await readTextFile(filePath);
|
|
10632
10562
|
if (!content)
|
|
10633
10563
|
return;
|
|
@@ -10644,14 +10574,14 @@ ${body}`;
|
|
|
10644
10574
|
async function revertBacklogConsumed(backlogDir, genId) {
|
|
10645
10575
|
let entries;
|
|
10646
10576
|
try {
|
|
10647
|
-
entries = await
|
|
10577
|
+
entries = await readdir13(backlogDir);
|
|
10648
10578
|
} catch {
|
|
10649
10579
|
return;
|
|
10650
10580
|
}
|
|
10651
10581
|
for (const filename of entries) {
|
|
10652
10582
|
if (!filename.endsWith(".md"))
|
|
10653
10583
|
continue;
|
|
10654
|
-
const filePath =
|
|
10584
|
+
const filePath = join15(backlogDir, filename);
|
|
10655
10585
|
const content = await readTextFile(filePath);
|
|
10656
10586
|
if (!content)
|
|
10657
10587
|
continue;
|
|
@@ -10688,8 +10618,8 @@ var exports_start = {};
|
|
|
10688
10618
|
__export(exports_start, {
|
|
10689
10619
|
execute: () => execute3
|
|
10690
10620
|
});
|
|
10691
|
-
import { join as
|
|
10692
|
-
import { readdir as
|
|
10621
|
+
import { join as join16 } from "path";
|
|
10622
|
+
import { readdir as readdir14 } from "fs/promises";
|
|
10693
10623
|
function getFlag2(args, name) {
|
|
10694
10624
|
const idx = args.indexOf(`--${name}`);
|
|
10695
10625
|
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
@@ -10738,18 +10668,18 @@ async function execute3(paths, phase, argv = []) {
|
|
|
10738
10668
|
}
|
|
10739
10669
|
let genomeVersion = 1;
|
|
10740
10670
|
try {
|
|
10741
|
-
const lineageEntries = await
|
|
10671
|
+
const lineageEntries = await readdir14(paths.lineage);
|
|
10742
10672
|
genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length + 1;
|
|
10743
10673
|
} catch {}
|
|
10744
10674
|
const state = await gm.create(goal, genomeVersion);
|
|
10745
|
-
const { nonce, hash } =
|
|
10746
|
-
state.
|
|
10675
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
10676
|
+
state.expectedHash = hash;
|
|
10747
10677
|
await gm.save(state);
|
|
10748
10678
|
if (backlogFile) {
|
|
10749
10679
|
await markBacklogConsumed(paths.backlog, backlogFile, state.id);
|
|
10750
10680
|
}
|
|
10751
|
-
const templateDir =
|
|
10752
|
-
const templatePath =
|
|
10681
|
+
const templateDir = join16(__require("os").homedir(), ".reap", "templates");
|
|
10682
|
+
const templatePath = join16(templateDir, "01-objective.md");
|
|
10753
10683
|
const destPath = paths.artifact("01-objective.md");
|
|
10754
10684
|
if (await fileExists(templatePath)) {
|
|
10755
10685
|
let template = await readTextFile(templatePath);
|
|
@@ -10808,12 +10738,204 @@ function checkSubmodules(projectRoot) {
|
|
|
10808
10738
|
}
|
|
10809
10739
|
var init_commit = () => {};
|
|
10810
10740
|
|
|
10741
|
+
// src/core/stage-transition.ts
|
|
10742
|
+
import { join as join17 } from "path";
|
|
10743
|
+
function verifyStageEntry(command, state) {
|
|
10744
|
+
if (!state.lastNonce) {
|
|
10745
|
+
return;
|
|
10746
|
+
}
|
|
10747
|
+
if (state.phase) {
|
|
10748
|
+
return;
|
|
10749
|
+
}
|
|
10750
|
+
if (!state.expectedHash) {
|
|
10751
|
+
emitError(command, "Stage transition error: lastNonce exists but expectedHash is missing. State may be corrupted.");
|
|
10752
|
+
}
|
|
10753
|
+
const isMerge = state.type === "merge";
|
|
10754
|
+
let prevStage;
|
|
10755
|
+
if (isMerge) {
|
|
10756
|
+
prevStage = MergeLifeCycle.prev(state.stage);
|
|
10757
|
+
} else {
|
|
10758
|
+
prevStage = LifeCycle.prev(state.stage);
|
|
10759
|
+
}
|
|
10760
|
+
if (!prevStage) {
|
|
10761
|
+
emitError(command, "Stage transition error: cannot determine previous stage for token verification.");
|
|
10762
|
+
}
|
|
10763
|
+
if (!verifyToken(state.lastNonce, state.id, prevStage, state.expectedHash)) {
|
|
10764
|
+
emitError(command, `Token verification failed. The stage chain token does not match. Re-run the previous stage command to get a valid token.`);
|
|
10765
|
+
}
|
|
10766
|
+
state.lastNonce = undefined;
|
|
10767
|
+
state.expectedHash = undefined;
|
|
10768
|
+
}
|
|
10769
|
+
function setPhaseNonce(state, stage, phase) {
|
|
10770
|
+
const { nonce, hash } = generateToken(state.id, stage, phase);
|
|
10771
|
+
state.lastNonce = nonce;
|
|
10772
|
+
state.expectedHash = hash;
|
|
10773
|
+
state.phase = phase;
|
|
10774
|
+
}
|
|
10775
|
+
function verifyPhaseEntry(command, state, stage, phase) {
|
|
10776
|
+
if (!state.lastNonce) {
|
|
10777
|
+
emitError(command, `Phase nonce missing. Complete the previous phase before running --phase ${phase}.`);
|
|
10778
|
+
}
|
|
10779
|
+
if (!state.expectedHash) {
|
|
10780
|
+
emitError(command, "Phase transition error: lastNonce exists but expectedHash is missing. State may be corrupted.");
|
|
10781
|
+
}
|
|
10782
|
+
if (!verifyToken(state.lastNonce, state.id, stage, state.expectedHash, phase)) {
|
|
10783
|
+
emitError(command, `Phase token verification failed. Re-run the previous phase to get a valid token.`);
|
|
10784
|
+
}
|
|
10785
|
+
state.lastNonce = undefined;
|
|
10786
|
+
state.expectedHash = undefined;
|
|
10787
|
+
state.phase = undefined;
|
|
10788
|
+
}
|
|
10789
|
+
async function performTransition(paths, state, saveFn) {
|
|
10790
|
+
const isMerge = state.type === "merge";
|
|
10791
|
+
let nextStage;
|
|
10792
|
+
if (isMerge) {
|
|
10793
|
+
nextStage = MergeLifeCycle.next(state.stage);
|
|
10794
|
+
} else {
|
|
10795
|
+
nextStage = LifeCycle.next(state.stage);
|
|
10796
|
+
}
|
|
10797
|
+
if (!nextStage) {
|
|
10798
|
+
throw new Error(`Cannot advance from '${state.stage}' — already at the last stage.`);
|
|
10799
|
+
}
|
|
10800
|
+
state.stage = nextStage;
|
|
10801
|
+
if (!state.timeline)
|
|
10802
|
+
state.timeline = [];
|
|
10803
|
+
state.timeline.push({ stage: nextStage, at: new Date().toISOString() });
|
|
10804
|
+
await saveFn(state);
|
|
10805
|
+
const artifactFile = isMerge ? MERGE_ARTIFACT[nextStage] : NORMAL_ARTIFACT[nextStage];
|
|
10806
|
+
if (artifactFile) {
|
|
10807
|
+
const templateDir = join17(__require("os").homedir(), ".reap", "templates");
|
|
10808
|
+
const templatePath = join17(templateDir, artifactFile);
|
|
10809
|
+
const destPath = paths.artifact(artifactFile);
|
|
10810
|
+
if (await fileExists(templatePath) && !await fileExists(destPath)) {
|
|
10811
|
+
const templateContent = await readTextFile(templatePath);
|
|
10812
|
+
if (templateContent) {
|
|
10813
|
+
await writeTextFile(destPath, templateContent);
|
|
10814
|
+
}
|
|
10815
|
+
}
|
|
10816
|
+
}
|
|
10817
|
+
const stageKey = isMerge && (nextStage === "validation" || nextStage === "completion") ? `${nextStage}:merge` : nextStage;
|
|
10818
|
+
const stageHookEvent = STAGE_HOOK[stageKey];
|
|
10819
|
+
const stageHookResults = stageHookEvent ? await executeHooks(paths.hooks, stageHookEvent, paths.projectRoot) : [];
|
|
10820
|
+
const transitionEvent = isMerge ? "onMergeTransited" : "onLifeTransited";
|
|
10821
|
+
const transitionHookResults = await executeHooks(paths.hooks, transitionEvent, paths.projectRoot);
|
|
10822
|
+
return {
|
|
10823
|
+
nextStage,
|
|
10824
|
+
artifactFile,
|
|
10825
|
+
stageHookResults,
|
|
10826
|
+
transitionHookResults
|
|
10827
|
+
};
|
|
10828
|
+
}
|
|
10829
|
+
var NORMAL_ARTIFACT, MERGE_ARTIFACT, STAGE_HOOK;
|
|
10830
|
+
var init_stage_transition = __esm(() => {
|
|
10831
|
+
init_lifecycle();
|
|
10832
|
+
init_merge_lifecycle();
|
|
10833
|
+
init_generation();
|
|
10834
|
+
init_fs();
|
|
10835
|
+
init_hook_engine();
|
|
10836
|
+
NORMAL_ARTIFACT = {
|
|
10837
|
+
planning: "02-planning.md",
|
|
10838
|
+
implementation: "03-implementation.md",
|
|
10839
|
+
validation: "04-validation.md",
|
|
10840
|
+
completion: "05-completion.md"
|
|
10841
|
+
};
|
|
10842
|
+
MERGE_ARTIFACT = {
|
|
10843
|
+
mate: "02-mate.md",
|
|
10844
|
+
merge: "03-merge.md",
|
|
10845
|
+
sync: "04-sync.md",
|
|
10846
|
+
validation: "05-validation.md",
|
|
10847
|
+
completion: "06-completion.md"
|
|
10848
|
+
};
|
|
10849
|
+
STAGE_HOOK = {
|
|
10850
|
+
planning: "onLifeObjected",
|
|
10851
|
+
implementation: "onLifePlanned",
|
|
10852
|
+
validation: "onLifeImplemented",
|
|
10853
|
+
completion: "onLifeValidated",
|
|
10854
|
+
mate: "onMergeDetected",
|
|
10855
|
+
merge: "onMergeMated",
|
|
10856
|
+
sync: "onMergeMerged",
|
|
10857
|
+
"validation:merge": "onMergeSynced",
|
|
10858
|
+
"completion:merge": "onMergeValidated"
|
|
10859
|
+
};
|
|
10860
|
+
});
|
|
10861
|
+
|
|
10811
10862
|
// src/cli/commands/run/completion.ts
|
|
10812
10863
|
var exports_completion = {};
|
|
10813
10864
|
__export(exports_completion, {
|
|
10814
10865
|
execute: () => execute4
|
|
10815
10866
|
});
|
|
10816
|
-
import { join as
|
|
10867
|
+
import { join as join18 } from "path";
|
|
10868
|
+
import { execSync as execSync6 } from "child_process";
|
|
10869
|
+
function detectGenomeImpact(projectRoot) {
|
|
10870
|
+
const impact = {
|
|
10871
|
+
newCommands: [],
|
|
10872
|
+
packageJsonChanged: false,
|
|
10873
|
+
coreChanges: []
|
|
10874
|
+
};
|
|
10875
|
+
let changedFiles;
|
|
10876
|
+
try {
|
|
10877
|
+
const output = execSync6("git diff --name-only HEAD~1", {
|
|
10878
|
+
cwd: projectRoot,
|
|
10879
|
+
encoding: "utf-8",
|
|
10880
|
+
timeout: 5000
|
|
10881
|
+
});
|
|
10882
|
+
changedFiles = output.trim().split(`
|
|
10883
|
+
`).filter(Boolean);
|
|
10884
|
+
} catch {
|
|
10885
|
+
return impact;
|
|
10886
|
+
}
|
|
10887
|
+
for (const file of changedFiles) {
|
|
10888
|
+
if (file.startsWith("src/cli/commands/") && file.endsWith(".ts")) {
|
|
10889
|
+
impact.newCommands.push(file);
|
|
10890
|
+
}
|
|
10891
|
+
if (file === "package.json") {
|
|
10892
|
+
impact.packageJsonChanged = true;
|
|
10893
|
+
}
|
|
10894
|
+
if (file.startsWith("src/core/") && file.endsWith(".ts")) {
|
|
10895
|
+
impact.coreChanges.push(file);
|
|
10896
|
+
}
|
|
10897
|
+
}
|
|
10898
|
+
return impact;
|
|
10899
|
+
}
|
|
10900
|
+
function buildGenomeImpactPrompt(impact) {
|
|
10901
|
+
const lines = [];
|
|
10902
|
+
if (impact.newCommands.length > 0) {
|
|
10903
|
+
lines.push(`- **Commands changed/added** (${impact.newCommands.length}): constraints.md의 Slash Commands 목록 업데이트 필요 여부 확인`);
|
|
10904
|
+
}
|
|
10905
|
+
if (impact.packageJsonChanged) {
|
|
10906
|
+
lines.push("- **package.json changed**: constraints.md의 Tech Stack 및 environment.md 업데이트 필요 여부 확인");
|
|
10907
|
+
}
|
|
10908
|
+
if (impact.coreChanges.length > 0) {
|
|
10909
|
+
lines.push(`- **Core modules changed** (${impact.coreChanges.length}): principles.md 및 source-map.md 업데이트 필요 여부 확인`);
|
|
10910
|
+
}
|
|
10911
|
+
if (lines.length === 0)
|
|
10912
|
+
return "";
|
|
10913
|
+
return [
|
|
10914
|
+
"",
|
|
10915
|
+
"",
|
|
10916
|
+
"## Genome/Environment Impact Detection",
|
|
10917
|
+
"다음 변경이 감지되었습니다. genome-change 또는 environment-change backlog 작성이 필요한지 검토하라:",
|
|
10918
|
+
"",
|
|
10919
|
+
...lines
|
|
10920
|
+
].join(`
|
|
10921
|
+
`);
|
|
10922
|
+
}
|
|
10923
|
+
function buildMdHookPrompt(hookResults) {
|
|
10924
|
+
const mdHooks = hookResults.filter((h) => h.type === "md" && h.status === "executed" && h.content);
|
|
10925
|
+
if (mdHooks.length === 0)
|
|
10926
|
+
return "";
|
|
10927
|
+
const sections = mdHooks.map((h) => `### ${h.name}
|
|
10928
|
+
${h.content}`);
|
|
10929
|
+
return [
|
|
10930
|
+
"",
|
|
10931
|
+
"",
|
|
10932
|
+
"## Hook Prompts",
|
|
10933
|
+
"다음 hook prompt를 순서대로 실행하라:",
|
|
10934
|
+
"",
|
|
10935
|
+
...sections
|
|
10936
|
+
].join(`
|
|
10937
|
+
`);
|
|
10938
|
+
}
|
|
10817
10939
|
async function execute4(paths, phase) {
|
|
10818
10940
|
const gm = new GenerationManager(paths);
|
|
10819
10941
|
const state = await gm.current();
|
|
@@ -10823,6 +10945,8 @@ async function execute4(paths, phase) {
|
|
|
10823
10945
|
if (state.stage !== "completion") {
|
|
10824
10946
|
emitError("completion", `Stage is '${state.stage}', expected 'completion'.`);
|
|
10825
10947
|
}
|
|
10948
|
+
verifyStageEntry("completion", state);
|
|
10949
|
+
await gm.save(state);
|
|
10826
10950
|
const validationArtifact = paths.artifact("04-validation.md");
|
|
10827
10951
|
if (!await fileExists(validationArtifact)) {
|
|
10828
10952
|
emitError("completion", "04-validation.md does not exist. Complete validation first.");
|
|
@@ -10833,8 +10957,8 @@ async function execute4(paths, phase) {
|
|
|
10833
10957
|
const implContent = await readTextFile(paths.artifact("03-implementation.md"));
|
|
10834
10958
|
const destPath = paths.artifact("05-completion.md");
|
|
10835
10959
|
if (!await fileExists(destPath)) {
|
|
10836
|
-
const templateDir =
|
|
10837
|
-
const templatePath =
|
|
10960
|
+
const templateDir = join18(__require("os").homedir(), ".reap", "templates");
|
|
10961
|
+
const templatePath = join18(templateDir, "05-completion.md");
|
|
10838
10962
|
if (await fileExists(templatePath)) {
|
|
10839
10963
|
const { writeTextFile: writeTextFile2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
|
|
10840
10964
|
const template = await readTextFile(templatePath);
|
|
@@ -10842,6 +10966,8 @@ async function execute4(paths, phase) {
|
|
|
10842
10966
|
await writeTextFile2(destPath, template);
|
|
10843
10967
|
}
|
|
10844
10968
|
}
|
|
10969
|
+
setPhaseNonce(state, "completion", "retrospective");
|
|
10970
|
+
await gm.save(state);
|
|
10845
10971
|
emitOutput({
|
|
10846
10972
|
status: "prompt",
|
|
10847
10973
|
command: "completion",
|
|
@@ -10856,11 +10982,13 @@ async function execute4(paths, phase) {
|
|
|
10856
10982
|
validationSummary: validationContent?.slice(0, 2000),
|
|
10857
10983
|
implSummary: implContent?.slice(0, 2000)
|
|
10858
10984
|
},
|
|
10859
|
-
prompt: "Fill 05-completion.md: Summary (goal, period, result, key changes), Lessons Learned (max 5), Genome Change Proposals, Garbage Collection (check conventions violations), Backlog Cleanup (add deferred tasks). Then run: reap run completion --phase
|
|
10860
|
-
nextCommand: "reap run completion --phase
|
|
10985
|
+
prompt: "Fill 05-completion.md: Summary (goal, period, result, key changes), Lessons Learned (max 5), Genome Change Proposals, Garbage Collection (check conventions violations), Backlog Cleanup (add deferred tasks). Then run: reap run completion --phase feedKnowledge",
|
|
10986
|
+
nextCommand: "reap run completion --phase feedKnowledge"
|
|
10861
10987
|
});
|
|
10862
10988
|
}
|
|
10863
|
-
if (phase === "
|
|
10989
|
+
if (phase === "feedKnowledge") {
|
|
10990
|
+
verifyPhaseEntry("completion", state, "completion", "retrospective");
|
|
10991
|
+
await gm.save(state);
|
|
10864
10992
|
const backlogItems = await scanBacklog(paths.backlog);
|
|
10865
10993
|
const genomeChanges = backlogItems.filter((b) => b.type === "genome-change" && b.status !== "consumed");
|
|
10866
10994
|
const envChanges = backlogItems.filter((b) => b.type === "environment-change" && b.status !== "consumed");
|
|
@@ -10868,6 +10996,8 @@ async function execute4(paths, phase) {
|
|
|
10868
10996
|
for (const item of toConsume) {
|
|
10869
10997
|
await markBacklogConsumed(paths.backlog, item.filename, state.id);
|
|
10870
10998
|
}
|
|
10999
|
+
const genomeImpact = detectGenomeImpact(paths.projectRoot);
|
|
11000
|
+
const impactPrompt = buildGenomeImpactPrompt(genomeImpact);
|
|
10871
11001
|
const hookResults = await executeHooks(paths.hooks, "onLifeCompleted", paths.projectRoot);
|
|
10872
11002
|
const submodules = checkSubmodules(paths.projectRoot);
|
|
10873
11003
|
const dirtySubmodules = submodules.filter((s) => s.dirty);
|
|
@@ -10897,9 +11027,10 @@ async function execute4(paths, phase) {
|
|
|
10897
11027
|
consumedCount: toConsume.length,
|
|
10898
11028
|
compression: { level1: compression.level1.length, level2: compression.level2.length },
|
|
10899
11029
|
hookResults,
|
|
10900
|
-
dirtySubmodules
|
|
11030
|
+
dirtySubmodules,
|
|
11031
|
+
genomeImpact
|
|
10901
11032
|
},
|
|
10902
|
-
prompt: dirtySubmodules.length > 0 ? `Dirty submodules detected: ${dirtySubmodules.map((s) => s.path).join(", ")}. Commit and push inside each submodule first, then commit the parent repo. Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.` : `Commit all changes (code + .reap/ artifacts). Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete
|
|
11033
|
+
prompt: (dirtySubmodules.length > 0 ? `Dirty submodules detected: ${dirtySubmodules.map((s) => s.path).join(", ")}. Commit and push inside each submodule first, then commit the parent repo. Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.` : `Commit all changes (code + .reap/ artifacts). Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.`) + impactPrompt + buildMdHookPrompt(hookResults),
|
|
10903
11034
|
message: `Generation ${state.id} archived. ${hasChanges ? "Genome/env changes applied." : "No genome/environment changes."} ${toConsume.length} backlog item(s) consumed.`
|
|
10904
11035
|
});
|
|
10905
11036
|
}
|
|
@@ -10936,7 +11067,7 @@ async function execute4(paths, phase) {
|
|
|
10936
11067
|
hookResults,
|
|
10937
11068
|
dirtySubmodules
|
|
10938
11069
|
},
|
|
10939
|
-
prompt: dirtySubmodules.length > 0 ? `Dirty submodules detected: ${dirtySubmodules.map((s) => s.path).join(", ")}. Commit and push inside each submodule first, then commit the parent repo. Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.` : `Commit all changes (code + .reap/ artifacts). Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete
|
|
11070
|
+
prompt: (dirtySubmodules.length > 0 ? `Dirty submodules detected: ${dirtySubmodules.map((s) => s.path).join(", ")}. Commit and push inside each submodule first, then commit the parent repo. Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.` : `Commit all changes (code + .reap/ artifacts). Commit message: feat/fix/chore(${state.id}): [goal summary]. Generation complete.`) + buildMdHookPrompt(hookResults),
|
|
10940
11071
|
message: `Generation ${state.id} archived.`
|
|
10941
11072
|
});
|
|
10942
11073
|
}
|
|
@@ -10947,6 +11078,7 @@ var init_completion = __esm(() => {
|
|
|
10947
11078
|
init_backlog();
|
|
10948
11079
|
init_hook_engine();
|
|
10949
11080
|
init_commit();
|
|
11081
|
+
init_stage_transition();
|
|
10950
11082
|
});
|
|
10951
11083
|
|
|
10952
11084
|
// src/cli/commands/run/abort.ts
|
|
@@ -10954,8 +11086,8 @@ var exports_abort = {};
|
|
|
10954
11086
|
__export(exports_abort, {
|
|
10955
11087
|
execute: () => execute5
|
|
10956
11088
|
});
|
|
10957
|
-
import { join as
|
|
10958
|
-
import { readdir as
|
|
11089
|
+
import { join as join19 } from "path";
|
|
11090
|
+
import { readdir as readdir15, unlink as unlink6 } from "fs/promises";
|
|
10959
11091
|
function getFlag3(args, name) {
|
|
10960
11092
|
const idx = args.indexOf(`--${name}`);
|
|
10961
11093
|
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
@@ -11030,15 +11162,15 @@ async function execute5(paths, phase, argv = []) {
|
|
|
11030
11162
|
sourceAction === "stash" ? "git stash pop으로 코드 복구" : sourceAction === "hold" ? "코드 변경이 working tree에 유지됨" : sourceAction === "rollback" ? "코드 변경이 revert됨. objective부터 재시작 필요" : "소스 코드 변경 없음"
|
|
11031
11163
|
].filter((line) => line !== null).join(`
|
|
11032
11164
|
`);
|
|
11033
|
-
await writeTextFile(
|
|
11165
|
+
await writeTextFile(join19(paths.backlog, `aborted-${state.id}.md`), backlogContent);
|
|
11034
11166
|
backlogSaved = true;
|
|
11035
11167
|
}
|
|
11036
11168
|
await revertBacklogConsumed(paths.backlog, state.id);
|
|
11037
11169
|
try {
|
|
11038
|
-
const lifeEntries = await
|
|
11170
|
+
const lifeEntries = await readdir15(paths.life);
|
|
11039
11171
|
for (const entry of lifeEntries) {
|
|
11040
11172
|
if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
|
|
11041
|
-
await
|
|
11173
|
+
await unlink6(join19(paths.life, entry));
|
|
11042
11174
|
}
|
|
11043
11175
|
}
|
|
11044
11176
|
} catch {}
|
|
@@ -11097,8 +11229,8 @@ var exports_objective = {};
|
|
|
11097
11229
|
__export(exports_objective, {
|
|
11098
11230
|
execute: () => execute7
|
|
11099
11231
|
});
|
|
11100
|
-
import { join as
|
|
11101
|
-
import { readdir as
|
|
11232
|
+
import { join as join20 } from "path";
|
|
11233
|
+
import { readdir as readdir16 } from "fs/promises";
|
|
11102
11234
|
async function execute7(paths, phase) {
|
|
11103
11235
|
const gm = new GenerationManager(paths);
|
|
11104
11236
|
const state = await gm.current();
|
|
@@ -11115,13 +11247,13 @@ async function execute7(paths, phase) {
|
|
|
11115
11247
|
const envSummary = await readTextFile(paths.environmentSummary);
|
|
11116
11248
|
let prevCompletion = null;
|
|
11117
11249
|
try {
|
|
11118
|
-
const lineageEntries = await
|
|
11250
|
+
const lineageEntries = await readdir16(paths.lineage);
|
|
11119
11251
|
const genDirs = lineageEntries.filter((e) => e.startsWith("gen-")).sort();
|
|
11120
11252
|
if (genDirs.length > 0) {
|
|
11121
11253
|
const lastGen = genDirs[genDirs.length - 1];
|
|
11122
|
-
prevCompletion = await readTextFile(
|
|
11254
|
+
prevCompletion = await readTextFile(join20(paths.lineage, lastGen, "05-completion.md"));
|
|
11123
11255
|
if (!prevCompletion) {
|
|
11124
|
-
const compressed = await readTextFile(
|
|
11256
|
+
const compressed = await readTextFile(join20(paths.lineage, `${lastGen}.md`));
|
|
11125
11257
|
if (compressed)
|
|
11126
11258
|
prevCompletion = compressed.slice(0, 2000);
|
|
11127
11259
|
}
|
|
@@ -11134,12 +11266,12 @@ async function execute7(paths, phase) {
|
|
|
11134
11266
|
const configContent = await readTextFile(paths.config);
|
|
11135
11267
|
let lineageCount = 0;
|
|
11136
11268
|
try {
|
|
11137
|
-
const entries = await
|
|
11269
|
+
const entries = await readdir16(paths.lineage);
|
|
11138
11270
|
lineageCount = entries.filter((e) => e.startsWith("gen-")).length;
|
|
11139
11271
|
} catch {}
|
|
11140
11272
|
if (!isReentry) {
|
|
11141
|
-
const templateDir =
|
|
11142
|
-
const templatePath =
|
|
11273
|
+
const templateDir = join20(__require("os").homedir(), ".reap", "templates");
|
|
11274
|
+
const templatePath = join20(templateDir, "01-objective.md");
|
|
11143
11275
|
if (await fileExists(templatePath)) {
|
|
11144
11276
|
let template = await readTextFile(templatePath);
|
|
11145
11277
|
if (template) {
|
|
@@ -11148,6 +11280,8 @@ async function execute7(paths, phase) {
|
|
|
11148
11280
|
}
|
|
11149
11281
|
}
|
|
11150
11282
|
}
|
|
11283
|
+
setPhaseNonce(state, "objective", "work");
|
|
11284
|
+
await gm.save(state);
|
|
11151
11285
|
emitOutput({
|
|
11152
11286
|
status: "prompt",
|
|
11153
11287
|
command: "objective",
|
|
@@ -11209,6 +11343,8 @@ async function execute7(paths, phase) {
|
|
|
11209
11343
|
});
|
|
11210
11344
|
}
|
|
11211
11345
|
if (phase === "complete") {
|
|
11346
|
+
verifyPhaseEntry("objective", state, "objective", "work");
|
|
11347
|
+
await gm.save(state);
|
|
11212
11348
|
const artifactPath = paths.artifact("01-objective.md");
|
|
11213
11349
|
if (!await fileExists(artifactPath)) {
|
|
11214
11350
|
emitError("objective", "01-objective.md does not exist. Complete the objective work first.");
|
|
@@ -11217,20 +11353,26 @@ async function execute7(paths, phase) {
|
|
|
11217
11353
|
if (!content || content.length < 50) {
|
|
11218
11354
|
emitError("objective", "01-objective.md appears incomplete (too short). Fill in the objective before completing.");
|
|
11219
11355
|
}
|
|
11220
|
-
const { nonce, hash } =
|
|
11221
|
-
state.
|
|
11222
|
-
|
|
11356
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11357
|
+
state.expectedHash = hash;
|
|
11358
|
+
state.lastNonce = nonce;
|
|
11223
11359
|
const hookResults = await executeHooks(paths.hooks, "onLifeObjected", paths.projectRoot);
|
|
11360
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11361
|
+
const nextCommand = `reap run ${transition.nextStage}`;
|
|
11224
11362
|
emitOutput({
|
|
11225
11363
|
status: "ok",
|
|
11226
11364
|
command: "objective",
|
|
11227
11365
|
phase: "complete",
|
|
11228
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks"],
|
|
11366
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks", "auto-transition"],
|
|
11229
11367
|
context: {
|
|
11230
11368
|
id: state.id,
|
|
11231
|
-
hookResults
|
|
11369
|
+
hookResults,
|
|
11370
|
+
nextStage: transition.nextStage,
|
|
11371
|
+
artifactFile: transition.artifactFile,
|
|
11372
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11232
11373
|
},
|
|
11233
|
-
message: `Objective stage complete.
|
|
11374
|
+
message: `Objective stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11375
|
+
nextCommand
|
|
11234
11376
|
});
|
|
11235
11377
|
}
|
|
11236
11378
|
}
|
|
@@ -11239,6 +11381,7 @@ var init_objective = __esm(() => {
|
|
|
11239
11381
|
init_fs();
|
|
11240
11382
|
init_backlog();
|
|
11241
11383
|
init_hook_engine();
|
|
11384
|
+
init_stage_transition();
|
|
11242
11385
|
});
|
|
11243
11386
|
|
|
11244
11387
|
// src/cli/commands/run/planning.ts
|
|
@@ -11246,7 +11389,7 @@ var exports_planning = {};
|
|
|
11246
11389
|
__export(exports_planning, {
|
|
11247
11390
|
execute: () => execute8
|
|
11248
11391
|
});
|
|
11249
|
-
import { join as
|
|
11392
|
+
import { join as join21 } from "path";
|
|
11250
11393
|
async function execute8(paths, phase) {
|
|
11251
11394
|
const gm = new GenerationManager(paths);
|
|
11252
11395
|
const state = await gm.current();
|
|
@@ -11256,6 +11399,8 @@ async function execute8(paths, phase) {
|
|
|
11256
11399
|
if (state.stage !== "planning") {
|
|
11257
11400
|
emitError("planning", `Current stage is '${state.stage}', not 'planning'.`);
|
|
11258
11401
|
}
|
|
11402
|
+
verifyStageEntry("planning", state);
|
|
11403
|
+
await gm.save(state);
|
|
11259
11404
|
const objectiveArtifact = paths.artifact("01-objective.md");
|
|
11260
11405
|
if (!await fileExists(objectiveArtifact)) {
|
|
11261
11406
|
emitError("planning", "01-objective.md does not exist. Complete the objective stage first.");
|
|
@@ -11270,14 +11415,16 @@ async function execute8(paths, phase) {
|
|
|
11270
11415
|
const principlesContent = await readTextFile(paths.principles);
|
|
11271
11416
|
const implContent = await readTextFile(paths.artifact("03-implementation.md"));
|
|
11272
11417
|
if (!isReentry) {
|
|
11273
|
-
const templateDir =
|
|
11274
|
-
const templatePath =
|
|
11418
|
+
const templateDir = join21(__require("os").homedir(), ".reap", "templates");
|
|
11419
|
+
const templatePath = join21(templateDir, "02-planning.md");
|
|
11275
11420
|
if (await fileExists(templatePath)) {
|
|
11276
11421
|
const template = await readTextFile(templatePath);
|
|
11277
11422
|
if (template)
|
|
11278
11423
|
await writeTextFile(artifactPath, template);
|
|
11279
11424
|
}
|
|
11280
11425
|
}
|
|
11426
|
+
setPhaseNonce(state, "planning", "work");
|
|
11427
|
+
await gm.save(state);
|
|
11281
11428
|
emitOutput({
|
|
11282
11429
|
status: "prompt",
|
|
11283
11430
|
command: "planning",
|
|
@@ -11334,6 +11481,8 @@ async function execute8(paths, phase) {
|
|
|
11334
11481
|
});
|
|
11335
11482
|
}
|
|
11336
11483
|
if (phase === "complete") {
|
|
11484
|
+
verifyPhaseEntry("planning", state, "planning", "work");
|
|
11485
|
+
await gm.save(state);
|
|
11337
11486
|
const artifactPath = paths.artifact("02-planning.md");
|
|
11338
11487
|
if (!await fileExists(artifactPath)) {
|
|
11339
11488
|
emitError("planning", "02-planning.md does not exist. Complete the planning work first.");
|
|
@@ -11342,20 +11491,26 @@ async function execute8(paths, phase) {
|
|
|
11342
11491
|
if (!content || content.length < 50) {
|
|
11343
11492
|
emitError("planning", "02-planning.md appears incomplete. Fill in the plan before completing.");
|
|
11344
11493
|
}
|
|
11345
|
-
const { nonce, hash } =
|
|
11346
|
-
state.
|
|
11347
|
-
|
|
11494
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11495
|
+
state.expectedHash = hash;
|
|
11496
|
+
state.lastNonce = nonce;
|
|
11348
11497
|
const hookResults = await executeHooks(paths.hooks, "onLifePlanned", paths.projectRoot);
|
|
11498
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11499
|
+
const nextCommand = `reap run ${transition.nextStage}`;
|
|
11349
11500
|
emitOutput({
|
|
11350
11501
|
status: "ok",
|
|
11351
11502
|
command: "planning",
|
|
11352
11503
|
phase: "complete",
|
|
11353
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks"],
|
|
11504
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks", "auto-transition"],
|
|
11354
11505
|
context: {
|
|
11355
11506
|
id: state.id,
|
|
11356
|
-
hookResults
|
|
11507
|
+
hookResults,
|
|
11508
|
+
nextStage: transition.nextStage,
|
|
11509
|
+
artifactFile: transition.artifactFile,
|
|
11510
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11357
11511
|
},
|
|
11358
|
-
message: `Planning stage complete.
|
|
11512
|
+
message: `Planning stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11513
|
+
nextCommand
|
|
11359
11514
|
});
|
|
11360
11515
|
}
|
|
11361
11516
|
}
|
|
@@ -11363,6 +11518,7 @@ var init_planning = __esm(() => {
|
|
|
11363
11518
|
init_generation();
|
|
11364
11519
|
init_fs();
|
|
11365
11520
|
init_hook_engine();
|
|
11521
|
+
init_stage_transition();
|
|
11366
11522
|
});
|
|
11367
11523
|
|
|
11368
11524
|
// src/cli/commands/run/implementation.ts
|
|
@@ -11370,7 +11526,7 @@ var exports_implementation = {};
|
|
|
11370
11526
|
__export(exports_implementation, {
|
|
11371
11527
|
execute: () => execute9
|
|
11372
11528
|
});
|
|
11373
|
-
import { join as
|
|
11529
|
+
import { join as join22 } from "path";
|
|
11374
11530
|
async function execute9(paths, phase) {
|
|
11375
11531
|
const gm = new GenerationManager(paths);
|
|
11376
11532
|
const state = await gm.current();
|
|
@@ -11380,6 +11536,8 @@ async function execute9(paths, phase) {
|
|
|
11380
11536
|
if (state.stage !== "implementation") {
|
|
11381
11537
|
emitError("implementation", `Current stage is '${state.stage}', not 'implementation'.`);
|
|
11382
11538
|
}
|
|
11539
|
+
verifyStageEntry("implementation", state);
|
|
11540
|
+
await gm.save(state);
|
|
11383
11541
|
const planningArtifact = paths.artifact("02-planning.md");
|
|
11384
11542
|
if (!await fileExists(planningArtifact)) {
|
|
11385
11543
|
emitError("implementation", "02-planning.md does not exist. Complete the planning stage first.");
|
|
@@ -11392,14 +11550,16 @@ async function execute9(paths, phase) {
|
|
|
11392
11550
|
const conventionsContent = await readTextFile(paths.conventions);
|
|
11393
11551
|
const constraintsContent = await readTextFile(paths.constraints);
|
|
11394
11552
|
if (!isReentry) {
|
|
11395
|
-
const templateDir =
|
|
11396
|
-
const templatePath =
|
|
11553
|
+
const templateDir = join22(__require("os").homedir(), ".reap", "templates");
|
|
11554
|
+
const templatePath = join22(templateDir, "03-implementation.md");
|
|
11397
11555
|
if (await fileExists(templatePath)) {
|
|
11398
11556
|
const template = await readTextFile(templatePath);
|
|
11399
11557
|
if (template)
|
|
11400
11558
|
await writeTextFile(artifactPath, template);
|
|
11401
11559
|
}
|
|
11402
11560
|
}
|
|
11561
|
+
setPhaseNonce(state, "implementation", "work");
|
|
11562
|
+
await gm.save(state);
|
|
11403
11563
|
emitOutput({
|
|
11404
11564
|
status: "prompt",
|
|
11405
11565
|
command: "implementation",
|
|
@@ -11459,24 +11619,32 @@ async function execute9(paths, phase) {
|
|
|
11459
11619
|
});
|
|
11460
11620
|
}
|
|
11461
11621
|
if (phase === "complete") {
|
|
11622
|
+
verifyPhaseEntry("implementation", state, "implementation", "work");
|
|
11623
|
+
await gm.save(state);
|
|
11462
11624
|
const artifactPath = paths.artifact("03-implementation.md");
|
|
11463
11625
|
if (!await fileExists(artifactPath)) {
|
|
11464
11626
|
emitError("implementation", "03-implementation.md does not exist. Complete the implementation work first.");
|
|
11465
11627
|
}
|
|
11466
|
-
const { nonce, hash } =
|
|
11467
|
-
state.
|
|
11468
|
-
|
|
11628
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11629
|
+
state.expectedHash = hash;
|
|
11630
|
+
state.lastNonce = nonce;
|
|
11469
11631
|
const hookResults = await executeHooks(paths.hooks, "onLifeImplemented", paths.projectRoot);
|
|
11632
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11633
|
+
const nextCommand = `reap run ${transition.nextStage}`;
|
|
11470
11634
|
emitOutput({
|
|
11471
11635
|
status: "ok",
|
|
11472
11636
|
command: "implementation",
|
|
11473
11637
|
phase: "complete",
|
|
11474
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "hooks"],
|
|
11638
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "hooks", "auto-transition"],
|
|
11475
11639
|
context: {
|
|
11476
11640
|
id: state.id,
|
|
11477
|
-
hookResults
|
|
11641
|
+
hookResults,
|
|
11642
|
+
nextStage: transition.nextStage,
|
|
11643
|
+
artifactFile: transition.artifactFile,
|
|
11644
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11478
11645
|
},
|
|
11479
|
-
message: `Implementation stage complete.
|
|
11646
|
+
message: `Implementation stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11647
|
+
nextCommand
|
|
11480
11648
|
});
|
|
11481
11649
|
}
|
|
11482
11650
|
}
|
|
@@ -11484,6 +11652,7 @@ var init_implementation = __esm(() => {
|
|
|
11484
11652
|
init_generation();
|
|
11485
11653
|
init_fs();
|
|
11486
11654
|
init_hook_engine();
|
|
11655
|
+
init_stage_transition();
|
|
11487
11656
|
});
|
|
11488
11657
|
|
|
11489
11658
|
// src/cli/commands/run/validation.ts
|
|
@@ -11491,7 +11660,7 @@ var exports_validation = {};
|
|
|
11491
11660
|
__export(exports_validation, {
|
|
11492
11661
|
execute: () => execute10
|
|
11493
11662
|
});
|
|
11494
|
-
import { join as
|
|
11663
|
+
import { join as join23 } from "path";
|
|
11495
11664
|
async function execute10(paths, phase) {
|
|
11496
11665
|
const gm = new GenerationManager(paths);
|
|
11497
11666
|
const state = await gm.current();
|
|
@@ -11501,6 +11670,8 @@ async function execute10(paths, phase) {
|
|
|
11501
11670
|
if (state.stage !== "validation") {
|
|
11502
11671
|
emitError("validation", `Current stage is '${state.stage}', not 'validation'.`);
|
|
11503
11672
|
}
|
|
11673
|
+
verifyStageEntry("validation", state);
|
|
11674
|
+
await gm.save(state);
|
|
11504
11675
|
const implArtifact = paths.artifact("03-implementation.md");
|
|
11505
11676
|
if (!await fileExists(implArtifact)) {
|
|
11506
11677
|
emitError("validation", "03-implementation.md does not exist. Complete the implementation stage first.");
|
|
@@ -11514,14 +11685,16 @@ async function execute10(paths, phase) {
|
|
|
11514
11685
|
const implContent = await readTextFile(implArtifact);
|
|
11515
11686
|
const objectiveContent = await readTextFile(paths.artifact("01-objective.md"));
|
|
11516
11687
|
if (!isReentry) {
|
|
11517
|
-
const templateDir =
|
|
11518
|
-
const templatePath =
|
|
11688
|
+
const templateDir = join23(__require("os").homedir(), ".reap", "templates");
|
|
11689
|
+
const templatePath = join23(templateDir, "04-validation.md");
|
|
11519
11690
|
if (await fileExists(templatePath)) {
|
|
11520
11691
|
const template = await readTextFile(templatePath);
|
|
11521
11692
|
if (template)
|
|
11522
11693
|
await writeTextFile(artifactPath, template);
|
|
11523
11694
|
}
|
|
11524
11695
|
}
|
|
11696
|
+
setPhaseNonce(state, "validation", "work");
|
|
11697
|
+
await gm.save(state);
|
|
11525
11698
|
emitOutput({
|
|
11526
11699
|
status: "prompt",
|
|
11527
11700
|
command: "validation",
|
|
@@ -11591,24 +11764,32 @@ async function execute10(paths, phase) {
|
|
|
11591
11764
|
});
|
|
11592
11765
|
}
|
|
11593
11766
|
if (phase === "complete") {
|
|
11767
|
+
verifyPhaseEntry("validation", state, "validation", "work");
|
|
11768
|
+
await gm.save(state);
|
|
11594
11769
|
const artifactPath = paths.artifact("04-validation.md");
|
|
11595
11770
|
if (!await fileExists(artifactPath)) {
|
|
11596
11771
|
emitError("validation", "04-validation.md does not exist. Complete the validation work first.");
|
|
11597
11772
|
}
|
|
11598
|
-
const { nonce, hash } =
|
|
11599
|
-
state.
|
|
11600
|
-
|
|
11773
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
11774
|
+
state.expectedHash = hash;
|
|
11775
|
+
state.lastNonce = nonce;
|
|
11601
11776
|
const hookResults = await executeHooks(paths.hooks, "onLifeValidated", paths.projectRoot);
|
|
11777
|
+
const transition = await performTransition(paths, state, (s) => gm.save(s));
|
|
11778
|
+
const nextCommand = transition.nextStage !== "completion" ? `reap run ${transition.nextStage}` : "reap run completion";
|
|
11602
11779
|
emitOutput({
|
|
11603
11780
|
status: "ok",
|
|
11604
11781
|
command: "validation",
|
|
11605
11782
|
phase: "complete",
|
|
11606
|
-
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks"],
|
|
11783
|
+
completed: ["gate", "context-collect", "artifact-ensure", "creative-work", "artifact-verify", "hooks", "auto-transition"],
|
|
11607
11784
|
context: {
|
|
11608
11785
|
id: state.id,
|
|
11609
|
-
hookResults
|
|
11786
|
+
hookResults,
|
|
11787
|
+
nextStage: transition.nextStage,
|
|
11788
|
+
artifactFile: transition.artifactFile,
|
|
11789
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
11610
11790
|
},
|
|
11611
|
-
message: `Validation stage complete.
|
|
11791
|
+
message: `Validation stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
11792
|
+
nextCommand
|
|
11612
11793
|
});
|
|
11613
11794
|
}
|
|
11614
11795
|
}
|
|
@@ -11616,6 +11797,7 @@ var init_validation = __esm(() => {
|
|
|
11616
11797
|
init_generation();
|
|
11617
11798
|
init_fs();
|
|
11618
11799
|
init_hook_engine();
|
|
11800
|
+
init_stage_transition();
|
|
11619
11801
|
});
|
|
11620
11802
|
|
|
11621
11803
|
// src/cli/commands/run/evolve.ts
|
|
@@ -11627,10 +11809,15 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
|
|
|
11627
11809
|
const lines = [];
|
|
11628
11810
|
lines.push("# REAP Subagent Instructions");
|
|
11629
11811
|
lines.push("");
|
|
11812
|
+
lines.push("## FIRST: Load REAP Context");
|
|
11813
|
+
lines.push("Before doing anything else, run `reap run refreshKnowledge` to load full REAP context (Genome, Environment, Generation state, Workflow Guide).");
|
|
11814
|
+
lines.push("Incorporate the returned context into your working knowledge before proceeding.");
|
|
11815
|
+
lines.push("");
|
|
11630
11816
|
lines.push("## Rules");
|
|
11631
11817
|
lines.push("- ALWAYS use `reap run <cmd>` commands to drive lifecycle. NEVER modify `current.yml` directly.");
|
|
11632
|
-
lines.push("-
|
|
11633
|
-
lines.push("-
|
|
11818
|
+
lines.push("- Each `--phase complete` auto-transitions to the next stage. No explicit `/reap.next` needed.");
|
|
11819
|
+
lines.push("- Use `/reap.back` to regress to a previous stage.");
|
|
11820
|
+
lines.push("- Each stage command verifies the stage chain token at entry (auto-verified from lastNonce).");
|
|
11634
11821
|
lines.push("- `/reap.completion` handles archiving and the final commit.");
|
|
11635
11822
|
lines.push("");
|
|
11636
11823
|
lines.push("## Current State");
|
|
@@ -11665,7 +11852,7 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
|
|
|
11665
11852
|
lines.push(`Resume from stage: **${state.stage}**`);
|
|
11666
11853
|
}
|
|
11667
11854
|
lines.push("");
|
|
11668
|
-
lines.push("### Stage Loop");
|
|
11855
|
+
lines.push("### Stage Loop (Auto-Transition)");
|
|
11669
11856
|
lines.push("1. Read `current.yml` to confirm the current stage.");
|
|
11670
11857
|
lines.push("2. Execute the stage command:");
|
|
11671
11858
|
lines.push(" - `objective` -> `/reap.objective`");
|
|
@@ -11674,25 +11861,42 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
|
|
|
11674
11861
|
lines.push(" - `validation` -> `/reap.validation`");
|
|
11675
11862
|
lines.push(" - `completion` -> `/reap.completion`");
|
|
11676
11863
|
lines.push("3. Write the required artifact BEFORE completing the stage.");
|
|
11677
|
-
lines.push("4. Run
|
|
11678
|
-
lines.push("5. If current stage
|
|
11679
|
-
lines.push("6.
|
|
11864
|
+
lines.push("4. Run `--phase complete` — this auto-transitions to the next stage.");
|
|
11865
|
+
lines.push("5. If current stage IS `completion`: `/reap.completion` auto-archives after feedKnowledge phase. Done.");
|
|
11866
|
+
lines.push("6. Otherwise: the output tells you the next command — go to step 1.");
|
|
11680
11867
|
lines.push("");
|
|
11681
|
-
lines.push("Note: `/reap.next` is a
|
|
11868
|
+
lines.push("Note: `--phase complete` auto-transitions. `/reap.next` is a fallback, not required.");
|
|
11682
11869
|
lines.push("");
|
|
11683
11870
|
lines.push("## Project");
|
|
11684
11871
|
lines.push(`- Path: ${paths.projectRoot}`);
|
|
11685
11872
|
lines.push("");
|
|
11686
|
-
lines.push("## Stage Chain Token");
|
|
11687
|
-
lines.push("- Each
|
|
11688
|
-
lines.push("-
|
|
11689
|
-
lines.push("-
|
|
11690
|
-
lines.push("- If token is missing or mismatched, re-run the current stage command to obtain a new token.");
|
|
11873
|
+
lines.push("## Stage Chain Token (Auto-Transition)");
|
|
11874
|
+
lines.push("- Each `--phase complete` generates a stage chain token and auto-transitions to the next stage.");
|
|
11875
|
+
lines.push("- The next stage command verifies the token at entry — this ensures stages were not skipped.");
|
|
11876
|
+
lines.push("- `/reap.next` is maintained as a fallback but is no longer required in the normal flow.");
|
|
11691
11877
|
lines.push("");
|
|
11692
11878
|
lines.push("## Commit Rules");
|
|
11693
11879
|
lines.push("- Create a git commit after implementation and after completion.");
|
|
11694
11880
|
lines.push("- Use conventional commit format: `feat|fix|chore(scope): description`");
|
|
11695
11881
|
lines.push("- Include the generation ID in the commit message.");
|
|
11882
|
+
lines.push("");
|
|
11883
|
+
lines.push("## Submodule Commit Rules");
|
|
11884
|
+
lines.push("- 커밋 전 반드시 `git -C tests status -s` 로 tests submodule의 dirty 상태를 확인하라.");
|
|
11885
|
+
lines.push("- dirty 파일이 있으면:");
|
|
11886
|
+
lines.push(' 1. `git -C tests add -A && git -C tests commit -m "..." && git -C tests push`');
|
|
11887
|
+
lines.push(" 2. parent repo에서 `git add tests` 로 submodule ref 업데이트");
|
|
11888
|
+
lines.push(" 3. 그 다음 parent repo의 나머지 파일과 함께 커밋");
|
|
11889
|
+
lines.push('- completion의 prompt에 "Dirty submodules detected"가 있으면 반드시 위 절차를 따르라.');
|
|
11890
|
+
lines.push("");
|
|
11891
|
+
lines.push("## Hook Prompt Execution");
|
|
11892
|
+
lines.push("- completion output의 `prompt` 필드에 '## Hook Prompts' 섹션이 포함되어 있으면, 해당 hook prompt를 반드시 순서대로 실행하라.");
|
|
11893
|
+
lines.push("- Hook prompt는 `.md` hook 파일의 내용으로, AI가 따라야 할 지시사항이다.");
|
|
11894
|
+
lines.push("- Hook prompt에 유저 확인이 필요한 경우(예: 프리뷰+컨펌), autonomous mode에서도 이를 존중하라.");
|
|
11895
|
+
lines.push("");
|
|
11896
|
+
lines.push("## Interrupt Protection");
|
|
11897
|
+
lines.push('- 사용자의 새 메시지가 중간에 들어와도, 명시적 kill/중단 요청("중단", "stop", "abort")이 아닌 한 현재 작업을 끝까지 완료하라.');
|
|
11898
|
+
lines.push("- 작업을 shortcut으로 건너뛰거나 결과를 추정하지 마라. 모든 validation은 실제 실행 결과를 확인하라.");
|
|
11899
|
+
lines.push("- E2E 테스트 등 외부 실행이 포함된 작업은 반드시 실제 실행하고 결과를 확인하라.");
|
|
11696
11900
|
return lines.join(`
|
|
11697
11901
|
`);
|
|
11698
11902
|
}
|
|
@@ -11788,7 +11992,7 @@ async function execute11(paths, phase) {
|
|
|
11788
11992
|
"",
|
|
11789
11993
|
"### HARD-GATE",
|
|
11790
11994
|
"NEVER modify `current.yml` directly to change the stage.",
|
|
11791
|
-
"
|
|
11995
|
+
"Stage transitions happen automatically via `--phase complete`. Use `/reap.back` to regress.",
|
|
11792
11996
|
"",
|
|
11793
11997
|
"### Autonomous Override",
|
|
11794
11998
|
"- Skip routine human confirmations. Proceed autonomously.",
|
|
@@ -11804,10 +12008,10 @@ async function execute11(paths, phase) {
|
|
|
11804
12008
|
"- `/reap.validation` -> `onLifeValidated`",
|
|
11805
12009
|
"- `/reap.completion` -> `onLifeCompleted` (before archiving and commit)",
|
|
11806
12010
|
"",
|
|
11807
|
-
"
|
|
12011
|
+
"`--phase complete` auto-transitions to the next stage. `/reap.next` is a fallback.",
|
|
11808
12012
|
"`/reap.completion` handles archiving and the final commit.",
|
|
11809
12013
|
"",
|
|
11810
|
-
"### Lifecycle Loop",
|
|
12014
|
+
"### Lifecycle Loop (Auto-Transition)",
|
|
11811
12015
|
"Execute the following loop until the generation is complete:",
|
|
11812
12016
|
"1. Read `current.yml` to determine the current stage",
|
|
11813
12017
|
"2. Execute the corresponding stage command:",
|
|
@@ -11816,14 +12020,13 @@ async function execute11(paths, phase) {
|
|
|
11816
12020
|
" - `implementation` -> `/reap.implementation`",
|
|
11817
12021
|
" - `validation` -> `/reap.validation`",
|
|
11818
12022
|
" - `completion` -> `/reap.completion`",
|
|
11819
|
-
"3.
|
|
11820
|
-
" - If the
|
|
11821
|
-
" - Otherwise:
|
|
12023
|
+
"3. `--phase complete` auto-transitions to the next stage.",
|
|
12024
|
+
" - If the stage is `completion`: the loop ends.",
|
|
12025
|
+
" - Otherwise: follow the `nextCommand` in the output to run the next stage.",
|
|
11822
12026
|
"",
|
|
11823
12027
|
"### Handling Issues",
|
|
11824
12028
|
"- If validation fails: `/reap.back` to return to implementation (or earlier), then resume the loop",
|
|
11825
|
-
"- If the human wants to pause: stop the loop"
|
|
11826
|
-
"- If the human wants to skip a stage: advance with `/reap.next` without running the stage command"
|
|
12029
|
+
"- If the human wants to pause: stop the loop"
|
|
11827
12030
|
].join(`
|
|
11828
12031
|
`)
|
|
11829
12032
|
});
|
|
@@ -11869,8 +12072,8 @@ var exports_sync_genome = {};
|
|
|
11869
12072
|
__export(exports_sync_genome, {
|
|
11870
12073
|
execute: () => execute13
|
|
11871
12074
|
});
|
|
11872
|
-
import { readdir as
|
|
11873
|
-
import { join as
|
|
12075
|
+
import { readdir as readdir17 } from "fs/promises";
|
|
12076
|
+
import { join as join24 } from "path";
|
|
11874
12077
|
async function execute13(paths, phase) {
|
|
11875
12078
|
const gm = new GenerationManager(paths);
|
|
11876
12079
|
const state = await gm.current();
|
|
@@ -11882,10 +12085,10 @@ async function execute13(paths, phase) {
|
|
|
11882
12085
|
const sourceMapContent = await readTextFile(paths.sourceMap);
|
|
11883
12086
|
const domainFiles = {};
|
|
11884
12087
|
try {
|
|
11885
|
-
const entries = await
|
|
12088
|
+
const entries = await readdir17(paths.domain);
|
|
11886
12089
|
for (const entry of entries) {
|
|
11887
12090
|
if (entry.endsWith(".md")) {
|
|
11888
|
-
const content = await readTextFile(
|
|
12091
|
+
const content = await readTextFile(join24(paths.domain, entry));
|
|
11889
12092
|
if (content)
|
|
11890
12093
|
domainFiles[entry] = content.slice(0, 1000);
|
|
11891
12094
|
}
|
|
@@ -11893,7 +12096,7 @@ async function execute13(paths, phase) {
|
|
|
11893
12096
|
} catch {}
|
|
11894
12097
|
let genomeVersion = 0;
|
|
11895
12098
|
try {
|
|
11896
|
-
const lineageEntries = await
|
|
12099
|
+
const lineageEntries = await readdir17(paths.lineage);
|
|
11897
12100
|
genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length;
|
|
11898
12101
|
} catch {}
|
|
11899
12102
|
emitOutput({
|
|
@@ -11966,8 +12169,8 @@ var exports_sync_environment = {};
|
|
|
11966
12169
|
__export(exports_sync_environment, {
|
|
11967
12170
|
execute: () => execute14
|
|
11968
12171
|
});
|
|
11969
|
-
import { readdir as
|
|
11970
|
-
import { join as
|
|
12172
|
+
import { readdir as readdir18 } from "fs/promises";
|
|
12173
|
+
import { join as join25 } from "path";
|
|
11971
12174
|
async function execute14(paths, phase) {
|
|
11972
12175
|
const gm = new GenerationManager(paths);
|
|
11973
12176
|
const state = await gm.current();
|
|
@@ -11976,10 +12179,10 @@ async function execute14(paths, phase) {
|
|
|
11976
12179
|
const envSummary = await readTextFile(paths.environmentSummary);
|
|
11977
12180
|
const envDocs = {};
|
|
11978
12181
|
try {
|
|
11979
|
-
const docsEntries = await
|
|
12182
|
+
const docsEntries = await readdir18(paths.environmentDocs);
|
|
11980
12183
|
for (const entry of docsEntries) {
|
|
11981
12184
|
if (entry.endsWith(".md")) {
|
|
11982
|
-
const content = await readTextFile(
|
|
12185
|
+
const content = await readTextFile(join25(paths.environmentDocs, entry));
|
|
11983
12186
|
if (content)
|
|
11984
12187
|
envDocs[entry] = content.slice(0, 1000);
|
|
11985
12188
|
}
|
|
@@ -11987,7 +12190,7 @@ async function execute14(paths, phase) {
|
|
|
11987
12190
|
} catch {}
|
|
11988
12191
|
let linksContent = null;
|
|
11989
12192
|
try {
|
|
11990
|
-
linksContent = await readTextFile(
|
|
12193
|
+
linksContent = await readTextFile(join25(paths.environmentResources, "links.md"));
|
|
11991
12194
|
} catch {}
|
|
11992
12195
|
emitOutput({
|
|
11993
12196
|
status: "prompt",
|
|
@@ -12059,7 +12262,7 @@ var exports_help = {};
|
|
|
12059
12262
|
__export(exports_help, {
|
|
12060
12263
|
execute: () => execute15
|
|
12061
12264
|
});
|
|
12062
|
-
import { join as
|
|
12265
|
+
import { join as join26 } from "path";
|
|
12063
12266
|
function detectLanguage(configContent) {
|
|
12064
12267
|
const raw = configContent?.match(/language:\s*(\S+)/)?.[1] ?? null;
|
|
12065
12268
|
if (raw && raw in LANGUAGE_ALIASES)
|
|
@@ -12094,7 +12297,7 @@ async function execute15(paths) {
|
|
|
12094
12297
|
const gm = new GenerationManager(paths);
|
|
12095
12298
|
const state = await gm.current();
|
|
12096
12299
|
const configContent = await readTextFile(paths.config);
|
|
12097
|
-
const installedVersion = "0.
|
|
12300
|
+
const installedVersion = "0.15.0";
|
|
12098
12301
|
const autoUpdate = configContent?.match(/autoUpdate:\s*(true|false)/)?.[1] === "true";
|
|
12099
12302
|
const versionDisplay = formatVersionLine(installedVersion, !autoUpdate);
|
|
12100
12303
|
const rawLang = detectLanguage(configContent);
|
|
@@ -12105,7 +12308,7 @@ async function execute15(paths) {
|
|
|
12105
12308
|
const stateDisplay = state?.id ? `Active: **${state.id}** — ${state.goal} (Stage: ${state.stage})` : "No active Generation → `/reap.start` or `/reap.evolve`";
|
|
12106
12309
|
const lines = buildLines(versionDisplay, lang, stateDisplay);
|
|
12107
12310
|
if (topic) {
|
|
12108
|
-
const guidePath =
|
|
12311
|
+
const guidePath = join26(ReapPaths.packageHooksDir, "reap-guide.md");
|
|
12109
12312
|
const reapGuide = await readTextFile(guidePath) ?? "";
|
|
12110
12313
|
emitOutput({
|
|
12111
12314
|
status: "prompt",
|
|
@@ -12410,8 +12613,8 @@ var init_merge = __esm(() => {
|
|
|
12410
12613
|
});
|
|
12411
12614
|
|
|
12412
12615
|
// src/core/merge-generation.ts
|
|
12413
|
-
import { readdir as
|
|
12414
|
-
import { join as
|
|
12616
|
+
import { readdir as readdir19, mkdir as mkdir9, rename as rename3 } from "fs/promises";
|
|
12617
|
+
import { join as join27 } from "path";
|
|
12415
12618
|
|
|
12416
12619
|
class MergeGenerationManager {
|
|
12417
12620
|
paths;
|
|
@@ -12587,7 +12790,7 @@ class MergeGenerationManager {
|
|
|
12587
12790
|
const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
|
|
12588
12791
|
const genDirName = `${state.id}-${goalSlug}`;
|
|
12589
12792
|
const genDir = this.paths.generationDir(genDirName);
|
|
12590
|
-
await
|
|
12793
|
+
await mkdir9(genDir, { recursive: true });
|
|
12591
12794
|
const meta = {
|
|
12592
12795
|
id: state.id,
|
|
12593
12796
|
type: "merge",
|
|
@@ -12597,19 +12800,19 @@ class MergeGenerationManager {
|
|
|
12597
12800
|
startedAt: state.startedAt,
|
|
12598
12801
|
completedAt: now
|
|
12599
12802
|
};
|
|
12600
|
-
await writeTextFile(
|
|
12601
|
-
const lifeEntries = await
|
|
12803
|
+
await writeTextFile(join27(genDir, "meta.yml"), import_yaml10.default.stringify(meta));
|
|
12804
|
+
const lifeEntries = await readdir19(this.paths.life);
|
|
12602
12805
|
for (const entry of lifeEntries) {
|
|
12603
12806
|
if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
|
|
12604
|
-
await rename3(
|
|
12807
|
+
await rename3(join27(this.paths.life, entry), join27(genDir, entry));
|
|
12605
12808
|
}
|
|
12606
12809
|
}
|
|
12607
|
-
const backlogDir =
|
|
12608
|
-
await
|
|
12810
|
+
const backlogDir = join27(genDir, "backlog");
|
|
12811
|
+
await mkdir9(backlogDir, { recursive: true });
|
|
12609
12812
|
try {
|
|
12610
|
-
const backlogEntries = await
|
|
12813
|
+
const backlogEntries = await readdir19(this.paths.backlog);
|
|
12611
12814
|
for (const entry of backlogEntries) {
|
|
12612
|
-
await rename3(
|
|
12815
|
+
await rename3(join27(this.paths.backlog, entry), join27(backlogDir, entry));
|
|
12613
12816
|
}
|
|
12614
12817
|
} catch {}
|
|
12615
12818
|
await writeTextFile(this.paths.currentYml, "");
|
|
@@ -12806,6 +13009,8 @@ async function execute18(paths, phase) {
|
|
|
12806
13009
|
emitError("merge-detect", "01-detect.md does not exist. Run /reap.merge.start first.");
|
|
12807
13010
|
}
|
|
12808
13011
|
const detectContent = await readTextFile(detectArtifact);
|
|
13012
|
+
setPhaseNonce(state, "detect", "review");
|
|
13013
|
+
await mgm.save(state);
|
|
12809
13014
|
emitOutput({
|
|
12810
13015
|
status: "prompt",
|
|
12811
13016
|
command: "merge-detect",
|
|
@@ -12834,24 +13039,37 @@ async function execute18(paths, phase) {
|
|
|
12834
13039
|
});
|
|
12835
13040
|
}
|
|
12836
13041
|
if (phase === "complete") {
|
|
13042
|
+
verifyPhaseEntry("merge-detect", state, "detect", "review");
|
|
13043
|
+
await mgm.save(state);
|
|
13044
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13045
|
+
state.expectedHash = hash;
|
|
13046
|
+
state.lastNonce = nonce;
|
|
12837
13047
|
const hookResults = await executeHooks(paths.hooks, "onMergeDetected", paths.projectRoot);
|
|
13048
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13049
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
12838
13050
|
emitOutput({
|
|
12839
13051
|
status: "ok",
|
|
12840
13052
|
command: "merge-detect",
|
|
12841
13053
|
phase: "complete",
|
|
12842
|
-
completed: ["gate", "artifact-read", "review", "hooks"],
|
|
13054
|
+
completed: ["gate", "artifact-read", "review", "hooks", "auto-transition"],
|
|
12843
13055
|
context: {
|
|
12844
13056
|
id: state.id,
|
|
12845
|
-
hookResults
|
|
13057
|
+
hookResults,
|
|
13058
|
+
nextStage: transition.nextStage,
|
|
13059
|
+
artifactFile: transition.artifactFile,
|
|
13060
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
12846
13061
|
},
|
|
12847
|
-
message:
|
|
13062
|
+
message: `Detect stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13063
|
+
nextCommand
|
|
12848
13064
|
});
|
|
12849
13065
|
}
|
|
12850
13066
|
}
|
|
12851
13067
|
var init_merge_detect = __esm(() => {
|
|
12852
13068
|
init_merge_generation();
|
|
13069
|
+
init_generation();
|
|
12853
13070
|
init_fs();
|
|
12854
13071
|
init_hook_engine();
|
|
13072
|
+
init_stage_transition();
|
|
12855
13073
|
});
|
|
12856
13074
|
|
|
12857
13075
|
// src/cli/commands/run/merge-mate.ts
|
|
@@ -12871,12 +13089,16 @@ async function execute19(paths, phase) {
|
|
|
12871
13089
|
if (state.stage !== "mate") {
|
|
12872
13090
|
emitError("merge-mate", `Stage is '${state.stage}', expected 'mate'.`);
|
|
12873
13091
|
}
|
|
13092
|
+
verifyStageEntry("merge-mate", state);
|
|
13093
|
+
await mgm.save(state);
|
|
12874
13094
|
const detectArtifact = paths.artifact("01-detect.md");
|
|
12875
13095
|
if (!await fileExists(detectArtifact)) {
|
|
12876
13096
|
emitError("merge-mate", "01-detect.md does not exist. Complete detect stage first.");
|
|
12877
13097
|
}
|
|
12878
13098
|
if (!phase || phase === "resolve") {
|
|
12879
13099
|
const detectContent = await readTextFile(detectArtifact);
|
|
13100
|
+
setPhaseNonce(state, "mate", "resolve");
|
|
13101
|
+
await mgm.save(state);
|
|
12880
13102
|
emitOutput({
|
|
12881
13103
|
status: "prompt",
|
|
12882
13104
|
command: "merge-mate",
|
|
@@ -12916,24 +13138,37 @@ async function execute19(paths, phase) {
|
|
|
12916
13138
|
});
|
|
12917
13139
|
}
|
|
12918
13140
|
if (phase === "complete") {
|
|
13141
|
+
verifyPhaseEntry("merge-mate", state, "mate", "resolve");
|
|
13142
|
+
await mgm.save(state);
|
|
13143
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13144
|
+
state.expectedHash = hash;
|
|
13145
|
+
state.lastNonce = nonce;
|
|
12919
13146
|
const hookResults = await executeHooks(paths.hooks, "onMergeMated", paths.projectRoot);
|
|
13147
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13148
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
12920
13149
|
emitOutput({
|
|
12921
13150
|
status: "ok",
|
|
12922
13151
|
command: "merge-mate",
|
|
12923
13152
|
phase: "complete",
|
|
12924
|
-
completed: ["gate", "artifact-read", "conflict-resolution", "hooks"],
|
|
13153
|
+
completed: ["gate", "artifact-read", "conflict-resolution", "hooks", "auto-transition"],
|
|
12925
13154
|
context: {
|
|
12926
13155
|
id: state.id,
|
|
12927
|
-
hookResults
|
|
13156
|
+
hookResults,
|
|
13157
|
+
nextStage: transition.nextStage,
|
|
13158
|
+
artifactFile: transition.artifactFile,
|
|
13159
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
12928
13160
|
},
|
|
12929
|
-
message:
|
|
13161
|
+
message: `Mate stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13162
|
+
nextCommand
|
|
12930
13163
|
});
|
|
12931
13164
|
}
|
|
12932
13165
|
}
|
|
12933
13166
|
var init_merge_mate = __esm(() => {
|
|
12934
13167
|
init_merge_generation();
|
|
13168
|
+
init_generation();
|
|
12935
13169
|
init_fs();
|
|
12936
13170
|
init_hook_engine();
|
|
13171
|
+
init_stage_transition();
|
|
12937
13172
|
});
|
|
12938
13173
|
|
|
12939
13174
|
// src/cli/commands/run/merge-merge.ts
|
|
@@ -12953,6 +13188,8 @@ async function execute20(paths, phase) {
|
|
|
12953
13188
|
if (state.stage !== "merge") {
|
|
12954
13189
|
emitError("merge-merge", `Stage is '${state.stage}', expected 'merge'.`);
|
|
12955
13190
|
}
|
|
13191
|
+
verifyStageEntry("merge-merge", state);
|
|
13192
|
+
await mgm.save(state);
|
|
12956
13193
|
const mateArtifact = paths.artifact("02-mate.md");
|
|
12957
13194
|
if (!await fileExists(mateArtifact)) {
|
|
12958
13195
|
emitError("merge-merge", "02-mate.md does not exist. Complete mate stage first.");
|
|
@@ -12961,6 +13198,8 @@ async function execute20(paths, phase) {
|
|
|
12961
13198
|
const mateContent = await readTextFile(mateArtifact);
|
|
12962
13199
|
const detectContent = await readTextFile(paths.artifact("01-detect.md"));
|
|
12963
13200
|
const targetBranch = state.goal.split(" + ").pop() ?? "";
|
|
13201
|
+
setPhaseNonce(state, "merge", "work");
|
|
13202
|
+
await mgm.save(state);
|
|
12964
13203
|
emitOutput({
|
|
12965
13204
|
status: "prompt",
|
|
12966
13205
|
command: "merge-merge",
|
|
@@ -12995,24 +13234,37 @@ async function execute20(paths, phase) {
|
|
|
12995
13234
|
});
|
|
12996
13235
|
}
|
|
12997
13236
|
if (phase === "complete") {
|
|
13237
|
+
verifyPhaseEntry("merge-merge", state, "merge", "work");
|
|
13238
|
+
await mgm.save(state);
|
|
13239
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13240
|
+
state.expectedHash = hash;
|
|
13241
|
+
state.lastNonce = nonce;
|
|
12998
13242
|
const hookResults = await executeHooks(paths.hooks, "onMergeMerged", paths.projectRoot);
|
|
13243
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13244
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
12999
13245
|
emitOutput({
|
|
13000
13246
|
status: "ok",
|
|
13001
13247
|
command: "merge-merge",
|
|
13002
13248
|
phase: "complete",
|
|
13003
|
-
completed: ["gate", "artifact-read", "source-merge", "hooks"],
|
|
13249
|
+
completed: ["gate", "artifact-read", "source-merge", "hooks", "auto-transition"],
|
|
13004
13250
|
context: {
|
|
13005
13251
|
id: state.id,
|
|
13006
|
-
hookResults
|
|
13252
|
+
hookResults,
|
|
13253
|
+
nextStage: transition.nextStage,
|
|
13254
|
+
artifactFile: transition.artifactFile,
|
|
13255
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
13007
13256
|
},
|
|
13008
|
-
message:
|
|
13257
|
+
message: `Merge stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13258
|
+
nextCommand
|
|
13009
13259
|
});
|
|
13010
13260
|
}
|
|
13011
13261
|
}
|
|
13012
13262
|
var init_merge_merge = __esm(() => {
|
|
13013
13263
|
init_merge_generation();
|
|
13264
|
+
init_generation();
|
|
13014
13265
|
init_fs();
|
|
13015
13266
|
init_hook_engine();
|
|
13267
|
+
init_stage_transition();
|
|
13016
13268
|
});
|
|
13017
13269
|
|
|
13018
13270
|
// src/cli/commands/run/merge-sync.ts
|
|
@@ -13032,6 +13284,8 @@ async function execute21(paths, phase) {
|
|
|
13032
13284
|
if (state.stage !== "sync") {
|
|
13033
13285
|
emitError("merge-sync", `Stage is '${state.stage}', expected 'sync'.`);
|
|
13034
13286
|
}
|
|
13287
|
+
verifyStageEntry("merge-sync", state);
|
|
13288
|
+
await mgm.save(state);
|
|
13035
13289
|
const mergeArtifact = paths.artifact("03-merge.md");
|
|
13036
13290
|
if (!await fileExists(mergeArtifact)) {
|
|
13037
13291
|
emitError("merge-sync", "03-merge.md does not exist. Complete merge stage first.");
|
|
@@ -13041,6 +13295,8 @@ async function execute21(paths, phase) {
|
|
|
13041
13295
|
const genomeConstraints = await readTextFile(paths.constraints);
|
|
13042
13296
|
const genomePrinciples = await readTextFile(paths.principles);
|
|
13043
13297
|
const mergeContent = await readTextFile(mergeArtifact);
|
|
13298
|
+
setPhaseNonce(state, "sync", "verify");
|
|
13299
|
+
await mgm.save(state);
|
|
13044
13300
|
emitOutput({
|
|
13045
13301
|
status: "prompt",
|
|
13046
13302
|
command: "merge-sync",
|
|
@@ -13085,24 +13341,37 @@ async function execute21(paths, phase) {
|
|
|
13085
13341
|
});
|
|
13086
13342
|
}
|
|
13087
13343
|
if (phase === "complete") {
|
|
13344
|
+
verifyPhaseEntry("merge-sync", state, "sync", "verify");
|
|
13345
|
+
await mgm.save(state);
|
|
13346
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13347
|
+
state.expectedHash = hash;
|
|
13348
|
+
state.lastNonce = nonce;
|
|
13088
13349
|
const hookResults = await executeHooks(paths.hooks, "onMergeSynced", paths.projectRoot);
|
|
13350
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13351
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
13089
13352
|
emitOutput({
|
|
13090
13353
|
status: "ok",
|
|
13091
13354
|
command: "merge-sync",
|
|
13092
13355
|
phase: "complete",
|
|
13093
|
-
completed: ["gate", "context-collect", "sync-verify", "hooks"],
|
|
13356
|
+
completed: ["gate", "context-collect", "sync-verify", "hooks", "auto-transition"],
|
|
13094
13357
|
context: {
|
|
13095
13358
|
id: state.id,
|
|
13096
|
-
hookResults
|
|
13359
|
+
hookResults,
|
|
13360
|
+
nextStage: transition.nextStage,
|
|
13361
|
+
artifactFile: transition.artifactFile,
|
|
13362
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
13097
13363
|
},
|
|
13098
|
-
message:
|
|
13364
|
+
message: `Sync stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13365
|
+
nextCommand
|
|
13099
13366
|
});
|
|
13100
13367
|
}
|
|
13101
13368
|
}
|
|
13102
13369
|
var init_merge_sync = __esm(() => {
|
|
13103
13370
|
init_merge_generation();
|
|
13371
|
+
init_generation();
|
|
13104
13372
|
init_fs();
|
|
13105
13373
|
init_hook_engine();
|
|
13374
|
+
init_stage_transition();
|
|
13106
13375
|
});
|
|
13107
13376
|
|
|
13108
13377
|
// src/cli/commands/run/merge-validation.ts
|
|
@@ -13122,12 +13391,16 @@ async function execute22(paths, phase) {
|
|
|
13122
13391
|
if (state.stage !== "validation") {
|
|
13123
13392
|
emitError("merge-validation", `Stage is '${state.stage}', expected 'validation'.`);
|
|
13124
13393
|
}
|
|
13394
|
+
verifyStageEntry("merge-validation", state);
|
|
13395
|
+
await mgm.save(state);
|
|
13125
13396
|
const syncArtifact = paths.artifact("04-sync.md");
|
|
13126
13397
|
if (!await fileExists(syncArtifact)) {
|
|
13127
13398
|
emitError("merge-validation", "04-sync.md does not exist. Complete sync stage first.");
|
|
13128
13399
|
}
|
|
13129
13400
|
if (!phase || phase === "work") {
|
|
13130
13401
|
const constraintsContent = await readTextFile(paths.constraints);
|
|
13402
|
+
setPhaseNonce(state, "validation", "work");
|
|
13403
|
+
await mgm.save(state);
|
|
13131
13404
|
emitOutput({
|
|
13132
13405
|
status: "prompt",
|
|
13133
13406
|
command: "merge-validation",
|
|
@@ -13164,28 +13437,41 @@ async function execute22(paths, phase) {
|
|
|
13164
13437
|
});
|
|
13165
13438
|
}
|
|
13166
13439
|
if (phase === "complete") {
|
|
13440
|
+
verifyPhaseEntry("merge-validation", state, "validation", "work");
|
|
13441
|
+
await mgm.save(state);
|
|
13167
13442
|
const validationArtifact = paths.artifact("05-validation.md");
|
|
13168
13443
|
if (!await fileExists(validationArtifact)) {
|
|
13169
13444
|
emitError("merge-validation", "05-validation.md does not exist. Complete validation work first.");
|
|
13170
13445
|
}
|
|
13446
|
+
const { nonce, hash } = generateToken(state.id, state.stage);
|
|
13447
|
+
state.expectedHash = hash;
|
|
13448
|
+
state.lastNonce = nonce;
|
|
13171
13449
|
const hookResults = await executeHooks(paths.hooks, "onMergeValidated", paths.projectRoot);
|
|
13450
|
+
const transition = await performTransition(paths, state, (s) => mgm.save(s));
|
|
13451
|
+
const nextCommand = `reap run merge-${transition.nextStage}`;
|
|
13172
13452
|
emitOutput({
|
|
13173
13453
|
status: "ok",
|
|
13174
13454
|
command: "merge-validation",
|
|
13175
13455
|
phase: "complete",
|
|
13176
|
-
completed: ["gate", "context-collect", "validation-work", "hooks"],
|
|
13456
|
+
completed: ["gate", "context-collect", "validation-work", "hooks", "auto-transition"],
|
|
13177
13457
|
context: {
|
|
13178
13458
|
id: state.id,
|
|
13179
|
-
hookResults
|
|
13459
|
+
hookResults,
|
|
13460
|
+
nextStage: transition.nextStage,
|
|
13461
|
+
artifactFile: transition.artifactFile,
|
|
13462
|
+
transitionHookResults: [...transition.stageHookResults, ...transition.transitionHookResults]
|
|
13180
13463
|
},
|
|
13181
|
-
message:
|
|
13464
|
+
message: `Validation stage complete. Auto-advanced to ${transition.nextStage}. Run: ${nextCommand}`,
|
|
13465
|
+
nextCommand
|
|
13182
13466
|
});
|
|
13183
13467
|
}
|
|
13184
13468
|
}
|
|
13185
13469
|
var init_merge_validation = __esm(() => {
|
|
13186
13470
|
init_merge_generation();
|
|
13471
|
+
init_generation();
|
|
13187
13472
|
init_fs();
|
|
13188
13473
|
init_hook_engine();
|
|
13474
|
+
init_stage_transition();
|
|
13189
13475
|
});
|
|
13190
13476
|
|
|
13191
13477
|
// src/cli/commands/run/merge-completion.ts
|
|
@@ -13205,6 +13491,8 @@ async function execute23(paths, phase) {
|
|
|
13205
13491
|
if (state.stage !== "completion") {
|
|
13206
13492
|
emitError("merge-completion", `Stage is '${state.stage}', expected 'completion'.`);
|
|
13207
13493
|
}
|
|
13494
|
+
verifyStageEntry("merge-completion", state);
|
|
13495
|
+
await mgm.save(state);
|
|
13208
13496
|
const validationArtifact = paths.artifact("05-validation.md");
|
|
13209
13497
|
if (!await fileExists(validationArtifact)) {
|
|
13210
13498
|
emitError("merge-completion", "05-validation.md does not exist. Complete validation first.");
|
|
@@ -13214,6 +13502,8 @@ async function execute23(paths, phase) {
|
|
|
13214
13502
|
const mateContent = await readTextFile(paths.artifact("02-mate.md"));
|
|
13215
13503
|
const mergeContent = await readTextFile(paths.artifact("03-merge.md"));
|
|
13216
13504
|
const validationContent = await readTextFile(validationArtifact);
|
|
13505
|
+
setPhaseNonce(state, "completion", "retrospective");
|
|
13506
|
+
await mgm.save(state);
|
|
13217
13507
|
emitOutput({
|
|
13218
13508
|
status: "prompt",
|
|
13219
13509
|
command: "merge-completion",
|
|
@@ -13244,6 +13534,8 @@ async function execute23(paths, phase) {
|
|
|
13244
13534
|
});
|
|
13245
13535
|
}
|
|
13246
13536
|
if (phase === "archive") {
|
|
13537
|
+
verifyPhaseEntry("merge-completion", state, "completion", "retrospective");
|
|
13538
|
+
await mgm.save(state);
|
|
13247
13539
|
const hookResults = await executeHooks(paths.hooks, "onMergeCompleted", paths.projectRoot);
|
|
13248
13540
|
const submodules = checkSubmodules(paths.projectRoot);
|
|
13249
13541
|
const dirtySubmodules = submodules.filter((s) => s.dirty);
|
|
@@ -13270,6 +13562,7 @@ var init_merge_completion = __esm(() => {
|
|
|
13270
13562
|
init_fs();
|
|
13271
13563
|
init_hook_engine();
|
|
13272
13564
|
init_commit();
|
|
13565
|
+
init_stage_transition();
|
|
13273
13566
|
});
|
|
13274
13567
|
|
|
13275
13568
|
// src/cli/commands/run/merge-evolve.ts
|
|
@@ -13322,10 +13615,10 @@ async function execute24(paths, phase) {
|
|
|
13322
13615
|
"- `/reap.merge.validation` -> `onMergeValidated`",
|
|
13323
13616
|
"- `/reap.merge.completion` -> `onMergeCompleted` (before archiving and commit)",
|
|
13324
13617
|
"",
|
|
13325
|
-
"
|
|
13618
|
+
"`--phase complete` auto-transitions to the next stage. `/reap.next` is a fallback.",
|
|
13326
13619
|
"`/reap.merge.completion` handles archiving and the final commit.",
|
|
13327
13620
|
"",
|
|
13328
|
-
"### Merge Lifecycle Loop",
|
|
13621
|
+
"### Merge Lifecycle Loop (Auto-Transition)",
|
|
13329
13622
|
"Execute the following loop until the generation is complete:",
|
|
13330
13623
|
"1. Read `current.yml` to determine the current stage",
|
|
13331
13624
|
"2. Execute the corresponding merge stage command:",
|
|
@@ -13335,9 +13628,17 @@ async function execute24(paths, phase) {
|
|
|
13335
13628
|
" - `sync` -> `/reap.merge.sync`",
|
|
13336
13629
|
" - `validation` -> `/reap.merge.validation`",
|
|
13337
13630
|
" - `completion` -> `/reap.merge.completion`",
|
|
13338
|
-
"3.
|
|
13339
|
-
" - If the
|
|
13340
|
-
" - Otherwise:
|
|
13631
|
+
"3. `--phase complete` auto-transitions to the next stage.",
|
|
13632
|
+
" - If the stage is `completion`: the loop ends.",
|
|
13633
|
+
" - Otherwise: follow the `nextCommand` in the output to run the next stage.",
|
|
13634
|
+
"",
|
|
13635
|
+
"### Submodule Commit Rules",
|
|
13636
|
+
"- 커밋 전 반드시 `git -C tests status -s` 로 tests submodule의 dirty 상태를 확인하라.",
|
|
13637
|
+
"- dirty 파일이 있으면:",
|
|
13638
|
+
' 1. `git -C tests add -A && git -C tests commit -m "..." && git -C tests push`',
|
|
13639
|
+
" 2. parent repo에서 `git add tests` 로 submodule ref 업데이트",
|
|
13640
|
+
" 3. 그 다음 parent repo의 나머지 파일과 함께 커밋",
|
|
13641
|
+
'- completion의 prompt에 "Dirty submodules detected"가 있으면 반드시 위 절차를 따르라.',
|
|
13341
13642
|
"",
|
|
13342
13643
|
"### Handling Issues",
|
|
13343
13644
|
"- If validation fails: `/reap.back merge` or `/reap.back mate`, then resume the loop",
|
|
@@ -13503,9 +13804,9 @@ async function execute26(paths, phase, argv = []) {
|
|
|
13503
13804
|
const localLatest = localMetas.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())[0];
|
|
13504
13805
|
const remoteLatest = remoteMetasRaw.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())[0];
|
|
13505
13806
|
const allMetas = [...localMetas];
|
|
13506
|
-
for (const
|
|
13507
|
-
if (!allMetas.find((m) => m.id ===
|
|
13508
|
-
allMetas.push(
|
|
13807
|
+
for (const rm5 of remoteMetasRaw) {
|
|
13808
|
+
if (!allMetas.find((m) => m.id === rm5.id))
|
|
13809
|
+
allMetas.push(rm5);
|
|
13509
13810
|
}
|
|
13510
13811
|
const ffResult = canFastForward(localLatest.id, remoteLatest.id, allMetas);
|
|
13511
13812
|
if (ffResult.fastForward) {
|
|
@@ -13596,12 +13897,177 @@ var init_config2 = __esm(() => {
|
|
|
13596
13897
|
init_config();
|
|
13597
13898
|
});
|
|
13598
13899
|
|
|
13900
|
+
// src/cli/commands/run/refresh-knowledge.ts
|
|
13901
|
+
var exports_refresh_knowledge = {};
|
|
13902
|
+
__export(exports_refresh_knowledge, {
|
|
13903
|
+
execute: () => execute28
|
|
13904
|
+
});
|
|
13905
|
+
import { join as join28 } from "path";
|
|
13906
|
+
import { readdir as readdir20 } from "fs/promises";
|
|
13907
|
+
async function loadGenome(genomeDir) {
|
|
13908
|
+
let content = "";
|
|
13909
|
+
let l1Lines = 0;
|
|
13910
|
+
let smLimit = null;
|
|
13911
|
+
const smContent = await readTextFile(join28(genomeDir, "source-map.md"));
|
|
13912
|
+
if (smContent) {
|
|
13913
|
+
const limitMatch = smContent.match(/줄 수 한도:\s*~?(\d+)줄/);
|
|
13914
|
+
if (limitMatch)
|
|
13915
|
+
smLimit = parseInt(limitMatch[1], 10);
|
|
13916
|
+
}
|
|
13917
|
+
for (const file of L1_FILES) {
|
|
13918
|
+
const fileContent = await readTextFile(join28(genomeDir, file));
|
|
13919
|
+
if (!fileContent)
|
|
13920
|
+
continue;
|
|
13921
|
+
const lines = fileContent.split(`
|
|
13922
|
+
`).length;
|
|
13923
|
+
const limit = file === "source-map.md" && smLimit ? smLimit : L1_LIMIT;
|
|
13924
|
+
l1Lines += lines;
|
|
13925
|
+
if (l1Lines <= limit) {
|
|
13926
|
+
content += `
|
|
13927
|
+
### ${file}
|
|
13928
|
+
${fileContent}
|
|
13929
|
+
`;
|
|
13930
|
+
} else {
|
|
13931
|
+
content += `
|
|
13932
|
+
### ${file} [TRUNCATED — L1 budget exceeded]
|
|
13933
|
+
${fileContent.split(`
|
|
13934
|
+
`).slice(0, 20).join(`
|
|
13935
|
+
`)}
|
|
13936
|
+
...
|
|
13937
|
+
`;
|
|
13938
|
+
}
|
|
13939
|
+
}
|
|
13940
|
+
const domainDir = join28(genomeDir, "domain");
|
|
13941
|
+
if (await fileExists(domainDir)) {
|
|
13942
|
+
let l2Lines = 0;
|
|
13943
|
+
let l2Overflow = false;
|
|
13944
|
+
try {
|
|
13945
|
+
const domainFiles = (await readdir20(domainDir)).filter((f) => f.endsWith(".md")).sort();
|
|
13946
|
+
for (const file of domainFiles) {
|
|
13947
|
+
const fileContent = await readTextFile(join28(domainDir, file));
|
|
13948
|
+
if (!fileContent)
|
|
13949
|
+
continue;
|
|
13950
|
+
const lines = fileContent.split(`
|
|
13951
|
+
`).length;
|
|
13952
|
+
l2Lines += lines;
|
|
13953
|
+
if (!l2Overflow && l2Lines <= L2_LIMIT) {
|
|
13954
|
+
content += `
|
|
13955
|
+
### domain/${file}
|
|
13956
|
+
${fileContent}
|
|
13957
|
+
`;
|
|
13958
|
+
} else {
|
|
13959
|
+
l2Overflow = true;
|
|
13960
|
+
const firstLine = fileContent.split(`
|
|
13961
|
+
`).find((l) => l.startsWith(">")) || fileContent.split(`
|
|
13962
|
+
`)[0];
|
|
13963
|
+
content += `
|
|
13964
|
+
### domain/${file} [summary]
|
|
13965
|
+
${firstLine}
|
|
13966
|
+
`;
|
|
13967
|
+
}
|
|
13968
|
+
}
|
|
13969
|
+
} catch {}
|
|
13970
|
+
}
|
|
13971
|
+
return { content, l1Lines };
|
|
13972
|
+
}
|
|
13973
|
+
function buildStrictSection(strict, genStage) {
|
|
13974
|
+
let sections = "";
|
|
13975
|
+
if (strict.edit) {
|
|
13976
|
+
if (genStage === "implementation") {
|
|
13977
|
+
sections += `
|
|
13978
|
+
|
|
13979
|
+
## Strict Mode — Edit (ACTIVE — SCOPED MODIFICATION ALLOWED)`;
|
|
13980
|
+
} else if (genStage === "none") {
|
|
13981
|
+
sections += `
|
|
13982
|
+
|
|
13983
|
+
## Strict Mode — Edit (ACTIVE — CODE MODIFICATION BLOCKED)`;
|
|
13984
|
+
} else {
|
|
13985
|
+
sections += `
|
|
13986
|
+
|
|
13987
|
+
## Strict Mode — Edit (ACTIVE — stage '${genStage}', CODE MODIFICATION BLOCKED)`;
|
|
13988
|
+
}
|
|
13989
|
+
}
|
|
13990
|
+
if (strict.merge) {
|
|
13991
|
+
sections += `
|
|
13992
|
+
|
|
13993
|
+
## Strict Mode — Merge (ACTIVE)`;
|
|
13994
|
+
}
|
|
13995
|
+
return sections;
|
|
13996
|
+
}
|
|
13997
|
+
async function execute28(paths) {
|
|
13998
|
+
const guidePath = join28(ReapPaths.packageHooksDir, "reap-guide.md");
|
|
13999
|
+
const reapGuide = await readTextFile(guidePath) || "";
|
|
14000
|
+
const { content: genomeContent } = await loadGenome(paths.genome);
|
|
14001
|
+
const envSummary = await readTextFile(paths.environmentSummary) || "";
|
|
14002
|
+
const gm = new GenerationManager(paths);
|
|
14003
|
+
const state = await gm.current();
|
|
14004
|
+
let generationContext;
|
|
14005
|
+
let genStage;
|
|
14006
|
+
if (state && state.id) {
|
|
14007
|
+
genStage = state.stage;
|
|
14008
|
+
generationContext = `Active Generation: ${state.id} | Goal: ${state.goal} | Stage: ${state.stage}`;
|
|
14009
|
+
} else {
|
|
14010
|
+
genStage = "none";
|
|
14011
|
+
generationContext = "No active Generation.";
|
|
14012
|
+
}
|
|
14013
|
+
let strict = { edit: false, merge: false };
|
|
14014
|
+
try {
|
|
14015
|
+
const config = await ConfigManager.read(paths);
|
|
14016
|
+
strict = ConfigManager.resolveStrict(config.strict);
|
|
14017
|
+
} catch {}
|
|
14018
|
+
const strictSection = buildStrictSection(strict, genStage);
|
|
14019
|
+
const envSection = envSummary ? `
|
|
14020
|
+
|
|
14021
|
+
---
|
|
14022
|
+
|
|
14023
|
+
## Environment (External Context)
|
|
14024
|
+
${envSummary}` : "";
|
|
14025
|
+
const reapContext = [
|
|
14026
|
+
"<REAP_CONTEXT>",
|
|
14027
|
+
reapGuide,
|
|
14028
|
+
"",
|
|
14029
|
+
"---",
|
|
14030
|
+
"",
|
|
14031
|
+
"## Genome (Project Knowledge)",
|
|
14032
|
+
genomeContent,
|
|
14033
|
+
envSection,
|
|
14034
|
+
"",
|
|
14035
|
+
"---",
|
|
14036
|
+
"",
|
|
14037
|
+
"## Current State",
|
|
14038
|
+
generationContext,
|
|
14039
|
+
strictSection,
|
|
14040
|
+
"</REAP_CONTEXT>"
|
|
14041
|
+
].join(`
|
|
14042
|
+
`);
|
|
14043
|
+
emitOutput({
|
|
14044
|
+
status: "ok",
|
|
14045
|
+
command: "refreshKnowledge",
|
|
14046
|
+
phase: "done",
|
|
14047
|
+
completed: ["load-guide", "load-genome", "load-environment", "load-state"],
|
|
14048
|
+
context: {
|
|
14049
|
+
hasGeneration: !!state?.id,
|
|
14050
|
+
generationId: state?.id || null,
|
|
14051
|
+
stage: genStage
|
|
14052
|
+
},
|
|
14053
|
+
prompt: reapContext
|
|
14054
|
+
});
|
|
14055
|
+
}
|
|
14056
|
+
var L1_LIMIT = 500, L2_LIMIT = 200, L1_FILES;
|
|
14057
|
+
var init_refresh_knowledge = __esm(() => {
|
|
14058
|
+
init_paths();
|
|
14059
|
+
init_fs();
|
|
14060
|
+
init_config();
|
|
14061
|
+
init_generation();
|
|
14062
|
+
L1_FILES = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
|
|
14063
|
+
});
|
|
14064
|
+
|
|
13599
14065
|
// src/cli/commands/run/index.ts
|
|
13600
14066
|
var exports_run = {};
|
|
13601
14067
|
__export(exports_run, {
|
|
13602
14068
|
runCommand: () => runCommand
|
|
13603
14069
|
});
|
|
13604
|
-
import { execSync as
|
|
14070
|
+
import { execSync as execSync7 } from "child_process";
|
|
13605
14071
|
async function runCommand(command, phase, argv = []) {
|
|
13606
14072
|
const cwd = process.cwd();
|
|
13607
14073
|
const paths = new ReapPaths(cwd);
|
|
@@ -13619,7 +14085,7 @@ async function runCommand(command, phase, argv = []) {
|
|
|
13619
14085
|
try {
|
|
13620
14086
|
const config = await ConfigManager.read(paths);
|
|
13621
14087
|
if (config.autoIssueReport) {
|
|
13622
|
-
const version = "0.
|
|
14088
|
+
const version = "0.15.0";
|
|
13623
14089
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13624
14090
|
const title = `[auto] reap run ${command}: ${errMsg.slice(0, 80)}`;
|
|
13625
14091
|
const body = [
|
|
@@ -13629,7 +14095,7 @@ async function runCommand(command, phase, argv = []) {
|
|
|
13629
14095
|
`**OS**: ${process.platform} ${process.arch}`,
|
|
13630
14096
|
`**Node**: ${process.version}`
|
|
13631
14097
|
].join("\\n");
|
|
13632
|
-
|
|
14098
|
+
execSync7(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,bug" --body "${body}"`, { stdio: "ignore", timeout: 1e4 });
|
|
13633
14099
|
}
|
|
13634
14100
|
} catch {}
|
|
13635
14101
|
emitError(command, err instanceof Error ? err.message : String(err));
|
|
@@ -13666,7 +14132,8 @@ var init_run = __esm(() => {
|
|
|
13666
14132
|
"merge-evolve": () => Promise.resolve().then(() => (init_merge_evolve(), exports_merge_evolve)),
|
|
13667
14133
|
merge: () => Promise.resolve().then(() => (init_merge2(), exports_merge)),
|
|
13668
14134
|
pull: () => Promise.resolve().then(() => (init_pull(), exports_pull)),
|
|
13669
|
-
config: () => Promise.resolve().then(() => (init_config2(), exports_config))
|
|
14135
|
+
config: () => Promise.resolve().then(() => (init_config2(), exports_config)),
|
|
14136
|
+
refreshKnowledge: () => Promise.resolve().then(() => (init_refresh_knowledge(), exports_refresh_knowledge))
|
|
13670
14137
|
};
|
|
13671
14138
|
});
|
|
13672
14139
|
|
|
@@ -13686,6 +14153,9 @@ var {
|
|
|
13686
14153
|
Help
|
|
13687
14154
|
} = import__.default;
|
|
13688
14155
|
|
|
14156
|
+
// src/cli/index.ts
|
|
14157
|
+
import { createInterface } from "readline";
|
|
14158
|
+
|
|
13689
14159
|
// src/cli/commands/init.ts
|
|
13690
14160
|
init_paths();
|
|
13691
14161
|
init_config();
|
|
@@ -14169,7 +14639,8 @@ var COMMAND_NAMES = [
|
|
|
14169
14639
|
"reap.merge",
|
|
14170
14640
|
"reap.pull",
|
|
14171
14641
|
"reap.push",
|
|
14172
|
-
"reap.config"
|
|
14642
|
+
"reap.config",
|
|
14643
|
+
"reap.refreshKnowledge"
|
|
14173
14644
|
];
|
|
14174
14645
|
async function initProject(projectRoot, projectName, entryMode, preset, onProgress) {
|
|
14175
14646
|
const log = onProgress ?? (() => {});
|
|
@@ -14205,7 +14676,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14205
14676
|
}
|
|
14206
14677
|
const detectedLanguage = await AgentRegistry.readLanguage();
|
|
14207
14678
|
const config = {
|
|
14208
|
-
version: "0.
|
|
14679
|
+
version: "0.15.0",
|
|
14209
14680
|
project: projectName,
|
|
14210
14681
|
entryMode,
|
|
14211
14682
|
strict: false,
|
|
@@ -14278,6 +14749,11 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14278
14749
|
if (detectedAgents.length === 0) {
|
|
14279
14750
|
log(" No AI agents detected.");
|
|
14280
14751
|
}
|
|
14752
|
+
const autoSynced = (entryMode === "adoption" || entryMode === "migration") && !preset;
|
|
14753
|
+
if (!autoSynced) {
|
|
14754
|
+
log(`
|
|
14755
|
+
\uD83D\uDCA1 Run /reap.sync to synchronize Genome with your project's actual state.`);
|
|
14756
|
+
}
|
|
14281
14757
|
return { agents: detectedAgents.map((a) => a.displayName) };
|
|
14282
14758
|
}
|
|
14283
14759
|
|
|
@@ -14774,37 +15250,47 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
14774
15250
|
result.updated.push(`Config: added ${backfillResult.added.join(", ")}`);
|
|
14775
15251
|
}
|
|
14776
15252
|
}
|
|
14777
|
-
const
|
|
14778
|
-
await mkdir6(projectClaudeCommands, { recursive: true });
|
|
15253
|
+
const projectClaudeSkills = join10(paths.projectRoot, ".claude", "skills");
|
|
14779
15254
|
const reapCmdFiles = (await readdir9(ReapPaths.userReapCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
|
|
14780
15255
|
let cmdInstalled = 0;
|
|
14781
15256
|
for (const file of reapCmdFiles) {
|
|
14782
15257
|
const src = await readTextFileOrThrow(join10(ReapPaths.userReapCommands, file));
|
|
14783
|
-
const
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
if (!dryRun)
|
|
14798
|
-
await
|
|
15258
|
+
const name = file.replace(/\.md$/, "");
|
|
15259
|
+
const skillDir = join10(projectClaudeSkills, name);
|
|
15260
|
+
const skillFile = join10(skillDir, "SKILL.md");
|
|
15261
|
+
const fmMatch = src.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
15262
|
+
const description = fmMatch ? fmMatch[1].match(/^description:\s*"?([^"\n]*)"?/m)?.[1]?.trim() ?? "" : "";
|
|
15263
|
+
const body = fmMatch ? fmMatch[2] : src;
|
|
15264
|
+
const skillContent = `---
|
|
15265
|
+
name: ${name}
|
|
15266
|
+
description: "${description}"
|
|
15267
|
+
---
|
|
15268
|
+
${body}`;
|
|
15269
|
+
const existing = await readTextFile(skillFile);
|
|
15270
|
+
if (existing !== null && existing === skillContent)
|
|
15271
|
+
continue;
|
|
15272
|
+
if (!dryRun) {
|
|
15273
|
+
await mkdir6(skillDir, { recursive: true });
|
|
15274
|
+
await writeTextFile(skillFile, skillContent);
|
|
15275
|
+
}
|
|
14799
15276
|
cmdInstalled++;
|
|
14800
15277
|
}
|
|
14801
15278
|
if (cmdInstalled > 0) {
|
|
14802
|
-
result.updated.push(`.claude/
|
|
15279
|
+
result.updated.push(`.claude/skills/ (${cmdInstalled} synced)`);
|
|
14803
15280
|
} else {
|
|
14804
|
-
result.skipped.push(`.claude/
|
|
15281
|
+
result.skipped.push(`.claude/skills/ (${reapCmdFiles.length} unchanged)`);
|
|
14805
15282
|
}
|
|
15283
|
+
const projectClaudeCommands = join10(paths.projectRoot, ".claude", "commands");
|
|
15284
|
+
try {
|
|
15285
|
+
const legacyFiles = (await readdir9(projectClaudeCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
|
|
15286
|
+
for (const file of legacyFiles) {
|
|
15287
|
+
if (!dryRun)
|
|
15288
|
+
await unlink4(join10(projectClaudeCommands, file));
|
|
15289
|
+
result.removed.push(`.claude/commands/${file} (legacy)`);
|
|
15290
|
+
}
|
|
15291
|
+
} catch {}
|
|
14806
15292
|
await migrateLegacyFiles(paths, dryRun, result);
|
|
14807
|
-
const currentVersion = "0.
|
|
15293
|
+
const currentVersion = "0.15.0";
|
|
14808
15294
|
const migrationResult = await MigrationRunner.run(paths, currentVersion, dryRun);
|
|
14809
15295
|
for (const m of migrationResult.migrated) {
|
|
14810
15296
|
result.updated.push(`[migration] ${m}`);
|
|
@@ -14995,14 +15481,273 @@ async function fixProject(projectRoot) {
|
|
|
14995
15481
|
return { issues, fixed };
|
|
14996
15482
|
}
|
|
14997
15483
|
|
|
15484
|
+
// src/cli/commands/destroy.ts
|
|
15485
|
+
init_fs();
|
|
15486
|
+
init_paths();
|
|
15487
|
+
init_config();
|
|
15488
|
+
import { rm as rm3, readdir as readdir10, unlink as unlink5 } from "fs/promises";
|
|
15489
|
+
import { join as join12 } from "path";
|
|
15490
|
+
async function getProjectName(projectRoot) {
|
|
15491
|
+
try {
|
|
15492
|
+
const paths = new ReapPaths(projectRoot);
|
|
15493
|
+
const config = await ConfigManager.read(paths);
|
|
15494
|
+
return config.project;
|
|
15495
|
+
} catch {
|
|
15496
|
+
return null;
|
|
15497
|
+
}
|
|
15498
|
+
}
|
|
15499
|
+
async function destroyProject(projectRoot) {
|
|
15500
|
+
const removed = [];
|
|
15501
|
+
const skipped = [];
|
|
15502
|
+
const reapDir = join12(projectRoot, ".reap");
|
|
15503
|
+
if (await fileExists(reapDir)) {
|
|
15504
|
+
await rm3(reapDir, { recursive: true, force: true });
|
|
15505
|
+
removed.push(".reap/");
|
|
15506
|
+
} else {
|
|
15507
|
+
skipped.push(".reap/ (not found)");
|
|
15508
|
+
}
|
|
15509
|
+
const claudeCommandsDir = join12(projectRoot, ".claude", "commands");
|
|
15510
|
+
await removeGlobFiles(claudeCommandsDir, "reap.", removed, skipped, ".claude/commands/");
|
|
15511
|
+
const claudeSkillsDir = join12(projectRoot, ".claude", "skills");
|
|
15512
|
+
await removeGlobDirs(claudeSkillsDir, "reap.", removed, skipped, ".claude/skills/");
|
|
15513
|
+
await cleanClaudeMd(projectRoot, removed, skipped);
|
|
15514
|
+
await cleanGitignore(projectRoot, removed, skipped);
|
|
15515
|
+
return { removed, skipped };
|
|
15516
|
+
}
|
|
15517
|
+
async function removeGlobFiles(dir, prefix, removed, skipped, displayPrefix) {
|
|
15518
|
+
try {
|
|
15519
|
+
const files = await readdir10(dir);
|
|
15520
|
+
const matched = files.filter((f) => f.startsWith(prefix));
|
|
15521
|
+
if (matched.length === 0) {
|
|
15522
|
+
skipped.push(`${displayPrefix}${prefix}* (none found)`);
|
|
15523
|
+
return;
|
|
15524
|
+
}
|
|
15525
|
+
for (const file of matched) {
|
|
15526
|
+
await unlink5(join12(dir, file));
|
|
15527
|
+
removed.push(`${displayPrefix}${file}`);
|
|
15528
|
+
}
|
|
15529
|
+
} catch {
|
|
15530
|
+
skipped.push(`${displayPrefix}${prefix}* (directory not found)`);
|
|
15531
|
+
}
|
|
15532
|
+
}
|
|
15533
|
+
async function removeGlobDirs(dir, prefix, removed, skipped, displayPrefix) {
|
|
15534
|
+
try {
|
|
15535
|
+
const entries = await readdir10(dir);
|
|
15536
|
+
const matched = entries.filter((e) => e.startsWith(prefix));
|
|
15537
|
+
if (matched.length === 0) {
|
|
15538
|
+
skipped.push(`${displayPrefix}${prefix}* (none found)`);
|
|
15539
|
+
return;
|
|
15540
|
+
}
|
|
15541
|
+
for (const entry of matched) {
|
|
15542
|
+
await rm3(join12(dir, entry), { recursive: true, force: true });
|
|
15543
|
+
removed.push(`${displayPrefix}${entry}`);
|
|
15544
|
+
}
|
|
15545
|
+
} catch {
|
|
15546
|
+
skipped.push(`${displayPrefix}${prefix}* (directory not found)`);
|
|
15547
|
+
}
|
|
15548
|
+
}
|
|
15549
|
+
async function cleanClaudeMd(projectRoot, removed, skipped) {
|
|
15550
|
+
const claudeMdPath = join12(projectRoot, ".claude", "CLAUDE.md");
|
|
15551
|
+
const content = await readTextFile(claudeMdPath);
|
|
15552
|
+
if (content === null) {
|
|
15553
|
+
skipped.push(".claude/CLAUDE.md (not found)");
|
|
15554
|
+
return;
|
|
15555
|
+
}
|
|
15556
|
+
const marker = "# REAP Project";
|
|
15557
|
+
if (!content.includes(marker)) {
|
|
15558
|
+
skipped.push(".claude/CLAUDE.md (no REAP section)");
|
|
15559
|
+
return;
|
|
15560
|
+
}
|
|
15561
|
+
const cleaned = content.replace(/# REAP Project[\s\S]*?(?=\n# |\s*$)/, "").trim();
|
|
15562
|
+
if (cleaned.length === 0) {
|
|
15563
|
+
await unlink5(claudeMdPath);
|
|
15564
|
+
removed.push(".claude/CLAUDE.md (deleted, was REAP-only)");
|
|
15565
|
+
} else {
|
|
15566
|
+
await writeTextFile(claudeMdPath, cleaned + `
|
|
15567
|
+
`);
|
|
15568
|
+
removed.push(".claude/CLAUDE.md (REAP section removed)");
|
|
15569
|
+
}
|
|
15570
|
+
}
|
|
15571
|
+
async function cleanGitignore(projectRoot, removed, skipped) {
|
|
15572
|
+
const gitignorePath = join12(projectRoot, ".gitignore");
|
|
15573
|
+
const content = await readTextFile(gitignorePath);
|
|
15574
|
+
if (content === null) {
|
|
15575
|
+
skipped.push(".gitignore (not found)");
|
|
15576
|
+
return;
|
|
15577
|
+
}
|
|
15578
|
+
const reapPatterns = [
|
|
15579
|
+
/^# REAP.*$/m,
|
|
15580
|
+
/^\.claude\/skills\/reap\..*$/m,
|
|
15581
|
+
/^\.claude\/commands\/reap\..*$/m,
|
|
15582
|
+
/^\.reap\/.*$/m
|
|
15583
|
+
];
|
|
15584
|
+
let cleaned = content;
|
|
15585
|
+
let anyRemoved = false;
|
|
15586
|
+
for (const pattern of reapPatterns) {
|
|
15587
|
+
if (pattern.test(cleaned)) {
|
|
15588
|
+
cleaned = cleaned.replace(new RegExp(pattern.source, "gm"), "");
|
|
15589
|
+
anyRemoved = true;
|
|
15590
|
+
}
|
|
15591
|
+
}
|
|
15592
|
+
if (!anyRemoved) {
|
|
15593
|
+
skipped.push(".gitignore (no REAP entries)");
|
|
15594
|
+
return;
|
|
15595
|
+
}
|
|
15596
|
+
cleaned = cleaned.replace(/\n{3,}/g, `
|
|
15597
|
+
|
|
15598
|
+
`).trim() + `
|
|
15599
|
+
`;
|
|
15600
|
+
await writeTextFile(gitignorePath, cleaned);
|
|
15601
|
+
removed.push(".gitignore (REAP entries removed)");
|
|
15602
|
+
}
|
|
15603
|
+
|
|
15604
|
+
// src/cli/commands/clean.ts
|
|
15605
|
+
init_paths();
|
|
15606
|
+
init_fs();
|
|
15607
|
+
init_generation();
|
|
15608
|
+
import { rm as rm4, readdir as readdir11, mkdir as mkdir8 } from "fs/promises";
|
|
15609
|
+
import { join as join13 } from "path";
|
|
15610
|
+
async function hasActiveGeneration(projectRoot) {
|
|
15611
|
+
const paths = new ReapPaths(projectRoot);
|
|
15612
|
+
try {
|
|
15613
|
+
const mgr = new GenerationManager(paths);
|
|
15614
|
+
const current = await mgr.current();
|
|
15615
|
+
return current !== null;
|
|
15616
|
+
} catch {
|
|
15617
|
+
return false;
|
|
15618
|
+
}
|
|
15619
|
+
}
|
|
15620
|
+
async function cleanProject(projectRoot, options) {
|
|
15621
|
+
const paths = new ReapPaths(projectRoot);
|
|
15622
|
+
const actions = [];
|
|
15623
|
+
const warnings = [];
|
|
15624
|
+
if (options.lineage === "delete") {
|
|
15625
|
+
await rm4(paths.lineage, { recursive: true, force: true });
|
|
15626
|
+
await mkdir8(paths.lineage, { recursive: true });
|
|
15627
|
+
actions.push("Lineage: 전체 삭제됨");
|
|
15628
|
+
} else {
|
|
15629
|
+
await compressLineage(paths, actions);
|
|
15630
|
+
}
|
|
15631
|
+
await cleanLife(paths, actions);
|
|
15632
|
+
if (options.hooks === "reset") {
|
|
15633
|
+
const hooksDir = paths.hooks;
|
|
15634
|
+
if (await fileExists(hooksDir)) {
|
|
15635
|
+
await rm4(hooksDir, { recursive: true, force: true });
|
|
15636
|
+
await mkdir8(hooksDir, { recursive: true });
|
|
15637
|
+
actions.push("Hooks: 초기화됨");
|
|
15638
|
+
} else {
|
|
15639
|
+
actions.push("Hooks: 디렉토리 없음 (skip)");
|
|
15640
|
+
}
|
|
15641
|
+
} else {
|
|
15642
|
+
actions.push("Hooks: 기존 유지");
|
|
15643
|
+
}
|
|
15644
|
+
if (options.genome === "template") {
|
|
15645
|
+
await resetGenomeToTemplate(paths, actions);
|
|
15646
|
+
} else if (options.genome === "keep") {
|
|
15647
|
+
actions.push("Genome/Environment: 기존 유지");
|
|
15648
|
+
} else {
|
|
15649
|
+
actions.push("Genome/Environment: 수동 편집 모드 (변경 없음)");
|
|
15650
|
+
}
|
|
15651
|
+
const backlogDir = paths.backlog;
|
|
15652
|
+
if (options.backlog === "delete") {
|
|
15653
|
+
if (await fileExists(backlogDir)) {
|
|
15654
|
+
await rm4(backlogDir, { recursive: true, force: true });
|
|
15655
|
+
await mkdir8(backlogDir, { recursive: true });
|
|
15656
|
+
actions.push("Backlog: 삭제됨");
|
|
15657
|
+
} else {
|
|
15658
|
+
actions.push("Backlog: 디렉토리 없음 (skip)");
|
|
15659
|
+
}
|
|
15660
|
+
} else {
|
|
15661
|
+
actions.push("Backlog: 보존됨");
|
|
15662
|
+
}
|
|
15663
|
+
return { actions, warnings };
|
|
15664
|
+
}
|
|
15665
|
+
async function compressLineage(paths, actions) {
|
|
15666
|
+
const lineageDir = paths.lineage;
|
|
15667
|
+
if (!await fileExists(lineageDir)) {
|
|
15668
|
+
actions.push("Lineage: 디렉토리 없음 (skip)");
|
|
15669
|
+
return;
|
|
15670
|
+
}
|
|
15671
|
+
let entries;
|
|
15672
|
+
try {
|
|
15673
|
+
entries = await readdir11(lineageDir);
|
|
15674
|
+
} catch {
|
|
15675
|
+
actions.push("Lineage: 읽기 실패 (skip)");
|
|
15676
|
+
return;
|
|
15677
|
+
}
|
|
15678
|
+
const genDirs = entries.filter((e) => e.startsWith("gen-"));
|
|
15679
|
+
if (genDirs.length === 0) {
|
|
15680
|
+
actions.push("Lineage: 세대 기록 없음 (skip)");
|
|
15681
|
+
return;
|
|
15682
|
+
}
|
|
15683
|
+
const epochId = `epoch-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}`;
|
|
15684
|
+
const summary = [
|
|
15685
|
+
`# Epoch: ${epochId}`,
|
|
15686
|
+
`# Compressed ${genDirs.length} generations`,
|
|
15687
|
+
`# Date: ${new Date().toISOString()}`,
|
|
15688
|
+
"",
|
|
15689
|
+
"## Generations",
|
|
15690
|
+
...genDirs.map((d) => `- ${d}`),
|
|
15691
|
+
""
|
|
15692
|
+
].join(`
|
|
15693
|
+
`);
|
|
15694
|
+
for (const dir of genDirs) {
|
|
15695
|
+
await rm4(join13(lineageDir, dir), { recursive: true, force: true });
|
|
15696
|
+
}
|
|
15697
|
+
await writeTextFile(join13(lineageDir, `${epochId}.md`), summary);
|
|
15698
|
+
actions.push(`Lineage: ${genDirs.length}개 세대를 ${epochId}로 압축`);
|
|
15699
|
+
}
|
|
15700
|
+
async function cleanLife(paths, actions) {
|
|
15701
|
+
const lifeDir = paths.life;
|
|
15702
|
+
if (!await fileExists(lifeDir)) {
|
|
15703
|
+
actions.push("Life: 디렉토리 없음 (skip)");
|
|
15704
|
+
return;
|
|
15705
|
+
}
|
|
15706
|
+
let entries;
|
|
15707
|
+
try {
|
|
15708
|
+
entries = await readdir11(lifeDir);
|
|
15709
|
+
} catch {
|
|
15710
|
+
actions.push("Life: 읽기 실패 (skip)");
|
|
15711
|
+
return;
|
|
15712
|
+
}
|
|
15713
|
+
let removedCount = 0;
|
|
15714
|
+
for (const entry of entries) {
|
|
15715
|
+
if (entry === "backlog")
|
|
15716
|
+
continue;
|
|
15717
|
+
const entryPath = join13(lifeDir, entry);
|
|
15718
|
+
await rm4(entryPath, { recursive: true, force: true });
|
|
15719
|
+
removedCount++;
|
|
15720
|
+
}
|
|
15721
|
+
actions.push(`Life: ${removedCount}개 파일/디렉토리 정리됨`);
|
|
15722
|
+
}
|
|
15723
|
+
async function resetGenomeToTemplate(paths, actions) {
|
|
15724
|
+
const genomeFiles = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
|
|
15725
|
+
for (const file of genomeFiles) {
|
|
15726
|
+
const templatePath = join13(ReapPaths.packageGenomeDir, file);
|
|
15727
|
+
const destPath = join13(paths.genome, file);
|
|
15728
|
+
try {
|
|
15729
|
+
const templateContent = await readTextFileOrThrow(templatePath);
|
|
15730
|
+
await writeTextFile(destPath, templateContent);
|
|
15731
|
+
} catch {}
|
|
15732
|
+
}
|
|
15733
|
+
const envSummaryTemplate = join13(ReapPaths.packageTemplatesDir, "environment", "summary.md");
|
|
15734
|
+
if (await fileExists(envSummaryTemplate)) {
|
|
15735
|
+
try {
|
|
15736
|
+
const content = await readTextFileOrThrow(envSummaryTemplate);
|
|
15737
|
+
await writeTextFile(paths.environmentSummary, content);
|
|
15738
|
+
} catch {}
|
|
15739
|
+
}
|
|
15740
|
+
actions.push("Genome/Environment: 템플릿으로 초기화됨");
|
|
15741
|
+
}
|
|
15742
|
+
|
|
14998
15743
|
// src/cli/index.ts
|
|
14999
15744
|
init_lifecycle();
|
|
15000
15745
|
init_paths();
|
|
15001
15746
|
init_fs();
|
|
15002
15747
|
init_version();
|
|
15003
15748
|
init_config();
|
|
15004
|
-
import { join as
|
|
15005
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
15749
|
+
import { join as join29 } from "path";
|
|
15750
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.0");
|
|
15006
15751
|
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) => {
|
|
15007
15752
|
try {
|
|
15008
15753
|
const cwd = process.cwd();
|
|
@@ -15058,7 +15803,7 @@ program.command("status").description("Show current project and Generation statu
|
|
|
15058
15803
|
const paths = new ReapPaths(cwd);
|
|
15059
15804
|
const config = await ConfigManager.read(paths);
|
|
15060
15805
|
const skipCheck = config.autoUpdate === false;
|
|
15061
|
-
const installedVersion = "0.
|
|
15806
|
+
const installedVersion = "0.15.0";
|
|
15062
15807
|
const versionLine = formatVersionLine(installedVersion, skipCheck);
|
|
15063
15808
|
console.log(`${versionLine} | Project: ${status.project} (${status.entryMode})`);
|
|
15064
15809
|
console.log(`Completed Generations: ${status.totalGenerations}`);
|
|
@@ -15137,16 +15882,113 @@ program.command("help").description("Show REAP commands, slash commands, and wor
|
|
|
15137
15882
|
if (l === "korean" || l === "ko")
|
|
15138
15883
|
lang = "ko";
|
|
15139
15884
|
}
|
|
15140
|
-
const helpDir =
|
|
15141
|
-
let helpText = await readTextFile(
|
|
15885
|
+
const helpDir = join29(ReapPaths.packageTemplatesDir, "help");
|
|
15886
|
+
let helpText = await readTextFile(join29(helpDir, `${lang}.txt`));
|
|
15142
15887
|
if (!helpText)
|
|
15143
|
-
helpText = await readTextFile(
|
|
15888
|
+
helpText = await readTextFile(join29(helpDir, "en.txt"));
|
|
15144
15889
|
if (!helpText) {
|
|
15145
15890
|
console.log("Help file not found. Run 'reap update' to install templates.");
|
|
15146
15891
|
return;
|
|
15147
15892
|
}
|
|
15148
15893
|
console.log(helpText);
|
|
15149
15894
|
});
|
|
15895
|
+
program.command("destroy").description("Remove all REAP files from this project").action(async () => {
|
|
15896
|
+
try {
|
|
15897
|
+
const cwd = process.cwd();
|
|
15898
|
+
const projectName = await getProjectName(cwd);
|
|
15899
|
+
if (!projectName) {
|
|
15900
|
+
console.error("Error: Not a REAP project (cannot read .reap/config.yml).");
|
|
15901
|
+
process.exit(1);
|
|
15902
|
+
}
|
|
15903
|
+
const expectedInput = `destroy ${projectName}`;
|
|
15904
|
+
console.log(`
|
|
15905
|
+
This will permanently remove all REAP files from this project.`);
|
|
15906
|
+
console.log(`To confirm, type '${expectedInput}':
|
|
15907
|
+
`);
|
|
15908
|
+
const answer = await prompt("> ");
|
|
15909
|
+
if (answer.trim() !== expectedInput) {
|
|
15910
|
+
console.log(`
|
|
15911
|
+
Confirmation mismatch. Destroy cancelled.`);
|
|
15912
|
+
process.exit(0);
|
|
15913
|
+
}
|
|
15914
|
+
console.log("");
|
|
15915
|
+
const result = await destroyProject(cwd);
|
|
15916
|
+
if (result.removed.length > 0) {
|
|
15917
|
+
console.log("Removed:");
|
|
15918
|
+
result.removed.forEach((f) => console.log(` - ${f}`));
|
|
15919
|
+
}
|
|
15920
|
+
if (result.skipped.length > 0) {
|
|
15921
|
+
console.log("Skipped:");
|
|
15922
|
+
result.skipped.forEach((f) => console.log(` - ${f}`));
|
|
15923
|
+
}
|
|
15924
|
+
console.log(`
|
|
15925
|
+
REAP has been removed from this project.`);
|
|
15926
|
+
} catch (e) {
|
|
15927
|
+
console.error(`Error: ${e.message}`);
|
|
15928
|
+
process.exit(1);
|
|
15929
|
+
}
|
|
15930
|
+
});
|
|
15931
|
+
program.command("clean").description("Reset REAP project with interactive options").action(async () => {
|
|
15932
|
+
try {
|
|
15933
|
+
const cwd = process.cwd();
|
|
15934
|
+
const paths = new ReapPaths(cwd);
|
|
15935
|
+
if (!await paths.isReapProject()) {
|
|
15936
|
+
console.error("Error: Not a REAP project (.reap/ not found).");
|
|
15937
|
+
process.exit(1);
|
|
15938
|
+
}
|
|
15939
|
+
if (await hasActiveGeneration(cwd)) {
|
|
15940
|
+
console.log(`
|
|
15941
|
+
⚠ Warning: There is an active generation in progress.`);
|
|
15942
|
+
const proceed = await prompt("Continue and discard it? (y/N): ");
|
|
15943
|
+
if (proceed.trim().toLowerCase() !== "y") {
|
|
15944
|
+
console.log("Clean cancelled.");
|
|
15945
|
+
process.exit(0);
|
|
15946
|
+
}
|
|
15947
|
+
}
|
|
15948
|
+
console.log(`
|
|
15949
|
+
--- REAP Clean ---
|
|
15950
|
+
`);
|
|
15951
|
+
console.log("1. Lineage (generation history):");
|
|
15952
|
+
console.log(" [1] Compress into epoch summary");
|
|
15953
|
+
console.log(" [2] Delete entirely");
|
|
15954
|
+
const lineageChoice = await prompt(" Choice (1/2): ");
|
|
15955
|
+
const lineage = lineageChoice.trim() === "2" ? "delete" : "compress";
|
|
15956
|
+
console.log(`
|
|
15957
|
+
2. Hooks:`);
|
|
15958
|
+
console.log(" [1] Keep existing hooks");
|
|
15959
|
+
console.log(" [2] Reset to defaults");
|
|
15960
|
+
const hooksChoice = await prompt(" Choice (1/2): ");
|
|
15961
|
+
const hooks = hooksChoice.trim() === "2" ? "reset" : "keep";
|
|
15962
|
+
console.log(`
|
|
15963
|
+
3. Genome / Environment:`);
|
|
15964
|
+
console.log(" [1] Override with templates (then run /reap.sync)");
|
|
15965
|
+
console.log(" [2] Keep current files");
|
|
15966
|
+
console.log(" [3] Manual editing (no changes)");
|
|
15967
|
+
const genomeChoice = await prompt(" Choice (1/2/3): ");
|
|
15968
|
+
const genome = genomeChoice.trim() === "1" ? "template" : genomeChoice.trim() === "3" ? "manual" : "keep";
|
|
15969
|
+
console.log(`
|
|
15970
|
+
4. Backlog:`);
|
|
15971
|
+
console.log(" [1] Keep existing backlog items");
|
|
15972
|
+
console.log(" [2] Delete all");
|
|
15973
|
+
const backlogChoice = await prompt(" Choice (1/2): ");
|
|
15974
|
+
const backlog = backlogChoice.trim() === "2" ? "delete" : "keep";
|
|
15975
|
+
console.log(`
|
|
15976
|
+
Applying...
|
|
15977
|
+
`);
|
|
15978
|
+
const result = await cleanProject(cwd, { lineage, hooks, genome, backlog });
|
|
15979
|
+
for (const action of result.actions) {
|
|
15980
|
+
console.log(` - ${action}`);
|
|
15981
|
+
}
|
|
15982
|
+
for (const warning of result.warnings) {
|
|
15983
|
+
console.log(` ⚠ ${warning}`);
|
|
15984
|
+
}
|
|
15985
|
+
console.log(`
|
|
15986
|
+
Clean complete. Run /reap.start to begin a new generation.`);
|
|
15987
|
+
} catch (e) {
|
|
15988
|
+
console.error(`Error: ${e.message}`);
|
|
15989
|
+
process.exit(1);
|
|
15990
|
+
}
|
|
15991
|
+
});
|
|
15150
15992
|
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) => {
|
|
15151
15993
|
const rawArgs = cmd.args.slice(1);
|
|
15152
15994
|
const passArgs = [];
|
|
@@ -15160,4 +16002,13 @@ program.command("run <command>").description("Run a REAP command script (interna
|
|
|
15160
16002
|
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_run(), exports_run));
|
|
15161
16003
|
await runCommand2(command, options.phase, passArgs);
|
|
15162
16004
|
});
|
|
16005
|
+
function prompt(question) {
|
|
16006
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
16007
|
+
return new Promise((resolve) => {
|
|
16008
|
+
rl.question(question, (answer) => {
|
|
16009
|
+
rl.close();
|
|
16010
|
+
resolve(answer ?? "");
|
|
16011
|
+
});
|
|
16012
|
+
});
|
|
16013
|
+
}
|
|
15163
16014
|
program.parse();
|