@c-d-cc/reap 0.2.1 → 0.3.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 +36 -20
- package/README.ko.md +36 -20
- package/README.md +36 -20
- package/README.zh-CN.md +36 -20
- package/dist/cli.js +35 -21
- package/dist/templates/commands/reap.back.md +12 -4
- package/dist/templates/commands/reap.completion.md +33 -0
- package/dist/templates/commands/reap.help.md +1 -1
- package/dist/templates/commands/reap.next.md +23 -7
- package/dist/templates/commands/reap.start.md +12 -4
- package/dist/templates/conditions/always.sh +3 -0
- package/dist/templates/conditions/has-code-changes.sh +13 -0
- package/dist/templates/conditions/version-bumped.sh +17 -0
- package/dist/templates/genome/constraints.md +1 -1
- package/dist/templates/genome/conventions.md +1 -1
- package/dist/templates/genome/principles.md +3 -7
- package/dist/templates/genome/source-map.md +22 -0
- package/dist/templates/hooks/reap-guide.md +31 -19
- package/dist/templates/hooks/session-start.cjs +245 -0
- package/dist/templates/presets/bun-hono-react/source-map.md +22 -0
- package/package.json +1 -1
- package/dist/templates/hooks/session-start.sh +0 -200
package/dist/cli.js
CHANGED
|
@@ -8840,7 +8840,7 @@ var {
|
|
|
8840
8840
|
} = import__.default;
|
|
8841
8841
|
|
|
8842
8842
|
// src/cli/commands/init.ts
|
|
8843
|
-
import { mkdir as mkdir3 } from "fs/promises";
|
|
8843
|
+
import { mkdir as mkdir3, readdir as readdir3, chmod } from "fs/promises";
|
|
8844
8844
|
import { join as join4 } from "path";
|
|
8845
8845
|
|
|
8846
8846
|
// src/core/paths.ts
|
|
@@ -8943,7 +8943,7 @@ class ReapPaths {
|
|
|
8943
8943
|
get legacyTemplates() {
|
|
8944
8944
|
return join(this.root, "templates");
|
|
8945
8945
|
}
|
|
8946
|
-
get
|
|
8946
|
+
get hooks() {
|
|
8947
8947
|
return join(this.root, "hooks");
|
|
8948
8948
|
}
|
|
8949
8949
|
get legacyClaudeCommands() {
|
|
@@ -9178,10 +9178,10 @@ class ClaudeCodeAdapter {
|
|
|
9178
9178
|
return { action: "migrated" };
|
|
9179
9179
|
}
|
|
9180
9180
|
getHookEntry() {
|
|
9181
|
-
const sessionStartPath = join2(ReapPaths.packageHooksDir, "session-start.
|
|
9181
|
+
const sessionStartPath = join2(ReapPaths.packageHooksDir, "session-start.cjs");
|
|
9182
9182
|
return {
|
|
9183
9183
|
matcher: "",
|
|
9184
|
-
hooks: [{ type: "command", command: `
|
|
9184
|
+
hooks: [{ type: "command", command: `node "${sessionStartPath}"` }]
|
|
9185
9185
|
};
|
|
9186
9186
|
}
|
|
9187
9187
|
hasReapHook(sessionStartHooks) {
|
|
@@ -9450,7 +9450,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9450
9450
|
};
|
|
9451
9451
|
await ConfigManager.write(paths, config);
|
|
9452
9452
|
log("Setting up Genome templates...");
|
|
9453
|
-
const genomeTemplates = ["principles.md", "conventions.md", "constraints.md"];
|
|
9453
|
+
const genomeTemplates = ["principles.md", "conventions.md", "constraints.md", "source-map.md"];
|
|
9454
9454
|
const genomeSourceDir = preset ? join4(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
|
|
9455
9455
|
for (const file of genomeTemplates) {
|
|
9456
9456
|
const src = join4(genomeSourceDir, file);
|
|
@@ -9468,6 +9468,19 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9468
9468
|
const domainGuideSrc = join4(ReapPaths.packageGenomeDir, "domain/README.md");
|
|
9469
9469
|
const domainGuideDest = join4(ReapPaths.userReapTemplates, "domain-guide.md");
|
|
9470
9470
|
await writeTextFile(domainGuideDest, await readTextFileOrThrow(domainGuideSrc));
|
|
9471
|
+
log("Installing hook conditions...");
|
|
9472
|
+
const conditionsSourceDir = join4(ReapPaths.packageTemplatesDir, "conditions");
|
|
9473
|
+
const conditionsDestDir = join4(paths.hooks, "conditions");
|
|
9474
|
+
await mkdir3(conditionsDestDir, { recursive: true });
|
|
9475
|
+
const conditionFiles = await readdir3(conditionsSourceDir);
|
|
9476
|
+
for (const file of conditionFiles) {
|
|
9477
|
+
if (!file.endsWith(".sh"))
|
|
9478
|
+
continue;
|
|
9479
|
+
const src = join4(conditionsSourceDir, file);
|
|
9480
|
+
const dest = join4(conditionsDestDir, file);
|
|
9481
|
+
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9482
|
+
await chmod(dest, 493);
|
|
9483
|
+
}
|
|
9471
9484
|
log("Detecting AI agents...");
|
|
9472
9485
|
const detectedAgents = await AgentRegistry.detectInstalled();
|
|
9473
9486
|
const sourceDir = ReapPaths.packageCommandsDir;
|
|
@@ -9484,7 +9497,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9484
9497
|
}
|
|
9485
9498
|
|
|
9486
9499
|
// src/cli/commands/update.ts
|
|
9487
|
-
import { readdir as
|
|
9500
|
+
import { readdir as readdir4, unlink as unlink3, rm, mkdir as mkdir4 } from "fs/promises";
|
|
9488
9501
|
import { join as join5 } from "path";
|
|
9489
9502
|
|
|
9490
9503
|
// src/core/hooks.ts
|
|
@@ -9510,7 +9523,7 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
9510
9523
|
const config = await ConfigManager.read(paths);
|
|
9511
9524
|
const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
|
|
9512
9525
|
const commandsDir = ReapPaths.packageCommandsDir;
|
|
9513
|
-
const commandFiles = await
|
|
9526
|
+
const commandFiles = await readdir4(commandsDir);
|
|
9514
9527
|
for (const adapter of adapters) {
|
|
9515
9528
|
const agentCmdDir = adapter.getCommandsDir();
|
|
9516
9529
|
const label = `${adapter.displayName}`;
|
|
@@ -9579,10 +9592,9 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
9579
9592
|
async function migrateLegacyFiles(paths, dryRun, result) {
|
|
9580
9593
|
await removeDirIfExists(paths.legacyCommands, ".reap/commands/", dryRun, result);
|
|
9581
9594
|
await removeDirIfExists(paths.legacyTemplates, ".reap/templates/", dryRun, result);
|
|
9582
|
-
await removeDirIfExists(paths.legacyHooks, ".reap/hooks/", dryRun, result);
|
|
9583
9595
|
try {
|
|
9584
9596
|
const claudeCmdDir = paths.legacyClaudeCommands;
|
|
9585
|
-
const files = await
|
|
9597
|
+
const files = await readdir4(claudeCmdDir);
|
|
9586
9598
|
for (const file of files) {
|
|
9587
9599
|
if (file.startsWith("reap.") && file.endsWith(".md")) {
|
|
9588
9600
|
if (!dryRun)
|
|
@@ -9630,7 +9642,7 @@ async function migrateLegacyFiles(paths, dryRun, result) {
|
|
|
9630
9642
|
}
|
|
9631
9643
|
async function removeDirIfExists(dirPath, label, dryRun, result) {
|
|
9632
9644
|
try {
|
|
9633
|
-
const entries = await
|
|
9645
|
+
const entries = await readdir4(dirPath);
|
|
9634
9646
|
if (entries.length > 0 || true) {
|
|
9635
9647
|
if (!dryRun)
|
|
9636
9648
|
await rm(dirPath, { recursive: true });
|
|
@@ -9641,7 +9653,7 @@ async function removeDirIfExists(dirPath, label, dryRun, result) {
|
|
|
9641
9653
|
|
|
9642
9654
|
// src/core/generation.ts
|
|
9643
9655
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
9644
|
-
import { readdir as
|
|
9656
|
+
import { readdir as readdir6, mkdir as mkdir5, rename } from "fs/promises";
|
|
9645
9657
|
import { join as join7 } from "path";
|
|
9646
9658
|
|
|
9647
9659
|
// src/types/index.ts
|
|
@@ -9696,13 +9708,14 @@ class LifeCycle {
|
|
|
9696
9708
|
}
|
|
9697
9709
|
|
|
9698
9710
|
// src/core/compression.ts
|
|
9699
|
-
import { readdir as
|
|
9711
|
+
import { readdir as readdir5, rm as rm2 } from "fs/promises";
|
|
9700
9712
|
import { join as join6 } from "path";
|
|
9701
|
-
var LINEAGE_MAX_LINES =
|
|
9713
|
+
var LINEAGE_MAX_LINES = 5000;
|
|
9702
9714
|
var MIN_GENERATIONS_FOR_COMPRESSION = 5;
|
|
9703
9715
|
var LEVEL1_MAX_LINES = 40;
|
|
9704
9716
|
var LEVEL2_MAX_LINES = 60;
|
|
9705
9717
|
var LEVEL2_BATCH_SIZE = 5;
|
|
9718
|
+
var RECENT_PROTECTED_COUNT = 3;
|
|
9706
9719
|
async function countLines(filePath) {
|
|
9707
9720
|
const content = await readTextFile(filePath);
|
|
9708
9721
|
if (content === null)
|
|
@@ -9713,7 +9726,7 @@ async function countLines(filePath) {
|
|
|
9713
9726
|
async function countDirLines(dirPath) {
|
|
9714
9727
|
let total = 0;
|
|
9715
9728
|
try {
|
|
9716
|
-
const entries = await
|
|
9729
|
+
const entries = await readdir5(dirPath, { withFileTypes: true });
|
|
9717
9730
|
for (const entry of entries) {
|
|
9718
9731
|
const fullPath = join6(dirPath, entry.name);
|
|
9719
9732
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -9728,7 +9741,7 @@ async function countDirLines(dirPath) {
|
|
|
9728
9741
|
async function scanLineage(paths) {
|
|
9729
9742
|
const entries = [];
|
|
9730
9743
|
try {
|
|
9731
|
-
const items = await
|
|
9744
|
+
const items = await readdir5(paths.lineage, { withFileTypes: true });
|
|
9732
9745
|
for (const item of items) {
|
|
9733
9746
|
const fullPath = join6(paths.lineage, item.name);
|
|
9734
9747
|
if (item.isDirectory() && item.name.startsWith("gen-")) {
|
|
@@ -9911,7 +9924,8 @@ async function compressLineageIfNeeded(paths) {
|
|
|
9911
9924
|
if (totalLines <= LINEAGE_MAX_LINES) {
|
|
9912
9925
|
return result;
|
|
9913
9926
|
}
|
|
9914
|
-
const
|
|
9927
|
+
const allDirs = entries.filter((e) => e.type === "dir").sort((a, b) => a.genNum - b.genNum);
|
|
9928
|
+
const dirs = allDirs.slice(0, Math.max(0, allDirs.length - RECENT_PROTECTED_COUNT));
|
|
9915
9929
|
for (const dir of dirs) {
|
|
9916
9930
|
const currentTotal = await countDirLines(paths.lineage);
|
|
9917
9931
|
if (currentTotal <= LINEAGE_MAX_LINES)
|
|
@@ -9998,7 +10012,7 @@ class GenerationManager {
|
|
|
9998
10012
|
const genDirName = `${state.id}-${goalSlug}`;
|
|
9999
10013
|
const genDir = this.paths.generationDir(genDirName);
|
|
10000
10014
|
await mkdir5(genDir, { recursive: true });
|
|
10001
|
-
const lifeEntries = await
|
|
10015
|
+
const lifeEntries = await readdir6(this.paths.life);
|
|
10002
10016
|
for (const entry of lifeEntries) {
|
|
10003
10017
|
if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
|
|
10004
10018
|
await rename(join7(this.paths.life, entry), join7(genDir, entry));
|
|
@@ -10007,13 +10021,13 @@ class GenerationManager {
|
|
|
10007
10021
|
const backlogDir = join7(genDir, "backlog");
|
|
10008
10022
|
await mkdir5(backlogDir, { recursive: true });
|
|
10009
10023
|
try {
|
|
10010
|
-
const backlogEntries = await
|
|
10024
|
+
const backlogEntries = await readdir6(this.paths.backlog);
|
|
10011
10025
|
for (const entry of backlogEntries) {
|
|
10012
10026
|
await rename(join7(this.paths.backlog, entry), join7(backlogDir, entry));
|
|
10013
10027
|
}
|
|
10014
10028
|
} catch {}
|
|
10015
10029
|
try {
|
|
10016
|
-
const mutEntries = await
|
|
10030
|
+
const mutEntries = await readdir6(this.paths.mutations);
|
|
10017
10031
|
if (mutEntries.length > 0) {
|
|
10018
10032
|
const mutDir = join7(genDir, "mutations");
|
|
10019
10033
|
await mkdir5(mutDir, { recursive: true });
|
|
@@ -10031,7 +10045,7 @@ class GenerationManager {
|
|
|
10031
10045
|
}
|
|
10032
10046
|
async listCompleted() {
|
|
10033
10047
|
try {
|
|
10034
|
-
const entries = await
|
|
10048
|
+
const entries = await readdir6(this.paths.lineage);
|
|
10035
10049
|
return entries.filter((e) => e.startsWith("gen-")).sort();
|
|
10036
10050
|
} catch {
|
|
10037
10051
|
return [];
|
|
@@ -10133,7 +10147,7 @@ async function fixProject(projectRoot) {
|
|
|
10133
10147
|
|
|
10134
10148
|
// src/cli/index.ts
|
|
10135
10149
|
import { join as join8 } from "path";
|
|
10136
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
10150
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.3.0");
|
|
10137
10151
|
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) => {
|
|
10138
10152
|
try {
|
|
10139
10153
|
const cwd = process.cwd();
|
|
@@ -45,10 +45,18 @@ description: "REAP Back — Return to a previous lifecycle stage"
|
|
|
45
45
|
- **Affected**: [affected subsequent artifacts]
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
### Hook Execution
|
|
49
|
-
7.
|
|
50
|
-
-
|
|
51
|
-
|
|
48
|
+
### Hook Execution (Regression)
|
|
49
|
+
7. Scan `.reap/hooks/` for files matching `onRegression.*`
|
|
50
|
+
- For each matched file (sorted by `order` from frontmatter, then alphabetically):
|
|
51
|
+
1. Read the frontmatter (`condition`, `order`)
|
|
52
|
+
2. Evaluate `condition` by running `.reap/hooks/conditions/{condition}.sh` (exit 0 = met, non-zero = skip):
|
|
53
|
+
- If `condition` is absent: treat as `always`
|
|
54
|
+
- If the condition script doesn't exist: warn and skip the hook
|
|
55
|
+
- Default conditions: `always`, `has-code-changes`, `version-bumped`
|
|
56
|
+
- Users can add custom conditions by placing scripts in `.reap/hooks/conditions/`
|
|
57
|
+
3. Execute based on file extension:
|
|
58
|
+
- `.md`: read the file content (after frontmatter) as AI prompt and follow the instructions
|
|
59
|
+
- `.sh`: run as shell script in the project root directory
|
|
52
60
|
|
|
53
61
|
## Completion
|
|
54
62
|
- "Returned to [stage] stage. Proceed with `/reap.[stage]`."
|
|
@@ -71,6 +71,39 @@ Do NOT finalize Genome changes without running Validation Commands.
|
|
|
71
71
|
- **If called standalone**: Show the modified genome/environment content to the human and get approval. Do NOT finalize changes until the human approves.
|
|
72
72
|
16. For each applied `type: genome-change` and `type: environment-change` backlog item, update its frontmatter to `status: consumed` and add `consumedBy: gen-XXX`
|
|
73
73
|
|
|
74
|
+
### Phase 5: Hook Suggestion
|
|
75
|
+
|
|
76
|
+
17. Read the last 3 completed generations from `.reap/lineage/` (sorted by gen number, most recent first)
|
|
77
|
+
- For each: read `03-implementation.md` (Implementation Notes) and `05-completion.md` (Retrospective)
|
|
78
|
+
- Identify **manual tasks that were repeated across 2+ generations**
|
|
79
|
+
- Examples: docs update, lint fix, dependency sync, test data setup, specific file regeneration
|
|
80
|
+
18. If a repeated pattern is found, engage the human with a step-by-step confirmation:
|
|
81
|
+
a. **Describe the pattern**: "최근 N개 generation에서 '[작업 설명]'이 반복적으로 수행되었습니다."
|
|
82
|
+
b. **Ask if it should be a hook**: "이 작업을 hook으로 자동화할까요? (yes/no)"
|
|
83
|
+
c. If yes, ask **event**: "어떤 이벤트에서 실행할까요?"
|
|
84
|
+
- `onGenerationComplete` — generation 완료 후
|
|
85
|
+
- `onStageTransition` — stage 전환 시
|
|
86
|
+
- `onGenerationStart` — generation 시작 시
|
|
87
|
+
- `onRegression` — stage 회귀 시
|
|
88
|
+
d. Ask **condition**: "실행 조건은 무엇인가요?"
|
|
89
|
+
- `always` — 항상
|
|
90
|
+
- `has-code-changes` — src/ 변경이 있을 때
|
|
91
|
+
- `version-bumped` — version bump가 있을 때
|
|
92
|
+
- Custom — 유저가 직접 기술
|
|
93
|
+
e. Ask **hook name**: "hook 이름을 지어주세요 (예: lint-fix, docs-sync)"
|
|
94
|
+
f. **Preview**: 생성될 hook 파일 내용을 보여주고 확인:
|
|
95
|
+
```
|
|
96
|
+
파일: .reap/hooks/{event}.{name}.md
|
|
97
|
+
---
|
|
98
|
+
condition: {condition}
|
|
99
|
+
order: 50
|
|
100
|
+
---
|
|
101
|
+
{작업 내용}
|
|
102
|
+
```
|
|
103
|
+
g. 유저 확인 후 `.reap/hooks/{event}.{name}.md` 생성
|
|
104
|
+
19. 반복 패턴이 없으면 skip — "반복 패턴이 감지되지 않았습니다."
|
|
105
|
+
20. **Limit**: 한 번에 최대 2개까지만 제안 (과부하 방지)
|
|
106
|
+
|
|
74
107
|
## Self-Verification
|
|
75
108
|
Before saving the artifact, verify:
|
|
76
109
|
- [ ] Are lessons concrete and applicable to the next generation? (No vague "do better next time")
|
|
@@ -82,5 +82,5 @@ For command-name topics: read `reap.{name}.md` from the same directory as this f
|
|
|
82
82
|
- **regression** — `/reap.back`: 이전 stage 회귀. timeline + artifact에 Regression 섹션 기록.
|
|
83
83
|
- **minor-fix** — 5분 이내, 설계 변경 없는 수정. stage 전환 없이 현재 artifact에 기록.
|
|
84
84
|
- **compression** — 10,000줄 + 5세대 이상 시 lineage 자동 압축. L1(40줄), L2(60줄).
|
|
85
|
-
- **author** — HyeonIL Choi
|
|
85
|
+
- **author** — HyeonIL Choi. Email: hichoi@c-d.cc / Homepage: https://c-d.cc / LinkedIn: https://www.linkedin.com/in/hichoi-dev / GitHub: https://github.com/casamia918
|
|
86
86
|
- **start** / **objective** / **planning** / **implementation** / **validation** / **completion** / **next** / **back** / **sync** / **status** / **update** / **help** — Read `reap.{name}.md` from same directory, explain.
|
|
@@ -21,9 +21,17 @@ description: "REAP Next — Advance to the next lifecycle stage"
|
|
|
21
21
|
- Immediately create the next stage's artifact file from template (empty, ready to fill)
|
|
22
22
|
|
|
23
23
|
### Hook Execution (Stage Transition)
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
- Scan `.reap/hooks/` for files matching `onStageTransition.*`
|
|
25
|
+
- For each matched file (sorted by `order` from frontmatter, then alphabetically):
|
|
26
|
+
1. Read the frontmatter (`condition`, `order`)
|
|
27
|
+
2. Evaluate `condition` by running `.reap/hooks/conditions/{condition}.sh` (exit 0 = met, non-zero = skip):
|
|
28
|
+
- If `condition` is absent: treat as `always`
|
|
29
|
+
- If the condition script doesn't exist: warn and skip the hook
|
|
30
|
+
- Default conditions: `always`, `has-code-changes`, `version-bumped`
|
|
31
|
+
- Users can add custom conditions by placing scripts in `.reap/hooks/conditions/`
|
|
32
|
+
3. Execute based on file extension:
|
|
33
|
+
- `.md`: read the file content (after frontmatter) as AI prompt and follow the instructions
|
|
34
|
+
- `.sh`: run as shell script in the project root directory
|
|
27
35
|
|
|
28
36
|
### When Advancing from Completion (Archiving)
|
|
29
37
|
- Add the current timestamp to `completedAt` in `current.yml`
|
|
@@ -43,10 +51,18 @@ description: "REAP Next — Advance to the next lifecycle stage"
|
|
|
43
51
|
- If there are no code changes (REAP-only generation), use `chore(reap): [goal summary]`
|
|
44
52
|
|
|
45
53
|
### Hook Execution (Generation Complete)
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
- Scan `.reap/hooks/` for files matching `onGenerationComplete.*`
|
|
55
|
+
- For each matched file (sorted by `order` from frontmatter, then alphabetically):
|
|
56
|
+
1. Read the frontmatter (`condition`, `order`)
|
|
57
|
+
2. Evaluate `condition` by running `.reap/hooks/conditions/{condition}.sh` (exit 0 = met, non-zero = skip):
|
|
58
|
+
- If `condition` is absent: treat as `always`
|
|
59
|
+
- If the condition script doesn't exist: warn and skip the hook
|
|
60
|
+
- Default conditions: `always`, `has-code-changes`, `version-bumped`
|
|
61
|
+
- Users can add custom conditions by placing scripts in `.reap/hooks/conditions/`
|
|
62
|
+
3. Execute based on file extension:
|
|
63
|
+
- `.md`: read the file content (after frontmatter) as AI prompt and follow the instructions
|
|
64
|
+
- `.sh`: run as shell script in the project root directory
|
|
65
|
+
- Note: hooks run AFTER the commit, so any changes from hooks will be uncommitted
|
|
50
66
|
|
|
51
67
|
## Completion
|
|
52
68
|
- If archived: "Generation [id] complete and archived. Run `/reap.start` to begin a new generation."
|
|
@@ -35,10 +35,18 @@ description: "REAP Start — Start a new Generation"
|
|
|
35
35
|
```
|
|
36
36
|
5. Immediately create `.reap/life/01-objective.md` from the artifact template with the Goal section filled in
|
|
37
37
|
|
|
38
|
-
### Hook Execution
|
|
39
|
-
6.
|
|
40
|
-
-
|
|
41
|
-
|
|
38
|
+
### Hook Execution (Generation Start)
|
|
39
|
+
6. Scan `.reap/hooks/` for files matching `onGenerationStart.*`
|
|
40
|
+
- For each matched file (sorted by `order` from frontmatter, then alphabetically):
|
|
41
|
+
1. Read the frontmatter (`condition`, `order`)
|
|
42
|
+
2. Evaluate `condition` by running `.reap/hooks/conditions/{condition}.sh` (exit 0 = met, non-zero = skip):
|
|
43
|
+
- If `condition` is absent: treat as `always`
|
|
44
|
+
- If the condition script doesn't exist: warn and skip the hook
|
|
45
|
+
- Default conditions: `always`, `has-code-changes`, `version-bumped`
|
|
46
|
+
- Users can add custom conditions by placing scripts in `.reap/hooks/conditions/`
|
|
47
|
+
3. Execute based on file extension:
|
|
48
|
+
- `.md`: read the file content (after frontmatter) as AI prompt and follow the instructions
|
|
49
|
+
- `.sh`: run as shell script in the project root directory
|
|
42
50
|
|
|
43
51
|
## Completion
|
|
44
52
|
- "Generation gen-XXX started. Proceed with `/reap.objective` to define the goal, or `/reap.evolve` to run the full lifecycle."
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# True if src/ files were changed in the most recent commit
|
|
3
|
+
# Exit 0 = condition met, Exit 1 = condition not met
|
|
4
|
+
last_commit=$(git log -1 --format="%H" 2>/dev/null)
|
|
5
|
+
if [ -z "$last_commit" ]; then
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
changes=$(git diff-tree --no-commit-id --name-only -r "$last_commit" -- src/ 2>/dev/null)
|
|
9
|
+
if [ -n "$changes" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
else
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# True if package.json version differs from the last git tag
|
|
3
|
+
# Exit 0 = version was bumped, Exit 1 = same version
|
|
4
|
+
last_tag=$(git describe --tags --abbrev=0 2>/dev/null)
|
|
5
|
+
if [ -z "$last_tag" ]; then
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
tag_version="${last_tag#v}"
|
|
9
|
+
pkg_version=$(node -e "console.log(require('./package.json').version)" 2>/dev/null)
|
|
10
|
+
if [ -z "$pkg_version" ]; then
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
if [ "$tag_version" != "$pkg_version" ]; then
|
|
14
|
+
exit 0
|
|
15
|
+
else
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **Writing principle**: This file should be a **map** of ~100 lines or fewer.
|
|
4
4
|
> Record not only the "what" but also the "why" of each technical choice.
|
|
5
5
|
> Agents refer to this file when making technical decisions.
|
|
6
|
-
> Modified only during the
|
|
6
|
+
> Modified only during the Completion stage.
|
|
7
7
|
|
|
8
8
|
## Tech Stack
|
|
9
9
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
> Write rules to be mechanically verifiable whenever possible.
|
|
5
5
|
> Instead of "write good code", describe it as "functions must not exceed 50 lines".
|
|
6
6
|
> Prioritize rules that can be enforced via linters/tests over documentation-only rules.
|
|
7
|
-
> Modified only during the
|
|
7
|
+
> Modified only during the Completion stage.
|
|
8
8
|
|
|
9
9
|
## Code Style
|
|
10
10
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **Writing principle**: This file should be a **map** of ~100 lines or fewer.
|
|
4
4
|
> Write at a level where agents can act immediately.
|
|
5
5
|
> Separate detailed content into files under `domain/`.
|
|
6
|
-
> Modified only during the
|
|
6
|
+
> Modified only during the Completion stage.
|
|
7
7
|
|
|
8
8
|
## Core Beliefs
|
|
9
9
|
|
|
@@ -22,10 +22,6 @@ Agents should be able to reason about "why it is this way".
|
|
|
22
22
|
|----|----------|-----------|------|
|
|
23
23
|
| | | | |
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## Source Map
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
Specify which layers can depend on which layers.
|
|
29
|
-
Write detailed layer rules in files under `domain/`.
|
|
30
|
-
|
|
31
|
-
- (Describe layer structure here)
|
|
27
|
+
→ See `genome/source-map.md` for C4 Container + Component level Mermaid diagrams.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Source Map
|
|
2
|
+
|
|
3
|
+
> C4 Container + Component level project structure map.
|
|
4
|
+
> Agents reference this before exploring code to quickly identify entry points.
|
|
5
|
+
> Line limit: ~150 lines (adaptive to codebase size).
|
|
6
|
+
> Modified only during the Completion stage.
|
|
7
|
+
|
|
8
|
+
## C4 Context
|
|
9
|
+
|
|
10
|
+
(Describe the system context: users, external systems, and relationships)
|
|
11
|
+
|
|
12
|
+
## C4 Container
|
|
13
|
+
|
|
14
|
+
(Describe containers: applications, data stores, and their interactions)
|
|
15
|
+
|
|
16
|
+
## Core Components
|
|
17
|
+
|
|
18
|
+
(Describe key components within each container)
|
|
19
|
+
|
|
20
|
+
## Data Flow
|
|
21
|
+
|
|
22
|
+
(Describe how data flows through the system)
|
|
@@ -92,7 +92,7 @@ Regression reason is recorded as a `## Regression` section in the target stage's
|
|
|
92
92
|
Trivial issues (typos, lint errors, etc.) are fixed directly in the current stage without a stage transition and recorded in the artifact. Judgment criterion: resolvable within 5 minutes without design changes.
|
|
93
93
|
|
|
94
94
|
### Lineage Compression
|
|
95
|
-
As generations accumulate, lineage grows. Auto-compression triggers when total exceeds
|
|
95
|
+
As generations accumulate, lineage grows. Auto-compression triggers when total exceeds 5,000 lines + 5 or more generations (most recent 3 generations are protected):
|
|
96
96
|
- **Level 1**: Generation folder → single .md (40 lines). Only goal + result + notable items preserved.
|
|
97
97
|
- **Level 2**: 5 Level 1 entries → epoch .md (60 lines). Only key flow preserved.
|
|
98
98
|
|
|
@@ -100,26 +100,34 @@ Compression runs automatically during `/reap.next` (archiving after completion).
|
|
|
100
100
|
|
|
101
101
|
## REAP Hooks
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
hooks
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
onGenerationComplete:
|
|
112
|
-
- command: "reap update"
|
|
113
|
-
- prompt: "Check if README needs updating based on this generation's changes."
|
|
114
|
-
onRegression:
|
|
115
|
-
- command: "echo 'Regressed'"
|
|
103
|
+
Hooks are defined as individual files in `.reap/hooks/` with the naming convention `{event}.{name}.{md|sh}`:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
.reap/hooks/
|
|
107
|
+
├── onGenerationComplete.version-bump.md
|
|
108
|
+
├── onGenerationComplete.reap-update.sh
|
|
109
|
+
├── onGenerationComplete.docs-update.md
|
|
110
|
+
└── onGenerationComplete.release-notes.md
|
|
116
111
|
```
|
|
117
112
|
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
- `
|
|
113
|
+
File naming: `{event}.{name}.{extension}`
|
|
114
|
+
- Event: onGenerationStart, onStageTransition, onGenerationComplete, onRegression
|
|
115
|
+
- Extension: `.md` (AI prompt) or `.sh` (shell script)
|
|
116
|
+
|
|
117
|
+
Frontmatter (`.md` files) or comment headers (`.sh` files):
|
|
118
|
+
- `condition`: name of a script in `.reap/hooks/conditions/` (default: `always`)
|
|
119
|
+
- `order`: execution order within the same event (default: 50, lower runs first)
|
|
120
|
+
|
|
121
|
+
### Conditions
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
Conditions are executable scripts in `.reap/hooks/conditions/`. Exit code 0 = condition met, non-zero = skip.
|
|
124
|
+
|
|
125
|
+
Default conditions (installed by `reap init`):
|
|
126
|
+
- `always.sh` — always true
|
|
127
|
+
- `has-code-changes.sh` — true if src/ files were changed in the last commit
|
|
128
|
+
- `version-bumped.sh` — true if package.json version ≠ last git tag
|
|
129
|
+
|
|
130
|
+
Custom conditions: add any `.sh` script to `.reap/hooks/conditions/`. The hook's `condition` field matches the filename (without `.sh`).
|
|
123
131
|
|
|
124
132
|
| Event | Trigger |
|
|
125
133
|
|-------|---------|
|
|
@@ -128,7 +136,11 @@ Only one of `command` or `prompt` should be set per entry.
|
|
|
128
136
|
| `onGenerationComplete` | After `/reap.next` archives a completed generation (after commit) |
|
|
129
137
|
| `onRegression` | After `/reap.back` returns to a previous stage |
|
|
130
138
|
|
|
131
|
-
Hooks are executed by the AI agent.
|
|
139
|
+
Hooks are executed by the AI agent by scanning `.reap/hooks/` for files matching the current event.
|
|
140
|
+
|
|
141
|
+
## Multi-Agent Support
|
|
142
|
+
|
|
143
|
+
REAP supports multiple AI agents simultaneously through the AgentAdapter abstraction. Currently supported: **Claude Code** and **OpenCode**. Detected agents are listed in `.reap/config.yml` under the `agents` field (managed by `reap init` / `reap update`). Slash commands and session hooks are installed to each detected agent's configuration directory.
|
|
132
144
|
|
|
133
145
|
## Role Separation
|
|
134
146
|
|