Skip to content

feat: project scoping for notes, export drift detection, per-kind decay defaults, hygiene nudges - #184

Merged
navbytes merged 5 commits into
mainfrom
claude/nt-design-review-a8xugr
Aug 15, 2026
Merged

feat: project scoping for notes, export drift detection, per-kind decay defaults, hygiene nudges#184
navbytes merged 5 commits into
mainfrom
claude/nt-design-review-a8xugr

Conversation

@navbytes

@navbytes navbytes commented Aug 15, 2026

Copy link
Copy Markdown
Owner

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 index was exact case-sensitive, recall tokenized, dedup lowercased), so --project WTCockpit was recallable but not indexable as wtcockpit.
  • MCP nt_index and nt_search gain a project filter. An agent over MCP previously had no project-scoped retrieval at all — the CLI-only nt index --project was the single hard filter, and strict-args turned an MCP attempt into an error. nt_search/nt search accept project alone (bare scope listing), since project: frontmatter is deliberately invisible to the text match.
  • nt search --project on the CLI; tasks filter by +project with the same fold.
  • Repair path for mis-scoped notes: nt edit --project <name|none> and the same project field on nt_note_edit (note.SetProject preserves adjacent unmodeled frontmatter).
  • nt tags now prints the project vocabulary (notes' project: + tasks' +project, case-folded) alongside tags, with a --projects mode; the default --json stays [{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 --out records target, selection, and content hash in $NT_DIR/export-state.json (hand-editable, beside views.json). nt doctor re-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-distill or added by /nt-learn no longer silently never reaches CLAUDE.md. Docs now recommend compiling into a dedicated file and @-importing it (exporting over CLAUDE.md itself is a full-file overwrite).

Per-kind decay defaults

A [decay] section in config.toml (lesson = "180d", ref = "270d", …) stamps a default half_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_life always wins; unparseable values stamp nothing and are reported by nt doctor. The AgeBasis doc 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) and nt_index (warning field) — nags once near-duplicate pairs (≥3) or reclaimable notes (≥5) accumulate, pointing at nt doctor/nt distill/nt gc (surface-appropriate: MCP points at nt_distill). The O(n²) pair scan is gated behind note.HygieneScanMaxNotes so 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

  • SPEC §5 documents the project: frontmatter contract (it was implemented but absent from the note-format schema); §7.3/§11 pick up the new flags and [decay].
  • README's recall claim now matches the implementation (a deliberately loose precision floor + confidence tiers, not "returns nothing when nothing is relevant"), and its TUI blurb reflects the new tab order.
  • The nt skill finally mentions $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

  • New tests: CLI project scoping/edit/tags/hygiene (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.
  • Full go test ./internal/... green; -race clean on all touched packages; gofmt/go vet clean.
  • A self-review pass over the branch produced three findings (ungated O(n²) scan, stale nt_search description, duplicated project-membership helper), all fixed in 604724f.

claude added 5 commits August 15, 2026 15:14
…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
@navbytes
navbytes merged commit 4ed88ff into main Aug 15, 2026
9 checks passed
@navbytes
navbytes deleted the claude/nt-design-review-a8xugr branch August 15, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants