Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Open a full-screen dashboard displaying Git analytics: churn (commits per file),
**Export (all report dashboards).** Each report webview (Git Analytics, Hygiene Analytics, Session Briefing) offers two save paths: **↓ CSV / ↓ JSON** quick-save the report dialog-free to `.meridian/artifacts/` with a timestamped filename (per [ADR 014](./adr/014-dotdir-doctrine.md); the dir self-ignores so artifacts never enter git), and **Save as…** opens a dialog (format + location) for saving anywhere.

### **git.sessionBriefing**
Generate a session-orientation summary. Aggregates git working-tree status, recent commits, run-log activity (`recentRuns`), git analytics (`activityWindow` — including momentum trends and a commit-frequency sparkline showing the shape behind the trend arrow), hygiene scan state (`hygieneSnapshot`), and a pending-change risk preview (`pendingChangeRisk` — each uncommitted file joined against the computed analytics risk model: churn, volatility, and risk tier, with files absent from the analytics window marked `new` (no history) or `cold` (changed but quiet — low, not unknown); a flag is raised when several high-risk files are in flight), and a pending-change companion preview (`pendingChangeCompanions` — files that historically ship in the same commit as your current edits but are not in the dirty set yet, i.e. possibly-forgotten siblings such as tests/types/docs; a flag is raised when several are likely missing) into a deterministic `SessionBriefing` record, then layers optional AI prose on top. Optional slices degrade gracefully when data is unavailable; the prose layer degrades to the raw aggregate when no language model is available. Useful for standup notes, context switching, pre-commit risk triage, or morning orientation.
Generate a session-orientation summary. Aggregates git working-tree status, recent commits, run-log activity (`recentRuns`), git analytics (`activityWindow` — including momentum trends and a commit-frequency sparkline showing the shape behind the trend arrow), hygiene scan state (`hygieneSnapshot`), and a pending-change risk preview (`pendingChangeRisk` — each uncommitted file joined against the computed analytics risk model: churn, volatility, and risk tier, with files absent from the analytics window marked `new` (no history) or `cold` (changed but quiet — low, not unknown); a flag is raised when several high-risk files are in flight), and a pending-change companion preview (`pendingChangeCompanions` — files that historically ship in the same commit as your current edits but are not in the dirty set yet, i.e. possibly-forgotten siblings such as tests/types/docs; a flag is raised when several are likely missing), and a longitudinal pulse slice (`pulse` — movement since your previous briefing plus trend lines over the stored history in `.meridian/pulse/`; snapshots are captured automatically per briefing, throttled to one per 10 minutes, capped and local-only per [ADR 019](./adr/019-pulse-and-retention.md)) into a deterministic `SessionBriefing` record, then layers optional AI prose on top. Optional slices degrade gracefully when data is unavailable; the prose layer degrades to the raw aggregate when no language model is available. Useful for standup notes, context switching, pre-commit risk triage, or morning orientation.

---

Expand All @@ -41,7 +41,7 @@ Scan the workspace for cleanup candidates:
- **Log files**: stale `.log` files above age and size thresholds
- **Markdown files**: documentation artifacts for review or archival

Respects `.gitignore` and `.meridian/.meridianignore` patterns. Returns a categorized list with file paths, sizes, ages, and reasons.
Respects `.gitignore` and `.meridian/.meridianignore` patterns. Exclusions are ecosystem-aware ([ADR 018](./adr/018-ecosystem-registry.md)): envs, caches, vendored deps, and build outputs for JS/TS, Python, JVM (Maven/Gradle/Kotlin/Scala), Go, Rust, .NET, Ruby, Elixir, and more derive from a single registry (`src/ecosystems.ts`). Returns a categorized list with file paths, sizes, ages, and reasons.

### **hygiene.cleanup**
Delete specified files. Batch removal of candidates surfaced by `hygiene.scan`. Defaults to dry-run: deletion happens only with an explicit `dryRun: false`, and the user-facing path (**Delete File**) requires a modal confirmation first. Not exposed in the command palette.
Expand All @@ -56,6 +56,9 @@ Open a dashboard displaying Hygiene analytics: prune candidates over time, file-
- **Delete File** — remove a file flagged by the last scan (confirmation required).
- **Ignore File** — append the file's pattern to `.meridian/.meridianignore`.

