@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/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 generateStageToken(genId, stage) {
9954
+ function generateToken(genId, stage, phase) {
9955
9955
  const nonce = randomBytes(16).toString("hex");
9956
- const hash = createHash("sha256").update(nonce + genId + stage).digest("hex");
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 verifyStageToken(token, genId, stage, expectedHash) {
9960
- const computed = createHash("sha256").update(token + genId + stage).digest("hex");
9961
- return computed === expectedHash;
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 readdir10 } from "fs/promises";
10248
- import { join as join12 } from "path";
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 = join12(hooksDir, "conditions");
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 readdir10(hooksDir);
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(join12(hooksDir, filename), match[2]);
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 = join12(conditionsDir, `${conditionName}.sh`);
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 "${join12(hooksDir, hook.filename)}"`, {
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(join12(hooksDir, hook.filename));
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 readdir11 } from "fs/promises";
10600
- import { join as join14 } from "path";
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 readdir11(backlogDir);
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(join14(backlogDir, filename));
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 = join14(backlogDir, filename);
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 readdir11(backlogDir);
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 = join14(backlogDir, filename);
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 join15 } from "path";
10692
- import { readdir as readdir12 } from "fs/promises";
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 readdir12(paths.lineage);
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 } = generateStageToken(state.id, state.stage);
10746
- state.expectedTokenHash = hash;
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 = join15(__require("os").homedir(), ".reap", "templates");
10752
- const templatePath = join15(templateDir, "01-objective.md");
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 join16 } from "path";
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 = join16(__require("os").homedir(), ".reap", "templates");
10837
- const templatePath = join16(templateDir, "05-completion.md");
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 genome",
10860
- nextCommand: "reap run completion --phase genome"
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 === "genome") {
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 join17 } from "path";
10958
- import { readdir as readdir13, unlink as unlink5 } from "fs/promises";
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(join17(paths.backlog, `aborted-${state.id}.md`), backlogContent);
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 readdir13(paths.life);
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 unlink5(join17(paths.life, entry));
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 join18 } from "path";
11101
- import { readdir as readdir14 } from "fs/promises";
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 readdir14(paths.lineage);
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(join18(paths.lineage, lastGen, "05-completion.md"));
11254
+ prevCompletion = await readTextFile(join20(paths.lineage, lastGen, "05-completion.md"));
11123
11255
  if (!prevCompletion) {
11124
- const compressed = await readTextFile(join18(paths.lineage, `${lastGen}.md`));
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 readdir14(paths.lineage);
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 = join18(__require("os").homedir(), ".reap", "templates");
11142
- const templatePath = join18(templateDir, "01-objective.md");
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 } = generateStageToken(state.id, state.stage);
11221
- state.expectedTokenHash = hash;
11222
- await gm.save(state);
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. Advance with: /reap.next ${nonce}`
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 join19 } from "path";
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 = join19(__require("os").homedir(), ".reap", "templates");
11274
- const templatePath = join19(templateDir, "02-planning.md");
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 } = generateStageToken(state.id, state.stage);
11346
- state.expectedTokenHash = hash;
11347
- await gm.save(state);
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. Advance with: /reap.next ${nonce}`
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 join20 } from "path";
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 = join20(__require("os").homedir(), ".reap", "templates");
11396
- const templatePath = join20(templateDir, "03-implementation.md");
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 } = generateStageToken(state.id, state.stage);
11467
- state.expectedTokenHash = hash;
11468
- await gm.save(state);
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. Advance with: /reap.next ${nonce}`
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 join21 } from "path";
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 = join21(__require("os").homedir(), ".reap", "templates");
11518
- const templatePath = join21(templateDir, "04-validation.md");
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 } = generateStageToken(state.id, state.stage);
11599
- state.expectedTokenHash = hash;
11600
- await gm.save(state);
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. Advance with: /reap.next ${nonce}`
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("- Use `/reap.next` to advance stages and `/reap.back` to regress.");
11633
- lines.push("- Each stage command runs its own hook automatically at completion.");
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 the stage complete phase if applicable (e.g., `reap run <stage> --phase complete`).");
11678
- lines.push("5. If current stage is NOT `completion`: run `/reap.next` to advance, then go to step 1.");
11679
- lines.push("6. If current stage IS `completion`: `/reap.completion` auto-archives after genome phase. Done.");
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 transition command, NOT a lifecycle stage.");
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 stage command returns a `stageToken` in its output context.");
11688
- lines.push("- You MUST pass this token to `/reap.next --token <TOKEN>` (or set `REAP_STAGE_TOKEN` env var).");
11689
- lines.push("- Without a valid token, stage transition will be REJECTED.");
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
- "ALWAYS use `/reap.next` to advance and `/reap.back` to regress.",
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
- "`/reap.next` only handles stage transitions -- it does NOT execute hooks or archiving.",
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. When the stage command completes (hooks already executed by the stage command):",
11820
- " - If the current stage is `completion`: the loop ends.",
11821
- " - Otherwise: run `/reap.next` to advance, then return to step 1.",
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 readdir15 } from "fs/promises";
11873
- import { join as join22 } from "path";
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 readdir15(paths.domain);
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(join22(paths.domain, entry));
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 readdir15(paths.lineage);
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 readdir16 } from "fs/promises";
11970
- import { join as join23 } from "path";
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 readdir16(paths.environmentDocs);
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(join23(paths.environmentDocs, entry));
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(join23(paths.environmentResources, "links.md"));
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 join24 } from "path";
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.13.4";
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 = join24(ReapPaths.packageHooksDir, "reap-guide.md");
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 readdir17, mkdir as mkdir8, rename as rename3 } from "fs/promises";
12414
- import { join as join25 } from "path";
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 mkdir8(genDir, { recursive: true });
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(join25(genDir, "meta.yml"), import_yaml10.default.stringify(meta));
12601
- const lifeEntries = await readdir17(this.paths.life);
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(join25(this.paths.life, entry), join25(genDir, entry));
12807
+ await rename3(join27(this.paths.life, entry), join27(genDir, entry));
12605
12808
  }
12606
12809
  }
12607
- const backlogDir = join25(genDir, "backlog");
12608
- await mkdir8(backlogDir, { recursive: true });
12810
+ const backlogDir = join27(genDir, "backlog");
12811
+ await mkdir9(backlogDir, { recursive: true });
12609
12812
  try {
12610
- const backlogEntries = await readdir17(this.paths.backlog);
12813
+ const backlogEntries = await readdir19(this.paths.backlog);
12611
12814
  for (const entry of backlogEntries) {
12612
- await rename3(join25(this.paths.backlog, entry), join25(backlogDir, entry));
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: "Detect stage complete. Run /reap.next to advance to mate stage."
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: "Mate stage complete. Run /reap.next to advance to merge stage."
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: "Merge stage complete. Run /reap.next to advance to sync stage."
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: "Sync stage complete. Run /reap.next to advance to validation stage."
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: "Validation stage complete. Run /reap.next to advance to completion stage."
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
- "`/reap.next` only handles stage transitions -- it does NOT execute hooks or archiving.",
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. When a stage command completes (hooks already executed by the stage command):",
13339
- " - If the current stage is `completion`: the loop ends.",
13340
- " - Otherwise: run `/reap.next` to advance, then return to step 1.",
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 rm3 of remoteMetasRaw) {
13507
- if (!allMetas.find((m) => m.id === rm3.id))
13508
- allMetas.push(rm3);
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 execSync6 } from "child_process";
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.13.4";
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
- execSync6(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,bug" --body "${body}"`, { stdio: "ignore", timeout: 1e4 });
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.13.4",
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 projectClaudeCommands = join10(paths.projectRoot, ".claude", "commands");
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 destPath = join10(projectClaudeCommands, file);
14784
- try {
14785
- const s = await import("fs/promises").then((m) => m.lstat(destPath));
14786
- if (s.isSymbolicLink()) {
14787
- if (!dryRun)
14788
- await unlink4(destPath);
14789
- } else {
14790
- const existing = await readTextFile(destPath);
14791
- if (existing !== null && existing === src)
14792
- continue;
14793
- if (!dryRun)
14794
- await unlink4(destPath);
14795
- }
14796
- } catch {}
14797
- if (!dryRun)
14798
- await writeTextFile(destPath, src);
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/commands/ (${cmdInstalled} synced)`);
15279
+ result.updated.push(`.claude/skills/ (${cmdInstalled} synced)`);
14803
15280
  } else {
14804
- result.skipped.push(`.claude/commands/ (${reapCmdFiles.length} unchanged)`);
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.13.4";
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 join26 } from "path";
15005
- program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.13.4");
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.13.4";
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 = join26(ReapPaths.packageTemplatesDir, "help");
15141
- let helpText = await readTextFile(join26(helpDir, `${lang}.txt`));
15885
+ const helpDir = join29(ReapPaths.packageTemplatesDir, "help");
15886
+ let helpText = await readTextFile(join29(helpDir, `${lang}.txt`));
15142
15887
  if (!helpText)
15143
- helpText = await readTextFile(join26(helpDir, "en.txt"));
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();