Friction: aggregate recompute at end of run (.pi-loop/friction/) + console Friction: line #229
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Cross-run learning (the primary purpose of the friction feature) needs a derived aggregate that is a pure function of the run directories — recomputed from scratch at the end of every run (success or failure), idempotent, and resume-safe by construction (no incremental merge state, no dedup bugs). The aggregate is the primary interface for the external analysis tool (capture now, defer the action — see
docs/adr/016-friction-logging.md), so it carries aschemaVersionand stable machine-first JSON.Depends on the foundation issue (artifact service, validator, renderer) and the implement/remediate wiring issues (they produce the per-run
friction.jsonfiles the aggregator scans).Aggregate contract
Derived files (recomputed, never hand-edited):
.pi-loop/friction/friction.json:.pi-loop/friction/FRICTION.md— human-readable summary: per-category counts (table or list), per-stage counts, then the recent entries rendered as blocks.Semantics:
.pi-loop/runs/*/friction.json(read + validate each; skip invalid files with a warning — never throw).recentEntriessorted byrecordedAtdesc, ties broken by runId asc;byCategory/byStage/byModelkeys sorted).Changes
src/friction/services/aggregateFriction.ts(new) —aggregateFriction(runsRoot: string, aggregateDir: string): void:.pi-loop/runs/*/friction.json(directories only; stable sorted order).validateFrictionArtifacteach; skip invalid with a warning.generatedAt)..pi-loop/friction/friction.json(pretty JSON) +.pi-loop/friction/FRICTION.md(via a summary renderer).mkdir -pthe aggregate dir (usefs.mkdirSync(dir, { recursive: true })).runsRoot/aggregateDiras parameters (callers derive them from the repo root) so tests can use temp dirs.src/friction/helpers/renderFrictionSummaryMd.ts(new) —renderFrictionSummaryMd(aggregate): string:# Friction Summarytitle,generatedAt+runs+totalEntriesline, per-category table (Category | Count), per-stage line, per-model line, then "## Recent entries" blocks (each: runId, stage, recordedAt, category, description, impact, resolution, ref if present), or "No friction recorded yet." whentotalEntries === 0.src/orchestrator/services/runPipeline.ts— in the finalization path that runs on both success and failure (after the final summary is built), callaggregateFriction(runsRoot, frictionAggregateDir)inside a try/catch that logs and never throws (non-gating). Do not add it to any resume stage-skip logic. Ensure the failure path (e.g. agent failure witherror.json) also recomputes so partial-run friction is captured.src/orchestrator/helpers/buildFinalSummary.ts— add aFriction:line to the final summary:Friction: 7 events (3 categories)whentotalEntries > 0(categories = number of keys inbyCategory);Friction: no eventswhentotalEntries === 0;Friction: aggregate unavailablewhen the recompute failed or the aggregate is missing.Acceptance criteria
.pi-loop/friction/friction.json+FRICTION.mdexist and validate against the aggregate schema.friction.jsonis skipped without throwing; other runs still aggregate.Friction:line in all three states.buildMrDescription/ write-back code).npm run lintandnpm testgreen.Unit tests (add alongside the seams)
aggregateFriction.test.ts— empty runs dir; single run; multiple runs; invalid friction.json skipped; per-category/per-stage/per-model counts;recentEntriesordering (recordedAt desc, runId asc tiebreak); idempotency (run twice → identical bytes); resume-safety (partial dirs);schemaVersionfield; summary renderer (entries + empty).runPipeline.test.ts— recompute hook called on success path and on failure path; aggregation failure does not fail the run (non-gating);Friction:line in the summary (with events / no events / unavailable).Dependencies
runPipelineinjectable seams for tests.pi-loop opened and merged a pull request for this issue: #244