### **hygiene.pruneStorage** (Meridian Storage)
Meridian polices its own storage ([ADR 019](./adr/019-pulse-and-retention.md)). The Hygiene view's **Meridian Storage** section shows the footprint of `.meridian/artifacts/` (exported reports), the run log, and the pulse history — including what the current retention policy would prune. **Prune Now** (inline action or `Hygiene: Prune Meridian Storage`) previews the effect, asks for confirmation, then deletes aged/surplus exports and compacts the run log. Retention is also enforced automatically at activation and after each report export.

---

## Sidebar Views & UI
Expand All @@ -81,6 +84,9 @@ All features respect workspace settings under the `meridian.*` namespace, includ
- `meridian.hygiene.prune.minAgeDays` — Minimum file age (days) before a file is a prune candidate (number, default: 30)
- `meridian.hygiene.prune.maxSizeMB` — Files larger than this (MB) are flagged when also older than `minAgeDays` (number, default: 1)
- `meridian.hygiene.prune.minLineCount` — Files with this many lines or more are flagged when also older than `minAgeDays`; 0 disables (number, default: 0)
- `meridian.retention.artifacts.maxCount` — Keep at most N exported reports in `.meridian/artifacts/`; 0 disables (number, default: 50)
- `meridian.retention.artifacts.maxAgeDays` — Prune exported reports older than N days; 0 disables (number, default: 30)
- `meridian.retention.runLog.maxEvents` — Compact the run log to its newest N events at activation; 0 disables (number, default: 5000)
- `meridian.sessionBriefing.autoLaunch` — Open a Session Briefing on activation (boolean, default: false)
- `meridian.startup.enableFileWatchers` — Register file watchers for auto tree/status refresh (boolean, default: true)

