Cross-language prior-art labelling + close the surfaced→pulled loop on get_task #87

Merged
bvandeusen merged 2 commits from dev into main 2026-07-30 11:03:57 -04:00
Owner

Two commits. CI green on dev head 6ca215d (run 3078, all six jobs).

390846a — fix(write-path): disclose cross-language prior art (#2244)

Retrieval matches on concept, and concepts are language-agnostic: a query about a
TypeScript union-find scores 0.72–0.73 against a Python snippet, over the 0.68
bar. Useful — a different-language solution gives you the shape — but the menu line
said nothing about it, so the reader either dismissed a good structural reference
or pasted Python into a .ts file.

This predates the concept-query change: raw TS code already matched the Python
snippet at 0.73, because the embedder reads identifiers and structure
semantically rather than syntactically.

> - #2222 [similar 0.72 · python] "group_pairs — Collapse ..."

plus one explanatory line, shown only when something on the menu is tagged.

Label, don't filter — a stricter bar for foreign hits would suppress exactly
the shape-borrowing this is for. Only claim a mismatch you can establish
unknown extension or unrecorded language means no tag.

6ca215d — fix(telemetry): record a pull on get_task (#2245)

note_usage_events recorded surfaced for every auto-inject line but pulled
only from get_note / get_snippet / the REST snippet route. Auto-inject ranks
kind-blind over a corpus that is overwhelmingly tasks, so the loop was open
exactly where the volume is — measured live, "write a function to debounce a
callback in the frontend" returned three tasks and zero snippets, none of which
could record a pull.

That rate is what #1038 and #2085 gate on, so the metric would have said
"auto-inject surfaces things nobody opens" — an artifact of instrumentation
pointing at a plausible wrong conclusion.

Guarded by a rule-#33 source-inspection test, because a missing side effect is
invisible to behavioural testing.

Deploy notes

No migration. No plugin change, so no manifest bump — server-side only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs

Two commits. CI green on `dev` head `6ca215d` (run 3078, all six jobs). ## `390846a` — fix(write-path): disclose cross-language prior art (#2244) Retrieval matches on concept, and concepts are language-agnostic: a query about a TypeScript union-find scores 0.72–0.73 against a **Python** snippet, over the 0.68 bar. Useful — a different-language solution gives you the shape — but the menu line said nothing about it, so the reader either dismissed a good structural reference or pasted Python into a `.ts` file. This predates the concept-query change: raw TS code already matched the Python snippet at 0.73, because the embedder reads identifiers and structure semantically rather than syntactically. > - #2222 [similar 0.72 · python] "group_pairs — Collapse ..." plus one explanatory line, shown only when something on the menu is tagged. **Label, don't filter** — a stricter bar for foreign hits would suppress exactly the shape-borrowing this is for. **Only claim a mismatch you can establish** — unknown extension or unrecorded language means no tag. ## `6ca215d` — fix(telemetry): record a pull on get_task (#2245) `note_usage_events` recorded `surfaced` for every auto-inject line but `pulled` only from `get_note` / `get_snippet` / the REST snippet route. Auto-inject ranks kind-blind over a corpus that is overwhelmingly tasks, so the loop was open exactly where the volume is — measured live, "write a function to debounce a callback in the frontend" returned three tasks and zero snippets, none of which could record a pull. That rate is what #1038 and #2085 gate on, so the metric would have said "auto-inject surfaces things nobody opens" — an artifact of instrumentation pointing at a plausible wrong conclusion. Guarded by a rule-#33 source-inspection test, because a missing side effect is invisible to behavioural testing. ## Deploy notes No migration. No plugin change, so no manifest bump — server-side only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
bvandeusen added 2 commits 2026-07-30 11:03:42 -04:00
fix(write-path): disclose cross-language prior art instead of hiding it
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 16s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Has been skipped
CI & Build / TypeScript typecheck (push) Failing after 2s
CI & Build / Python lint (push) Successful in 3s
390846a3d5
Closes #2244. Retrieval matches on concept, and concepts are language-agnostic:
asking about a TypeScript union-find scores 0.72-0.73 against a PYTHON snippet,
comfortably over the 0.68 bar. That is useful — a different-language solution
gives you the shape even when the code isn't reusable — but the menu line said
nothing about it, so the reader either dismissed a good structural reference or
pasted Python into a .ts file.

Worth noting this predates the concept-query change: raw TS code already matched
the Python snippet at 0.73, because the embedder reads identifiers and structure
semantically rather than syntactically. The fail state has been shipping quietly;
#2242 only made it an intended use rather than an accident.

- knowledge._note_to_item projects `language` from the data mirror, same shape as
  the existing verification projection — a plain column read, no body parsing.
- The semantic arm carries language through on the item it builds; it is the arm
  where these arise, since a snippet recorded AT the path you're editing is
  almost never in another language.
- _prior_art_line folds it into the marker: [similar 0.72 · python]. Together
  with the score rather than after the title, because the two jointly are the
  judgement being offered.
- One explanatory line is added to the menu, and only when something on it is
  actually tagged.

Two deliberate calls:

LABEL, DON'T FILTER. A stricter threshold for foreign-language hits would
suppress exactly the shape-borrowing this exists for. They were never the
problem; their being undisclosed was.

ONLY CLAIM A MISMATCH YOU CAN ESTABLISH. _foreign_language returns "" when either
side is unknown — unrecognised extension, or a snippet with no recorded language.
A wrong "· python" is worse than no tag. Same-language hits stay unlabelled, so
the common case keeps a clean line and the preamble stays off the menu entirely.
Operator-typed language names fold through an alias table first (py/python3 →
python, tsx → typescript, c++ → cpp); unrecognised names pass through lowercased,
which still makes an unknown-but-equal pair compare equal.

Trap found while building: _note() in the tests is a MagicMock, so `note.data`
auto-created a truthy mock that would have rendered its repr into a menu line.
Both test helpers now set data = None explicitly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
fix(telemetry): record a pull on get_task, closing the surfaced→pulled loop
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / TypeScript typecheck (push) Successful in 10s
CI & Build / integration (push) Successful in 12s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build & push image (push) Successful in 39s
6ca215d2b6
Closes #2245. note_usage_events recorded `surfaced` for every auto-inject menu
line regardless of kind, but `pulled` only from get_note, get_snippet and the
REST snippet route. get_task recorded nothing.

Auto-inject ranks kind-blind over a corpus that is overwhelmingly tasks and
issues, so tasks are most of what it surfaces. Measured live, "write a function
to debounce a callback in the frontend" returned three tasks and zero snippets —
all three written as surfaced, none able to record a pull.

surfaced and pulled only mean anything as a PAIR; the rate between them is what
#1038 and #2085 gate on. So the gap sat exactly where the volume is, and the
metric would have said "auto-inject surfaces things nobody opens" for its own
dominant kind — an artifact of the instrumentation, not a fact about the feature,
and one that pointed at a plausible-sounding wrong conclusion.

get_note already carried a comment stating this was meant to cover ANY note kind
precisely so tasks wouldn't look like dead weight. get_task is a separate tool in
a separate module and never got the call — sibling drift, invisible because a
missing side effect changes no return value.

Guarded by a rule-#33 contract test that asserts, by source inspection, that
every getter reachable from an auto-inject menu calls record_pulled. Source
inspection because no behavioural test can see a call that isn't there.

Not fixed here: the REST note/task detail routes still record nothing while the
REST snippet route records `rest_snippet`. That asymmetry is real, but a human
reading a note in a browser is arguably not the same event as an agent recalling
one, and collapsing them could skew the signal the other way. Raised as a
question for the retrieval survey instead of decided in passing.

Pre-fix rows under-count task pulls, one-sidedly by kind — treat them as unknown
rather than zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
bvandeusen merged commit 1adf57739d into main 2026-07-30 11:03:57 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledScribe#87