|
| 1 | +--- |
| 2 | +name: clean-fix-harness |
| 3 | +description: Use every time the user asks to fix, debug, repair, patch, harden, clean up, or investigate broken behavior in this repo. Applies especially to agent harness, model/tool routing, SQL family, widget data, MCP, streaming, workspace bridge, citations, artifacts, evals, and tests. Enforces the repo's lean-harness rule: reproduce defects faithfully, fix at the root, keep the model responsible for choosing paths, avoid deterministic retries, avoid content/word-based routing, avoid hidden normalization, and reject overfit special cases. |
| 4 | +allowed-tools: Read, Glob, Grep, Edit, Write, Bash |
| 5 | +--- |
| 6 | + |
| 7 | +# Clean Fix Harness |
| 8 | + |
| 9 | +Use this skill for every fix request in this repo. It complements `fix-with-test`: this skill defines |
| 10 | +what a good fix is allowed to look like, and `fix-with-test` defines the red/green discipline. |
| 11 | + |
| 12 | +The core rule: **the harness prepares state and exposes tools; the model chooses the path.** Fixes |
| 13 | +must remove root causes, not build deterministic crutches around the model. |
| 14 | + |
| 15 | +## Required Workflow |
| 16 | + |
| 17 | +1. **Read the real context first** |
| 18 | + - Inspect the current branch diff against `main` when the bug is PR-related. |
| 19 | + - Read the relevant source, tests, and logs before proposing a fix. |
| 20 | + - Use `rg` for suspicious strings and code paths; do not rely on memory. |
| 21 | + - Keep existing user changes. Never reset or revert unrelated work. |
| 22 | + |
| 23 | +2. **Classify the defect** |
| 24 | + - Pure code defect: parser, schema, SQL loader, artifact builder, event formatter, MCP handler. |
| 25 | + - Harness behavior defect: loop dispatch, streaming, artifact queue, state restoration, bridge routing. |
| 26 | + - Model behavior defect: wrong tool chosen, wrong tool args, bad final formatting, wrong reasoning path. |
| 27 | + - Contract defect: prompt/tool description/schema says the wrong thing or leaves ambiguity. |
| 28 | + |
| 29 | +3. **Reproduce before fixing** |
| 30 | + - For bugs, follow `fix-with-test`: write a failing unit/integration test or eval first. |
| 31 | + - Confirm the red is for the real behavior, not bad test setup. |
| 32 | + - If the issue is model/tool choice, use a real-LLM eval with deterministic graders where possible. |
| 33 | + - If the issue is deterministic code, use unit or integration tests at the lowest faithful layer. |
| 34 | + |
| 35 | +4. **Fix at the root** |
| 36 | + - Broken implementation: patch the implementation. |
| 37 | + - Broken schema contract: patch the Zod schema or typed contract. |
| 38 | + - Broken tool description: patch the tool description. |
| 39 | + - Broken model instruction: patch the prompt or eval surface. |
| 40 | + - Broken display rendering: patch the renderer or protocol, not model-output cleanup, unless the |
| 41 | + protocol explicitly requires sanitization. |
| 42 | + |
| 43 | +5. **Verify** |
| 44 | + - Run the focused failing test/eval until green. |
| 45 | + - Run nearby tests affected by the changed contract. |
| 46 | + - Run `bun run typecheck`. |
| 47 | + - Run `LIVE_DEBUG_TRACES=0 bun test tests/` before handing off a merge-ready fix. |
| 48 | + |
| 49 | +## Harness Boundary Rules |
| 50 | + |
| 51 | +The agent harness may: |
| 52 | + |
| 53 | +- Dispatch structurally by tool source or protocol event type. |
| 54 | +- Validate tool input against Zod schemas. |
| 55 | +- Preserve request-scoped state such as `pendingTables`, `artifactQueue`, citations, loaded skills, and |
| 56 | + widget fetch queues. |
| 57 | +- Expose state-bound tools that operate on request state, such as SQL-family tools. |
| 58 | +- Emit structural SSE events for tool calls, tool results, artifacts, citations, and bridge commands. |
| 59 | +- Enforce safety boundaries, such as SQL read-only checks and blocking SQLite internals. |
| 60 | +- Strip narrow protocol placeholders that should never be user-visible. |
| 61 | +- Cache or rehydrate state when the protocol explicitly requires it. |
| 62 | + |
| 63 | +The agent harness must not: |
| 64 | + |
| 65 | +- Decide the analytical path from user wording. |
| 66 | +- Infer that a specific content shape means a specific action. |
| 67 | +- Repair model mistakes by silently changing tool args. |
| 68 | +- Retry a failed path deterministically because an error string matched a known pattern. |
| 69 | +- Convert one identifier kind into another to make a model call "work". |
| 70 | +- Hide an invalid model call by dropping conflicting fields or choosing a preferred source. |
| 71 | +- Promote, suppress, or transform final answer content because it contains certain words. |
| 72 | +- Parse tool-output prose to create a special UI state unless the output is a typed protocol payload. |
| 73 | +- Add local branches that encode one observed failure trace instead of fixing the underlying contract. |
| 74 | + |
| 75 | +## Patterns We Never Want |
| 76 | + |
| 77 | +### Content-Based Routing |
| 78 | + |
| 79 | +Never add logic like: |
| 80 | + |
| 81 | +```ts |
| 82 | +if (userText.includes("csv")) createArtifact(); |
| 83 | +if (output.startsWith("SQL error:")) showSqlFailurePath(); |
| 84 | +if (looksLikeMarkdownTable(finalText)) removeIt(); |
| 85 | +if (dataLooksStructured(value)) useSqlPath(); |
| 86 | +if (message.includes("error")) retry(); |
| 87 | +``` |
| 88 | + |
| 89 | +Why: this moves decisions from the model into brittle string matching. It creates overfit behavior that |
| 90 | +works for one trace and fails in adjacent cases. |
| 91 | + |
| 92 | +Correct alternatives: |
| 93 | + |
| 94 | +- Make the tool/prompt contract clear enough that the model chooses the right tool. |
| 95 | +- Return typed results for things the harness must understand. |
| 96 | +- Add a deterministic eval for routing behavior. |
| 97 | +- Fix the renderer if display is broken. |
| 98 | + |
| 99 | +### Forced Retry or Deterministic Recovery |
| 100 | + |
| 101 | +Never add logic like: |
| 102 | + |
| 103 | +```ts |
| 104 | +if (toolFailed) callSearchAgain(); |
| 105 | +if (sqlError.includes("no such column")) rewriteSql(); |
| 106 | +if (widgetFetchFailed) trySameNameWidget(); |
| 107 | +if (modelStopped) runAnother orchestration loop with a new instruction; |
| 108 | +``` |
| 109 | + |
| 110 | +Allowed exception: a retry may exist only for a narrow external transient with a structural signal, such |
| 111 | +as a stopped sandbox process or a network timeout. The retry must not inspect semantic content, must be |
| 112 | +bounded, and must be documented in the owning tool layer. |
| 113 | + |
| 114 | +Correct alternatives: |
| 115 | + |
| 116 | +- Surface the failure to the model as tool output. |
| 117 | +- Give the model enough state to choose the next call. |
| 118 | +- Patch the prompt/tool description when the model repeatedly chooses the wrong recovery path. |
| 119 | +- Patch the root service/tool when the failure is not model-controllable. |
| 120 | + |
| 121 | +### Hidden Identifier Standardization |
| 122 | + |
| 123 | +Never silently convert or reconcile identifiers: |
| 124 | + |
| 125 | +```ts |
| 126 | +widget.uuid ??= widget.widget_id; |
| 127 | +find(w => w.uuid === id || w.widget_id === id); |
| 128 | +dashboardId = lookupByDashboardTitle(userText); |
| 129 | +widget_uuid = widget_id; |
| 130 | +origin = origin.trim() || null; |
| 131 | +``` |
| 132 | + |
| 133 | +Why: identifiers are protocol contracts. If the model sends the wrong identifier kind, silently fixing it |
| 134 | +teaches the system to depend on ambiguity and masks prompt/schema bugs. |
| 135 | + |
| 136 | +Correct alternatives: |
| 137 | + |
| 138 | +- Make the prompt/tool description say exactly which identifier is required. |
| 139 | +- Make schemas reject wrong or missing fields. |
| 140 | +- Pass through bridge arguments when the browser bridge owns interpretation. |
| 141 | +- Add an eval or integration test proving the model uses the correct identifier. |
| 142 | + |
| 143 | +### Error-String Interpretation |
| 144 | + |
| 145 | +Never parse generic error text to pick a specialized path: |
| 146 | + |
| 147 | +```ts |
| 148 | +if (/no such column/.test(error)) suggestColumn(); |
| 149 | +if (/date_trunc/.test(error)) rewriteForSQLite(); |
| 150 | +if (/ORDER BY term/.test(error)) injectSortKey(); |
| 151 | +``` |
| 152 | + |
| 153 | +Correct alternatives: |
| 154 | + |
| 155 | +- Return raw tool errors plus structural context such as available table/column inventories. |
| 156 | +- Improve tool descriptions and prompts with general dialect constraints. |
| 157 | +- Let the model decide how to adjust its next SQL/tool call. |
| 158 | + |
| 159 | +### Tool Output Text Parsing |
| 160 | + |
| 161 | +Never parse human-readable tool output to create artifacts/statuses: |
| 162 | + |
| 163 | +```ts |
| 164 | +const rows = parseRowsAfter(output, /^Rows:/); |
| 165 | +if (output.match(/^Created chart artifact/)) status = "Chart"; |
| 166 | +if (output.match(/^Table "([^"]+)"/)) status = "Missing table"; |
| 167 | +``` |
| 168 | + |
| 169 | +Correct alternatives: |
| 170 | + |
| 171 | +- Use typed `SSEEvent`, typed MCP content, Zod schemas, or explicit JSON structures. |
| 172 | +- Keep human-readable text as preview only. |
| 173 | +- If the UI needs structure, change the tool to return a typed structure rather than parsing prose. |
| 174 | + |
| 175 | +### Silent Source Precedence |
| 176 | + |
| 177 | +Never accept conflicting model inputs and silently choose one: |
| 178 | + |
| 179 | +```ts |
| 180 | +const source = sql ?? fromTableId ?? data; |
| 181 | +if (sql && fromTableId) ignoreFromTableId(); |
| 182 | +``` |
| 183 | + |
| 184 | +Correct alternative: |
| 185 | + |
| 186 | +- Reject conflicting inputs with a schema/tool error. |
| 187 | +- Describe the contract clearly: exactly one source, exactly one mode, or explicit precedence if the |
| 188 | + protocol truly requires precedence. |
| 189 | + |
| 190 | +### Final Answer Surgery |
| 191 | + |
| 192 | +Do not "clean up" final text with broad transformations: |
| 193 | + |
| 194 | +```ts |
| 195 | +removeMarkdownTables(finalText); |
| 196 | +if (userAskedForExport) suppressInlineCsv(finalText); |
| 197 | +if (artifactExists) stripMatchingSections(finalText); |
| 198 | +``` |
| 199 | + |
| 200 | +Correct alternatives: |
| 201 | + |
| 202 | +- Prompt the model to keep final text concise and put tabular output in artifacts. |
| 203 | +- Emit artifacts through the artifact queue. |
| 204 | +- Strip only narrow protocol placeholder leaks that should never be visible, such as artifact tags. |
| 205 | +- Fix renderer bugs where valid Markdown is displayed incorrectly. |
| 206 | + |
| 207 | +### Special-Case Fixes From One Trace |
| 208 | + |
| 209 | +Be suspicious of fixes that mention a single metric, widget, table, column, provider, or error string |
| 210 | +unless the defect is truly in that named integration. |
| 211 | + |
| 212 | +Bad smell examples: |
| 213 | + |
| 214 | +- Branches for a particular widget name. |
| 215 | +- Branches for one SQL table like `executive_esg_kpi_snapshot`. |
| 216 | +- Regex for one model's phrasing. |
| 217 | +- Tests that assert one exact model answer instead of the contract. |
| 218 | +- Prompt changes that describe one incident instead of a reusable rule. |
| 219 | + |
| 220 | +## What Good Fixes Look Like |
| 221 | + |
| 222 | +Good fixes are usually one of these: |
| 223 | + |
| 224 | +- **Contract fix**: sharpen a tool description, prompt rule, Zod schema, or protocol type. |
| 225 | +- **Root implementation fix**: correct the function that owns the behavior. |
| 226 | +- **Typed payload fix**: replace prose parsing with structured data. |
| 227 | +- **Validation fix**: reject ambiguous/conflicting inputs early. |
| 228 | +- **Safety fix**: block unsafe operations structurally. |
| 229 | +- **Renderer/protocol fix**: fix how events or artifacts are rendered instead of rewriting model text. |
| 230 | +- **Regression guard**: focused test/eval that proves the bad behavior does not return. |
| 231 | + |
| 232 | +## Testing Guidance |
| 233 | + |
| 234 | +Use the lowest faithful layer: |
| 235 | + |
| 236 | +- Unit tests for pure logic, schemas, tool handlers, SQL safety, artifact creation. |
| 237 | +- Integration tests for agent loop behavior, streaming, SSE ordering, state restoration, decoration. |
| 238 | +- Real evals for model routing and tool-choice behavior. |
| 239 | + |
| 240 | +Do not write tests that bake in model internals: |
| 241 | + |
| 242 | +- Avoid exact final-answer prose unless the formatter is deterministic. |
| 243 | +- Avoid exact tool order unless order is the harness contract. |
| 244 | +- Do not hard-code a mocked model tool choice to prove a model-routing bug. |
| 245 | +- Grade end-state and contracts: tool was available, tool was called/not called, args shape is correct, |
| 246 | + artifact exists, invalid input is rejected, no bridge call is emitted. |
| 247 | + |
| 248 | +## Review Checklist Before Finishing a Fix |
| 249 | + |
| 250 | +Ask these before finalizing: |
| 251 | + |
| 252 | +- Did I reproduce the defect at the right layer? |
| 253 | +- Did the test fail before the fix for the right reason? |
| 254 | +- Did I remove, rather than add, deterministic pathing where possible? |
| 255 | +- Did I avoid content/word-based routing? |
| 256 | +- Did I avoid hidden identifier conversion or source precedence? |
| 257 | +- Did I avoid broad final-answer surgery? |
| 258 | +- Did I keep schema validation as validation, not normalization? |
| 259 | +- Did I leave unrelated user changes alone? |
| 260 | +- Did I update tests/evals to guard the actual contract? |
| 261 | +- Did I run `bun run typecheck` and the relevant Bun tests? |
| 262 | + |
| 263 | +## When To Stop And Re-Plan |
| 264 | + |
| 265 | +Stop and re-plan if: |
| 266 | + |
| 267 | +- The only fix you can think of is `includes(...)`, `match(...)`, or a regex on model/tool prose. |
| 268 | +- The test can only be made red by mocking the model into a bad call. |
| 269 | +- The fix needs to special-case a single observed answer. |
| 270 | +- The code must silently guess what the model meant. |
| 271 | +- The branch adds a second source of truth for identifiers, table names, or widget mappings. |
| 272 | +- A prompt change grows into a long incident-specific explanation. |
| 273 | +- You are about to remove user text from the final answer because it "looks duplicated". |
| 274 | + |
| 275 | +In these cases, find the root contract. Usually it is one of: |
| 276 | + |
| 277 | +- Wrong tool description. |
| 278 | +- Ambiguous prompt rule. |
| 279 | +- Missing schema validation. |
| 280 | +- Missing typed payload. |
| 281 | +- UI renderer issue. |
| 282 | +- Test/eval at the wrong layer. |
| 283 | + |
| 284 | +## Commit Discipline |
| 285 | + |
| 286 | +- Do not commit unless the user asks. |
| 287 | +- If committing, stage only files relevant to the fix. |
| 288 | +- Never include unrelated dirty files or generated logs by accident. |
| 289 | +- Mention untracked or intentionally excluded files in the final response. |
0 commit comments