Expand All @@ -90,6 +96,7 @@ Per-workspace Meridian state lives under `.meridian/` at the workspace root (see

- `.meridian/.meridianignore` — gitignore-syntax patterns excluded from hygiene scans. Editor syntax highlighting is provided via the built-in `ignore` language association. Legacy `.meridianignore` at the workspace root is auto-relocated on activation.
- `.meridian/settings.json` — sparse JSON overrides for `meridian.*` settings. Present keys take precedence over VS Code user/workspace settings; absent keys fall through. Example: `{ "hygiene.prune.minAgeDays": 7 }`.
- `.meridian/pulse/` — local, self-gitignored pulse history (`pulse.v1.jsonl`) behind the session briefing's pulse slice; self-capping, no maintenance needed ([ADR 019](./adr/019-pulse-and-retention.md)).

---

Expand Down
23 changes: 18 additions & 5 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ deferred. (See [ADR 012](./adr/012-product-reanchor.md) for the re-anchor.)

## Now

1. **Session-briefing UI polish** — the briefing is the connective tissue that
makes the analytics/hygiene panels get reopened. Richer presentation over
the existing `aggregateSessionBriefing()` record: actionable cards, inline
panel links, quick filters. Prose stays optional and degrades to the raw
aggregate when `vscode.lm` is unavailable.
1. **Session-briefing UI polish (continued)** — richer presentation over the
`aggregateSessionBriefing()` record: actionable cards, inline panel links,
quick filters. The pulse slice (ADR 019) landed; remaining polish is
presentation-only. Prose stays optional and degrades to the raw aggregate
when `vscode.lm` is unavailable.
- Primary files: `src/domains/git/session-briefing-ui/`,
`src/domains/git/session-aggregator.ts`.

2. **Agent-readable snapshot (stretch, file-shaped)** — a stable, versioned
JSON "latest" convention (e.g. `.meridian/latest/briefing.json`) that
Copilot/Cursor/CLI agents read directly, plus a documented pointer users
paste into their agent rules. No runtime integration, no LM-tool surface —
the inversion of the pre-ADR-012 approach.

---

## Deferred
Expand All @@ -38,6 +44,13 @@ deferred. (See [ADR 012](./adr/012-product-reanchor.md) for the re-anchor.)

## Done

- **Ecosystem registry** (ADR 018) — single source for language/toolchain
semantics; JVM (Maven/Gradle/Kotlin/Scala) first-class; exclusion/bucket/
categorization drift structurally eliminated.
- **Pulse history + retention + storage surface** (ADR 019) — longitudinal
pulse slice in the session briefing; self-policing retention for
`.meridian/artifacts/`, the run log, and pulse history; "Meridian Storage"
tree section with Prune Now.
- **2.0 re-anchor** (ADR 012) — retired the LLM-commodity, chat-participant,
LM-tool, workflow, and agent surfaces; kept the computed-insight core.
- **Foundations** — run log (ADR 009), dispatch lifecycle (ADR 008),
Expand Down
127 changes: 127 additions & 0 deletions docs/adr/018-ecosystem-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# ADR 018 — Ecosystem Registry: Single Source for Language/Toolchain Semantics

**Date:** 2026-07-03
**Status:** Accepted

## Context

Meridian's knowledge of language ecosystems — which directory names are
environments, caches, build outputs, or vendored dependencies, and which file
extensions are sources, configs, or compiled artifacts — was spread across
five independently hand-maintained lists in two files:

1. `HYGIENE_SETTINGS.EXCLUDE_PATTERNS` (constants.ts) — hygiene scan exclusions
2. `HYGIENE_ANALYTICS_EXCLUDE_PATTERNS` (constants.ts) — analytics walker exclusions
3. `HEAVY_ARTIFACT_DIRS` (hygiene/analytics-service.ts) — never-recurse placeholder dirs
4. `COLLECTION_BUCKETS` (hygiene/analytics-utils.ts) — Collections bucketing
5. `ARTIFACT_EXTS` / `ARTIFACT_DIRS` / `SOURCE_EXTS` / `CONFIG_EXTS` (analytics-utils.ts) — categorization

The lists had already drifted: `.gradle` appeared in 2 and 4 but not 1;
`target` in 4 and 5 but not 1 or 2. The practical consequence was a
**JVM-shaped hole**: pointing the hygiene scan at a Maven repository descended
into `target/` and flooded candidates with `.class` files; `pom.xml` and
`application.properties` categorized as "other"; `.jar`/`.war` were not
artifacts. The tested stacks (Python, JS/TS) were rich; the enterprise Java
footprint was effectively unsupported.

## Decision

1. **`src/ecosystems.ts` is the single source of ecosystem semantics.** It
declares `EcosystemProfile` entries (`envDirs`, `cacheDirs`, `buildDirs`,
`vendorDirs`, `artifactExts`, `sourceExts`, `configExts`) per ecosystem
(common, node, python, jvm, go, rust, dotnet, ruby, elixir, native,
terraform, dart, haskell, clojure). The module imports nothing, so every
layer — including `constants.ts` — can consume it without cycles. Adding
ecosystem coverage is one profile entry; consumer lists are never edited.

2. **All five former lists are derived unions.** Derivation semantics:
- Hygiene scan excludes = base + env ∪ cache ∪ vendor ∪ build (the scan
hunts stray files, not generated-tree contents).
- Analytics excludes = base + env ∪ cache ∪ vendor. Build dirs are
**recursed** so contents surface as prune candidates.
- Heavy/placeholder dirs = env ∪ cache ∪ vendor — definitionally identical
to the analytics exclusion set, so exclusion and placeholder membership
cannot drift apart.
- Collections buckets map 1:1 to the four dir kinds.
- `ARTIFACT_DIRS` (categorization) = env ∪ cache ∪ build; vendor dirs are
never recursed, so their contents never reach `categorize()`.

3. **Suffix-matched names stay literal.** `*.egg-info` cannot be expressed in
a name-keyed registry; the glob in `constants.ts` and the `endsWith` check
in the analytics walker remain special cases, documented at both sites.

4. **JVM profile.** `target` (build); `.gradle`, `.kotlin`, `.bloop`,
`.metals` (caches); `.class`, `.jar`, `.war`, `.ear`, `.hprof` (artifacts);
`.java`, `.kt`, `.kts`, `.scala`, `.groovy` (sources); `.xml`,
`.properties`, `.gradle` (configs). Eclipse `.settings/` joins
`WORKSPACE_EXCLUDE_BASE` as IDE metadata (same class as `.idea`/`.vscode`).

5. **Deliberate omissions.** Bare `bin/` is NOT excluded or bucketed — script
`bin/` dirs with real source are common, and the false-positive cost
outweighs the Eclipse/.NET benefit. `packages/` is likewise omitted
entirely (revised in the production-readiness pass): yarn/pnpm monorepos
keep first-party source there, and silently blinding the hygiene scan to
it costs more than legacy NuGet layouts gain — NuGet shops opt back out
via `.meridianignore`. `obj/` (dotnet) IS excluded (the name is
toolchain-specific enough). `env/` IS excluded — kept for parity with
venv detection; a first-party `env/` dir is rarer than a virtualenv named
`env`, and `.meridianignore` remains the escape hatch. All pinned by test.

6. **Glob forms are per-consumer.** `dirExcludeGlobs(sets, form)` emits
descendant-only globs (`**/<dir>/**`) by default — the hygiene scan
matches file paths, and the bare form would wrongly exclude a *file*
named `dist` or `deps`. The analytics walker requests `"both"` because it
pattern-matches directory paths themselves to stop recursion and emit the
placeholder row. Side effect worth naming: pre-registry, the Python heavy
dirs lacked bare forms in the analytics list, so the walker recursed one
readdir level into every venv and emitted no placeholder for it — the
uniform derivation fixed that latent inconsistency.

## Intended behavior changes

All strictly toward correctness; guarded by legacy-superset tests in
`tests/ecosystems.test.ts` (every pre-registry pattern must appear in the
derived sets, with documented exceptions):

- The hygiene scan now excludes `target/`, `.gradle/`, `.yarn/`, `vendor/`,
`deps/`, `_build/`, `packages/`, `.terraform/`, etc. (previously flooded).
- Files under build-output dirs (`dist/`, `target/`, `_build/`, …) categorize
as `artifact`, making aged build products prune candidates — the
generalized form of the Java `target/` fix.
- `_build/` (Elixir) is no longer analytics-excluded: as a build dir it is
now recursed for prune candidates, consistent with `dist/` and `target/`.
- `deps/` reclassifies from buildOutputs to vendoredDeps (Mix deps are
fetched packages); `packages/` leaves the registry entirely (see
decision 5) — it is no longer analytics-excluded, heavy-placeholdered,
or bucketed.
- New cache coverage: `coverage/`, `.nyc_output/`, `.next/`, `.nuxt/`,
`.parcel-cache/`, `.kotlin/`, `.bloop/`, `.metals/` now excluded and
placeholdered everywhere, not just in whichever list happened to have them.

## Alternatives considered

**Flat, better-commented union sets (no profiles).** Delivers the same
anti-drift derivation with slightly less structure, but re-creates
drift-within-file (a new ecosystem still edits seven sets) and cannot be
fixture-tested per ecosystem. Rejected.

**Per-ecosystem enable/disable setting.** No demonstrated need; exclusion
supersets are cheap and harmless on repos that lack the dirs. Rejected as
speculative surface (YAGNI).

**Registry in `src/domains/hygiene/`.** Rejected: `constants.ts` must consume
it, and constants → domains would invert layering.

## Consequences

- **Positive.** Drift is structurally impossible; JVM repos are first-class;
future ecosystems are one-entry additions with a fixture-test pattern to
copy. Exclusion lists, placeholders, buckets, and categorization can no
longer disagree.
- **Cost.** One more top-level module. Derived globs are marginally broader
than the old hand lists (more dirs excluded) — this is the point, but it
does mean scan results change on repos that previously surfaced
generated-tree noise.
- **Cross-references:** ADR 014 (dotdir doctrine — `.meridian` remains in the
walker's skip set), `tests/ecosystems.test.ts` (superset + disjointness +
JVM fixtures).
Loading
Loading