@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/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 legacyHooks() {
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.sh");
9181
+ const sessionStartPath = join2(ReapPaths.packageHooksDir, "session-start.cjs");
9182
9182
  return {
9183
9183
  matcher: "",
9184
- hooks: [{ type: "command", command: `bash "${sessionStartPath}"` }]
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 readdir3, unlink as unlink3, rm, mkdir as mkdir4 } from "fs/promises";
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 readdir3(commandsDir);
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 readdir3(claudeCmdDir);
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 readdir3(dirPath);
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 readdir5, mkdir as mkdir5, rename } from "fs/promises";
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 readdir4, rm as rm2 } from "fs/promises";
9711
+ import { readdir as readdir5, rm as rm2 } from "fs/promises";
9700
9712
  import { join as join6 } from "path";
9701
- var LINEAGE_MAX_LINES = 1e4;
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 readdir4(dirPath, { withFileTypes: true });
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 readdir4(paths.lineage, { withFileTypes: true });
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 dirs = entries.filter((e) => e.type === "dir").sort((a, b) => a.genNum - b.genNum);
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 readdir5(this.paths.life);
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 readdir5(this.paths.backlog);
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 readdir5(this.paths.mutations);
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 readdir5(this.paths.lineage);
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.2.1");
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. Read `.reap/config.yml` if `hooks.onRegression` is defined, execute each hook in order:
50
- - If hook has `command`: run the shell command
51
- - If hook has `prompt`: follow the prompt instructions (AI agent executes the described task)
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 (At C to D). hichoi@c-d.cc / https://reap.cc / https://github.com/c-d-cc/reap
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
- - Read `.reap/config.yml` if `hooks.onStageTransition` is defined, execute each hook in order:
25
- - If hook has `command`: run the shell command
26
- - If hook has `prompt`: follow the prompt instructions (AI agent executes the described task)
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
- - Read `.reap/config.yml` if `hooks.onGenerationComplete` is defined, execute each hook in order:
47
- - If hook has `command`: run the shell command
48
- - If hook has `prompt`: follow the prompt instructions (AI agent executes the described task)
49
- - Note: hooks run AFTER the commit, so any changes from hooks will be uncommitted
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. Read `.reap/config.yml` if `hooks.onGenerationStart` is defined, execute each hook in order:
40
- - If hook has `command`: run the shell command
41
- - If hook has `prompt`: follow the prompt instructions (AI agent executes the described task)
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,3 @@
1
+ #!/usr/bin/env bash
2
+ # Always true — hook always executes
3
+ exit 0
@@ -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 Birth phase.
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 Birth phase.
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 Birth phase.
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
- ## Layer Map
25
+ ## Source Map
26
26
 
27
- Describe the project's layer structure and dependency direction.
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 10,000 lines + 5 or more generations:
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
- Projects can define hooks in `.reap/config.yml` to run commands or prompts at lifecycle events:
104
-
105
- ```yaml
106
- hooks:
107
- onGenerationStart:
108
- - command: "echo 'Generation started'"
109
- onStageTransition:
110
- - command: "echo 'Stage changed'"
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
- Each hook entry supports two types:
119
- - `command` Run a shell command in the project root directory
120
- - `prompt` AI agent instruction. The agent reads and executes the described task.
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
- Only one of `command` or `prompt` should be set per entry.
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