feat(save): generate the questions and names a context should be found by - #80
Merged
Merged
Conversation
…d by #79 added the storage; nothing filled it. This is the half that does the work, and it does it at save time on purpose: no plugin process has a model, but the session doing the saving does, and it has just read the material. Doing the language work once, where it is free, beats doing it on every question, where it is impossible. Every host's save document now asks for two lists alongside the routing description: the questions this context should answer, in the words a user would type rather than the words the profile uses, and the names that appear in this work and rarely anywhere else. Both are matched against and never shown, which the documents say outright — a list written for a reader is a worse list than one written to be matched. Omitting them on an update leaves the stored lists alone, so a save from a host or a turn that generated none cannot wipe what an earlier one wrote. pi takes a save as tool arguments rather than a capture file, so its tool schema and runtime carry the fields directly. A test sweeps every host's save document for both fields and both rules. The storage was shared core and arrived everywhere at once; this instruction is per host, and a host that forgot to ask would store contexts that are quietly harder to find than the same work saved elsewhere.
tanglearncode
added a commit
that referenced
this pull request
Aug 8, 2026
The lists added in #79 travel inside the bundle, and the guidance in #80 told the model to fill them with "commands, hosts, people". Hostnames and people mean nothing on anyone else's machine, and asking for people contradicts the save command's own rule against writing unnecessary personal information. A matching list is the easiest place for a context to quietly stop being shareable: an absolute path looks like a usefully rare term while it is written, and is worthless or revealing the moment the bundle is handed over. The hosts had also drifted apart — three asked for hosts and people, two asked for neither — so the same work saved from different hosts produced different bundles. One wording everywhere now, asking for names that belong to the subject: services, components, repositories, ticket ids and prefixes, error strings, product and system names. Each document states that the lists travel and must stay free of absolute paths, home directories, usernames, personal names and email addresses. If the work genuinely is about a particular environment, the profile is where that belongs and is untouched. What changes is that the matching index is not. And a filter, so none of this rests on prose: entries carrying a drive letter path, a UNC path, ~/, /home/, /Users/, /root/, or an email address are dropped from both lists at save time. Ordinary domain terms that merely look technical are kept, which the tests pin.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#79 added the storage. Nothing filled it. This is the half that does.
Why at save time
No plugin process has a model. The session doing the saving does, and it has just read the material. Doing the language work once, where a model is already present and free, beats trying to do it on every question, where it is impossible. That asymmetry is the most useful property this architecture has.
What changes
Every host's save document now asks for two lists alongside the routing description:
routingQuestions— 10 to 15 questions this context should answer, in the words a user would type rather than the words the profile uses. Including the vague ones.routingEntities— the names that appear in this work and rarely anywhere else: services, repos, ticket ids, error strings, commands, hosts.Both documents state outright that neither list is ever shown. That matters: a list written for a reader is a materially worse list than one written to be matched against.
Omitting them on an update leaves the stored lists alone, so a save from a host — or a turn — that generated none cannot wipe what an earlier save wrote.
pi is the one host that takes a save as tool arguments rather than a capture file, so its tool schema and runtime carry the fields directly. Every other host passes the capture straight through, so no CLI change was needed.
Tests
A sweep over every host's save document, asserting both fields and both rules (never shown; omitting leaves stored lists alone). This is the guard that matters here: the storage is shared core and arrived everywhere at once, but this instruction is per host — and a host that forgot to ask would store contexts that are quietly harder to find than the same work saved from another host, with nothing failing anywhere to say so. It is the same shape of bug the command sweep already exists for.
Behavioural tests on pi's save path for both directions: supplied lists are stored, and a save that says nothing leaves them intact.
That second one is deliberate. Diff coverage reported these lines covered before I wrote it —
if (x !== undefined) capture.x = x;counts as run when the condition is merely evaluated. Line coverage cannot tell that branch from the other one, so the assertion has to.Diff coverage: 31 changed lines, 0 uncovered. Full suite 409 passing.