@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 +1 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/dist/cli.js +313 -42
- package/dist/templates/commands/reap.refreshKnowledge.md +6 -0
- package/dist/templates/help/en.txt +1 -0
- package/dist/templates/help/ko.txt +1 -0
- package/dist/templates/hooks/genome-loader.cjs +40 -0
- package/dist/templates/hooks/reap-guide.md +5 -5
- package/dist/templates/hooks/session-start.cjs +64 -22
- package/package.json +1 -1
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
|
|
10860
|
-
nextCommand: "reap run completion --phase
|
|
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 === "
|
|
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
|
|
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
|
|
12185
|
+
const installedVersion = "0.14.0";
|
|
12098
12186
|
const autoUpdate = configContent?.match(/autoUpdate:\s*(true|false)/)?.[1] === "true";
|
|
12099
|
-
const versionDisplay = formatVersionLine(
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
if (!dryRun)
|
|
14798
|
-
await
|
|
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/
|
|
15063
|
+
result.updated.push(`.claude/skills/ (${cmdInstalled} synced)`);
|
|
14803
15064
|
} else {
|
|
14804
|
-
result.skipped.push(`.claude/
|
|
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.
|
|
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
|
|
15005
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
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
|
|
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 =
|
|
15140
|
-
let helpText = await readTextFile(
|
|
15410
|
+
const helpDir = join27(ReapPaths.packageTemplatesDir, "help");
|
|
15411
|
+
let helpText = await readTextFile(join27(helpDir, `${lang}.txt`));
|
|
15141
15412
|
if (!helpText)
|
|
15142
|
-
helpText = await readTextFile(
|
|
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
|
|
@@ -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
|
-
|
|
11
|
-
Design
|
|
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
|
-
- **
|
|
15
|
-
- **Evolution** — The process by which
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
47
|
-
if (
|
|
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
|
-
|
|
69
|
+
|
|
70
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
71
|
+
fs.writeFileSync(skillFile, skillContent, 'utf-8');
|
|
58
72
|
installed++;
|
|
59
73
|
}
|
|
60
|
-
|
|
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
|
|
91
|
+
const newEntry = '.claude/skills/reap.*';
|
|
92
|
+
const legacyEntry = '.claude/commands/reap.*';
|
|
63
93
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
113
|
+
process.stderr.write(`[REAP] Warning: failed to install skills: ${err.message}\n`);
|
|
72
114
|
}
|
|
73
115
|
}
|
|
74
116
|
|