@c-d-cc/reap 0.2.2 → 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 +31 -19
- package/README.ko.md +31 -19
- package/README.md +31 -19
- package/README.zh-CN.md +31 -19
- 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.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 +27 -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
|
@@ -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")
|
|
@@ -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
|
|
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
|
|
121
129
|
|
|
122
|
-
|
|
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,7 @@ 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.
|
|
132
140
|
|
|
133
141
|
## Multi-Agent Support
|
|
134
142
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// REAP SessionStart hook — injects REAP guide + Genome + current generation context
|
|
3
|
+
// Single Node.js process (replaces session-start.sh for better performance)
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const startTime = Date.now();
|
|
9
|
+
let step = 0;
|
|
10
|
+
const totalSteps = 6;
|
|
11
|
+
|
|
12
|
+
function log(msg) {
|
|
13
|
+
step++;
|
|
14
|
+
process.stderr.write(`[REAP ${step}/${totalSteps} +${Date.now() - startTime}ms] ${msg}\n`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readFile(p) {
|
|
18
|
+
try { return fs.readFileSync(p, 'utf-8'); } catch { return null; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fileExists(p) {
|
|
22
|
+
try { return fs.statSync(p).isFile(); } catch { return false; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function dirExists(p) {
|
|
26
|
+
try { return fs.statSync(p).isDirectory(); } catch { return false; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function exec(cmd) {
|
|
30
|
+
try { return execSync(cmd, { encoding: 'utf-8', timeout: 10000 }).trim(); } catch { return ''; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Paths
|
|
34
|
+
const scriptDir = __dirname;
|
|
35
|
+
const projectRoot = process.cwd();
|
|
36
|
+
const reapDir = path.join(projectRoot, '.reap');
|
|
37
|
+
const genomeDir = path.join(reapDir, 'genome');
|
|
38
|
+
const configFile = path.join(reapDir, 'config.yml');
|
|
39
|
+
const currentYml = path.join(reapDir, 'life', 'current.yml');
|
|
40
|
+
const guideFile = path.join(scriptDir, 'reap-guide.md');
|
|
41
|
+
|
|
42
|
+
// Check REAP project
|
|
43
|
+
if (!dirExists(reapDir)) {
|
|
44
|
+
process.stderr.write('[REAP] Not a REAP project, skipping\n');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Step 1: Auto-update
|
|
49
|
+
log('Checking for updates...');
|
|
50
|
+
let autoUpdateMessage = '';
|
|
51
|
+
const configContent = readFile(configFile);
|
|
52
|
+
if (configContent) {
|
|
53
|
+
const autoUpdate = /^autoUpdate:\s*true/m.test(configContent);
|
|
54
|
+
if (autoUpdate) {
|
|
55
|
+
const installed = exec('reap --version');
|
|
56
|
+
const latest = exec('npm view @c-d-cc/reap version');
|
|
57
|
+
if (installed && latest && installed !== latest) {
|
|
58
|
+
const updated = exec('npm update -g @c-d-cc/reap');
|
|
59
|
+
if (updated !== null) {
|
|
60
|
+
exec('reap update');
|
|
61
|
+
autoUpdateMessage = `REAP auto-updated: v${installed} → v${latest}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Step 2: Load REAP guide
|
|
68
|
+
log('Loading REAP guide...');
|
|
69
|
+
const reapGuide = readFile(guideFile) || '';
|
|
70
|
+
|
|
71
|
+
// Step 3: Load Genome (tiered)
|
|
72
|
+
log('Loading Genome...');
|
|
73
|
+
const L1_LIMIT = 500;
|
|
74
|
+
const L2_LIMIT = 200;
|
|
75
|
+
let genomeContent = '';
|
|
76
|
+
let l1Lines = 0;
|
|
77
|
+
|
|
78
|
+
const l1Files = ['principles.md', 'conventions.md', 'constraints.md', 'source-map.md'];
|
|
79
|
+
for (const file of l1Files) {
|
|
80
|
+
const content = readFile(path.join(genomeDir, file));
|
|
81
|
+
if (!content) continue;
|
|
82
|
+
const lines = content.split('\n').length;
|
|
83
|
+
l1Lines += lines;
|
|
84
|
+
if (l1Lines <= L1_LIMIT) {
|
|
85
|
+
genomeContent += `\n### ${file}\n${content}\n`;
|
|
86
|
+
} else {
|
|
87
|
+
genomeContent += `\n### ${file} [TRUNCATED — L1 budget exceeded, read full file directly]\n${content.split('\n').slice(0, 20).join('\n')}\n...\n`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// L2: domain/ files
|
|
92
|
+
const domainDir = path.join(genomeDir, 'domain');
|
|
93
|
+
if (dirExists(domainDir)) {
|
|
94
|
+
let l2Lines = 0;
|
|
95
|
+
let l2Overflow = false;
|
|
96
|
+
const domainFiles = fs.readdirSync(domainDir).filter(f => f.endsWith('.md')).sort();
|
|
97
|
+
for (const file of domainFiles) {
|
|
98
|
+
const content = readFile(path.join(domainDir, file));
|
|
99
|
+
if (!content) continue;
|
|
100
|
+
const lines = content.split('\n').length;
|
|
101
|
+
l2Lines += lines;
|
|
102
|
+
if (!l2Overflow && l2Lines <= L2_LIMIT) {
|
|
103
|
+
genomeContent += `\n### domain/${file}\n${content}\n`;
|
|
104
|
+
} else {
|
|
105
|
+
l2Overflow = true;
|
|
106
|
+
const firstLine = content.split('\n').find(l => l.startsWith('>')) || content.split('\n')[0];
|
|
107
|
+
genomeContent += `\n### domain/${file} [summary — read full file for details]\n${firstLine}\n`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Step 4: Check Genome & source-map sync
|
|
113
|
+
log('Checking sync...');
|
|
114
|
+
let genomeStaleWarning = '';
|
|
115
|
+
let commitsSince = 0;
|
|
116
|
+
if (dirExists(path.join(projectRoot, '.git'))) {
|
|
117
|
+
const lastGenomeCommit = exec(`git -C "${projectRoot}" log -1 --format="%H" -- ".reap/genome/"`);
|
|
118
|
+
if (lastGenomeCommit) {
|
|
119
|
+
commitsSince = parseInt(exec(`git -C "${projectRoot}" rev-list --count "${lastGenomeCommit}..HEAD" -- src/ tests/ package.json tsconfig.json scripts/`) || '0', 10);
|
|
120
|
+
if (commitsSince > 10) {
|
|
121
|
+
genomeStaleWarning = `WARNING: Genome may be stale — ${commitsSince} commits since last Genome update. Consider running /reap.sync to synchronize.`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let sourcemapDriftWarning = '';
|
|
127
|
+
let documented = 0, actual = 0;
|
|
128
|
+
const sourcemapFile = path.join(genomeDir, 'source-map.md');
|
|
129
|
+
const srcCoreDir = path.join(projectRoot, 'src', 'core');
|
|
130
|
+
if (fileExists(sourcemapFile) && dirExists(srcCoreDir)) {
|
|
131
|
+
const smContent = readFile(sourcemapFile) || '';
|
|
132
|
+
documented = (smContent.match(/Component\(/g) || []).length;
|
|
133
|
+
const coreEntries = fs.readdirSync(srcCoreDir);
|
|
134
|
+
actual = coreEntries.filter(e => e.endsWith('.ts')).length
|
|
135
|
+
+ coreEntries.filter(e => { try { return fs.statSync(path.join(srcCoreDir, e)).isDirectory(); } catch { return false; } }).length;
|
|
136
|
+
if (documented > 0 && actual > 0 && documented !== actual) {
|
|
137
|
+
sourcemapDriftWarning = `WARNING: source-map.md drift — ${documented} components documented, ${actual} core files found. Consider running /reap.sync.`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Step 5: Read generation state
|
|
142
|
+
log('Reading generation state...');
|
|
143
|
+
const strictMode = configContent ? /^strict:\s*true/m.test(configContent) : false;
|
|
144
|
+
|
|
145
|
+
let genStage = 'none', genId = '', genGoal = '', generationContext = '';
|
|
146
|
+
const currentContent = readFile(currentYml);
|
|
147
|
+
if (currentContent && currentContent.trim()) {
|
|
148
|
+
genId = (currentContent.match(/^id:\s*(.+)/m) || [])[1] || '';
|
|
149
|
+
genGoal = (currentContent.match(/^goal:\s*(.+)/m) || [])[1] || '';
|
|
150
|
+
genStage = (currentContent.match(/^stage:\s*(.+)/m) || [])[1] || 'none';
|
|
151
|
+
if (genId && genStage !== 'none') {
|
|
152
|
+
generationContext = `Active Generation: ${genId} | Goal: ${genGoal} | Stage: ${genStage}`;
|
|
153
|
+
} else {
|
|
154
|
+
genStage = 'none';
|
|
155
|
+
generationContext = 'No active Generation. Run `/reap.start` to start one.';
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
generationContext = 'No active Generation. Run `/reap.start` to start one.';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const stageCommands = { objective: '/reap.objective', planning: '/reap.planning', implementation: '/reap.implementation', validation: '/reap.validation', completion: '/reap.completion' };
|
|
162
|
+
const nextCmd = stageCommands[genStage] || '/reap.start';
|
|
163
|
+
|
|
164
|
+
// Build strict mode section
|
|
165
|
+
let strictSection = '';
|
|
166
|
+
if (strictMode) {
|
|
167
|
+
if (genStage === 'implementation') {
|
|
168
|
+
strictSection = '\n\n## Strict Mode (ACTIVE — SCOPED MODIFICATION ALLOWED)\n<HARD-GATE>\nStrict mode is enabled. Code modification is ALLOWED only within the scope of the current Generation\'s plan.\n- You MUST read `.reap/life/02-planning.md` before writing any code.\n- You may ONLY modify files and modules listed in the plan\'s task list.\n- Changes outside the plan\'s scope are BLOCKED. If you discover out-of-scope work is needed, add it to the backlog instead of implementing it.\n- If the user explicitly requests to bypass strict mode (e.g., "override", "bypass strict"), you may proceed — but inform them that strict mode is being bypassed.\n</HARD-GATE>';
|
|
169
|
+
} else if (genStage === 'none') {
|
|
170
|
+
strictSection = '\n\n## Strict Mode (ACTIVE — CODE MODIFICATION BLOCKED)\n<HARD-GATE>\nStrict mode is enabled and there is NO active Generation.\nYou MUST NOT write, edit, or create any source code files.\nAllowed actions: reading files, analyzing code, answering questions, running commands.\nTo start coding, the user must first run `/reap.start` and advance to the implementation stage.\nIf the user explicitly requests to bypass strict mode (e.g., "override", "bypass strict", "just do it"), you may proceed — but inform them that strict mode is being bypassed.\n</HARD-GATE>';
|
|
171
|
+
} else {
|
|
172
|
+
strictSection = `\n\n## Strict Mode (ACTIVE — CODE MODIFICATION BLOCKED)\n<HARD-GATE>\nStrict mode is enabled. Current stage is '${genStage}', which is NOT the implementation stage.\nYou MUST NOT write, edit, or create any source code files.\nAllowed actions: reading files, analyzing code, answering questions, running commands, writing REAP artifacts.\nAdvance to the implementation stage via the REAP lifecycle to unlock code modification.\nIf the user explicitly requests to bypass strict mode (e.g., "override", "bypass strict", "just do it"), you may proceed — but inform them that strict mode is being bypassed.\n</HARD-GATE>`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Build staleness section
|
|
177
|
+
let staleSection = '';
|
|
178
|
+
if (genomeStaleWarning) {
|
|
179
|
+
staleSection = `\n\n## Genome Staleness\n${genomeStaleWarning}\nIf the user wants to proceed without syncing, ask: "The Genome may be stale. Would you like to run /reap.sync now, or do it later?" and respect their choice.`;
|
|
180
|
+
}
|
|
181
|
+
if (sourcemapDriftWarning) {
|
|
182
|
+
staleSection += `\n${sourcemapDriftWarning}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Build auto-update section
|
|
186
|
+
let updateSection = '';
|
|
187
|
+
if (autoUpdateMessage) {
|
|
188
|
+
updateSection = `\n\n## Auto-Update\n${autoUpdateMessage}. Tell the user: "${autoUpdateMessage}"`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Write session init log
|
|
192
|
+
const initLines = [];
|
|
193
|
+
if (autoUpdateMessage) initLines.push(`🟢 ${autoUpdateMessage}`);
|
|
194
|
+
|
|
195
|
+
// Genome status (single line)
|
|
196
|
+
const issues = [];
|
|
197
|
+
let severity = 'ok';
|
|
198
|
+
if (l1Lines === 0) { issues.push('empty'); severity = 'danger'; }
|
|
199
|
+
for (const f of [...l1Files, 'domain/']) {
|
|
200
|
+
const check = f.endsWith('/') ? dirExists(path.join(genomeDir, f.slice(0, -1))) : fileExists(path.join(genomeDir, f));
|
|
201
|
+
if (!check) { issues.push(`missing ${f}`); severity = 'danger'; }
|
|
202
|
+
}
|
|
203
|
+
if (!fileExists(configFile)) { issues.push('no config.yml'); severity = 'danger'; }
|
|
204
|
+
if (sourcemapDriftWarning) {
|
|
205
|
+
const diff = Math.abs(documented - actual);
|
|
206
|
+
issues.push(`source-map drift (${documented}→${actual})`);
|
|
207
|
+
if (diff > 3) { if (severity === 'ok') severity = 'danger'; }
|
|
208
|
+
else { if (severity === 'ok') severity = 'warn'; }
|
|
209
|
+
}
|
|
210
|
+
if (genomeStaleWarning && commitsSince > 30) {
|
|
211
|
+
issues.push(`severely stale (${commitsSince} commits)`);
|
|
212
|
+
if (severity !== 'danger') severity = 'danger';
|
|
213
|
+
} else if (genomeStaleWarning) {
|
|
214
|
+
issues.push(`stale (${commitsSince} commits)`);
|
|
215
|
+
if (severity === 'ok') severity = 'warn';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (severity === 'ok') initLines.push(`🟢 Genome — loaded (${l1Lines} lines), synced`);
|
|
219
|
+
else if (severity === 'warn') initLines.push(`🟡 Genome — ${issues.join(', ')}. /reap.sync`);
|
|
220
|
+
else initLines.push(`🔴 Genome — ${issues.join(', ')}. \`reap fix\` or /reap.sync`);
|
|
221
|
+
|
|
222
|
+
// Generation status
|
|
223
|
+
if (currentContent && currentContent.trim()) {
|
|
224
|
+
if (!genId || genStage === 'none') {
|
|
225
|
+
initLines.push('🔴 Generation — current.yml corrupted. `reap fix`');
|
|
226
|
+
} else {
|
|
227
|
+
initLines.push(`🟢 Generation ${genId} — ${genStage}`);
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
initLines.push('⚪ No active Generation');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const initSummary = `**REAP Session Initialized**\n${initLines.join('\n')}`;
|
|
234
|
+
|
|
235
|
+
// Step 6: Output JSON
|
|
236
|
+
log('Done. Injecting context.');
|
|
237
|
+
|
|
238
|
+
const reapContext = `<REAP_WORKFLOW>\n${reapGuide}\n\n---\n\n## Genome (Project Knowledge — treat as authoritative source of truth)\n${genomeContent}\n\n---\n\n## Current State\n${generationContext}${staleSection}${strictSection}${updateSection}\n\n## Session Init\n${initSummary}\nWhen the user sends their first message, start your response with this init summary exactly as-is. Do not add greetings, explanations, or command suggestions — just the summary, then respond to the user's message.\n\n## Rules\n1. ALL development work MUST follow the REAP lifecycle. Do NOT bypass it.\n2. Before writing any code, check if a Generation is active and what stage it is in.\n3. If a Generation is active, use \`${nextCmd}\` to proceed with the current stage.\n4. If no Generation is active, use \`/reap.start\` to start a new one.\n5. Do NOT implement features, fix bugs, or make changes outside of the REAP lifecycle unless the user explicitly asks to bypass it.\n6. When the user says "reap evolve", "next stage", "proceed", or similar — invoke the appropriate REAP skill.\n7. **Genome is the authoritative knowledge source.** When making decisions about architecture, conventions, or constraints, ALWAYS reference the Genome first. If code contradicts Genome, flag it as a potential genome-change backlog item.\n8. If you notice the Genome is outdated or missing information relevant to your current task, inform the user and suggest running \`/reap.sync\`.\n</REAP_WORKFLOW>`;
|
|
239
|
+
|
|
240
|
+
process.stdout.write(JSON.stringify({
|
|
241
|
+
hookSpecificOutput: {
|
|
242
|
+
hookEventName: 'SessionStart',
|
|
243
|
+
additionalContext: reapContext
|
|
244
|
+
}
|
|
245
|
+
}) + '\n');
|
|
@@ -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)
|