Compare commits
4 Commits
v2.8.7-lla
...
v2.8.11-ag
| Author | SHA1 | Date | |
|---|---|---|---|
| e5183cc71b | |||
| 9abc14ef82 | |||
| 7ef479639a | |||
| 89a6ffe8a0 |
37
.learnings/HEALS.md
Normal file
37
.learnings/HEALS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Self-healing log
|
||||
|
||||
Verified fixes for runtime failures. Each entry documents a failure, its root cause, the applied fix, and the verification proof.
|
||||
|
||||
**Pattern-Key discipline:** before filing a new HEAL, search this file for an existing Pattern-Key. If found, increment `Recurrence-Count` and update `Last-Seen` — do not duplicate.
|
||||
|
||||
**Lifecycle:** verified heals at Recurrence-Count ≥ 3 across distinct tasks get a `Handoff` block for promotion to project memory (`CLAUDE.md`, `AGENTS.md`, or a skill).
|
||||
|
||||
---
|
||||
|
||||
## [HEAL-YYYYMMDD-XXX] short_kebab_name
|
||||
|
||||
**Logged**: ISO-8601 timestamp
|
||||
**Status**: pending-verify
|
||||
**Trigger**: tool-failure | missing-capability | env-issue | external-change | <free-form>
|
||||
**Area**: free-form tag (e.g. `build`, `tests`, `ci`, `auth`, `data-pipeline`)
|
||||
**Priority**: low | medium | high | critical
|
||||
|
||||
### Failure
|
||||
Concrete error: command, error message, exit code, blocked action.
|
||||
|
||||
### Diagnosis
|
||||
Root cause as understood after investigation. What was verified during diagnosis.
|
||||
|
||||
### Fix
|
||||
Patch applied. Verbatim commands, code snippets, or pointers to `.learnings/heals/<HEAL-ID>/`.
|
||||
|
||||
### Verification
|
||||
What was run after the fix and what it returned. Exit code, output snippet, test pass count. **Proof.**
|
||||
|
||||
### Metadata
|
||||
- Related Files: path/to/file.ext
|
||||
- See Also: HEAL-... | LRN-... | ERR-...
|
||||
- Pattern-Key: lower.snake.case (e.g. `env.lockfile_mismatch`)
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen: YYYY-MM-DD
|
||||
- Last-Seen: YYYY-MM-DD
|
||||
@@ -4,6 +4,7 @@ import { loadConfig } from './config.js';
|
||||
import { getPool, closeDb } from './db.js';
|
||||
import { registerHealthRoutes } from './routes/health.js';
|
||||
import { registerTerminalRoutes } from './routes/terminals.js';
|
||||
import { registerSessionRoutes } from './routes/sessions.js';
|
||||
import { registerWsAttachRoute } from './ws/attach.js';
|
||||
|
||||
async function main(): Promise<void> {
|
||||
@@ -33,6 +34,7 @@ async function main(): Promise<void> {
|
||||
|
||||
registerHealthRoutes(app);
|
||||
registerTerminalRoutes(app, config.TMUX_CONF_PATH);
|
||||
registerSessionRoutes(app);
|
||||
registerWsAttachRoute(app, config.TMUX_CONF_PATH);
|
||||
|
||||
const shutdown = async (signal: string) => {
|
||||
|
||||
44
apps/booterm/src/pty/registry.ts
Normal file
44
apps/booterm/src/pty/registry.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export interface SessionMeta {
|
||||
paneId: string;
|
||||
sessionId: string;
|
||||
projectPath: string;
|
||||
title?: string;
|
||||
createdAt: Date;
|
||||
lastActivityAt: Date;
|
||||
}
|
||||
|
||||
const sessions = new Map<string, SessionMeta>();
|
||||
|
||||
export function register(
|
||||
sessionId: string,
|
||||
paneId: string,
|
||||
projectPath: string,
|
||||
title?: string,
|
||||
): void {
|
||||
const now = new Date();
|
||||
const existing = sessions.get(paneId);
|
||||
if (existing) {
|
||||
existing.lastActivityAt = now;
|
||||
return;
|
||||
}
|
||||
sessions.set(paneId, {
|
||||
paneId,
|
||||
sessionId,
|
||||
projectPath,
|
||||
title,
|
||||
createdAt: now,
|
||||
lastActivityAt: now,
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister(paneId: string): void {
|
||||
sessions.delete(paneId);
|
||||
}
|
||||
|
||||
export function list(): SessionMeta[] {
|
||||
return Array.from(sessions.values());
|
||||
}
|
||||
|
||||
export function get(paneId: string): SessionMeta | undefined {
|
||||
return sessions.get(paneId);
|
||||
}
|
||||
18
apps/booterm/src/routes/sessions.ts
Normal file
18
apps/booterm/src/routes/sessions.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { list } from '../pty/registry.js';
|
||||
|
||||
export function registerSessionRoutes(app: FastifyInstance): void {
|
||||
app.get('/api/term/sessions', async (_req, reply) => {
|
||||
const active = list();
|
||||
return reply.code(200).send({
|
||||
sessions: active.map((s) => ({
|
||||
paneId: s.paneId,
|
||||
sessionId: s.sessionId,
|
||||
projectPath: s.projectPath,
|
||||
title: s.title ?? null,
|
||||
createdAt: s.createdAt.toISOString(),
|
||||
lastActivityAt: s.lastActivityAt.toISOString(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../pty/manager.js';
|
||||
import { attachPty } from '../pty/pty.js';
|
||||
import { getUser } from '../auth.js';
|
||||
import { register, unregister } from '../pty/registry.js';
|
||||
|
||||
export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string): void {
|
||||
app.get<{
|
||||
@@ -57,6 +58,8 @@ export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string
|
||||
return;
|
||||
}
|
||||
|
||||
register(sid, pid, session.project_path);
|
||||
|
||||
let handle: IPty;
|
||||
try {
|
||||
handle = attachPty({
|
||||
@@ -157,6 +160,7 @@ export function registerWsAttachRoute(app: FastifyInstance, tmuxConfPath: string
|
||||
// teardown happens via the /kill route called from the frontend when the
|
||||
// user closes the pane.
|
||||
socket.on('close', () => {
|
||||
unregister(pid);
|
||||
try {
|
||||
handle.kill();
|
||||
} catch {
|
||||
|
||||
@@ -17,7 +17,7 @@ top_p: 0.95
|
||||
top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, search_symbols, view_file, watch_changes, request_read_access, view_truncated_output, ask_user_input, git_status, get_blast_radius, get_hot_files, get_middleware, get_routes]
|
||||
tools: [find_files, get_blast_radius, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_hot_files, get_middleware, get_routes, get_semantic_neighborhoods, get_symbol_info, git_status, grep, list_dir, request_read_access, search_symbols, view_file, view_truncated_output]
|
||||
description: Reviews code for bugs, security issues, and maintainability. Read-only.
|
||||
---
|
||||
You review code. Find real problems, not style nits.
|
||||
@@ -56,7 +56,7 @@ top_p: 0.95
|
||||
top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, search_symbols, view_file, watch_changes, request_read_access, view_truncated_output, ask_user_input, git_status, get_blast_radius, get_hot_files, get_middleware, get_routes]
|
||||
tools: [ask_user_input, find_files, get_blast_radius, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_hot_files, get_semantic_neighborhoods, get_symbol_info, git_status, grep, list_dir, request_read_access, search_symbols, view_file, view_truncated_output, watch_changes]
|
||||
description: Diagnoses bugs from error messages, logs, or described symptoms.
|
||||
---
|
||||
You diagnose bugs. Form a hypothesis, prove it with evidence from the code.
|
||||
@@ -82,7 +82,7 @@ top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
steps: 5
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, search_symbols, view_file, watch_changes, request_read_access, view_truncated_output, ask_user_input, git_status, get_blast_radius, get_hot_files, get_middleware, get_routes]
|
||||
tools: [find_files, get_blast_radius, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_hot_files, get_middleware, get_routes, get_semantic_neighborhoods, get_symbol_info, git_status, grep, list_dir, request_read_access, search_symbols, view_file, view_truncated_output, watch_changes]
|
||||
description: Proposes refactors for clarity, deduplication, or decoupling. Read-only — outputs plans, not edits.
|
||||
---
|
||||
You propose refactors. You do not apply them. The user applies via OpenCode or Claude Code.
|
||||
@@ -125,7 +125,7 @@ top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 1.5
|
||||
steps: 20
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, search_symbols, view_file, watch_changes, request_read_access, view_truncated_output, ask_user_input, git_status, get_blast_radius, get_hot_files, get_middleware, get_routes]
|
||||
tools: [find_files, get_blast_radius, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_hot_files, get_middleware, get_routes, get_semantic_neighborhoods, get_symbol_info, git_status, grep, list_dir, request_read_access, search_symbols, view_file, view_truncated_output, watch_changes, web_fetch, web_search]
|
||||
description: Designs new features, modules, or architectural changes. Outputs a build plan.
|
||||
---
|
||||
You design. You produce build plans, not code.
|
||||
@@ -167,7 +167,7 @@ top_p: 0.95
|
||||
top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, search_symbols, view_file, watch_changes, request_read_access, view_truncated_output, ask_user_input, git_status, get_blast_radius, get_hot_files, get_middleware, get_routes]
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, grep, list_dir, request_read_access, search_symbols, view_file, view_truncated_output]
|
||||
description: Audits code for security vulnerabilities. Read-only.
|
||||
---
|
||||
You audit for security issues. Concrete findings only, no generic warnings.
|
||||
@@ -212,7 +212,7 @@ top_p: 0.95
|
||||
top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
tools: [view_file, list_dir, grep, find_files]
|
||||
tools: [find_files, get_codebase_overview, grep, list_dir, view_file]
|
||||
description: Builds prompts for OpenCode, Claude Code, or BooCode dispatch.
|
||||
---
|
||||
You write prompts that another coding agent will execute. Your output is the prompt, not the work.
|
||||
@@ -250,7 +250,7 @@ top_p: 0.95
|
||||
top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, search_symbols, view_file, watch_changes, request_read_access, view_truncated_output, ask_user_input, git_status, get_blast_radius, get_hot_files, get_middleware, get_routes]
|
||||
tools: [find_files, get_blast_radius, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_hot_files, get_middleware, get_routes, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, request_read_access, search_symbols, view_file, view_truncated_output, watch_changes]
|
||||
description: Discovers and maps unfamiliar codebases. Reads architecture, traces data flow, identifies key symbols.
|
||||
---
|
||||
You map codebases. Start broad, then drill into specifics.
|
||||
@@ -278,7 +278,7 @@ top_k: 20
|
||||
min_p: 0.0
|
||||
presence_penalty: 0.0
|
||||
steps: 10
|
||||
tools: [find_files, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_semantic_neighborhoods, get_symbol_info, grep, list_dir, search_symbols, view_file, watch_changes, request_read_access, view_truncated_output, ask_user_input, git_status, get_blast_radius, get_hot_files, get_middleware, get_routes]
|
||||
tools: [ask_user_input, find_files, get_blast_radius, get_codebase_overview, get_dependencies, get_file_analysis, get_framework_analysis, get_hot_files, get_middleware, get_routes, get_semantic_neighborhoods, get_symbol_info, git_status, grep, list_dir, request_read_access, search_symbols, view_file, watch_changes]
|
||||
description: Produces actionable step plans from requirements. Read-only — never modifies files.
|
||||
---
|
||||
You produce actionable step plans. You do not modify files.
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
"CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
|
||||
},
|
||||
"enabled": false
|
||||
},
|
||||
"type-inject": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["-y", "@nick-vi/type-inject-mcp"],
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
287
data/skills/boocode/self-healing/SKILL.md
Normal file
287
data/skills/boocode/self-healing/SKILL.md
Normal file
@@ -0,0 +1,287 @@
|
||||
---
|
||||
name: self-healing
|
||||
description: "Active runtime recovery for coding agents: when something breaks mid-task, diagnose the root cause, write a fix, VERIFY by re-running the broken thing, then file a `HEAL-` entry to `.learnings/HEALS.md` with proof. Use whenever a command, test, build, or lint fails or exits non-zero; on missing tooling, dependency/lockfile mismatch, wrong runtime version, venv or permission errors, port conflicts, dirty git state, or a missing `.env`; when the agent needs a helper or one-off script that doesn't exist yet; when an external API, tool, or MCP errors or rate-limits; or when a test flakes. Search `HEALS.md` by `Pattern-Key` first — most heals are recurrences, so increment `Recurrence-Count` instead of duplicating. Verify is mandatory: mark `pending-verify` honestly if sandboxed, `abandoned` if the fix can't be made to work. Pairs with `self-improvement` (which promotes recurring heals to durable memory) but owns the verify-before-persist discipline self-improvement doesn't."
|
||||
---
|
||||
|
||||
# Self-Healing
|
||||
|
||||
Active runtime recovery for coding agents. When something breaks, run the loop: **diagnose → patch → verify → file**. Leave behind a reusable, verified artifact instead of a swept-under-the-rug failure.
|
||||
|
||||
The premise mirrors [browser-use/browser-harness](https://github.com/browser-use/browser-harness): *the harness improves itself every run*. An agent that hits a gap doesn't fail — it writes the fix during execution, verifies it works, and files the durable artifact for future runs. Coding tasks deserve the same loop.
|
||||
|
||||
## What this skill is for
|
||||
|
||||
When a coding agent hits a wall mid-task, the default failure modes are:
|
||||
|
||||
1. **Paper over it** — "let me try a different approach" — and lose the recovery
|
||||
2. **Pretend the fix worked** — without re-running the broken thing
|
||||
3. **Symptom-fix** — skip the test, swallow the error, retry until green
|
||||
|
||||
All three turn a one-time failure into a recurrence. The next agent on the same project hits the same wall.
|
||||
|
||||
This skill enforces one discipline: **verify before persist**. A patch isn't real until you've re-run the failing operation and watched it succeed. When it does, file the verified fix so the next run benefits.
|
||||
|
||||
## Relationship to self-improvement
|
||||
|
||||
These two skills are deliberately split. Run both — they feed each other but don't overlap.
|
||||
|
||||
| Aspect | `self-healing` (this skill) | `self-improvement` |
|
||||
| ----------- | -------------------------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| **When** | During execution, failure is live | After the fact, at natural breakpoints |
|
||||
| **Verb** | Heal now — restore working state | Remember for later — accumulate knowledge |
|
||||
| **Outcome** | Verified patch + (optional) reusable artifact | Logged learning, correction, request |
|
||||
| **Verify** | **Mandatory** — no persist without proof | Not required |
|
||||
| **Files** | `.learnings/HEALS.md` + `.learnings/heals/<HEAL-ID>/` (lazy) | `.learnings/ERRORS.md`, `LEARNINGS.md`, `FEATURE_REQUESTS.md` |
|
||||
| **Trigger** | Failure observed mid-task | Correction, knowledge gap, feature request, recurrence |
|
||||
|
||||
**Boundary rule:** if you're capturing a fact, a correction, or a wish — that's `self-improvement`. If you're applying and verifying a fix to a live failure — that's `self-healing`.
|
||||
|
||||
## The Heal Loop
|
||||
|
||||
```
|
||||
● failure observed
|
||||
│
|
||||
● 1. DIAGNOSE capture context — command, error, env, what was attempted
|
||||
│ search HEALS.md for the same Pattern-Key first
|
||||
│ (most heals are recurrences; don't reinvent)
|
||||
│
|
||||
● 2. PATCH write the fix — script, helper, env tweak, alt command
|
||||
│ artifacts → .learnings/heals/<HEAL-ID>/ (only if needed)
|
||||
│
|
||||
● 3. VERIFY re-run the failing op — must succeed
|
||||
│ ↻ if still failing: refine and retry, cap at 3 attempts
|
||||
│ ✗ if uncrackable: file Status: abandoned with notes
|
||||
│
|
||||
● 4. FILE write HEAL-YYYYMMDD-XXX to .learnings/HEALS.md
|
||||
│ with Pattern-Key, status, verification proof
|
||||
│
|
||||
✓ working state restored, heal persisted
|
||||
|
||||
(conditional) PROMOTE if Pattern-Key recurrence ≥ 3 across distinct tasks,
|
||||
append a Handoff block → self-improvement promotes to memory
|
||||
```
|
||||
|
||||
If you abandon a heal mid-loop, don't pretend it succeeded. File a `HEAL-` entry with `Status: abandoned` and notes on what didn't work. The next agent learns from the dead end too.
|
||||
|
||||
## When to trigger
|
||||
|
||||
Self-healing fires on **active failures during execution** — the agent has just observed something not working and needs to make it work to continue. Five shapes:
|
||||
|
||||
### 1. Tool failure (command / test / build / lint)
|
||||
Any invocation exits non-zero or produces wrong output. Don't acknowledge and retry verbatim — diagnose, patch, verify.
|
||||
|
||||
*Examples:* `npm install` errors when a `pnpm-lock.yaml` is present (switch tool); `pytest` fails with `ModuleNotFoundError` (activate the venv); `tsc` flags a stale type (regenerate the client); `eslint` reports a config error (install the missing parser).
|
||||
|
||||
### 2. Missing capability / tool gap
|
||||
The agent needs something that doesn't exist yet — a script, a helper, a wrapper, a glue function. Write it in the moment. This is the closest analog to browser-harness's `agent_helpers.py`.
|
||||
|
||||
*Examples:* dedupe a CSV by custom key (write a small Python helper); bootstrap 12 microservices the same way (write `scripts/bootstrap-all.sh`); bulk-rename branches matching a pattern (write a `gh`-based shell helper).
|
||||
|
||||
### 3. Environment issue
|
||||
The local environment isn't what the project expects. Detect, patch, verify.
|
||||
|
||||
*Examples:* runtime version mismatch (`nvm use`, `pyenv local`, `rustup override`); stale dependency cache after a branch switch; dirty git state blocking a checkout; missing `.env` (copy from `.env.example` and surface gaps).
|
||||
|
||||
### 4. External service / API change
|
||||
A service the agent depends on returns something unexpected. Find a workaround and capture it.
|
||||
|
||||
*Examples:* an MCP tool returns `InputValidationError` because the schema changed (patch the call shape); a public API hits a rate limit (back off, switch endpoint, batch); an upstream lib bumped a default and broke a script (pin the version).
|
||||
|
||||
### 5. About-to-retry-the-same-broken-approach
|
||||
The agent catches itself about to redo the failing step. That self-recognition is a heal forming — capture the alternate approach as the patch.
|
||||
|
||||
### Detection signals to watch for
|
||||
|
||||
- Non-zero exit codes
|
||||
- Stack traces in tool output
|
||||
- The same operation failing twice with the same error
|
||||
- "I'll try a different approach" — capture it as a heal
|
||||
- `command not found` / `module not found` / `permission denied`
|
||||
- Stale assertions, snapshot mismatches, type errors that weren't there before
|
||||
- "Weird" output that suggests environmental rather than logical bugs
|
||||
|
||||
## HEAL Entry Format
|
||||
|
||||
Append to `.learnings/HEALS.md` (create if missing):
|
||||
|
||||
```markdown
|
||||
## [HEAL-YYYYMMDD-XXX] short_kebab_name
|
||||
|
||||
**Logged**: ISO-8601 timestamp
|
||||
**Status**: verified | pending-verify | abandoned
|
||||
**Trigger**: tool-failure | missing-capability | env-issue | external-change | <free-form>
|
||||
**Active-Context**: (optional) — current skill, task phase, or workflow stage; omit if not applicable
|
||||
**Area**: free-form tag — what part of the system (`build`, `tests`, `ci`, `auth`, `data-pipeline`, `mobile`, ...)
|
||||
**Priority**: low | medium | high | critical
|
||||
|
||||
### Failure
|
||||
What broke — concrete: the command, the error message, the action that was blocked. Include exit codes and verbatim error lines.
|
||||
|
||||
### Diagnosis
|
||||
The root cause as understood after investigation. Why the obvious approach didn't work. Not a guess — what was actually verified during the heal.
|
||||
|
||||
### Fix
|
||||
The patch that was applied. Verbatim commands, code snippets, or pointers to files under `.learnings/heals/<HEAL-ID>/`. Keep it minimal — just enough to reproduce.
|
||||
|
||||
### Verification
|
||||
What was run after the fix and what it returned. Exit code, output snippet, test pass count. **This is the proof.** Without it, the entry is `pending-verify` or `abandoned`.
|
||||
|
||||
### Artifacts
|
||||
(omit this section if no files were generated; otherwise list relative paths under `.learnings/heals/<HEAL-ID>/`)
|
||||
|
||||
### Metadata
|
||||
- Related Files: path/to/file.ext
|
||||
- See Also: HEAL-... | LRN-... | ERR-... (related entries)
|
||||
- Pattern-Key: lower.snake.case key for recurrence detection (e.g. `env.lockfile_mismatch`)
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen / Last-Seen: YYYY-MM-DD
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
### Field guidance
|
||||
|
||||
- **Status** — `verified` = the verify step passed. `pending-verify` = patch applied but couldn't be fully proven (sandboxed/offline/CI-only) — surface to the user. `abandoned` = patch didn't work or diagnosis was wrong — document what was tried.
|
||||
- **Trigger** — free-form is fine. The listed values are common shapes; what matters is that the failure shape is described enough for future agents to match against.
|
||||
- **Active-Context** — optional. Use it if your environment has a meaningful "what was I doing" tag (an active skill, a current task phase, a build stage, an agent role). Skip if not applicable. The browser-harness analog is the per-domain scoping of `domain-skills/<site>/`.
|
||||
- **Area** — free-form. Pick whatever helps future agents find this. `frontend`, `data-pipeline`, `ci`, `auth`, `terraform`, `mobile`, `embedded` — anything that fits your project shape.
|
||||
- **Pattern-Key** — lower.snake.case, stable, reusable across projects. Two heals with the same key are recurrences. `env.lockfile_mismatch` is good; `fixed_thing_tuesday` isn't.
|
||||
|
||||
## ID generation
|
||||
|
||||
Format: `HEAL-YYYYMMDD-XXX`. `XXX` is sequential 3-digit or 3-char random alphanumeric. Examples: `HEAL-20260524-001`, `HEAL-20260524-A7B`.
|
||||
|
||||
## Artifacts directory (lazy)
|
||||
|
||||
Only create `.learnings/heals/<HEAL-ID>/` when the heal generated something worth preserving. One-line fixes don't need a folder; the HEAL entry text is enough. Abandoned heals with no applied patch also skip the folder.
|
||||
|
||||
```
|
||||
.learnings/
|
||||
├── HEALS.md
|
||||
├── ERRORS.md / LEARNINGS.md / FEATURE_REQUESTS.md (self-improvement)
|
||||
└── heals/
|
||||
└── HEAL-20260524-001/
|
||||
├── helper.sh
|
||||
├── patch.diff
|
||||
└── notes.md
|
||||
```
|
||||
|
||||
**Put here:** generated scripts/helpers, patch files, supplementary notes, output captures that document the diagnosis.
|
||||
**Don't put here:** project source changes (those go in the project tree, referenced via Related Files); secrets; output already captured in the HEAL text.
|
||||
|
||||
## Verification rules
|
||||
|
||||
Verify is the load-bearing wall. The whole point of self-healing over self-improvement is that the fix is *proven*, not theorized.
|
||||
|
||||
### What counts as proof
|
||||
|
||||
| Failure shape | Verification |
|
||||
| ------------------------------------- | ------------------------------------------------------------------ |
|
||||
| Tool / command / test / build / lint | Re-run the original invocation; expect exit 0 / pass |
|
||||
| Missing capability | Invoke the helper end-to-end on a real input; expect the intent |
|
||||
| Environment drift | Re-run the operation that triggered the diagnosis |
|
||||
| External service workaround | Re-run the failed call with the patch; expect a usable response |
|
||||
|
||||
### Sandboxed / offline / CI-only failures
|
||||
|
||||
When you genuinely can't run the verify step (no network, no real remote, sandboxed shell, CI-only reproduction), file `Status: pending-verify` with:
|
||||
|
||||
- The exact command the user / CI should run
|
||||
- The acceptance criteria — what counts as proof
|
||||
- A simulated proof if you can construct one (e.g. a dry-run mode, a stub of the failing call, a sandbox script)
|
||||
|
||||
`pending-verify` is honest. Faking `verified` is the failure mode this skill exists to prevent.
|
||||
|
||||
### When to invest in a proof script
|
||||
|
||||
Most heals don't need a separate proof script — the verify step is just re-running the failing thing. Build a proper proof script when:
|
||||
|
||||
- The heal generates a reusable helper that needs to be exercised across cases
|
||||
- The failure can't be reproduced live but can be reproduced in a sandbox (clean git repo, mock service, fake input)
|
||||
- You expect the heal to be re-applied across projects — the proof script then doubles as a regression check
|
||||
|
||||
### If verification fails
|
||||
|
||||
1. **Once** — refine the patch and retry. First diagnosis is often wrong.
|
||||
2. **Twice** — step back and reconsider the diagnosis. Maybe the root cause is elsewhere.
|
||||
3. **Three times** — stop. File `Status: abandoned` with notes on what you tried. Surface to the user. Don't flail.
|
||||
|
||||
### What does NOT count as verification
|
||||
|
||||
- "It looks right" / "I think this should work"
|
||||
- Re-running a *different* command than the one that originally failed
|
||||
- Suppressing the failure (`|| true`, `--ignore-errors`) — that's hiding
|
||||
- Skipping or deleting the failing test — that's regression
|
||||
- Passing because the cache was warm from before the fix
|
||||
|
||||
### Reversibility
|
||||
|
||||
Prefer reversible patches. If your heal modifies project files, capture the diff in `patch.diff`. If the heal is destructive (deletes generated files, rewrites locks), note it explicitly — a future agent reading the HEAL needs to know what was destroyed.
|
||||
|
||||
## Recurrence and promotion
|
||||
|
||||
Most heals are recurrences. Before filing a new HEAL, search:
|
||||
|
||||
```bash
|
||||
grep -n "Pattern-Key: <your-pattern-key>" .learnings/HEALS.md
|
||||
```
|
||||
|
||||
If found:
|
||||
|
||||
- Increment `Recurrence-Count`
|
||||
- Update `Last-Seen`
|
||||
- Add the current occurrence as a See Also link
|
||||
- **Do not** create a duplicate entry
|
||||
|
||||
### Promotion threshold
|
||||
|
||||
Add a `Handoff` block to an existing entry when **all** are true:
|
||||
|
||||
- `Recurrence-Count >= 3`
|
||||
- Seen across at least 2 distinct tasks
|
||||
- The fix is generalizable (not project-specific in a way that's already in a memory file)
|
||||
|
||||
```markdown
|
||||
### Handoff
|
||||
- **Promoted To**: self-improvement at YYYY-MM-DD
|
||||
- **Promotion Target**: CLAUDE.md | AGENTS.md | .github/copilot-instructions.md | new-skill
|
||||
- **Distilled Rule**: One-line prevention guidance derived from the heal
|
||||
```
|
||||
|
||||
Then `self-improvement` (or a learning aggregator) takes over: distills the rule, writes it into the right context file, or extracts a reusable skill. The HEAL stays for traceability.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
1. **Logging without verifying.** A HEAL filed before the fix is proven turns this into noisier self-improvement. If verify hasn't passed, the entry is `pending-verify` or `abandoned`.
|
||||
2. **Healing the symptom, not the cause.** A failing test isn't healed by skipping it (`pytest.skip`, `it.skip`, `xit`). A flaky CI isn't healed by `--retry`. Find the root cause; if you can't, abandon honestly.
|
||||
3. **Generating a new fix without trying existing ones first.** Search `HEALS.md` by Pattern-Key. Most heals are recurrences.
|
||||
4. **Inventing helpers when the project already has them.** Look in `scripts/`, `Makefile`, `justfile`, `package.json`, `pyproject.toml` first. Heal = write what's missing, not what's there.
|
||||
5. **Scope creep.** A heal is scoped to one failure. Cleanup belongs in a quality pass; refactors are features. Scope creep makes heals unreviewable.
|
||||
6. **Empty artifact folders.** Don't create `.learnings/heals/<HEAL-ID>/` if nothing goes in it.
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Heal eagerly, file always.** Even abandoned heals teach the next agent what doesn't work.
|
||||
2. **Verify before persist.** The non-negotiable rule.
|
||||
3. **Minimal and reversible patches.** A 3-line fix is a heal; a 300-line refactor is a feature.
|
||||
4. **Stable Pattern-Keys.** `env.node_version_mismatch` is reusable; `fixed_the_thing_on_tuesday` isn't.
|
||||
5. **Reference, don't duplicate.** Cross-link related HEAL/LRN/ERR via See Also.
|
||||
6. **Hand off recurrences.** A heal seen 3 times deserves to be in the project's permanent memory.
|
||||
7. **Don't gate the main tree on heal artifacts.** Files under `.learnings/heals/` are reference material; if a script becomes load-bearing, promote it to `scripts/`.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
mkdir -p .learnings # heals/ is lazy — created only when artifacts exist
|
||||
touch .learnings/HEALS.md
|
||||
```
|
||||
|
||||
Gitignore choices match `self-improvement`. Keep heals local (`.learnings/` in `.gitignore`) or share them as team knowledge (don't gitignore — they become reviewable durable context).
|
||||
|
||||
## Multi-agent use
|
||||
|
||||
The skill is agent-agnostic. The `.learnings/HEALS.md` format is plain markdown — any agent (Claude Code, BooCode agents, OpenCode, Copilot, Cursor, Aider, ...) can read and write it.
|
||||
|
||||
## See also
|
||||
|
||||
- [`references/examples.md`](references/examples.md) — canonical HEAL entry shapes (command failure, missing capability, env drift, external API workaround, abandoned heal)
|
||||
35
data/skills/boocode/self-healing/eval.yaml
Normal file
35
data/skills/boocode/self-healing/eval.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
skill: self-healing
|
||||
tasks:
|
||||
- prompt: "I'm in a project root that has pnpm-lock.yaml present but no package-lock.json. I just tried to run `npm install` and it failed. Get me to a working state so I can keep working — I have other things to do, just unblock me. After fixing it, make sure future agents in this project know what happened."
|
||||
grader:
|
||||
- the response invokes the self-healing skill
|
||||
- the response diagnoses pnpm vs npm mismatch as the root cause
|
||||
- the response runs pnpm install successfully
|
||||
- the response files a HEAL entry to .learnings/HEALS.md with Status: verified
|
||||
- the HEAL entry has Trigger: tool-failure
|
||||
- the HEAL entry has a Pattern-Key resembling env.lockfile_mismatch
|
||||
- the HEAL entry includes the verification output
|
||||
- prompt: "I need to bulk-rename 8 git branches in this repo from `feat-XXX-name` to `feat/XXX-name`. There's no existing script for this and `gh` doesn't have a bulk-rename. Write what's needed, prove it works on a dry run, and capture the work so it's not lost if I need it again."
|
||||
grader:
|
||||
- the response invokes the self-healing skill
|
||||
- the response recognizes this as a missing-capability heal
|
||||
- the response writes a helper script under .learnings/heals/HEAL-<date>-<seq>/
|
||||
- the response runs a dry-run verification
|
||||
- the response files a HEAL entry with Status: verified and Trigger: missing-capability
|
||||
- the HEAL entry references the helper script in the Artifacts section
|
||||
- prompt: "I just ran `pytest` and got `ModuleNotFoundError: No module named 'pydantic'`. There's already a `.learnings/HEALS.md` in this project with a prior heal for a similar venv-not-activated issue. Fix this, and do the right thing with the heal records."
|
||||
grader:
|
||||
- the response invokes the self-healing skill
|
||||
- the response searches HEALS.md first (using find-similar-heals.sh or grep) before writing a new fix
|
||||
- the response finds the existing HEAL entry and applies its fix (activate venv)
|
||||
- the response increments Recurrence-Count on the existing entry
|
||||
- the response updates Last-Seen on the existing entry
|
||||
- the response does NOT create a duplicate HEAL entry
|
||||
- prompt: "A test in this repo is failing intermittently — the snapshot for `Card.test.tsx` flakes. I've already tried fixing it once by stubbing the date; it passes twice then flakes again because there's a UUID that's also non-deterministic. I don't have time to refactor the Card component to inject dependencies. Just do the right thing — get me to a state that's honest about what's known and not known, and don't pretend the heal worked."
|
||||
grader:
|
||||
- the response invokes the self-healing skill
|
||||
- the response diagnoses that the initial patch attempt was incomplete
|
||||
- the response files a HEAL entry with Status: abandoned
|
||||
- the HEAL entry documents what was tried and why it failed
|
||||
- the response does NOT mark anything as verified
|
||||
- the response surfaces the situation honestly to the user
|
||||
248
data/skills/boocode/self-healing/references/examples.md
Normal file
248
data/skills/boocode/self-healing/references/examples.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Self-Healing Examples
|
||||
|
||||
Concrete HEAL entries showing the format applied to real failure shapes. Use these as templates when filing your own heals. All examples use the iteration-2 schema (free-form `Trigger` / `Area`, optional `Active-Context`, no `Source` field, lazy artifact folders).
|
||||
|
||||
---
|
||||
|
||||
## Example 1 — Tool failure (lockfile mismatch)
|
||||
|
||||
```markdown
|
||||
## [HEAL-20260524-001] npm_install_pnpm_lockfile
|
||||
|
||||
**Logged**: 2026-05-24T14:22:01Z
|
||||
**Status**: verified
|
||||
**Trigger**: tool-failure
|
||||
**Area**: build
|
||||
**Priority**: medium
|
||||
|
||||
### Failure
|
||||
`npm install` exited 1 with `npm ERR! code EUSAGE` and a notice that `pnpm-lock.yaml` is present but `package-lock.json` is missing. The project uses pnpm workspaces; npm refuses to install against a pnpm lockfile.
|
||||
|
||||
### Diagnosis
|
||||
Project root contains `pnpm-lock.yaml`. The README and CI both invoke `pnpm`. `npm` was a habit from previous projects, not the actual project's package manager.
|
||||
|
||||
### Fix
|
||||
Use pnpm instead:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Verification
|
||||
```
|
||||
$ pnpm install
|
||||
Lockfile is up to date, resolution step is skipped
|
||||
Already up to date
|
||||
✓ Done in 1.4s
|
||||
```
|
||||
Exit 0.
|
||||
|
||||
### Metadata
|
||||
- Related Files: package.json, pnpm-lock.yaml
|
||||
- See Also: (none yet)
|
||||
- Pattern-Key: env.lockfile_mismatch
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen: 2026-05-24
|
||||
- Last-Seen: 2026-05-24
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
Pattern-Key `env.lockfile_mismatch` is reusable across projects (yarn.lock, bun.lockb, etc.). At Recurrence ≥ 3, this should be promoted to `CLAUDE.md` or `AGENTS.md` as a verification step.
|
||||
|
||||
No Artifacts section — the fix is a tool swap, no files generated. Lazy folder pattern: nothing to put in `.learnings/heals/HEAL-20260524-001/`, so the folder isn't created.
|
||||
|
||||
---
|
||||
|
||||
## Example 2 — Missing capability (helper written on the fly)
|
||||
|
||||
```markdown
|
||||
## [HEAL-20260524-002] bulk_rename_branches_helper
|
||||
|
||||
**Logged**: 2026-05-24T15:10:44Z
|
||||
**Status**: verified
|
||||
**Trigger**: missing-capability
|
||||
**Area**: ci
|
||||
**Priority**: low
|
||||
|
||||
### Failure
|
||||
Need to rename 12 feature branches from `feat-XXX-name` to `feat/XXX-name`. No existing project script handles this; `gh` doesn't have a bulk-rename primitive.
|
||||
|
||||
### Diagnosis
|
||||
This is glue work, not a project bug. A small shell helper using `gh api` per branch is the right level — not worth a top-level script, but worth keeping the file for the next time someone asks.
|
||||
|
||||
### Fix
|
||||
Wrote `.learnings/heals/HEAL-20260524-002/rename-branches.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
git fetch --all
|
||||
for branch in $(git branch -r | grep 'origin/feat-' | sed 's|origin/||'); do
|
||||
new="${branch/feat-/feat/}"
|
||||
echo "$branch → $new"
|
||||
gh api -X POST "repos/{owner}/{repo}/git/refs" \
|
||||
-f "ref=refs/heads/$new" \
|
||||
-f "sha=$(git rev-parse "origin/$branch")"
|
||||
gh api -X DELETE "repos/{owner}/{repo}/git/refs/heads/$branch"
|
||||
done
|
||||
```
|
||||
|
||||
### Verification
|
||||
Dry-run (commented out the API calls) printed the 12 expected mappings.
|
||||
Live run renamed all 12; `git branch -r | grep 'feat-' | wc -l` returns 0.
|
||||
|
||||
### Artifacts
|
||||
- `.learnings/heals/HEAL-20260524-002/rename-branches.sh`
|
||||
|
||||
### Metadata
|
||||
- Related Files: (none — operates on git refs)
|
||||
- See Also: (none)
|
||||
- Pattern-Key: tool.gh.bulk_branch_rename
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen: 2026-05-24
|
||||
- Last-Seen: 2026-05-24
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
Helper script lives under `.learnings/heals/<HEAL-ID>/` — referenceable, but not assumed to be load-bearing. If it gets reused frequently, promote to `scripts/`.
|
||||
|
||||
---
|
||||
|
||||
## Example 3 — Environment issue (runtime version)
|
||||
|
||||
```markdown
|
||||
## [HEAL-20260524-003] nvm_use_project_node
|
||||
|
||||
**Logged**: 2026-05-24T16:01:12Z
|
||||
**Status**: verified
|
||||
**Trigger**: env-issue
|
||||
**Active-Context**: verify-gate
|
||||
**Area**: tests
|
||||
**Priority**: medium
|
||||
|
||||
### Failure
|
||||
`pnpm test` exited 1 with `engine "node" is incompatible with this module. Expected version "^20.10.0". Got "18.19.0"`.
|
||||
|
||||
### Diagnosis
|
||||
`.nvmrc` requests node 20.10.0; current shell has 18.19.0 from a previous project context. The shell's nvm wasn't switched after `cd`-ing into the repo.
|
||||
|
||||
### Fix
|
||||
```bash
|
||||
nvm use # reads .nvmrc
|
||||
```
|
||||
|
||||
### Verification
|
||||
```
|
||||
$ node --version
|
||||
v20.10.0
|
||||
$ pnpm test
|
||||
✓ 47 tests passed
|
||||
```
|
||||
|
||||
### Metadata
|
||||
- Related Files: .nvmrc, package.json
|
||||
- See Also: (none)
|
||||
- Pattern-Key: env.node_version_mismatch
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen: 2026-05-24
|
||||
- Last-Seen: 2026-05-24
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
`Active-Context: verify-gate` because that's the workflow phase the agent was in when the test step blew up. An upstream context loader could surface this entry next time `verify-gate` runs in a node project. If you don't have an analogous concept in your pipeline, omit the field.
|
||||
|
||||
---
|
||||
|
||||
## Example 4 — External service workaround
|
||||
|
||||
```markdown
|
||||
## [HEAL-20260524-004] gh_api_rate_limit_backoff
|
||||
|
||||
**Logged**: 2026-05-24T17:33:08Z
|
||||
**Status**: verified
|
||||
**Trigger**: external-change
|
||||
**Area**: ci
|
||||
**Priority**: high
|
||||
|
||||
### Failure
|
||||
Looping `gh api repos/.../issues` over 200 issues started returning `403 rate limit exceeded` after ~60 calls. Unauthenticated burst limit (abuse detection on rapid successive calls).
|
||||
|
||||
### Diagnosis
|
||||
Script was using `gh api` REST without batching. `gh` is authenticated but the secondary rate limit fires on rapid successive calls — not the primary 5000/hour limit. Switching to a single paginated GraphQL query bypasses the secondary limit entirely.
|
||||
|
||||
### Fix
|
||||
```bash
|
||||
gh api graphql -f query='
|
||||
query($owner:String!,$repo:String!,$cursor:String) {
|
||||
repository(owner:$owner,name:$repo) {
|
||||
issues(first:100,after:$cursor) { ... }
|
||||
}
|
||||
}' -F owner=... -F repo=...
|
||||
```
|
||||
Took ~3 calls total instead of 200.
|
||||
|
||||
### Verification
|
||||
Full run completed in 4.8s, no 403s, all 200 issues retrieved. Compared output against a sample of the original per-issue calls — fields match.
|
||||
|
||||
### Artifacts
|
||||
- `.learnings/heals/HEAL-20260524-004/fetch-issues.sh`
|
||||
|
||||
### Metadata
|
||||
- Related Files: (none — ad-hoc query)
|
||||
- See Also: (none)
|
||||
- Pattern-Key: api.gh.rate_limit
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen: 2026-05-24
|
||||
- Last-Seen: 2026-05-24
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example 5 — Abandoned heal (diagnosis was wrong)
|
||||
|
||||
```markdown
|
||||
## [HEAL-20260524-005] vitest_flaky_snapshot
|
||||
|
||||
**Logged**: 2026-05-24T18:14:22Z
|
||||
**Status**: abandoned
|
||||
**Trigger**: tool-failure
|
||||
**Active-Context**: verify-gate
|
||||
**Area**: tests
|
||||
**Priority**: medium
|
||||
|
||||
### Failure
|
||||
`vitest` snapshot test `Card > renders default` flaked twice in three runs. Diff showed a timestamp string differing by ~3 seconds.
|
||||
|
||||
### Diagnosis (initial — wrong)
|
||||
Assumed flake was timezone drift in the snapshot fixture. Patched the fixture to use a fixed `Date.now()` stub.
|
||||
|
||||
### Diagnosis (current — correct)
|
||||
The snapshot depends on multiple non-deterministic values: timestamp AND a `crypto.randomUUID()`. The clock stub addressed only one of them. The UUID is still random per render, so the snapshot keeps drifting on subsequent runs.
|
||||
|
||||
### Fix (attempted)
|
||||
Added `vi.useFakeTimers({ now: 1700000000000 })` to the test setup.
|
||||
|
||||
### Verification
|
||||
Test passed twice, then flaked again on the third run — same `Card > renders default`, different diff (this time the UUID changed). Original diagnosis was incomplete.
|
||||
|
||||
### Abandonment notes
|
||||
The right fix is to make the component deterministic via dependency injection (pass a `clock` and `idGen` prop), not to stub globally. That's a real change to the component contract — out of scope for a heal. Filed `FEAT-20260524-001` via self-improvement; surfaced to the user.
|
||||
|
||||
### Metadata
|
||||
- Related Files: src/components/Card.tsx, src/components/Card.test.tsx
|
||||
- See Also: FEAT-20260524-001
|
||||
- Pattern-Key: tests.flaky_snapshot_multi_nondeterminism
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen: 2026-05-24
|
||||
- Last-Seen: 2026-05-24
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
Abandoned heals are first-class. They document a dead end so the next agent doesn't re-walk it. The handoff to a `FEAT-` entry via self-improvement is the right next step when the real fix is a feature, not a heal.
|
||||
|
||||
No Artifacts section — the attempted patch was reverted; nothing reusable was generated.
|
||||
54
data/skills/boocode/self-healing/scripts/detect-failure.sh
Executable file
54
data/skills/boocode/self-healing/scripts/detect-failure.sh
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# detect-failure.sh — PostToolUse hook for Bash invocations.
|
||||
# Reads the tool result JSON on stdin (per Claude Code hook spec); if exit_code != 0,
|
||||
# emits a system reminder pointing the agent at self-healing.
|
||||
#
|
||||
# Wire up in .claude/settings.json:
|
||||
# "hooks": {
|
||||
# "PostToolUse": [{ "matcher": "Bash",
|
||||
# "hooks": [{ "type": "command",
|
||||
# "command": "./data/skills/boocode/self-healing/scripts/detect-failure.sh" }] }]
|
||||
# }
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Hook payload arrives on stdin. We tolerate either jq-style JSON or raw text.
|
||||
PAYLOAD="$(cat || true)"
|
||||
|
||||
# Try to parse exit_code; fall through silently on parse failure.
|
||||
EXIT_CODE=$(printf '%s' "$PAYLOAD" | python3 -c '
|
||||
import json, sys
|
||||
try:
|
||||
data = json.loads(sys.stdin.read() or "{}")
|
||||
# Common shapes: {"tool_result": {"exit_code": N}}, {"exit_code": N}, {"output": "...", "exit_code": N}
|
||||
for path in (("tool_result","exit_code"), ("exit_code",), ("result","exit_code")):
|
||||
d = data
|
||||
ok = True
|
||||
for k in path:
|
||||
if isinstance(d, dict) and k in d:
|
||||
d = d[k]
|
||||
else:
|
||||
ok = False
|
||||
break
|
||||
if ok and isinstance(d, int):
|
||||
print(d)
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
pass
|
||||
print(0)
|
||||
' 2>/dev/null || echo 0)
|
||||
|
||||
if [[ "$EXIT_CODE" != "0" ]]; then
|
||||
cat <<'EOF'
|
||||
<self-healing-trigger>
|
||||
A Bash command just exited non-zero. This is a heal opportunity.
|
||||
|
||||
Before retrying the same command verbatim:
|
||||
1. DIAGNOSE — read the error; identify the root cause (env? missing dep? wrong tool?)
|
||||
2. Search .learnings/HEALS.md for a matching Pattern-Key (don't re-solve a solved problem)
|
||||
3. PATCH — write the fix (or apply a known one)
|
||||
4. VERIFY — re-run the command; require exit 0
|
||||
5. FILE — append a HEAL entry to .learnings/HEALS.md via data/skills/boocode/self-healing/scripts/new-heal.sh
|
||||
</self-healing-trigger>
|
||||
EOF
|
||||
fi
|
||||
52
data/skills/boocode/self-healing/scripts/find-similar-heals.sh
Executable file
52
data/skills/boocode/self-healing/scripts/find-similar-heals.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# find-similar-heals.sh — Search existing heals before generating a new fix.
|
||||
# Usage: ./find-similar-heals.sh <pattern-key-or-keyword>
|
||||
#
|
||||
# Prints matching HEAL entries with their Pattern-Key, Status, and Recurrence-Count
|
||||
# so the agent can decide whether to re-apply an existing fix or write a new one.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
QUERY="${1:-}"
|
||||
HEALS_FILE="$(pwd)/.learnings/HEALS.md"
|
||||
|
||||
if [[ -z "$QUERY" ]]; then
|
||||
echo "usage: $0 <pattern-key-or-keyword>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ ! -f "$HEALS_FILE" ]]; then
|
||||
echo "(no .learnings/HEALS.md yet — no prior heals to consult)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find HEAL section headers that contain the query in their body (Pattern-Key, name, or text).
|
||||
python3 - <<PY "$QUERY" "$HEALS_FILE"
|
||||
import sys, re
|
||||
query, path = sys.argv[1].lower(), sys.argv[2]
|
||||
with open(path) as f:
|
||||
text = f.read()
|
||||
# Split into entries by ^## [HEAL-...]
|
||||
entries = re.split(r"(?m)^## \[HEAL-", text)[1:]
|
||||
hits = []
|
||||
for body in entries:
|
||||
if query in body.lower():
|
||||
head = body.splitlines()[0]
|
||||
pk = re.search(r"Pattern-Key:\s*(\S+)", body)
|
||||
status = re.search(r"Status\*\*:\s*(\S+)", body) or re.search(r"Status:\s*(\S+)", body)
|
||||
rc = re.search(r"Recurrence-Count:\s*(\d+)", body)
|
||||
hits.append({
|
||||
"id": "HEAL-" + head.split("]")[0],
|
||||
"name": head.split("]", 1)[1].strip() if "]" in head else head,
|
||||
"pattern_key": pk.group(1) if pk else "?",
|
||||
"status": status.group(1) if status else "?",
|
||||
"recurrence": rc.group(1) if rc else "1",
|
||||
})
|
||||
if not hits:
|
||||
print(f"(no heals match '{query}')")
|
||||
else:
|
||||
print(f"Found {len(hits)} matching heal(s):\n")
|
||||
for h in hits:
|
||||
print(f" {h['id']} {h['name']}")
|
||||
print(f" pattern={h['pattern_key']} status={h['status']} recurrence={h['recurrence']}")
|
||||
PY
|
||||
74
data/skills/boocode/self-healing/scripts/new-heal.sh
Executable file
74
data/skills/boocode/self-healing/scripts/new-heal.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# new-heal.sh — Initialize a new HEAL-<date>-<seq> entry skeleton.
|
||||
# Usage: ./new-heal.sh <short_kebab_name> [trigger]
|
||||
# trigger: tool-failure | missing-capability | env-issue | external-change | <free-form>
|
||||
#
|
||||
# Appends a templated HEAL entry to .learnings/HEALS.md and prints the HEAL-ID.
|
||||
# Does NOT create .learnings/heals/<HEAL-ID>/ — that folder is lazy, created
|
||||
# only when artifacts are written.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NAME="${1:-}"
|
||||
TRIGGER="${2:-tool-failure}"
|
||||
|
||||
if [[ -z "$NAME" ]]; then
|
||||
echo "usage: $0 <short_kebab_name> [trigger]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
LEARNINGS_DIR="$(pwd)/.learnings"
|
||||
HEALS_FILE="$LEARNINGS_DIR/HEALS.md"
|
||||
mkdir -p "$LEARNINGS_DIR"
|
||||
|
||||
DATE="$(date +%Y%m%d)"
|
||||
SEQ=$(grep -c "^## \[HEAL-${DATE}-" "$HEALS_FILE" 2>/dev/null || echo 0)
|
||||
NEXT=$(printf "%03d" $((SEQ + 1)))
|
||||
HEAL_ID="HEAL-${DATE}-${NEXT}"
|
||||
|
||||
# Active-Context is optional. The agent / harness can set ACTIVE_CONTEXT in env.
|
||||
ACTIVE_CONTEXT="${ACTIVE_CONTEXT:-}"
|
||||
ACTIVE_LINE=""
|
||||
if [[ -n "$ACTIVE_CONTEXT" ]]; then
|
||||
ACTIVE_LINE="**Active-Context**: $ACTIVE_CONTEXT
|
||||
"
|
||||
fi
|
||||
|
||||
cat >> "$HEALS_FILE" <<EOF
|
||||
|
||||
## [$HEAL_ID] $NAME
|
||||
|
||||
**Logged**: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
**Status**: pending-verify
|
||||
**Trigger**: $TRIGGER
|
||||
${ACTIVE_LINE}**Area**: TODO
|
||||
**Priority**: medium
|
||||
|
||||
### Failure
|
||||
TODO — concrete error, command, exit code
|
||||
|
||||
### Diagnosis
|
||||
TODO — root cause after investigation
|
||||
|
||||
### Fix
|
||||
TODO — patch applied (commands, snippets, or pointers to .learnings/heals/$HEAL_ID/ if files were generated)
|
||||
|
||||
### Verification
|
||||
TODO — what was run after the fix, what it returned. **Update Status to "verified" only after this passes.**
|
||||
|
||||
### Metadata
|
||||
- Related Files: TODO
|
||||
- See Also: TODO
|
||||
- Pattern-Key: TODO
|
||||
- Recurrence-Count: 1
|
||||
- First-Seen: $(date +%Y-%m-%d)
|
||||
- Last-Seen: $(date +%Y-%m-%d)
|
||||
|
||||
---
|
||||
EOF
|
||||
|
||||
# stdout = the HEAL-ID alone, so `ID=$(new-heal.sh ...)` captures it cleanly.
|
||||
# Human guidance goes to stderr.
|
||||
echo "$HEAL_ID"
|
||||
echo "$HEALS_FILE" >&2
|
||||
echo "(create .learnings/heals/$HEAL_ID/ only if you generate artifacts to put there)" >&2
|
||||
178
data/skills/boocode/verify-gate/SKILL.md
Normal file
178
data/skills/boocode/verify-gate/SKILL.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
name: verify-gate
|
||||
description: "Runs project compile, test, and lint commands between implementation and quality review. Gates simplify-and-harden behind machine verification. If checks fail, enters a fix loop with diagnostics. If checks pass, signals ready for quality pass. Use after any implementation work completes and before signaling done. Essential for the inner loop's verify step."
|
||||
---
|
||||
|
||||
# Verify Gate
|
||||
|
||||
Machine verification gate between implementation and quality review. Runs the project's compile, test, and lint commands. If any fail, enters a fix loop. If all pass, unblocks the quality pass.
|
||||
|
||||
This is the inner loop's **verify** step. Without it, the agent hands off code with zero machine signal about whether it actually works.
|
||||
|
||||
## When to Use
|
||||
|
||||
- After any implementation work completes, before signaling "done"
|
||||
- Before running simplify-and-harden or quality review
|
||||
- After fixing audit findings from code review
|
||||
- Any time you want a machine-verified green signal
|
||||
|
||||
## Pipeline Position
|
||||
|
||||
```
|
||||
[implementation] → verify-gate → [quality review / simplify-and-harden]
|
||||
↻ fix loop — on failure, diagnose and retry
|
||||
```
|
||||
|
||||
## Step 1: Discover Project Commands
|
||||
|
||||
Read the project's configuration to find verification commands. Check these sources in order:
|
||||
|
||||
1. **Project instruction files** (`CLAUDE.md`, `data/AGENTS.md`) — look for a `## Verification` or `## Test Commands` section
|
||||
2. **package.json** — `scripts.test`, `scripts.lint`, `scripts.typecheck`, `scripts.build`. BooCode uses pnpm, so prefer `pnpm run <script>` when `pnpm-lock.yaml` is present.
|
||||
3. **Makefile** / **Justfile** — `test`, `lint`, `check`, `build` targets
|
||||
4. **Cargo.toml** — `cargo build`, `cargo test`, `cargo clippy`
|
||||
5. **pyproject.toml** / **setup.cfg** — `pytest`, `mypy`, `ruff`
|
||||
6. **go.mod** — `go build ./...`, `go test ./...`, `go vet ./...`
|
||||
7. **deno.json** / **deno.jsonc** — `deno task <name>` for any defined tasks
|
||||
|
||||
If no commands are discoverable, ask the user once and suggest they add a `## Verification` section to `CLAUDE.md` for future sessions:
|
||||
|
||||
```markdown
|
||||
## Verification
|
||||
|
||||
- Build: `pnpm run build`
|
||||
- Test: `pnpm test`
|
||||
- Lint: `pnpm run lint`
|
||||
- Type check: `npx tsc -p apps/server/tsconfig.json --noEmit`
|
||||
```
|
||||
|
||||
## Step 2: Run Verification
|
||||
|
||||
Run discovered commands in this order. Stop at the first failure category.
|
||||
|
||||
### Phase 1: Compile / Type Check
|
||||
Run the build or type-check command. These catch structural errors before wasting time on tests.
|
||||
|
||||
```
|
||||
Exit 0 → proceed to Phase 2
|
||||
Exit non-zero → enter fix loop with compiler output
|
||||
```
|
||||
|
||||
### Phase 2: Tests
|
||||
Run the test command. Scope to changed files if the test runner supports it.
|
||||
|
||||
```
|
||||
Exit 0 → proceed to Phase 3
|
||||
Exit non-zero → enter fix loop with test output
|
||||
```
|
||||
|
||||
### Phase 3: Lint (optional, skippable with --skip-lint)
|
||||
Run the lint command. Lint failures are lower severity but still worth catching.
|
||||
|
||||
```
|
||||
Exit 0 → all phases green, gate passes
|
||||
Exit non-zero → enter fix loop with lint output
|
||||
```
|
||||
|
||||
## Step 3: Fix Loop
|
||||
|
||||
When a phase fails:
|
||||
|
||||
1. **Read the output.** Parse the error output for actionable diagnostics — file paths, line numbers, error messages.
|
||||
2. **Scope the fix.** Only fix what the verification caught. Do not refactor, improve, or touch unrelated code.
|
||||
3. **Apply the fix.** Make the minimal change to resolve the failure.
|
||||
4. **Re-run the failed phase.** Not all phases — just the one that failed.
|
||||
5. **If it passes**, continue to the next phase.
|
||||
6. **If it fails again**, increment the attempt counter.
|
||||
|
||||
### Fix Loop Limits
|
||||
|
||||
- **Default max attempts:** 3 per phase (configurable via `--fix-limit N`)
|
||||
- **Counter increments on every attempt**, even if the error changes. Fixing Error A and uncovering Error B counts as attempt 2, not attempt 1. The counter tracks fix attempts, not unique errors.
|
||||
- **If limit reached:** Stop. Report what failed, what was tried, and the remaining error output. Do not guess further — signal to the user that manual intervention is needed.
|
||||
- **Total budget:** The fix loop should not exceed 20% of the original implementation effort. If fixes are snowballing, stop and report.
|
||||
|
||||
## Step 4: Gate Signal
|
||||
|
||||
When all phases pass:
|
||||
|
||||
```markdown
|
||||
## Verify Gate: PASSED
|
||||
|
||||
- Build: passed
|
||||
- Tests: passed (N tests, M suites)
|
||||
- Lint: passed (or skipped)
|
||||
|
||||
Ready for quality review.
|
||||
```
|
||||
|
||||
When the fix loop is exhausted:
|
||||
|
||||
```markdown
|
||||
## Verify Gate: BLOCKED
|
||||
|
||||
- Build: passed
|
||||
- Tests: FAILED (attempt 3/3)
|
||||
- [file:line] error description
|
||||
- [file:line] error description
|
||||
- Lint: not reached
|
||||
|
||||
Fix loop exhausted. Manual intervention needed before quality review.
|
||||
```
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
### boocode simplify-and-harden / quality review
|
||||
verify-gate should gate any quality pass. Run verify-gate first; only proceed to review if the gate passes.
|
||||
|
||||
### self-healing (if available)
|
||||
On any failure during the verify run, consider handing the diagnostics to a self-healing loop (diagnose → patch → verify → persist). Verify-gate then re-runs the checks. Up to 3 heal attempts per phase before abandoning.
|
||||
|
||||
### self-improvement
|
||||
If a recurring error pattern emerges across verify runs, capture it in `CLAUDE.md` or as a new skill under `data/skills/boocode/` so future verify-gate runs don't rediscover the same fix.
|
||||
|
||||
## What This Skill Does NOT Do
|
||||
|
||||
- Does not review code quality (that's a separate review pass)
|
||||
- Does not check security
|
||||
- Does not verify spec compliance
|
||||
- Does not modify test files or add new tests
|
||||
- Does not run tests for code it didn't change (unless the test runner doesn't support scoping)
|
||||
|
||||
## Configuration
|
||||
|
||||
If the project has a `verify-gate` section in `CLAUDE.md` or `data/AGENTS.md`:
|
||||
|
||||
```yaml
|
||||
## Verify Gate Config
|
||||
|
||||
build: pnpm run build
|
||||
test: pnpm test
|
||||
lint: pnpm run lint
|
||||
type_check: npx tsc -p apps/server/tsconfig.json --noEmit
|
||||
fix_limit: 3
|
||||
skip_lint: false
|
||||
test_scope: changed # changed | all
|
||||
```
|
||||
|
||||
If no configuration exists, discover commands automatically (Step 1) and suggest persisting them.
|
||||
|
||||
### Custom Verification Steps
|
||||
|
||||
Projects with custom invariants can define additional verification phases. These run as extra phases after the standard compile/test/lint checks.
|
||||
|
||||
Example — a project that needs API schema validation:
|
||||
|
||||
```yaml
|
||||
## Verify Gate Config
|
||||
|
||||
custom_checks:
|
||||
- name: validate-schema
|
||||
command: python scripts/validate_schema.py --strict
|
||||
- name: check-no-legacy-imports
|
||||
command: grep -r "from legacy" src/ --include="*.py" && exit 1 || exit 0
|
||||
```
|
||||
|
||||
When custom checks are defined, verify-gate runs them as **Phase 4** after lint. Each check's exit code determines pass/fail. Failed checks enter the same fix loop as standard phases.
|
||||
|
||||
This moves project-specific invariants from "knowledge in your head" to "knowledge in the harness" — exactly where the agent can reach it.
|
||||
Reference in New Issue
Block a user