Repository navigation
Flag learnings that name code that's gone - #731
anirudhkumar-nanonets wants to merge 2 commits into
Conversation
A learning whose touches: lists a file the checkout no longer has, or a file#Symbol no code-map symbol is named, is shown as "may be stale" in trail ask, MCP find_code, the prompt and file-touch hooks, and the session-start standing list. query gets a stale count next to notes. trail check flags notes.ts:419 twice; both are word matches: the line passes the learning's id to --replaces (what the replaces learning decided), and the Stop-hook check-name learning is unrelated.
🌱 graft blast radius5 areas changed → 10 areas can be affected. 31 dependent symbols, depth 2. flowchart TB
A0(("Session State Management<br/>10 symbols"))
A1(("Cloud Collaboration<br/>7 symbols"))
A2(("Token Query Interface<br/>3 symbols"))
A3(("MCP Tool Invocation<br/>2 symbols"))
A4(("Contextual Notes<br/>2 symbols"))
AX(("5 smaller areas<br/>7 symbols"))
classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
class A0,A1,A2,A3,A4 reached;
classDef tail fill:#EEF2F3,stroke:#9AA4A9,stroke-width:1px,color:#3A4247;
class AX tail;
Who knows this code — 3 people across 15 areas
Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no All 31 dependent symbols, grouped by areaSession State Management — 10 symbols in 10 files
Cloud Collaboration — 7 symbols in 4 files
Token Query Interface — 3 symbols in 1 file
MCP Tool Invocation — 2 symbols in 1 file
Contextual Notes — 2 symbols in 2 files
Initialization — 2 symbols in 2 files
Event Telemetry — 2 symbols in 2 files
Engine Core — 1 symbol in 1 file
CLI Skills — 1 symbol in 1 file
Learning Sessions — 1 symbol in 1 file
Test signal per changed area — 2 ✓ · 1 ✗ · 2 –Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.
60 test suites also reference this code63 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.
Open the interactive graph → — click an area to see its dependent symbols at file:line. |
Trail Cloud has no code, so the CLI does the check and sends the gone touches per learning (`missing`, [] when all are there); the sync goes again when that list changes. The symbol check reads the touched file's text instead of the code map, the rule both sides now state the same way, and drops the 0.4s map load from hooks.
Issue: committed learnings name files and functions that were deleted since, and Claude was told to follow them as written.
Why: trail showed every learning as saved; nothing compared the code a learning names with the code that's there now.
Fix: each learning about to be shown has its
touches:checked against the code in the checkout (no LLM call); one that names gone code is marked "may be stale", with what's gone andtrail note --replaces <id>. The same list goes to Trail Cloud at sync.Moves: learnings reused, measured as
queryevents whosenotesis 1 or 2 (atrail askthat showed learnings), PostHog via Metabase db 11 (posthog_event_full_view): 136,557 from 1,074 people in the 7 days to 2026-10-29 12:15 IST, out of 193,148 asks in repos that keep learnings. This PR addsstale(0/1/2) next tonotes, so from the next release the same event counts how many shown learnings were stale. (learning_reusedisn't used: it left the code in #658.)What Claude sees
Shown in:
trail ask, MCPtrail_find_code, the prompt hook, the file-touch hook, and the standing learnings at session start (as an indentedmay be stale:line).The rule (one place; Trail Cloud shows what the CLI sends)
Only a learning's
touches:is judged, entry by entry:pathis gone when the checkout has no file or folder there.path#Symbolis gone when its path is, or when that file's text no longer has the name as a whole word (last part ofType.method,~Nstripped).Trail Cloud: it has no copy of the code, so
PUT /api/cli/learningsnow carriesmissingon each HEAD learning: the gone touches verbatim,[]when all are there, left out for branch copies and skills. The sync fingerprint includesmissing, so a deleted file re-sends unchanged learnings (every repo re-syncs once after upgrade, since the fingerprint changed). Today's server ignores the field (Goencoding/json, noDisallowUnknownFields), so this is safe before the trail-cloud side ships. The rule was agreed with the trail-cloud "Stale-learning cleanup" session.What I chose, and why
touches:only, not names in the text. Checking backticked names in the body against code-map symbols flagged 10 of 38 real learnings (Trail-Code + assign), and 9 of those names were still in the code: properties (costUsd), module variables (livePair), Node built-ins (execFileSync).touches:only ever holds what existed when the learning was saved, so a miss there is real: on this repo's main checkout, 3 of 29 learnings are flagged, all for the deletedsrc/ask/jev-ask.ts.query, notlearning_reused.stalesits besidenotes; allowlisted insrc/telemetry/contract.tsandTELEMETRY.md.trail check
Exits 1 with 2 conflicts on
src/notes/notes.ts:419, both word matches:--replaces, which is what that learning decided.The learning this PR first added describes the code-map rule; the second one replaces it (
--replaces l-d70a9e57), since learnings aren't edited.Tests
test/trail-stale-learnings.test.ts(8 tests): file and folder present vs gone, symbol as a whole word vs gone, unmerged-branch learning, theaskline, the session-start line, the sync payload'smissingand the re-sync when it changes, the telemetry key.trail-upload-safety,trail-cloud,workspace-sync-skills) pass.npm test(scratch HOME): 4,143 tests, 4,131 pass, 3 fail. All 3 also fail on origin/main (19865ff) on this machine: two graft-to-trail moves and the "machine without trail" install line, which depend on whether trail is installed here.Follow-ups
trail noterecords only files intouches:, so a learning that names a function only in its text is flagged when the file goes, not when the function does. Recordingfile#Symbolfor backticked names the graph knows (ascodeAnchorsalready does for init) closes that.trail check?🤖 Generated with Claude Code