feat: UX-3 — project history + memesh why file attribution - #181
Merged
Conversation
…ssion metadata The chain: local git resolves which commits touched a file (log, or blame for --line), the graph answers what it remembers about them, and every gap is a TYPED abstention (not_a_git_repo, file_not_tracked, line_uncommitted, no_commit_entity, no_session_link) — never a guess. Load-bearing join: post-commit names entities commit-<ABBREV> (git's own "[branch abc1234]" line) while blame/log emit full 40-char SHAs, so exact name lookup finds nothing, ever. explainCommits prefix-matches in both directions (stored abbrev shorter than query, or an API caller's 7-char hash shorter than a longer stored abbrev). Surfaces: - src/core/why.ts — resolveFileCommits (git half, CLI-only) + explainCommits (DB half, shared). - POST /v1/why takes commit HASHES, not a repo path: the server never shells out to git; strict schema rejects any repo-path key. - memesh why <file> [--line N] renders abstentions as sentences; file-tag memories are labelled "associated, not commit-derived". - post-commit now records metadata.session_id + metadata.files (cap 50) on commit entities — the commit-to-session hop for future commits, as METADATA not tags, so pre-edit-recall's file-tag join cannot start injecting commit noise into edits. captureEntity grew an INSERT-only metadata param that cannot displace the provenance stamps. Verification (this session): - vitest tests/core/why.test.ts + tests/hooks/post-commit.test.ts: 32 passed, exit 0 - vitest tests/transports/http.test.ts: 58 passed, exit 0 - vitest tests/cli/why.test.ts (against dist): 2 passed, exit 0 - npm run typecheck: exit 0; npm run build: exit 0 (6 smoke tests passed)
… view, URL deep links
Four additions to the Project tab, each honest about what it measures:
- Capture density by category: a per-cluster histogram of created_at
(the same axis the phase strip segments on — NOT last_accessed, which
the flat fallback groups by). Named for what it shows: what memesh
captured, not everything that happened; the caveat renders next to the
title. Bar is aria-hidden ornament; title, note and legend counts
carry the information. Quiet periods render as real empty buckets —
the gap is the data. New component CaptureDensityBand, registered in
component-contracts with its own degenerate-input test file.
- Lineage overlay: supersedes (solid neutral) and contradicts (dashed
warning) arcs on the tree timeline, measured off the DOM so an arc is
only drawn between rows actually on screen; the visible legend counts
drawn arcs only. Only the two behavioural relation types are drawn —
`caused` appears in MCP prose but no writer emits it. ProjectTab now
re-admits the archived TARGETS of active supersedes/contradicts edges
(supersedes auto-archives its target on write, so the plain active
filter hid exactly the node every chain points at); general archived
noise stays out. Rule extracted as selectProjectEntities() and pinned
by its own test.
- Decisions view (ADR): one card per decision-type entity, newest
first, with a two-state status derived from the graph — superseded
when a supersedes edge points at it or it arrived archived, active
otherwise; no invented proposed/accepted lifecycle. Supersession
chains spelled out with jump links into the tree view. Expander per
DESIGN.md: aria-expanded + aria-controls, lazy body, memory voice
(serif) for titles and observations.
- URL deep links: App writes ?tab= back via history.replaceState (the
read side existed since the 5-tab shell; nothing ever wrote it), and
the Project tab reads/writes ?project= — a copied URL now shows the
reader the view being looked at.
Also: CLUSTER_DOT swatch map moved from MemoriesTab to entity-display
as the single owner (the density band is its second consumer);
ARCHITECTURE counts follow (28 commands, 33 routes) plus a core/why.ts
module line; API_REFERENCE documents POST /v1/why and memesh why.
i18n: en keys added in this commit; the ten remaining locales land in
the follow-up i18n sync commit (dashboard-i18n gate is red until then,
by design of the split).
Verification (this session):
- vitest tests/dashboard/{CaptureDensityBand,ProjectRoadmap,component-contracts}: 200 passed, exit 0
- vitest tests/dashboard/ProjectTab.select.test.ts + component-contracts: 189 passed, exit 0
- npm run typecheck: exit 0
- node scripts/check-doc-claims.mjs: exit 0
Six baseline entries moved by pure line shifts (why command inserted in cli.ts, CLUSTER_DOT block in entity-display, its removal in MemoriesTab) — re-keyed with the statement verified unchanged, not re-triaged. Nine new C5 hits are the optional-input defaults of the why chain and the lineage/ADR views (each reason states why the empty default is the honest answer, not masked failure), and three C1 hits are the new test files, each pairing emptiness with positive assertions. Verification: node scripts/audit/verification-audit.mjs exit 0 (every hit triaged) after the change; exit 1 with 15 untriaged before.
roadmap.viewDecisions / densityTitle / densityNote / lineageSupersedes /
lineageConflicts and the five adr.* keys, inserted at the same relative
position (after roadmap.moreEntities) in zh-TW, zh-CN, ja, ko, pt, fr,
de, vi, es and th, keeping the blocks structurally parallel. {n}
placeholders and the literal type value "decision" stay untranslated.
ja/ko direction words follow each locale's existing relation.supersedes
vocabulary so the ADR chain labels read as prefix + link.
Verification: npx vitest run tests/dashboard-i18n.test.ts — 20 passed,
exit 0 (run in this tree after the merge, not only in the lane
worktree); npm run typecheck exit 0.
…d changes check-generated-mirror caught the staleness: plugin-marketplace installs run dist/ as committed and never build. [Verified-By: npm run build exit=0 — '✅ All 6 smoke tests passed / Build is ready for use']
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.
UX-3 — project history +
memesh whyThe work-topology arc's history slice: the Project tab learns to tell one project's story honestly, and a new
memesh why <file>answers "why does this file look like this" from local git plus the graph — with every gap in the chain reported as a typed abstention, never guessed around.memesh why <file>(CLI +POST /v1/why)git log --follow, orgit blamefor--line N) → the graph joins each hash to the commit entity the post-commit hook captured →metadata.session_idwalks to the session's entities →file:<basename>tags collect associated memories, labelled "associated, not commit-derived".commit-<ABBREVIATED hash>(git's own[branch abc1234]output line) while blame/log emit full 40-char SHAs — an exact-name lookup finds nothing, ever. Break-tested: reverting the prefix match to equality turns 3 tests red.not_a_git_repo,file_not_tracked,git_unavailable,line_out_of_range,line_uncommitted,no_commit_entity,no_session_link): core emits codes, the CLI renders sentences. A commit memesh never saw says so; a commit captured before session recording says so.POST /v1/whytakes commit hashes from the caller; its strict schema has no repo-path field on purpose, so the server is never handed a directory to execute anything in (pinned by a test that posts a sneakycwdkey and expects 400).metadata.session_id+metadata.files(cap 50) on commit entities — metadata rather than tags, so pre-edit-recall'sfile:*join cannot start injecting commit noise into every edit. Break-tested (removing the metadata write turns the new hook test red).captureEntitygrew an INSERT-onlymetadataparam that cannot displace the provenance stamps.Project tab
created_at(the same axis the phase strip segments on), honestly named: it shows what memesh captured, not everything that happened, and the caveat renders beside the title. Bar is aria-hidden; title/note/legend counts carry the information. Quiet periods stay as real empty buckets — the gap is the data.supersedes(solid neutral) andcontradicts(dashed warning) arcs on the tree timeline, measured off the DOM so an arc only ever connects rows actually on screen; the visible legend counts drawn arcs only. Only the two behavioural relation types are drawn —causedexists in MCP prose but no writer emits it.supersedesauto-archives its target on write, so the old active-only filter hid exactly the node every chain points at.selectProjectEntities()re-admits archived entities an active one points at with a lineage edge — and only those (pinned by its own test file).active/superseded; no invented proposed/accepted lifecycle) and its supersession chain spelled out with jump links. Expander per DESIGN.md (aria-expanded/aria-controls, lazy body, memory voice for content).?tab=back (the read side existed since the 5-tab shell; nothing ever wrote it) and the Project tab reads/writes?project=, so a copied URL shows the reader the view being looked at.Docs & gates in the same PR
POST /v1/why+memesh why; ARCHITECTURE: counts follow (28 commands, 33 routes) +core/why.tsmodule line; CHANGELOG under[Unreleased].Verification (run in this session, verdicts by exit code)
Break-tests against the committed baseline (mutate → red → restore → green):
tests/core/why.test.ts3 failed (exit 1); restored → 12 passed.tests/hooks/post-commit.test.ts1 failed (exit 1); restored → 20 passed.