@c-d-cc/reap 0.13.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -324,6 +324,7 @@ autoIssueReport: true # デフォルト: true(gh CLIがある場合)
324
324
  | `/reap.merge.sync` | Genome-ソース間の整合性を検証(AI比較、ユーザー確認) |
325
325
  | `/reap.merge.validation` | 機械的テストを実行(bun test、tsc、build) |
326
326
  | **`/reap.merge.evolve`** | **マージライフサイクル全体を自動実行** |
327
+ | `/reap.refreshKnowledge` | サブエージェント用REAPコンテキストのロード(Genome、Environment、状態) |
327
328
 
328
329
  ### SessionStart Hook [↗](https://reap.cc/docs/hooks)
329
330
 
package/README.ko.md CHANGED
@@ -324,6 +324,7 @@ Slash command가 `.claude/commands/`에 설치되어 전체 워크플로우를
324
324
  | `/reap.merge.sync` | Genome-소스 간 일관성 검증 (AI 비교, 사용자 확인) |
325
325
  | `/reap.merge.validation` | 기계적 테스트 실행 (bun test, tsc, build) |
326
326
  | **`/reap.merge.evolve`** | **전체 머지 라이프사이클 자동 실행** |
327
+ | `/reap.refreshKnowledge` | 서브에이전트용 REAP 컨텍스트 로드 (Genome, Environment, 상태) |
327
328
 
328
329
  ### SessionStart Hook [↗](https://reap.cc/docs/hooks)
329
330
 
package/README.md CHANGED
@@ -323,6 +323,7 @@ Slash commands are installed in `.claude/commands/` and drive the entire workflo
323
323
  | `/reap.merge.sync` | Verify genome–source consistency (AI compares, user confirms) |
324
324
  | `/reap.merge.validation` | Run mechanical testing (bun test, tsc, build) |
325
325
  | **`/reap.merge.evolve`** | **Run the full merge lifecycle automatically** |
326
+ | `/reap.refreshKnowledge` | Load REAP context for subagents (Genome, Environment, state) |
326
327
 
327
328
  ### SessionStart Hook [↗](https://reap.cc/docs/hooks)
328
329
 
package/README.zh-CN.md CHANGED
@@ -324,6 +324,7 @@ autoIssueReport: true # 默认值: true(检测到gh CLI时)
324
324
  | `/reap.merge.sync` | 验证Genome与源代码的一致性(AI比较,用户确认) |
325
325
  | `/reap.merge.validation` | 运行机械化测试(bun test、tsc、build) |
326
326
  | **`/reap.merge.evolve`** | **自动运行完整的合并生命周期** |
327
+ | `/reap.refreshKnowledge` | 为子代理加载REAP上下文(Genome、Environment、状态) |
327
328
 
328
329
  ### SessionStart Hook [↗](https://reap.cc/docs/hooks)
329
330
 
package/dist/cli.js CHANGED
@@ -10814,6 +10814,77 @@ __export(exports_completion, {
10814
10814
  execute: () => execute4
10815
10815
  });
10816
10816
  import { join as join16 } from "path";
10817
+ import { execSync as execSync6 } from "child_process";
10818
+ function detectGenomeImpact(projectRoot) {
10819
+ const impact = {
10820
+ newCommands: [],
10821
+ packageJsonChanged: false,
10822
+ coreChanges: []
10823
+ };
10824
+ let changedFiles;
10825
+ try {
10826
+ const output = execSync6("git diff --name-only HEAD~1", {
10827
+ cwd: projectRoot,
10828
+ encoding: "utf-8",
10829
+ timeout: 5000
10830
+ });
10831
+ changedFiles = output.trim().split(`
10832
+ `).filter(Boolean);
10833
+ } catch {
10834
+ return impact;
10835
+ }
10836
+ for (const file of changedFiles) {
10837
+ if (file.startsWith("src/cli/commands/") && file.endsWith(".ts")) {
10838
+ impact.newCommands.push(file);
10839
+ }
10840
+ if (file === "package.json") {
10841
+ impact.packageJsonChanged = true;
10842
+ }
10843
+ if (file.startsWith("src/core/") && file.endsWith(".ts")) {
10844
+ impact.coreChanges.push(file);
10845
+ }
10846
+ }
10847
+ return impact;
10848
+ }
10849
+ function buildGenomeImpactPrompt(impact) {
10850
+ const lines = [];
10851
+ if (impact.newCommands.length > 0) {
10852
+ lines.push(`- **Commands changed/added** (${impact.newCommands.length}): constraints.md의 Slash Commands 목록 업데이트 필요 여부 확인`);
10853
+ }
10854
+ if (impact.packageJsonChanged) {
10855
+ lines.push("- **package.json changed**: constraints.md의 Tech Stack 및 environment.md 업데이트 필요 여부 확인");
10856
+ }
10857
+ if (impact.coreChanges.length > 0) {
10858
+ lines.push(`- **Core modules changed** (${impact.coreChanges.length}): principles.md 및 source-map.md 업데이트 필요 여부 확인`);
10859
+ }
10860
+ if (lines.length === 0)
10861
+ return "";
10862
+ return [
10863
+ "",
10864
+ "",
10865
+ "## Genome/Environment Impact Detection",
10866
+ "다음 변경이 감지되었습니다. genome-change 또는 environment-change backlog 작성이 필요한지 검토하라:",
10867
+ "",
10868
+ ...lines
10869
+ ].join(`
10870
+ `);
10871
+ }
10872
+ function buildMdHookPrompt(hookResults) {
10873
+ const mdHooks = hookResults.filter((h) => h.type === "md" && h.status === "executed" && h.content);
10874
+ if (mdHooks.length === 0)
10875
+ return "";
10876
+ const sections = mdHooks.map((h) => `### ${h.name}
10877
+ ${h.content}`);
10878
+ return [
10879
+ "",
10880
+ "",
10881
+ "## Hook Prompts",
10882
+ "다음 hook prompt를 순서대로 실행하라:",
10883
+ "",
10884
+ ...sections
10885
+ ].join(`
10886
+ `);
10887
+ }
10817
10888
  async function execute4(paths, phase) {
10818
10889
  const gm = new GenerationManager(paths);
10819
10890
  const state = await gm.current();
@@ -10856,11 +10927,11 @@ async function execute4(paths, phase) {
10856
10927
  validationSummary: validationContent?.slice(0, 2000),
10857
10928
  implSummary: implContent?.slice(0, 2000)
10858
10929
  },
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"
10930
+ 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",
10931
+ nextCommand: "reap run completion --phase feedKnowledge"
10861
10932
  });
