Friction: wire remediate stage — parse friction first, strip-before-findings, artifact write #228

Closed
opened 2026-08-16 22:12:20 +00:00 by david · 1 comment
Owner

Context

The remediate stage's final message is both the annotated findings document (the exit condition that parseRemainingFindings / validateFindings consume) and, after this issue, a friction report. The two must never collide: a friction entry shaped like - [ ] … would be misparsed as an unresolved finding. The safe design (per docs/adr/016-friction-logging.md) is:

  1. the ## Friction section is appended after the annotated findings;
  2. pi-loop parses the friction section first;
  3. pi-loop strips the section, then parses/validates findings on the remainder.

Depends on the foundation issue (parser, strip helper, artifact service, renderer).

Contracts (from the foundation issue)

  • ## Friction section appended last in the final message — i.e. after the last findings severity section (## Low).
  • Entry format: - category: <enum> + indented description, impact, resolution, optional ref.
  • Never use checkbox syntax in friction entries; the strip-before-findings step additionally guarantees nothing leaks.
  • Per-run artifacts: .pi-loop/runs/<run-id>/friction.json (provenance stamped stage: "remediate") + rendered FRICTION.md.

Changes

  1. src/remediate/helpers/buildRemediateWrapper.ts — add the friction contract to the wrapper (keep the existing annotated-findings exit condition text intact):

    • "After the annotated findings document, end your final message with a ## Friction section."
    • Entry format example + field list + category enum + resolution enum.
    • "Never use checkbox syntax in friction entries." + "Never place the friction section between severity headings — the annotated findings must remain intact."
    • Low reporting bar (same definition as implement): any event that cost meaningful time or influenced decisions — doc/requirements gaps, build/test/lint failures, tool/git problems, stuck loops/approach changes, workarounds, findings you could not resolve. If none, omit the section.
  2. src/skills/remediateSkill.ts — add a short "Log friction" reminder (same wording style as implement).

  3. src/remediate/services/runRemediateStage.ts — wire capture + parse + strip + artifacts:

    • The session result is already captured: const { text: annotatedFindings } = await runStageFn({ ... });.
    • Parse friction first: const frictionResult = parseFriction(annotatedFindings); (log warnings).
    • Strip: const findingsOnly = stripFrictionSection(annotatedFindings);.
    • Findings on the remainder: const remainingFindings = parseRemainingFindings(findingsOnly); and const { wellFormed } = validateFindings(findingsOnly);not on the raw text.
    • Write artifacts: writeFriction(runDir, artifact) (stamped stage: 'remediate', runId, model, recordedAt) + writeFrictionMd(...).
    • Short-circuit path (no unchecked findings → session skipped): write an empty friction artifact (entries: [], wellFormed: true) + rendered FRICTION.md stating "No friction recorded.", so per-run friction files always exist.
    • Add friction: { entries: FrictionEntry[]; wellFormed: boolean } to RunRemediateStageResult.
  4. src/remediate/types/remediate.ts — extend RunRemediateStageResult with the friction field (import types from src/friction/types).

Acceptance criteria

  • Final message = findings + friction section → remainingFindings and validateFindings.wellFormed are computed on the stripped text and are identical to what they would be for the findings-only message.
  • A friction entry written with - [ ] … syntax inside the friction section is never counted in remainingFindings (regression-tested).
  • Friction with unknown/malformed entries → wellFormed: false + warnings; findings parsing unaffected; stage still succeeds.
  • No-findings short-circuit writes an empty friction artifact + FRICTION.md.
  • The annotated-findings exit condition text in the wrapper is preserved (existing wrapper tests updated).
  • npm run lint and npm test green.

Unit tests (add alongside the seams)

  • runRemediateStage.test.ts — mock runStageFn:
    • findings + friction → friction parsed, remainingFindings computed on stripped text, artifacts written, result carries friction;
    • findings only → empty friction artifact, wellFormed: true;
    • checkbox-collision regression: friction section containing - [ ] fooremainingFindings unchanged (empty), friction entry ignored;
    • malformed friction → wellFormed: false, stage succeeds;
    • short-circuit path (no unchecked findings) → empty friction artifact written.
  • buildRemediateWrapper.test.ts — friction contract present; annotated-findings exit condition still present.
  • Interaction test with parseRemainingFindings + validateFindings on stripped vs raw text.

Dependencies

  • Foundation issue (parser, strip helper, artifact service, renderer) merged first.
  • No new seams needed in runRemediateStage beyond runStageFn (already injectable).
## Context The remediate stage's final message is **both** the annotated findings document (the exit condition that `parseRemainingFindings` / `validateFindings` consume) and, after this issue, a friction report. The two must never collide: a friction entry shaped like `- [ ] …` would be misparsed as an unresolved finding. The safe design (per `docs/adr/016-friction-logging.md`) is: 1. the `## Friction` section is appended **after** the annotated findings; 2. pi-loop parses the friction section **first**; 3. pi-loop **strips** the section, then parses/validates findings on the remainder. Depends on the **foundation** issue (parser, strip helper, artifact service, renderer). ## Contracts (from the foundation issue) - `## Friction` section appended **last** in the final message — i.e. after the last findings severity section (`## Low`). - Entry format: `- category: <enum>` + indented `description`, `impact`, `resolution`, optional `ref`. - **Never use checkbox syntax in friction entries**; the strip-before-findings step additionally guarantees nothing leaks. - Per-run artifacts: `.pi-loop/runs/<run-id>/friction.json` (provenance stamped `stage: "remediate"`) + rendered `FRICTION.md`. ## Changes 1. **`src/remediate/helpers/buildRemediateWrapper.ts`** — add the friction contract to the wrapper (keep the existing annotated-findings exit condition text intact): - "After the annotated findings document, end your final message with a `## Friction` section." - Entry format example + field list + category enum + resolution enum. - "Never use checkbox syntax in friction entries." + "Never place the friction section between severity headings — the annotated findings must remain intact." - Low reporting bar (same definition as implement): any event that cost meaningful time or influenced decisions — doc/requirements gaps, build/test/lint failures, tool/git problems, stuck loops/approach changes, workarounds, findings you could not resolve. If none, omit the section. 2. **`src/skills/remediateSkill.ts`** — add a short "Log friction" reminder (same wording style as implement). 3. **`src/remediate/services/runRemediateStage.ts`** — wire capture + parse + strip + artifacts: - The session result is already captured: `const { text: annotatedFindings } = await runStageFn({ ... });`. - **Parse friction first:** `const frictionResult = parseFriction(annotatedFindings);` (log warnings). - **Strip:** `const findingsOnly = stripFrictionSection(annotatedFindings);`. - **Findings on the remainder:** `const remainingFindings = parseRemainingFindings(findingsOnly);` and `const { wellFormed } = validateFindings(findingsOnly);` — **not** on the raw text. - Write artifacts: `writeFriction(runDir, artifact)` (stamped `stage: 'remediate'`, `runId`, `model`, `recordedAt`) + `writeFrictionMd(...)`. - **Short-circuit path** (no unchecked findings → session skipped): write an empty friction artifact (`entries: []`, `wellFormed: true`) + rendered `FRICTION.md` stating "No friction recorded.", so per-run friction files always exist. - Add `friction: { entries: FrictionEntry[]; wellFormed: boolean }` to `RunRemediateStageResult`. 4. **`src/remediate/types/remediate.ts`** — extend `RunRemediateStageResult` with the `friction` field (import types from `src/friction/types`). ## Acceptance criteria - Final message = findings + friction section → `remainingFindings` and `validateFindings.wellFormed` are computed on the stripped text and are **identical** to what they would be for the findings-only message. - A friction entry written with `- [ ] …` syntax inside the friction section is **never** counted in `remainingFindings` (regression-tested). - Friction with unknown/malformed entries → `wellFormed: false` + warnings; findings parsing unaffected; stage still succeeds. - No-findings short-circuit writes an empty friction artifact + FRICTION.md. - The annotated-findings exit condition text in the wrapper is preserved (existing wrapper tests updated). - `npm run lint` and `npm test` green. ## Unit tests (add alongside the seams) - `runRemediateStage.test.ts` — mock `runStageFn`: - findings + friction → friction parsed, `remainingFindings` computed on stripped text, artifacts written, result carries `friction`; - findings only → empty friction artifact, `wellFormed: true`; - **checkbox-collision regression**: friction section containing `- [ ] foo` → `remainingFindings` unchanged (empty), friction entry ignored; - malformed friction → `wellFormed: false`, stage succeeds; - short-circuit path (no unchecked findings) → empty friction artifact written. - `buildRemediateWrapper.test.ts` — friction contract present; annotated-findings exit condition still present. - Interaction test with `parseRemainingFindings` + `validateFindings` on stripped vs raw text. ## Dependencies - Foundation issue (parser, strip helper, artifact service, renderer) merged first. - No new seams needed in `runRemediateStage` beyond `runStageFn` (already injectable).
david closed this issue 2026-08-17 02:24:19 +00:00
Author
Owner

pi-loop opened and merged a pull request for this issue: #243

pi-loop opened and merged a pull request for this issue: https://git.excelera.net/david/pi-loop/pulls/243
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
david/pi-loop#228
No description provided.