feat: project scoping for notes, export drift detection, per-kind decay defaults, hygiene nudges - #184
Merged
Merged
Conversation
…e nudges
Code-review follow-ups making notes' project dimension first-class at query
time and the store's maintenance loops self-surfacing:
- project: one case-insensitive comparison (note.ProjectKey/SameProject) shared
by every surface; nt_index/nt_search gain the project filter (MCP previously
had NO project-scoped retrieval and strict args made the attempt an error);
nt search --project on the CLI; nt edit --project / nt_note_edit project
("none" clears) to fix mis-scoped notes; nt tags [--projects] lists the
project vocabulary so "check the store's vocabulary" is actionable.
- export drift: nt export --out records target + selection + hash in
export-state.json; nt doctor re-renders each recorded selection and warns
(with the exact refresh command) when a compiled file no longer matches the
store — the compiled-rules staleness trap, previously invisible.
- decay: config [decay] <kind> = "180d" stamps a default half_life on new
notes of that kind (explicit value wins; stamped frontmatter, never an
invisible read-time rule); doctor reports unparseable values; AgeBasis doc
now states the mtime caveat (git checkout resets decay clocks).
- hygiene: the unscoped index (CLI stderr, nt_index warning) nags once
near-duplicate pairs or reclaimable notes cross shared thresholds; gc's
default retention is one constant shared with doctor instead of two
hardcoded 30-day literals.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LWGyQgqF8KTzDfrRGqtwTG
…aults - SPEC §5 gains the project: frontmatter contract (set/clear, case-insensitive comparisons, hard filter vs recall's soft boost, unscoped = domain knowledge); §7.3 command lines and §11 config pick up the new flags and [decay] section. - claude-integration: compile rules into a dedicated file and @-import it from CLAUDE.md — nt export --out rewrites the whole target, so exporting over CLAUDE.md itself would destroy the rest; tracked exports + doctor drift check documented. - README: recall's precision claim now matches the implementation (a loose floor for multi-concept queries + confidence tiers, not "returns nothing when nothing is relevant"). - nt skill: synonyms.txt is now discoverable at the moment of a recall miss (it existed but no skill mentioned it); search/tags/edit project additions; per-kind decay defaults; distill's closing note mentions doctor's drift backstop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LWGyQgqF8KTzDfrRGqtwTG
- Gate the proactive hygiene scan behind note.HygieneScanMaxNotes: NearDupPairs is O(n²) and the unscoped index is the hottest read — below the cap the scan is milliseconds, past it doctor remains the uncapped check. Same gate on CLI and MCP. - One shared note.AnyProject for task +project membership, replacing the identical loop implemented separately in cli and mcp — the drift ProjectKey exists to prevent. - nt_search's tool description no longer claims query/tag are the only valid filters (project alone is accepted, and is the only way to list a project's notes); the empty-project index warning points at nt tags --projects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LWGyQgqF8KTzDfrRGqtwTG
The knowledge base is the primary surface, so the notes tab now comes first and the TUI opens on it. Presentation order lives in one place (tabOrder): the header renders from it, the mouse hit-boxes are recorded in the same loop, and [ / ] / tab cycle through it — so the visual order and the cycling can never disagree. The tab enum values stay stable identities (tabCursor/tabOffset indexing), deliberately decoupled from display order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LWGyQgqF8KTzDfrRGqtwTG
…lncheck Every workflow resolves its toolchain from go.mod (go-version-file), so the 1.25.12 pin is what put GO-2026-6218/6090/6089/6088/5972 — all fixed in 1.25.13 — on the runners. Bumped in both modules (release.yml pins desktop builds from desktop/go.mod). Verified locally: govulncheck now reports zero reachable vulnerabilities in the root and desktop modules, and the full test suite passes on 1.25.13. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LWGyQgqF8KTzDfrRGqtwTG
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.
Implements the small-effort recommendations from the note-creation design review, focused on the cross-project knowledge use case: project-specific notes cleanly separable at query time, domain notes discoverable from anywhere, and the store's maintenance loops surfacing themselves instead of waiting to be run.
Project scoping — one rule, every surface
note.ProjectKey/SameProject/AnyProject: one case-insensitive, whitespace-trimmed comparison shared by index, search, and the dedup fold. Previously three surfaces matched projects three different ways (nt indexwas exact case-sensitive, recall tokenized, dedup lowercased), so--project WTCockpitwas recallable but not indexable aswtcockpit.nt_indexandnt_searchgain aprojectfilter. An agent over MCP previously had no project-scoped retrieval at all — the CLI-onlynt index --projectwas the single hard filter, and strict-args turned an MCP attempt into an error.nt_search/nt searchacceptprojectalone (bare scope listing), sinceproject:frontmatter is deliberately invisible to the text match.nt search --projecton the CLI; tasks filter by+projectwith the same fold.nt edit --project <name|none>and the sameprojectfield onnt_note_edit(note.SetProjectpreserves adjacent unmodeled frontmatter).nt tagsnow prints the project vocabulary (notes'project:+ tasks'+project, case-folded) alongside tags, with a--projectsmode; the default--jsonstays[{tag,count}](existing machine contract). The skill's "check the store's vocabulary before scoping" advice — and the index empty-match warning — are now actionable.Export drift detection
nt export --outrecords target, selection, and content hash in$NT_DIR/export-state.json(hand-editable, besideviews.json).nt doctorre-renders each recorded selection against the current store and warns — with the copy-pasteable refresh command — when the compiled file no longer matches, including "edited outside nt export" and "file is gone" cases. This closes the compiled-rules staleness trap: a rule pruned by/nt-distillor added by/nt-learnno longer silently never reachesCLAUDE.md. Docs now recommend compiling into a dedicated file and@-importing it (exporting overCLAUDE.mditself is a full-file overwrite).Per-kind decay defaults
A
[decay]section inconfig.toml(lesson = "180d",ref = "270d", …) stamps a defaulthalf_life:on new notes of that kind when the caller gives none — CLI and MCP alike. Stamped frontmatter, never an invisible read-time rule, so a config change can't silently re-rank existing notes; an explicit--half-life/half_lifealways wins; unparseable values stamp nothing and are reported bynt doctor. TheAgeBasisdoc now states the mtime caveat (any file write — notably a fresh git checkout of the store — resets decay clocks).Proactive hygiene
The unscoped session-start read —
nt index(stderr) andnt_index(warningfield) — nags once near-duplicate pairs (≥3) or reclaimable notes (≥5) accumulate, pointing atnt doctor/nt distill/nt gc(surface-appropriate: MCP points atnt_distill). The O(n²) pair scan is gated behindnote.HygieneScanMaxNotesso the hottest read never pays it on large stores; doctor remains the uncapped check. Doctor and gc now share one retention constant instead of two independently hardcoded 30-day literals.TUI: notes first
The tab order is now notes · tasks · logbook, and the TUI opens on notes — the knowledge base is the primary surface. Presentation order lives in one place (
tabOrder): the header renders from it, the mouse hit-boxes are recorded in the same loop, and[/]/ tab cycle through it, so the visual order and cycling can never disagree. The tab enum values stay stable identities (they index per-tab cursor/offset state), decoupled from display order.Docs
project:frontmatter contract (it was implemented but absent from the note-format schema); §7.3/§11 pick up the new flags and[decay].$NT_DIR/synonyms.txt— the recall-vocabulary escape hatch existed but no skill told the agent, so a miss ("gateway" vs a note saying "ingress") had no discoverable fix at the moment it happened.Testing
project_scope_test.go), export drift lifecycle (exportdrift_test.go), decay defaults (decaydefault_test.go), MCP equivalents (projectscope_test.go), the note-package helpers (projectkey_test.go), and updated TUI tab-order/click/cycling tests.go test ./internal/...green;-raceclean on all touched packages;gofmt/go vetclean.nt_searchdescription, duplicated project-membership helper), all fixed in604724f.