10862
10933
  }
10863
- if (phase === "genome") {
10934
+ if (phase === "feedKnowledge") {
10864
10935
  const backlogItems = await scanBacklog(paths.backlog);
10865
10936
  const genomeChanges = backlogItems.filter((b) => b.type === "genome-change" && b.status !== "consumed");
10866
10937
  const envChanges = backlogItems.filter((b) => b.type === "environment-change" && b.status !== "consumed");
@@ -10868,6 +10939,8 @@ async function execute4(paths, phase) {
10868
10939
  for (const item of toConsume) {
10869
10940
  await markBacklogConsumed(paths.backlog, item.filename, state.id);
10870
10941
  }
10942
+ const genomeImpact = detectGenomeImpact(paths.projectRoot);
10943
+ const impactPrompt = buildGenomeImpactPrompt(genomeImpact);
10871
10944
  const hookResults = await executeHooks(paths.hooks, "onLifeCompleted", paths.projectRoot);
10872
10945
  const submodules = checkSubmodules(paths.projectRoot);
10873
10946
  const dirtySubmodules = submodules.filter((s) => s.dirty);
@@ -10897,9 +10970,10 @@ async function execute4(paths, phase) {
10897
10970
  consumedCount: toConsume.length,
10898
10971
  compression: { level1: compression.level1.length, level2: compression.level2.length },
10899
10972
  hookResults,
10900
- dirtySubmodules
10973
+ dirtySubmodules,
10974
+ genomeImpact
10901
10975
  },
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.`,
10976
+ 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
10977
  message: `Generation ${state.id} archived. ${hasChanges ? "Genome/env changes applied." : "No genome/environment changes."} ${toConsume.length} backlog item(s) consumed.`
10904
10978
  });
10905
10979
  }
@@ -10936,7 +11010,7 @@ async function execute4(paths, phase) {
10936
11010
  hookResults,
10937
11011
  dirtySubmodules
10938
11012
  },
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.`,
11013
+ 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
11014
  message: `Generation ${state.id} archived.`
10941
11015
  });
10942
11016
  }
@@ -11627,6 +11701,10 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
11627
11701
  const lines = [];
11628
11702
  lines.push("# REAP Subagent Instructions");
11629
11703
  lines.push("");
11704
+ lines.push("## FIRST: Load REAP Context");
11705
+ lines.push("Before doing anything else, run `reap run refreshKnowledge` to load full REAP context (Genome, Environment, Generation state, Workflow Guide).");
11706
+ lines.push("Incorporate the returned context into your working knowledge before proceeding.");
11707
+ lines.push("");
11630
11708
  lines.push("## Rules");
11631
11709
  lines.push("- ALWAYS use `reap run <cmd>` commands to drive lifecycle. NEVER modify `current.yml` directly.");
11632
11710
  lines.push("- Use `/reap.next` to advance stages and `/reap.back` to regress.");
@@ -11676,7 +11754,7 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
11676
11754
  lines.push("3. Write the required artifact BEFORE completing the stage.");
11677
11755
  lines.push("4. Run the stage complete phase if applicable (e.g., `reap run <stage> --phase complete`).");
11678
11756
  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.");
11757
+ lines.push("6. If current stage IS `completion`: `/reap.completion` auto-archives after feedKnowledge phase. Done.");
11680
11758
  lines.push("");
11681
11759
  lines.push("Note: `/reap.next` is a transition command, NOT a lifecycle stage.");
11682
11760
  lines.push("");
@@ -11693,6 +11771,16 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
11693
11771
  lines.push("- Create a git commit after implementation and after completion.");
