feat(runtime): v0.1.0 — adopt Voyager critic + Mindcraft modes/library/lint

Five concrete patterns from Voyager and Mindcraft, applied in our shape
without abandoning the git-as-evolution-substrate that makes pepa
distinct. Plus a first multi-agent surface so two bots from the same
repo can share intent.

1. runtime/critic.js (Voyager critic.txt)
   - Spawns `pi -p` with a JSON-only critic prompt before a proposal is
     written. {reasoning, success, critique}.
   - success=true short-circuits the proposal (bot recovered between
     detector tripping and now), saving Pi tokens on false positives.
   - critique is spliced into the proposal body via attachCritique() so
     the downstream auto-patcher has a sharp spec.
   - Graceful: pi missing / timeout / unparseable JSON → proposal still
     filed without the critic block.

2. scripts/lint-patch.js (Mindcraft coder._lintCode)
   - Pre-flight gate between Pi commit and npm test: node --check, dynamic
     import (catches missing named exports), regex extraction of
     runSkill("id") calls cross-checked against the live registry.
   - Cheaper than npm test, fails fast with a clear reason.

3. runtime/stuck-incident.renderActionTemplate (Voyager action_template.txt)
   - All proposal bodies now follow the same fixed-section layout: Task /
     Last result / Execution error / State / Metrics / Journal /
     Scenarios / Critique / Fix / Edit scope / Forbidden.

4. runtime/skill-library.js (Mindcraft skill_library.getRelevantSkillDocs)
   - Word-overlap ranking (Mindcraft's offline fallback) — zero deps,
     deterministic. auto-patch.js injects top-3 similar skills into the
     Pi prompt as "look at these patterns".

5. runtime/modes.js (Mindcraft modes.js)
   - Declarative {name, interrupts, on, active, update(ctx)} chain that
     runs BEFORE the curriculum each tick.
   - Ships self_preservation (low HP → eat/flee), hunger (food<14 → eat),
     night_shelter (night + bed in hand → sleep). Cleaner than ad-hoc
     lastFleeAttempt cooldowns in reflex.js.

6. runtime/social/conversation.js + cmd:conv-say/conv-recent/conv-list
   - File-JSONL topic channel so two bots from the same repo (different
     usernames, different host dirs under state/) can append turns and
     read peers. Skeleton — multi-agent collaboration on top later.

Differentiator preserved: every Pi-written skill still lands on main via
auto-patch.js (real git branch + smoke gate + cherry-pick). Voyager
keeps skills in a Chroma JSON, Mindcraft keeps them in RAM — pepa keeps
them as versioned source code reviewable in `git log`.

package.json: 0.0.1 → 0.1.0. 174/174 tests pass. README + AGENTS updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 15:16:42 +03:00
co-authored by Claude Opus 4.7
parent 25e39c7244
commit 4ae63dabe1
18 changed files with 1196 additions and 113 deletions
+31 -1
View File
@@ -22,6 +22,7 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { parseEditScope, validateChangedFiles, effectiveScope } from "./edit-scope.js";
import { lintPatch } from "./lint-patch.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -121,6 +122,18 @@ if (checkout.status !== 0) exit(2, `cannot create branch ${branch}: ${checkout.s
acquireLock();
log("info", `acquired ${LOCK_FILE}`);
// Pick top-k similar existing skills so Pi can crib patterns instead of
// reinventing them (Mindcraft skill_library.getRelevantSkillDocs). Lazy
// import — skill registry pulls in mineflayer transitively which is
// expensive, and we don't need it on early-exit paths.
let relevantDocsBlock = "_(skill library unavailable)_";
try {
const { renderRelevantDocs } = await import("../runtime/skill-library.js");
relevantDocsBlock = renderRelevantDocs(proposalText, { k: 3 });
} catch (e) {
log("warn", `skill-library render failed: ${e.message}`);
}
const scopeBullet = scope.map((p) => ` - \`${p}\``).join("\n");
const prompt = [
"You are patching the pepa-pi-bot repo to address an automatically-detected failure.",
@@ -131,6 +144,10 @@ const prompt = [
"",
proposalText,
"",
"## Relevant existing skills (top-3 by word overlap — use these as patterns)",
"",
relevantDocsBlock,
"",
"## Hard rules (non-negotiable)",
"",
"1. Touch ONLY files matching the edit scope below. Any other path will be rejected after you commit and the patch will be discarded:",
@@ -167,7 +184,7 @@ const timer = setTimeout(() => {
pi.kill("SIGTERM");
}, PI_TIMEOUT_MS);
pi.on("exit", (code) => {
pi.on("exit", async (code) => {
clearTimeout(timer);
log("info", `pi exited code=${code}; stdout=${piStdout.length}B stderr=${piStderr.length}B`);
@@ -200,6 +217,19 @@ pi.on("exit", (code) => {
exit(2, "patch touched off-limits files");
}
// Pre-flight lint gate (Mindcraft coder._lintCode pattern, scripts/lint-patch.js).
// Cheaper than npm test — catches parse errors, missing named imports,
// and runSkill(id) where id isn't in the registry. Seconds, not 30s.
log("info", "running lint pre-flight gate");
const lint = await lintPatch({ repoRoot: REPO_ROOT, changedFiles: filesChanged });
if (!lint.ok) {
log("error", `lint FAILED — discarding:\n${lint.errors.join("\n")}`);
git(["checkout", "main"]);
git(["branch", "-D", branch]);
exit(2, "patch failed lint");
}
log("info", "lint gate passed");
// Smoke gate: run `npm test` on the patched branch BEFORE cherry-picking.
// Anything that turns the suite red gets thrown away — even if Pi thinks
// the change is correct.