@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.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "REAP Evolve Recovery — Review past generations and create a recovery generation if needed"
3
+ ---
4
+
5
+ Run `reap run evolve-recovery $ARGUMENTS` and follow the stdout instructions exactly.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "REAP Update Genome — Apply pending genome-change backlog without a generation"
3
+ ---
4
+
5
+ Run `reap run update-genome $ARGUMENTS` and follow the stdout instructions exactly.
@@ -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
- return { strictEdit, strictMerge, language, configContent };
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
- * @returns {{ genomeStaleWarning: string, commitsSince: number }}
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
- if (dirExists(path.join(projectRoot, '.git'))) {
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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-d-cc/reap",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Recursive Evolutionary Autonomous Pipeline — AI and humans evolve software across generations",
5
5
  "type": "module",
6
6
  "license": "MIT",