11694
11772
  lines.push("- Use conventional commit format: `feat|fix|chore(scope): description`");
11695
11773
  lines.push("- Include the generation ID in the commit message.");
11774
+ lines.push("");
11775
+ lines.push("## Hook Prompt Execution");
11776
+ lines.push("- completion output의 `prompt` 필드에 '## Hook Prompts' 섹션이 포함되어 있으면, 해당 hook prompt를 반드시 순서대로 실행하라.");
11777
+ lines.push("- Hook prompt는 `.md` hook 파일의 내용으로, AI가 따라야 할 지시사항이다.");
11778
+ lines.push("- Hook prompt에 유저 확인이 필요한 경우(예: 프리뷰+컨펌), autonomous mode에서도 이를 존중하라.");
11779
+ lines.push("");
11780
+ lines.push("## Interrupt Protection");
11781
+ lines.push('- 사용자의 새 메시지가 중간에 들어와도, 명시적 kill/중단 요청("중단", "stop", "abort")이 아닌 한 현재 작업을 끝까지 완료하라.');
11782
+ lines.push("- 작업을 shortcut으로 건너뛰거나 결과를 추정하지 마라. 모든 validation은 실제 실행 결과를 확인하라.");
11783
+ lines.push("- E2E 테스트 등 외부 실행이 포함된 작업은 반드시 실제 실행하고 결과를 확인하라.");
11696
11784
  return lines.join(`
11697
11785
  `);
11698
11786
  }
