@c-d-cc/reap 0.15.0 → 0.15.1
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 +7 -4
- package/README.ko.md +7 -4
- package/README.md +7 -4
- package/README.zh-CN.md +7 -4
- package/dist/cli.js +1037 -323
- package/dist/templates/commands/reap.evolve.recovery.md +5 -0
- package/dist/templates/commands/reap.update-genome.md +5 -0
- package/dist/templates/hooks/genome-loader.cjs +50 -7
- package/dist/templates/hooks/onLifeCompleted.integrity-check.sh +14 -0
- package/dist/templates/hooks/reap-guide.md +7 -1
- package/dist/templates/hooks/session-start.cjs +13 -7
- package/package.json +1 -1
|
@@ -123,7 +123,18 @@ function parseConfig(configFile) {
|
|
|
123
123
|
const langMatch = configContent.match(/^language:\s*(.+)$/m);
|
|
124
124
|
if (langMatch) language = langMatch[1].trim();
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
|
|
127
|
+
// Parse lastSyncedGeneration (and legacy lastSyncedCommit for backward compat)
|
|
128
|
+
let lastSyncedGeneration = '';
|
|
129
|
+
let lastSyncedCommit = '';
|
|
130
|
+
if (configContent) {
|
|
131
|
+
const genMatch = configContent.match(/^lastSyncedGeneration:\s*["']?(.*)["']?\s*$/m);
|
|
132
|
+
if (genMatch) lastSyncedGeneration = genMatch[1].trim().replace(/^["']|["']$/g, '') || '';
|
|
133
|
+
const commitMatch = configContent.match(/^lastSyncedCommit:\s*["']?([a-f0-9]*)["']?$/m);
|
|
134
|
+
if (commitMatch) lastSyncedCommit = commitMatch[1] || '';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { strictEdit, strictMerge, language, configContent, lastSyncedGeneration, lastSyncedCommit };
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
/**
|
|
@@ -152,14 +163,40 @@ function parseCurrentYml(currentYml) {
|
|
|
152
163
|
}
|
|
153
164
|
|
|
154
165
|
/**
|
|
155
|
-
* Detect Genome staleness.
|
|
166
|
+
* Detect Genome staleness based on lastSyncedGeneration (with lastSyncedCommit fallback).
|
|
156
167
|
* @param {string} projectRoot
|
|
157
|
-
* @
|
|
168
|
+
* @param {string} [lastSyncedGeneration] - from config.yml; empty string = never synced
|
|
169
|
+
* @param {string} [lastSyncedCommit] - legacy field for backward compatibility
|
|
170
|
+
* @returns {{ genomeStaleWarning: string, commitsSince: number, neverSynced: boolean }}
|
|
158
171
|
*/
|
|
159
|
-
function detectStaleness(projectRoot) {
|
|
172
|
+
function detectStaleness(projectRoot, lastSyncedGeneration, lastSyncedCommit) {
|
|
160
173
|
let genomeStaleWarning = '';
|
|
161
174
|
let commitsSince = 0;
|
|
162
|
-
|
|
175
|
+
let neverSynced = false;
|
|
176
|
+
|
|
177
|
+
if (!dirExists(path.join(projectRoot, '.git'))) {
|
|
178
|
+
return { genomeStaleWarning, commitsSince, neverSynced };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Primary: lastSyncedGeneration — if non-empty, genome has been synced at least once
|
|
182
|
+
if (lastSyncedGeneration) {
|
|
183
|
+
// Use git log to count commits since last genome change as staleness heuristic
|
|
184
|
+
const lastGenomeCommit = exec(`git -C "${projectRoot}" log -1 --format="%H" -- ".reap/genome/"`);
|
|
185
|
+
if (lastGenomeCommit) {
|
|
186
|
+
commitsSince = parseInt(exec(`git -C "${projectRoot}" rev-list --count "${lastGenomeCommit}..HEAD" -- src/ tests/ package.json tsconfig.json scripts/`) || '0', 10);
|
|
187
|
+
if (commitsSince > 10) {
|
|
188
|
+
genomeStaleWarning = `WARNING: Genome may be stale — ${commitsSince} commits since last sync (${lastSyncedGeneration}). Consider running /reap.sync to synchronize.`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} else if (lastSyncedCommit) {
|
|
192
|
+
// Legacy fallback: lastSyncedCommit (commit hash) for backward compatibility
|
|
193
|
+
commitsSince = parseInt(exec(`git -C "${projectRoot}" rev-list --count "${lastSyncedCommit}..HEAD" -- src/ tests/ package.json tsconfig.json scripts/`) || '0', 10);
|
|
194
|
+
if (commitsSince > 10) {
|
|
195
|
+
genomeStaleWarning = `WARNING: Genome may be stale — ${commitsSince} commits since last sync. Consider running /reap.sync to synchronize.`;
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
// Neither field set — never synced
|
|
199
|
+
// Fall back to git log based detection
|
|
163
200
|
const lastGenomeCommit = exec(`git -C "${projectRoot}" log -1 --format="%H" -- ".reap/genome/"`);
|
|
164
201
|
if (lastGenomeCommit) {
|
|
165
202
|
commitsSince = parseInt(exec(`git -C "${projectRoot}" rev-list --count "${lastGenomeCommit}..HEAD" -- src/ tests/ package.json tsconfig.json scripts/`) || '0', 10);
|
|
@@ -167,9 +204,10 @@ function detectStaleness(projectRoot) {
|
|
|
167
204
|
genomeStaleWarning = `WARNING: Genome may be stale — ${commitsSince} commits since last Genome update. Consider running /reap.sync to synchronize.`;
|
|
168
205
|
}
|
|
169
206
|
}
|
|
207
|
+
neverSynced = true;
|
|
170
208
|
}
|
|
171
209
|
|
|
172
|
-
return { genomeStaleWarning, commitsSince };
|
|
210
|
+
return { genomeStaleWarning, commitsSince, neverSynced };
|
|
173
211
|
}
|
|
174
212
|
|
|
175
213
|
/**
|
|
@@ -215,7 +253,7 @@ function hasPlaceholders(filePath) {
|
|
|
215
253
|
* @param {object} params
|
|
216
254
|
* @returns {{ initLines: string[], severity: string }}
|
|
217
255
|
*/
|
|
218
|
-
function buildGenomeHealth({ l1Lines, genomeDir, configFile, genomeStaleWarning, commitsSince }) {
|
|
256
|
+
function buildGenomeHealth({ l1Lines, genomeDir, configFile, genomeStaleWarning, commitsSince, neverSynced }) {
|
|
219
257
|
const issues = [];
|
|
220
258
|
let severity = 'ok';
|
|
221
259
|
if (l1Lines === 0) { issues.push('empty'); severity = 'danger'; }
|
|
@@ -240,6 +278,11 @@ function buildGenomeHealth({ l1Lines, genomeDir, configFile, genomeStaleWarning,
|
|
|
240
278
|
}
|
|
241
279
|
}
|
|
242
280
|
|
|
281
|
+
if (neverSynced) {
|
|
282
|
+
issues.push('never synced');
|
|
283
|
+
if (severity === 'ok') severity = 'warn';
|
|
284
|
+
}
|
|
285
|
+
|
|
243
286
|
if (genomeStaleWarning && commitsSince > 30) {
|
|
244
287
|
issues.push(`severely stale (${commitsSince} commits)`);
|
|
245
288
|
if (severity !== 'danger') severity = 'danger';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# condition: always
|
|
3
|
+
# order: 90
|
|
4
|
+
|
|
5
|
+
# Structural integrity check — runs after generation completion
|
|
6
|
+
# Outputs warnings only; does NOT block the commit (always exits 0)
|
|
7
|
+
|
|
8
|
+
result=$(reap fix --check 2>&1) || true
|
|
9
|
+
|
|
10
|
+
if [ -n "$result" ]; then
|
|
11
|
+
echo "$result"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
exit 0
|
|
@@ -58,6 +58,8 @@ Objective → Planning → Implementation ⟷ Validation → Completion
|
|
|
58
58
|
### Generation
|
|
59
59
|
A single generation. Carries one goal through the Life Cycle. State is tracked in `life/current.yml`.
|
|
60
60
|
|
|
61
|
+
**Generation types**: `normal` (standard lifecycle), `merge` (distributed merge lifecycle), `recovery` (recover from failed/interrupted generation via `/reap.evolve.recovery`).
|
|
62
|
+
|
|
61
63
|
**Generation ID format**: `gen-{NNN}-{hash}` (e.g. `gen-042-a3f8c2`)
|
|
62
64
|
- `{hash}`: 6-char hex from sha256. **MUST be real hex (0-9a-f), NEVER words/slugs.**
|
|
63
65
|
- Lineage directory appends a goal slug: `gen-042-a3f8c2-fix-login-bug`
|
|
@@ -146,11 +148,15 @@ Hooks are executed by the AI agent by scanning `.reap/hooks/` for files matching
|
|
|
146
148
|
|
|
147
149
|
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.
|
|
148
150
|
|
|
151
|
+
## CLI Subcommands
|
|
152
|
+
|
|
153
|
+
8 CLI subcommands: `reap init`, `reap status`, `reap update`, `reap fix` (`--check` for read-only), `reap clean`, `reap destroy`, `reap help`, `reap run`.
|
|
154
|
+
|
|
149
155
|
## Role Separation
|
|
150
156
|
|
|
151
157
|
| Component | Role |
|
|
152
158
|
|-----------|------|
|
|
153
|
-
| **CLI (`reap`)** | Project setup and maintenance. Init, status, update, fix |
|
|
159
|
+
| **CLI (`reap`)** | Project setup and maintenance. Init, status, update, fix, clean, destroy |
|
|
154
160
|
| **AI Agent** | Workflow executor. Performs each stage's work via slash commands |
|
|
155
161
|
| **Human** | Decision maker. Sets goals, finalizes specs, reviews code, approves stage transitions |
|
|
156
162
|
|
|
@@ -148,18 +148,24 @@ const { content: genomeContent, l1Lines } = gl.loadGenome(genomeDir);
|
|
|
148
148
|
const envSummaryFile = path.join(reapDir, 'environment', 'summary.md');
|
|
149
149
|
const envSummary = gl.readFile(envSummaryFile) || '';
|
|
150
150
|
|
|
151
|
-
// Step 4: Check Genome staleness
|
|
152
|
-
log('Checking sync...');
|
|
153
|
-
const { genomeStaleWarning, commitsSince } = gl.detectStaleness(projectRoot);
|
|
154
|
-
|
|
155
151
|
// Step 5: Read generation state
|
|
156
152
|
log('Reading generation state...');
|
|
157
|
-
const { strictEdit, strictMerge } = gl.parseConfig(configFile);
|
|
153
|
+
const { strictEdit, strictMerge, language, lastSyncedGeneration, lastSyncedCommit } = gl.parseConfig(configFile);
|
|
154
|
+
|
|
155
|
+
// Step 4: Check Genome staleness
|
|
156
|
+
log('Checking sync...');
|
|
157
|
+
const { genomeStaleWarning, commitsSince, neverSynced } = gl.detectStaleness(projectRoot, lastSyncedGeneration, lastSyncedCommit);
|
|
158
158
|
const { genStage, genId, generationContext, nextCmd } = gl.parseCurrentYml(currentYml);
|
|
159
159
|
|
|
160
160
|
// Build strict mode section
|
|
161
161
|
const strictSection = gl.buildStrictSection(strictEdit, strictMerge, genStage);
|
|
162
162
|
|
|
163
|
+
// Build language instruction
|
|
164
|
+
let langSection = '';
|
|
165
|
+
if (language) {
|
|
166
|
+
langSection = `\n\n## Language\nAlways respond in ${language}. Use ${language} for all explanations, comments, and communications with the user. Technical terms and code identifiers should remain in their original form.`;
|
|
167
|
+
}
|
|
168
|
+
|
|
163
169
|
// Build staleness section
|
|
164
170
|
let staleSection = '';
|
|
165
171
|
if (genomeStaleWarning) {
|
|
@@ -177,7 +183,7 @@ const initLines = [];
|
|
|
177
183
|
if (autoUpdateMessage) initLines.push(`🟢 ${autoUpdateMessage}`);
|
|
178
184
|
|
|
179
185
|
// Genome health
|
|
180
|
-
const health = gl.buildGenomeHealth({ l1Lines, genomeDir, configFile, genomeStaleWarning, commitsSince });
|
|
186
|
+
const health = gl.buildGenomeHealth({ l1Lines, genomeDir, configFile, genomeStaleWarning, commitsSince, neverSynced });
|
|
181
187
|
initLines.push(...health.initLines);
|
|
182
188
|
|
|
183
189
|
// Generation status
|
|
@@ -210,7 +216,7 @@ log('Done. Injecting context.');
|
|
|
210
216
|
|
|
211
217
|
const envSection = envSummary ? `\n\n---\n\n## Environment (External Context)\n${envSummary}` : '';
|
|
212
218
|
|
|
213
|
-
const reapContext = `<REAP_WORKFLOW>\n${reapGuide}\n\n---\n\n## Genome (Project Knowledge — treat as authoritative source of truth)\n${genomeContent}${envSection}\n\n---\n\n## Current State\n${generationContext}${staleSection}${strictSection}${updateSection}\n\n## Session Init (display to user on first message)\n${sessionInitDisplay}\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>`;
|
|
219
|
+
const reapContext = `<REAP_WORKFLOW>\n${reapGuide}\n\n---\n\n## Genome (Project Knowledge — treat as authoritative source of truth)\n${genomeContent}${envSection}\n\n---\n\n## Current State\n${generationContext}${staleSection}${strictSection}${updateSection}${langSection}\n\n## Session Init (display to user on first message)\n${sessionInitDisplay}\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>`;
|
|
214
220
|
|
|
215
221
|
process.stdout.write(JSON.stringify({
|
|
216
222
|
hookSpecificOutput: {
|