@@ -12094,9 +12182,9 @@ async function execute15(paths) {
12094
12182
  const gm = new GenerationManager(paths);
12095
12183
  const state = await gm.current();
12096
12184
  const configContent = await readTextFile(paths.config);
12097
- const configVersion = configContent?.match(/version:\s*([\d.]+)/)?.[1] ?? "0.0.0";
12185
+ const installedVersion = "0.14.0";
12098
12186
  const autoUpdate = configContent?.match(/autoUpdate:\s*(true|false)/)?.[1] === "true";
12099
- const versionDisplay = formatVersionLine(configVersion, !autoUpdate);
12187
+ const versionDisplay = formatVersionLine(installedVersion, !autoUpdate);
12100
12188
  const rawLang = detectLanguage(configContent);
12101
12189
  const supported = isSupportedLanguage(rawLang);
12102
12190
  const lang = supported ? rawLang : "en";
@@ -13596,12 +13684,177 @@ var init_config2 = __esm(() => {
13596
13684
  init_config();
13597
13685
  });
13598
13686
 
13687
+ // src/cli/commands/run/refresh-knowledge.ts
13688
+ var exports_refresh_knowledge = {};
13689
+ __export(exports_refresh_knowledge, {
13690
+ execute: () => execute28
13691
+ });
13692
+ import { join as join26 } from "path";
13693
+ import { readdir as readdir18 } from "fs/promises";
13694
+ async function loadGenome(genomeDir) {
13695
+ let content = "";
13696
+ let l1Lines = 0;
13697
+ let smLimit = null;
13698
+ const smContent = await readTextFile(join26(genomeDir, "source-map.md"));
13699
+ if (smContent) {
13700
+ const limitMatch = smContent.match(/줄 수 한도:\s*~?(\d+)줄/);
13701
+ if (limitMatch)
13702
+ smLimit = parseInt(limitMatch[1], 10);
13703
+ }
13704
+ for (const file of L1_FILES) {
13705
+ const fileContent = await readTextFile(join26(genomeDir, file));
13706
+ if (!fileContent)
13707
+ continue;
13708
+ const lines = fileContent.split(`
13709
+ `).length;
13710
+ const limit = file === "source-map.md" && smLimit ? smLimit : L1_LIMIT;
13711
+ l1Lines += lines;
13712
+ if (l1Lines <= limit) {
13713
+ content += `
13714
+ ### ${file}
13715
+ ${fileContent}
13716
+ `;
13717
+ } else {
13718
+ content += `
13719
+ ### ${file} [TRUNCATED — L1 budget exceeded]
13720
+ ${fileContent.split(`
13721
+ `).slice(0, 20).join(`
13722
+ `)}
13723
+ ...
13724
+ `;
13725
+ }
13726
+ }
13727
+ const domainDir = join26(genomeDir, "domain");
13728
+ if (await fileExists(domainDir)) {
13729
+ let l2Lines = 0;
13730
+ let l2Overflow = false;
13731
+ try {
13732
+ const domainFiles = (await readdir18(domainDir)).filter((f) => f.endsWith(".md")).sort();
13733
+ for (const file of domainFiles) {
13734
+ const fileContent = await readTextFile(join26(domainDir, file));
13735
+ if (!fileContent)
13736
+ continue;
13737
+ const lines = fileContent.split(`
13738
+ `).length;
13739
+ l2Lines += lines;
13740
+ if (!l2Overflow && l2Lines <= L2_LIMIT) {
13741
+ content += `
13742
+ ### domain/${file}
13743
+ ${fileContent}
13744
+ `;
13745
+ } else {
13746
+ l2Overflow = true;
13747
+ const firstLine = fileContent.split(`
13748
+ `).find((l) => l.startsWith(">")) || fileContent.split(`
13749
+ `)[0];
13750
+ content += `
13751
+ ### domain/${file} [summary]
13752
+ ${firstLine}
13753
+ `;
13754
+ }
13755
+ }
13756
+ } catch {}
13757
+ }
13758
+ return { content, l1Lines };
13759
+ }
13760
+ function buildStrictSection(strict, genStage) {
13761
+ let sections = "";
13762
+ if (strict.edit) {
13763
+ if (genStage === "implementation") {
13764
+ sections += `
13765
+
13766
+ ## Strict Mode — Edit (ACTIVE — SCOPED MODIFICATION ALLOWED)`;
13767
+ } else if (genStage === "none") {
13768
+ sections += `
13769
+
13770
+ ## Strict Mode — Edit (ACTIVE — CODE MODIFICATION BLOCKED)`;
13771
+ } else {
13772
+ sections += `
13773
+
13774
+ ## Strict Mode — Edit (ACTIVE — stage '${genStage}', CODE MODIFICATION BLOCKED)`;
13775
+ }
13776
+ }
13777
+ if (strict.merge) {
13778
+ sections += `
13779
+
13780
+ ## Strict Mode — Merge (ACTIVE)`;
13781
+ }
13782
+ return sections;
13783
+ }
13784
+ async function execute28(paths) {
13785
+ const guidePath = join26(ReapPaths.packageHooksDir, "reap-guide.md");
13786
+ const reapGuide = await readTextFile(guidePath) || "";
13787
+ const { content: genomeContent } = await loadGenome(paths.genome);
13788
+ const envSummary = await readTextFile(paths.environmentSummary) || "";
13789
+ const gm = new GenerationManager(paths);
13790
+ const state = await gm.current();
13791
+ let generationContext;
13792
+ let genStage;
13793
+ if (state && state.id) {
13794
+ genStage = state.stage;
13795
+ generationContext = `Active Generation: ${state.id} | Goal: ${state.goal} | Stage: ${state.stage}`;
13796
+ } else {
13797
+ genStage = "none";
13798
+ generationContext = "No active Generation.";
13799
+ }
13800
+ let strict = { edit: false, merge: false };
13801
+ try {
13802
+ const config = await ConfigManager.read(paths);
13803
+ strict = ConfigManager.resolveStrict(config.strict);
13804
+ } catch {}
13805
+ const strictSection = buildStrictSection(strict, genStage);
13806
+ const envSection = envSummary ? `
13807
+
13808
+ ---
13809
+
13810
+ ## Environment (External Context)
13811
+ ${envSummary}` : "";
13812
+ const reapContext = [
13813
+ "<REAP_CONTEXT>",
13814
+ reapGuide,
13815
+ "",
13816
+ "---",
13817
+ "",
13818
+ "## Genome (Project Knowledge)",
13819
+ genomeContent,
13820
+ envSection,
13821
+ "",
13822
+ "---",
13823
+ "",
13824
+ "## Current State",
13825
+ generationContext,
13826
+ strictSection,
13827
+ "</REAP_CONTEXT>"
13828
+ ].join(`
13829
+ `);
13830
+ emitOutput({
13831
+ status: "ok",
13832
+ command: "refreshKnowledge",
13833
+ phase: "done",
13834
+ completed: ["load-guide", "load-genome", "load-environment", "load-state"],
13835
+ context: {
13836
+ hasGeneration: !!state?.id,
13837
+ generationId: state?.id || null,
13838
+ stage: genStage
13839
+ },
13840
+ prompt: reapContext
13841
+ });
13842
+ }
13843
+ var L1_LIMIT = 500, L2_LIMIT = 200, L1_FILES;
13844
+ var init_refresh_knowledge = __esm(() => {
13845
+ init_paths();
13846
+ init_fs();
13847
+ init_config();
13848
+ init_generation();
13849
+ L1_FILES = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
13850
+ });
13851
+
13599
13852
  // src/cli/commands/run/index.ts
13600
13853
  var exports_run = {};
13601
13854
  __export(exports_run, {
13602
13855
  runCommand: () => runCommand
13603
13856
  });
13604
- import { execSync as execSync6 } from "child_process";
13857
+ import { execSync as execSync7 } from "child_process";
13605
13858
  async function runCommand(command, phase, argv = []) {
13606
13859
  const cwd = process.cwd();
13607
13860
  const paths = new ReapPaths(cwd);
@@ -13619,7 +13872,7 @@ async function runCommand(command, phase, argv = []) {
13619
13872
  try {
13620
13873
  const config = await ConfigManager.read(paths);
13621
13874
  if (config.autoIssueReport) {
13622
- const version = "0.13.3";
13875
+ const version = "0.14.0";
13623
13876
  const errMsg = err instanceof Error ? err.message : String(err);
13624
13877
  const title = `[auto] reap run ${command}: ${errMsg.slice(0, 80)}`;
13625
13878
  const body = [
@@ -13629,7 +13882,7 @@ async function runCommand(command, phase, argv = []) {
13629
13882
  `**OS**: ${process.platform} ${process.arch}`,
13630
13883
  `**Node**: ${process.version}`
13631
13884
  ].join("\\n");
13632
- execSync6(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,bug" --body "${body}"`, { stdio: "ignore", timeout: 1e4 });
13885
+ execSync7(`gh issue create --repo c-d-cc/reap --title "${title}" --label "auto-reported,bug" --body "${body}"`, { stdio: "ignore", timeout: 1e4 });
13633
13886
  }
13634
13887
  } catch {}
13635
13888
  emitError(command, err instanceof Error ? err.message : String(err));
@@ -13666,7 +13919,8 @@ var init_run = __esm(() => {
13666
13919
  "merge-evolve": () => Promise.resolve().then(() => (init_merge_evolve(), exports_merge_evolve)),
13667
13920
  merge: () => Promise.resolve().then(() => (init_merge2(), exports_merge)),
13668
13921
  pull: () => Promise.resolve().then(() => (init_pull(), exports_pull)),
13669
- config: () => Promise.resolve().then(() => (init_config2(), exports_config))
13922
+ config: () => Promise.resolve().then(() => (init_config2(), exports_config)),
13923
+ refreshKnowledge: () => Promise.resolve().then(() => (init_refresh_knowledge(), exports_refresh_knowledge))
13670
13924
  };
13671
13925
  });
13672
13926
 
@@ -14169,7 +14423,8 @@ var COMMAND_NAMES = [
14169
14423
  "reap.merge",
14170
14424
  "reap.pull",
14171
14425
  "reap.push",
14172
- "reap.config"
14426
+ "reap.config",
14427
+ "reap.refreshKnowledge"
14173
14428
  ];
14174
14429
  async function initProject(projectRoot, projectName, entryMode, preset, onProgress) {
14175
14430
  const log = onProgress ?? (() => {});
@@ -14205,7 +14460,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14205
14460
  }
14206
14461
  const detectedLanguage = await AgentRegistry.readLanguage();
14207
14462
  const config = {
14208
- version: "0.13.3",
14463
+ version: "0.14.0",
14209
14464
  project: projectName,
14210
14465
  entryMode,
14211
14466
  strict: false,
@@ -14278,6 +14533,11 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
14278
14533
  if (detectedAgents.length === 0) {
14279
14534
  log(" No AI agents detected.");
14280
14535
  }
14536
+ const autoSynced = (entryMode === "adoption" || entryMode === "migration") && !preset;
14537
+ if (!autoSynced) {
14538
+ log(`
14539
+ \uD83D\uDCA1 Run /reap.sync to synchronize Genome with your project's actual state.`);
14540
+ }
14281
14541
  return { agents: detectedAgents.map((a) => a.displayName) };
14282
14542
  }
14283
14543
 
@@ -14774,37 +15034,47 @@ async function updateProject(projectRoot, dryRun = false) {
14774
15034
  result.updated.push(`Config: added ${backfillResult.added.join(", ")}`);
14775
15035
  }
14776
15036
  }
14777
- const projectClaudeCommands = join10(paths.projectRoot, ".claude", "commands");
14778
- await mkdir6(projectClaudeCommands, { recursive: true });
15037
+ const projectClaudeSkills = join10(paths.projectRoot, ".claude", "skills");
14779
15038
  const reapCmdFiles = (await readdir9(ReapPaths.userReapCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
14780
15039
  let cmdInstalled = 0;
14781
15040
  for (const file of reapCmdFiles) {
14782
15041
  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);
15042
+ const name = file.replace(/\.md$/, "");
15043
+ const skillDir = join10(projectClaudeSkills, name);
15044
+ const skillFile = join10(skillDir, "SKILL.md");
15045
+ const fmMatch = src.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
15046
+ const description = fmMatch ? fmMatch[1].match(/^description:\s*"?([^"\n]*)"?/m)?.[1]?.trim() ?? "" : "";
15047
+ const body = fmMatch ? fmMatch[2] : src;
15048
+ const skillContent = `---
15049
+ name: ${name}
15050
+ description: "${description}"
15051
+ ---
15052
+ ${body}`;
15053
+ const existing = await readTextFile(skillFile);
15054
+ if (existing !== null && existing === skillContent)
15055
+ continue;
15056
+ if (!dryRun) {
15057
+ await mkdir6(skillDir, { recursive: true });
15058
+ await writeTextFile(skillFile, skillContent);
15059
+ }
14799
15060
  cmdInstalled++;
14800
15061
  }
14801
15062
  if (cmdInstalled > 0) {
14802
- result.updated.push(`.claude/commands/ (${cmdInstalled} synced)`);
15063
+ result.updated.push(`.claude/skills/ (${cmdInstalled} synced)`);
14803
15064
  } else {
14804
- result.skipped.push(`.claude/commands/ (${reapCmdFiles.length} unchanged)`);
15065
+ result.skipped.push(`.claude/skills/ (${reapCmdFiles.length} unchanged)`);
14805
15066
  }
15067
+ const projectClaudeCommands = join10(paths.projectRoot, ".claude", "commands");
15068
+ try {
15069
+ const legacyFiles = (await readdir9(projectClaudeCommands)).filter((f) => f.startsWith("reap.") && f.endsWith(".md"));
15070
+ for (const file of legacyFiles) {
15071
+ if (!dryRun)
15072
+ await unlink4(join10(projectClaudeCommands, file));
15073
+ result.removed.push(`.claude/commands/${file} (legacy)`);
15074
+ }
15075
+ } catch {}
14806
15076
  await migrateLegacyFiles(paths, dryRun, result);
14807
- const currentVersion = "0.13.3";
15077
+ const currentVersion = "0.14.0";
14808
15078
  const migrationResult = await MigrationRunner.run(paths, currentVersion, dryRun);
14809
15079
  for (const m of migrationResult.migrated) {
14810
15080
  result.updated.push(`[migration] ${m}`);
@@ -15001,8 +15271,8 @@ init_paths();
15001
15271
  init_fs();
15002
15272
  init_version();
15003
15273
  init_config();
15004
- import { join as join26 } from "path";
15005
- program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.13.3");
15274
+ import { join as join27 } from "path";
15275
+ program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.14.0");
15006
15276
  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
15277
  try {
15008
15278
  const cwd = process.cwd();
@@ -15058,7 +15328,8 @@ program.command("status").description("Show current project and Generation statu
15058
15328
  const paths = new ReapPaths(cwd);
15059
15329
  const config = await ConfigManager.read(paths);
15060
15330
  const skipCheck = config.autoUpdate === false;
15061
- const versionLine = formatVersionLine(status.version, skipCheck);
15331
+ const installedVersion = "0.14.0";
15332
+ const versionLine = formatVersionLine(installedVersion, skipCheck);
15062
15333
  console.log(`${versionLine} | Project: ${status.project} (${status.entryMode})`);
15063
15334
  console.log(`Completed Generations: ${status.totalGenerations}`);
15064
15335
  if (status.generation) {
@@ -15136,10 +15407,10 @@ program.command("help").description("Show REAP commands, slash commands, and wor
15136
15407
  if (l === "korean" || l === "ko")
15137
15408
  lang = "ko";
15138
15409
  }
15139
- const helpDir = join26(ReapPaths.packageTemplatesDir, "help");
15140
- let helpText = await readTextFile(join26(helpDir, `${lang}.txt`));
15410
+ const helpDir = join27(ReapPaths.packageTemplatesDir, "help");
15411
+ let helpText = await readTextFile(join27(helpDir, `${lang}.txt`));
15141
15412
  if (!helpText)
15142
- helpText = await readTextFile(join26(helpDir, "en.txt"));
15413
+ helpText = await readTextFile(join27(helpDir, "en.txt"));
15143
15414
  if (!helpText) {
15144
15415
  console.log("Help file not found. Run 'reap update' to install templates.");
15145
15416
  return;
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: "REAP Refresh Knowledge — Load REAP context (Genome, Environment, Generation state)"
3
+ ---
4
+
5
+ Run `reap run refreshKnowledge` and incorporate the returned REAP context into your working knowledge.
6
+ This is useful when you don't have REAP context loaded (e.g., in a subagent or after context compaction).
@@ -20,6 +20,7 @@ Slash Commands (use in Claude Code):
20
20
  /reap.status Show current state
21
21
  /reap.sync Synchronize Genome with source code
22
22
  /reap.help Contextual help (AI-powered)
23
+ /reap.refreshKnowledge Load REAP context for subagents
23
24
 
24
25
  Quick Start:
25
26
  1. reap init my-project
@@ -20,6 +20,7 @@ CLI 명령어:
20
20
  /reap.status 현재 상태 확인
21
21
  /reap.sync Genome을 소스 코드와 동기화
22
22
  /reap.help 상황별 도움말 (AI 기반)
23
+ /reap.refreshKnowledge 서브에이전트용 REAP 컨텍스트 로드
23
24
 
24
25
  빠른 시작:
25
26
  1. reap init my-project
@@ -7,6 +7,17 @@ const { execSync } = require('child_process');
7
7
  const L1_LIMIT = 500;
8
8
  const L2_LIMIT = 200;
9
9
  const L1_FILES = ['principles.md', 'conventions.md', 'constraints.md', 'source-map.md'];
10
+ const PLACEHOLDER_PATTERNS = [
11
+ /\(Add .+ here\)/,
12
+ /\(Describe .+\)/,
13
+ /\(language and version\)/,
14
+ /\(External .+\)/,
15
+ /\(runtime environment\)/,
16
+ /\(framework\)/,
17
+ /\(database\)/,
18
+ /^\|\s*\|\s*\|\s*\|\s*\|$/m,
19
+ ];
20
+
10
21
  const STAGE_COMMANDS = {
11
22
  objective: '/reap.objective',
12
23
  planning: '/reap.planning',
@@ -188,6 +199,17 @@ function buildStrictSection(strictEdit, strictMerge, genStage) {
188
199
  return sections;
189
200
  }
190
201
 
202
+ /**
203
+ * Check if a genome file contains placeholder template content.
204
+ * @param {string} filePath - path to a genome file
205
+ * @returns {boolean} true if the file contains placeholder patterns
206
+ */
207
+ function hasPlaceholders(filePath) {
208
+ const content = readFile(filePath);
209
+ if (!content) return false;
210
+ return PLACEHOLDER_PATTERNS.some(pattern => pattern.test(content));
211
+ }
212
+
191
213
  /**
192
214
  * Build Genome health status for session init display.
193
215
  * @param {object} params
@@ -202,6 +224,22 @@ function buildGenomeHealth({ l1Lines, genomeDir, configFile, genomeStaleWarning,
202
224
  if (!check) { issues.push(`missing ${f}`); severity = 'danger'; }
203
225
  }
204
226
  if (!fileExists(configFile)) { issues.push('no config.yml'); severity = 'danger'; }
227
+
228
+ // Check for placeholder content in L1 files
229
+ const placeholderFiles = L1_FILES.filter(f => {
230
+ const fp = path.join(genomeDir, f);
231
+ return fileExists(fp) && hasPlaceholders(fp);
232
+ });
233
+ if (placeholderFiles.length > 0) {
234
+ if (placeholderFiles.length === L1_FILES.length) {
235
+ issues.push(`needs customization (${placeholderFiles.length}/${L1_FILES.length} files)`);
236
+ severity = 'danger';
237
+ } else {
238
+ issues.push(`needs customization (${placeholderFiles.length}/${L1_FILES.length} files)`);
239
+ if (severity === 'ok') severity = 'warn';
240
+ }
241
+ }
242
+
205
243
  if (genomeStaleWarning && commitsSince > 30) {
206
244
  issues.push(`severely stale (${commitsSince} commits)`);
207
245
  if (severity !== 'danger') severity = 'danger';
@@ -223,6 +261,7 @@ module.exports = {
223
261
  L2_LIMIT,
224
262
  L1_FILES,
225
263
  STAGE_COMMANDS,
264
+ PLACEHOLDER_PATTERNS,
226
265
  readFile,
227
266
  fileExists,
228
267
  dirExists,
@@ -233,4 +272,5 @@ module.exports = {
233
272
  detectStaleness,
234
273
  buildStrictSection,
235
274
  buildGenomeHealth,
275
+ hasPlaceholders,
236
276
  };
@@ -7,12 +7,12 @@ REAP (Recursive Evolutionary Autonomous Pipeline) is a development pipeline wher
7
7
  ## 3-Layer Model
8
8
 
9
9
  ```
10
- Genome (Genetic Information) → Evolution (Cross-generational Evolution) → Civilization (Source Code)
11
- Design and knowledge Life cycle, mutation, adaptation Accumulated artifacts
10
+ Knowledge Base (Genome + Environment) → Evolution (Generational Progress) → Civilization (Source Code)
11
+ Design, knowledge & external context Life cycle, mutation, adaptation Accumulated artifacts
12
12
  ```
13
13
 
14
- - **Genome** — Design and knowledge for building the Application. Stored in `.reap/genome/`.
15
- - **Evolution** — The process by which Genome evolves and Civilization grows through repeated Generations.
14
+ - **Knowledge Base** — Genome (architecture, conventions, constraints) and Environment (external APIs, infrastructure). Stored in `.reap/genome/` and `.reap/environment/`.
15
+ - **Evolution** — The process by which knowledge evolves and Civilization grows through repeated Generations.
16
16
  - **Civilization** — Source Code. The entire project codebase outside `.reap/`.
17
17
 
18
18
  ## Genome Structure
@@ -170,7 +170,7 @@ objective → planning → implementation → validation → completion
170
170
  6. `/reap.completion` — Retrospective + genome updates + archiving (auto)
171
171
 
172
172
  `/reap.next` is a **transition command**, not a lifecycle stage. It advances `current.yml` to the next stage.
173
- `/reap.completion` auto-archives after the genome phase — no separate `/reap.next` needed at the end.
173
+ `/reap.completion` auto-archives after the feedKnowledge phase — no separate `/reap.next` needed at the end.
174
174
 
175
175
  ## Language
176
176
 
@@ -28,47 +28,89 @@ if (!gl.dirExists(reapDir)) {
28
28
  process.exit(0);
29
29
  }
30
30
 
31
- // Step 0: Install project-level command files (copy, not symlink — Claude Code doesn't follow symlinks)
31
+ // Step 0: Install project-level skill files (.claude/skills/{name}/SKILL.md)
32
32
  const fs = require('fs');
33
33
  const os = require('os');
34
34
  const userReapCommands = path.join(os.homedir(), '.reap', 'commands');
35
- const projectClaudeCommands = path.join(projectRoot, '.claude', 'commands');
35
+ const projectClaudeSkills = path.join(projectRoot, '.claude', 'skills');
36
+
37
+ /**
38
+ * Parse frontmatter from a command .md file.
39
+ * Returns { description, body } where body is the content after frontmatter.
40
+ */
41
+ function parseFrontmatter(content) {
42
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
43
+ if (!match) return { description: '', body: content };
44
+ const fm = match[1];
45
+ const body = match[2];
46
+ const descMatch = fm.match(/^description:\s*"?([^"\n]*)"?/m);
47
+ return { description: descMatch ? descMatch[1].trim() : '', body };
48
+ }
36
49
 
37
50
  if (gl.dirExists(userReapCommands)) {
38
51
  try {
39
- fs.mkdirSync(projectClaudeCommands, { recursive: true });
40
52
  const cmdFiles = fs.readdirSync(userReapCommands).filter(f => f.startsWith('reap.') && f.endsWith('.md'));
41
53
  let installed = 0;
42
54
  for (const file of cmdFiles) {
43
55
  const src = path.join(userReapCommands, file);
44
- const dest = path.join(projectClaudeCommands, file);
56
+ const name = file.replace(/\.md$/, ''); // e.g. reap.objective
57
+ const skillDir = path.join(projectClaudeSkills, name);
58
+ const skillFile = path.join(skillDir, 'SKILL.md');
59
+
60
+ const srcContent = fs.readFileSync(src, 'utf-8');
61
+ const { description, body } = parseFrontmatter(srcContent);
62
+ const skillContent = `---\nname: ${name}\ndescription: "${description}"\n---\n${body}`;
63
+
64
+ // Skip if content is identical
45
65
  try {
46
- const stat = fs.lstatSync(dest);
47
- if (stat.isSymbolicLink()) {
48
- fs.unlinkSync(dest); // replace legacy symlink with real file
49
- } else {
50
- // Skip if content is identical
51
- const srcContent = fs.readFileSync(src);
52
- const destContent = fs.readFileSync(dest);
53
- if (srcContent.equals(destContent)) continue;
54
- fs.unlinkSync(dest);
55
- }
66
+ const existing = fs.readFileSync(skillFile, 'utf-8');
67
+ if (existing === skillContent) continue;
56
68
  } catch { /* dest doesn't exist */ }
57
- fs.copyFileSync(src, dest);
69
+
70
+ fs.mkdirSync(skillDir, { recursive: true });
71
+ fs.writeFileSync(skillFile, skillContent, 'utf-8');
58
72
  installed++;
59
73
  }
60
- // Ensure .gitignore excludes these files
74
+
75
+ // Clean up legacy .claude/commands/reap.* files
76
+ const projectClaudeCommands = path.join(projectRoot, '.claude', 'commands');
77
+ try {
78
+ if (fs.existsSync(projectClaudeCommands)) {
79
+ const legacyFiles = fs.readdirSync(projectClaudeCommands).filter(f => f.startsWith('reap.') && f.endsWith('.md'));
80
+ for (const file of legacyFiles) {
81
+ fs.unlinkSync(path.join(projectClaudeCommands, file));
82
+ }
83
+ if (legacyFiles.length > 0) {
84
+ process.stderr.write(`[REAP] Cleaned up ${legacyFiles.length} legacy .claude/commands/reap.* files\n`);
85
+ }
86
+ }
87
+ } catch { /* best effort */ }
88
+
89
+ // Ensure .gitignore excludes skill files (and migrate legacy entry)
61
90
  const gitignorePath = path.join(projectRoot, '.gitignore');
62
- const gitignoreEntry = '.claude/commands/reap.*';
91
+ const newEntry = '.claude/skills/reap.*';
92
+ const legacyEntry = '.claude/commands/reap.*';
63
93
  try {
64
- const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
65
- if (!gitignore.includes(gitignoreEntry)) {
66
- fs.appendFileSync(gitignorePath, `\n# REAP command files (managed by session-start hook)\n${gitignoreEntry}\n`);
94
+ let gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
95
+ let changed = false;
96
+ // Replace legacy entry with new entry
97
+ if (gitignore.includes(legacyEntry)) {
98
+ gitignore = gitignore.replace(legacyEntry, newEntry);
99
+ changed = true;
100
+ }
101
+ // Add new entry if not present
102
+ if (!gitignore.includes(newEntry)) {
103
+ gitignore += `\n# REAP skill files (managed by session-start hook)\n${newEntry}\n`;
104
+ changed = true;
105
+ }
106
+ if (changed) {
107
+ fs.writeFileSync(gitignorePath, gitignore, 'utf-8');
67
108
  }
68
109
  } catch { /* best effort */ }
69
- process.stderr.write(`[REAP] Installed ${installed} commands to .claude/commands/ (${cmdFiles.length - installed} unchanged)\n`);
110
+
111
+ process.stderr.write(`[REAP] Installed ${installed} skills to .claude/skills/ (${cmdFiles.length - installed} unchanged)\n`);
70
112
  } catch (err) {
71
- process.stderr.write(`[REAP] Warning: failed to install commands: ${err.message}\n`);
113
+ process.stderr.write(`[REAP] Warning: failed to install skills: ${err.message}\n`);
72
114
  }
73
115
  }
74
116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-d-cc/reap",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "description": "Recursive Evolutionary Autonomous Pipeline — AI and humans evolve software across generations",
5
5
  "type": "module",
6
6
  "license": "MIT",