Skip to content

Activate ruvector self-learning, agentic-qe, security + activation status line - #1

Merged
pacphi merged 20 commits into
mainfrom
explore/ruvector-self-learning-aqe
May 29, 2026
Merged

Activate ruvector self-learning, agentic-qe, security + activation status line#1
pacphi merged 20 commits into
mainfrom
explore/ruvector-self-learning-aqe

Conversation

@pacphi

@pacphi pacphi commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

Extends the machine kit beyond memory persistence to activate and verify the features that silently stay dormant on Node 24/26, and adds a one-command re-apply for upgrades.

  • 🧠 Self-learningruflo-enable-learning (native SQLite + 5 real capability probes) and ruflo-learning-verify (train → patterns persist 0→N). Verified live: 5/5 active, 0→7 patterns.
  • 🛡️ Securityruflo-security-verify confirms @claude-flow/security + @claude-flow/aidefence load, prompt-injection defense fires (exit-code based, robust to an upstream render crash), and flags the CVE-DB gap.
  • 🎓 Agentic-QE (opt-in)ruflo-setup-aqe. New finding: agentic-qe carries the same Node-≥24 native-SQLite bug as ruflo (aqe init fails at persistence-db init); the helper repairs it first, then handles half-init.
  • 📟 Status line — append-only, upgrade-safe two-line footer: 🧠 SONA … 🛡 aidefence on / 🎓 Agentic QE …. Renders only what's active.
  • 🔁 ruflo-resync — one command to re-apply everything an upgrade wipes.

Corrected diagnosis

The colleague gist's controller-registry.js patches are already upstream in ruflo 3.10.5 (agentdb v3, ESM fix, ReasoningBank embedder). The real lever is the missing native better-sqlite3 binary; "HNSW: Not loaded" in neural status is a cosmetic lazy-display, not real dormancy. Full story in docs/BACKGROUND.md. Prior art credited: Ciprian Melian's gist + the agentic-qe repo.

Test plan

All verified live on ruflo 3.10.5 / Node 26 (darwin-arm64):

  • ruflo-enable-learning → 5/5 capability probes green
  • ruflo-learning-verify → patterns 0→7, 50 learned, 55 trajectories on disk
  • ruflo-security-verify → security + aidefence load, injection flagged, scan/secrets run
  • ruflo-setup-aqe → 118 skills, both markers, idempotent
  • status-line footer renders; idempotent + upgrade-safe (1 marker after 3x apply)
  • ruflo-resync → full chain green
  • bash -n clean on all shell artifacts; --help on all bins
  • security review fix: statusline sqlite3 uses execFileSync (no shell)

Docs

Design spec + implementation plan under docs/superpowers/; docs/BACKGROUND.md, docs/TROUBLESHOOTING.md, the machine-wide CLAUDE.md reference, and a fully rewritten README all updated.

🤖 Generated with Claude Code

pacphi added 20 commits May 28, 2026 19:06
… self-learning

Patches native better-sqlite3 (6 agentdb dirs), guarded controller-registry
compat check (no-op on >=3.10), guarded @ruvector native repair, then asserts
real capability (core/VectorDb, sona, gnn, agentdb v3) rather than the lazy
'neural status' display strings. 5/5 green on ruflo 3.10.5 / Node 26.
… disk

Trains in an isolated temp dir and asserts patterns 0->>0 read directly from
.claude-flow/neural/patterns.json + stats.json (on-disk truth), not the lazy
neural-status display. Verified 0->7 patterns, 50 learned, 55 trajectories.
… note CVE gap

Uses 'security defend' exit code (1=threat, 0=clean) to assert proactive defense,
robust to an upstream stdout render crash. Documents the cve --list no-database gap
with npm audit fallback. All green on ruflo 3.10.5.
…segments

Extends ruflo-fix-statusline-version to inject a fast fs-only helper (no subprocess)
that appends 🧠 N (trained patterns) / 🛡 on (aidefence present) / 🎓 qe (.agentic-qe
db) — each rendered only when active. Shebang-safe insertion, marker-guarded idempotent.
…alf-init repair

Discovers and fixes a NEW bug beyond the gist: agentic-qe depends on
better-sqlite3@^12 directly and ships without the prebuilt .node on Node >=24,
so 'aqe init' fails at persistence-db init. setup-aqe installs the native binary
into the global agentic-qe first, then runs aqe init --auto with half-init repair
(re-run --upgrade if the .claude/skills/agentic-quality-engineering marker is missing).
Verified: 86 skills, both markers, idempotent.
…pply

Statusline footer upgraded from minimal (🧠 N 🛡 on 🎓 qe) to a two-line labeled
render: '🧠 SONA <patterns>·<traj>[·⚡HNSW]  🛡 aidefence on' and
'🎓 Agentic QE <patterns>[·traj][·vec]·<size>'. Append-only (never rewrites ruflo's
lines), upgrade-safe (strips legacy or BEGIN/END block then re-injects), fs-only +
one guarded sqlite3 for the QE line.

Adds ruflo-resync: one command to re-apply everything an upgrade wipes
(enable-learning + agentic-qe native repair + statusline). Extracts shared
_ruflo_aqe_ensure_native helper. Verified live: 5/5 learning, footer renders.
…write README

- Security: the agentic-qe footer's sqlite3 read now uses execFileSync('sqlite3',
  [db, sql]) instead of a shell-interpolated execSync, so the cwd-derived db path is
  never shell-evaluated (flagged by automated review, MEDIUM command-injection).
- README: full rewrite — decomposed, dual-audience (developer + non-technical),
  emoji section headers, friendly tone, and citations to ruflo, agentic-qe,
  Ciprian Melian's gist, ruflo#2219, better-sqlite3, and Claude Code.
uninstall.sh removed only the two original bins, leaving ruflo-enable-learning,
ruflo-learning-verify, and ruflo-security-verify behind. Both scripts now iterate
bin/* so they stay in sync automatically as bins are added/removed. uninstall.sh
header also clarifies that sourced functions (ruflo-resync/-setup-aqe) go away with
the rc source line, and that per-project artifacts are intentionally left untouched.
…s in the current repo

Strips the activation footer (ruflo-seg block), the console.log wrap, and the
version-probe injection, restoring ruflo's native render. Backs up first; leaves the
statusline file and all ruflo/agentic-qe data intact (points to 'ruflo cleanup --force'
for data). Flag parsing now loops so --dry-run and --this-project compose.
…hijacks it)

aqe init repoints .claude/settings.json statusLine.command at its minimal
statusline-v3.cjs, hiding the activation footer even though statusline.cjs is patched.
ruflo-fix-statusline-version now also makes statusline.cjs primary (idempotent;
falls back to v3 then a literal), so ruflo-resync self-heals it. Adds a TROUBLESHOOTING
entry.

Docs accuracy pass: corrected the README status-line mockup (it implied ruflo's native
'DDD Domains' line is relabeled 'Learning' — we don't; the footer is append-only),
documented uninstall --this-project, and softened 'reverses everything' claims. Verified
all cited specifics (controller-registry.js:313-315/:655, agentdb 3.0.0-alpha.14,
security 3.0.0-alpha.8, aidefence 3.0.3).
… + ruflo-neural-train

Matches the additional fields in Ciprian's statusline, append-only (no relabeling of
ruflo's native lines):
- SONA line: volume bar + Δ LoRA (cached) + ⚡HNSW
- Agentic QE line: git branch (⎇), icon-tagged 🎓 patterns / 🧭 traj / 🧬 vec⚡ / 💾 size
- ruflo-neural-train: wraps 'ruflo neural train' and caches MicroLoRA Delta Norm to
  .claude-flow/neural/lora-delta.json. Source finding: deltaNorm is a transient
  last-step metric (ruvector-training.js), not persisted and not derivable from the
  lora-checkpoint, so capture-at-train is the only faithful way to surface it.
…dy in ruflo header)

Also: docs for the enriched footer (SONA bar + Δ LoRA + AQE icons), ruflo-neural-train,
and the Δ LoRA source finding (BACKGROUND.md, spec R16/R16a).
…not empty 'vectors'

This aqe version stores per-pattern embeddings in qe_pattern_embeddings; the 'vectors'
table is empty, so 🧬 vec was omitted. Now tries qe_pattern_embeddings → vectors →
embeddings and uses the first with data (robust across aqe schema versions).
…README bullet

Updates the 'one guarded sqlite3 read' wording (it's now a few reads) and documents the
vec table fallback (qe_pattern_embeddings → vectors → embeddings) in README, the
reference block, BACKGROUND.md, and the spec. Also removes a stale duplicate
SONA/aidefence/Agentic-QE bullet block left in the README status-line section.
@pacphi
pacphi merged commit 29799dc into main May 29, 2026
@pacphi
pacphi deleted the explore/ruvector-self-learning-aqe branch May 29, 2026 04:04
pacphi added a commit that referenced this pull request Aug 7, 2026
…reclaimables

The collectors from the previous run were built and tested but never called —
index.mjs had no owner, so nothing composed them into the payload. Wiring them
up is most of this change; the rest is what wiring them exposed.

Now live (verified against GET /api/system, not asserted):
  - projects: 50 ever seen / 25 on disk / 21 git repos, de-duped across hosts by
    resolved real path. Was 4, because discovery reused discoverRuvfloProjects()
    — which requires .claude-flow/neural/ state and answers a different question
    that Intelligence still depends on, so it is left alone.
  - consumers: 80 roots, top 20 ranked, grouped by ecosystem. The panel called
    npx cache the #1 consumer at 6 GB; it is #12. The real leaders were entirely
    unscanned — Ollama 141 GB, LM Studio 49 GB, Hugging Face 36 GB, npm's
    _cacache 22 GB. Overlapping roots collapse by path so the list cannot go
    self-similar (~/.npm never appears beside its own _cacache).
  - snapshot persists consumers; runtime is still absent, and that allow-list is
    what structurally enforces the ephemeral-census invariant.
  - refreshDeep finally takes includeProjectTrees, which the caller was already
    passing into a zero-arity function. Sticky across rescans; default off,
    because one repository here is 175 GB and flattens every other row.

A HARD HANG, found only because the wider discovery reached it: the deep scan
parked forever at 0% CPU inside a Dropbox tree. Cloud providers leave evicted
placeholders — stat returns instantly, read blocks in the kernel until the
provider materializes the bytes, which never happens while it is signed out.
There is no timeout, so the scan never completed and the dashboard served a
17-hour-old snapshot. Worse, reading them silently pulls the file down. Guarded
by the allocated-blocks basis the DDD already names (blocks === 0 && size > 0):
placeholders are stat-ed, never opened. Zero false positives across 3,667 files
here, including sub-2KB files that APFS stores inline. The tree that hung
forever now returns in 50ms; a full scan takes 204s.

Also: Playwright was invisible on macOS (the scan looked only at the XDG and
Windows paths, so 1.86 GB read as a measured zero), and the RuvNet Brain was
under-reported by 85% because only kb/ was measured — the other 11 GB is five
dated kb.bak snapshots, now broken out rather than silently folded in.

Reclaimables grew from two detectors to cover those backups, npm's regenerable
cache, orphaned transcripts, and browser downloads — split into two safety
tiers that never sum: 'regenerable' for caches a tool refetches on demand, and
'review' for things like mise's 8 node versions, where recommending deletion of
a live runtime would be worse than saying nothing.
pacphi added a commit that referenced this pull request Aug 8, 2026
…rent model catalog (#123)

* fix(usage): catch the "<synthetic>" placeholder even without isApiErrorMessage

Some builds emit Claude Code's dropped-connection placeholder turn
(model: "<synthetic>", zero usage) without setting isApiErrorMessage,
so it slipped past the exception filter and surfaced as a real \$0
"model in play" on the scorecard. The literal model marker is now
checked alongside the flag, SCHEMA_VERSION bumps to 9 so cached
sessions re-derive, and a regression test covers the flagless shape.

Also re-anchors the usage-doc file:line citations shifted by this
file's line movement (doc-citations gate).

* feat(dashboard): date-windowed Observability History browsing

Observability's History scope was limited to the live tailer's moving
window (256 newest transcript files, 100-session projection). It now
browses retained sessions over an explicit calendar window — 1d, 7d,
14d (default), 1mo, 3mo, 6mo, 1y, all — like Usage's day chips.

- discoverJsonl() gains an optional sinceMs mtime cutoff
- LiveSessionsService.historySnapshot({sinceMs}): a one-shot scan with
  its own projection (never touches live tailer state), swept with
  all-zero windows so unterminated sessions read as stale, not live
- GET /api/live/history?window=<token>, same publicLivePayload
  scrubbing as /api/live; 501 when the service lacks historySnapshot
- window chips in the History sub-nav; History renders from a separate
  state.historySnapshot bucket so live SSE deltas can never clobber it

The UI harness also allowlists /api/live/intelligence EventSource
teardown aborts (pre-existing flake — Chromium reports deliberate
stream closes as ERR_ABORTED, same as the existing events/transcripts
entries) and gives LIVE_STUB a historySnapshot.

* docs: propose the System area — ADR-0025, machine-footprint domain, design mock

Drafts for review; nothing implemented yet.

- ADR-0025 (Proposed): a Machine footprint bounded context and a fourth
  System primary area (Summary / Storage / Runtime / Catalog /
  Projects), tiered honest collection with a persisted asOf snapshot,
  the initial metric taxonomy (install, runtime, storage, catalog,
  per-project LOC/disk, git-remote links), GET /api/system + ak
  footprint delivery, advisory-only reclaimables, and a documented
  absolute-path exception
- docs/ddd/machine-footprint.md: purpose, boundaries against Usage /
  Observability / Project intelligence / Integration management, the
  FootprintSnapshot model, measurement semantics, 12 invariants, and
  proposed ubiquitous-language terms
- docs/assets/system-tab-mock.html: self-contained both-theme mock of
  the System area on the dashboard's own tokens, every card annotated
  with its chart-form rationale; illustrative data only

* docs: propose the About area — ADR-0026, component-directory domain, design mock

Drafts for review; nothing implemented yet.

- ADR-0026 (Proposed): a leftmost About primary area introducing every
  component ak installs or configures — curated editorial copy joined
  with existing detection facts (no new endpoint, no probing), a
  registry↔directory parity gate so a managed tool cannot ship without
  its About card, official host marks + honest monogram tiles, and
  outbound user-initiated links inside the zero-egress contract
- docs/ddd/component-directory.md: the editorial/detection split as
  the load-bearing boundary, the new-user register contract (~50-word
  plain-language paragraphs, no runtime claims in prose), 10
  invariants, and proposed ubiquitous-language terms
- docs/assets/about-tab-mock.html: self-contained both-theme mock on
  the dashboard's own tokens — hero orientation strip with a
  how-it-fits map, category card grid (hosts first, honest
  not-installed state shown, configured surfaces with manage:
  commands), per-section design notes, and an annotated card anatomy

* feat(about): component directory, ak about, and a registry parity gate

Implements ADR-0026. A curated directory module carries each component's
editorial identity — tagline, one plain-language paragraph, source/npm/docs
links, icon, category — rewritten from verified upstream sources rather than
assumption. It collects nothing: state chips join client-side against the
status payload the dashboard already polls, so prose never claims runtime
state and a failed join degrades chips to unknown while content still renders.

The parity test is the point: every managed tool must have exactly one entry
and no entry may exist for something ak neither installs nor configures, so a
future tool cannot ship without its card.

* feat(system): machine-footprint collectors incl. first-class Windows census

Implements ADR-0025's collectors: a bounded walker (never follows symlinks,
one bad subtree degrades to unknown), install/storage/catalog/projects, and
an ephemeral runtime census. Unknown is never rendered as zero — every figure
carries measured/partial/unknown provenance, and lower bounds print as ">= N".

Windows is real rather than unsupported. A shipped PowerShell script gives the
guaranteed floor via Get-CimInstance (pid/ppid/CPU/RSS/uptime, argv
deliberately excluded), and a best-effort inline P/Invoke walks
NtQueryInformationProcess -> PEB -> RTL_USER_PROCESS_PARAMETERS for true cwd.
A bitness mismatch is detected rather than read through with wrong offsets,
and any probe failure degrades to an honest "not attributable" — never a
fabricated path, and never taking the census down with it. No dependency
added; package.json still declares none.

The script lives beside its consumer under src/ because `files` ships src/
wholesale — under scripts/ it would never have reached an npm-installed
Windows user at all. A guard test asserts that placement on every platform,
and three live tests execute the real PowerShell on windows-latest, checking
the census and the PEB walk against this process's own pid and cwd.

* feat(system): GET /api/system, snapshot persistence, and the ak system CLI

Cheap tier (census + known-file stats + snapshot carry-forward, TTL-cached)
on every read; deep tier explicit and single-flight so concurrent refreshes
attach to the in-flight scan. The deep result persists with an asOf; a missing
or corrupt snapshot reads as "never measured", never as zeros. Rescan is
manual only — nothing scans on dashboard open.

The payload deliberately carries absolute paths, unlike /api/live's leaf-only
reduction, because in this domain the path is the answer; file contents are
never read, so nothing sensitive can travel with them.

dashboard.test.cjs's self-contained assertion is replaced with a shared
assertSelfContained() helper. The old regex conflated "no external fetch" with
"no https string" and so failed on About's curated link pills; the replacement
pins the invariant to the directory itself — every external URL must be one
about-directory.mjs declares — and still bans external script/stylesheet/img.
That is strictly stronger, and the browser suite independently asserts the run
requests nothing off the loopback origin.

* feat(dashboard): About and System primary areas

About lands leftmost as the reading-order entry point; Overview remains the
default landing view, with a dismissible first-run nudge rather than a
hijacked view. System adds Summary/Storage/Runtime/Catalog/Projects with the
charted treatments from the design mock, a freshness label that nudges once a
snapshot goes stale, and honest empty states — "not measured yet" is never
rendered as a zero.

This makes five primary areas. ADR-0005's "exactly three stable primary areas"
assertion is updated to state the new contract, which both ADR-0025 and
ADR-0026 record as a deliberate amendment.

* docs: mark ADR-0025/0026 implemented and document About and System

Both ADRs flip Proposed -> Implemented, and their "open points for review"
sections are rewritten as resolved decisions with rationale: tab and CLI both
"System" (ak system), Projects stays its own sub-view, manual-only rescan with
a staleness nudge, Windows gets a guaranteed census plus best-effort P/Invoke
cwd, About gets a dismissible nudge, ak about ships, six configured cards.

Both bounded contexts join the context map and ubiquitous language, the ADR
index gains rows and narrative, and DASHBOARD.md documents the two new areas
for users — including how to read ">= N" lower bounds, "not measured yet",
approximate LOC, and the platform differences. README's command list gains
ak about and ak system.

The DDD docs lose their draft banners and record what shipped rather than what
was planned, including limits stated honestly: daemon budget state, the
statusline two-row join, and unattributable Codex bytes.

* fix(dashboard): strip mock annotations, report every version, unbreak Windows CI

Three fixes from reviewing the running dashboard.

Mock leakage. The design mocks were written as reviewer-facing artifacts with
their rationale inline, then handed to the UI agent as the visual spec, so it
faithfully reproduced the annotations along with the design. Removed 4 About
"Design note" blocks, 11 System "Why …" blocks, the "Two bars, two colour jobs"
note, all 13 chart-type pills ("radial gauge", "ranked bars", …), the About
footer, and the page-wide read-only footer, plus the CSS those left orphaned.
Kept every genuine data caveat — "line counts are approximate", "durations are
session span", "secrets masked server-side" — because those qualify the numbers
rather than explaining the design. Also dropped the hero's installed tally,
which restated each card's own chip less precisely.

Version chips: 6 of 9 components reported a version, for three different
reasons. driftReport() walks npm globals only, so a host installed by
mise/brew/native (Claude Code) was invisible to it; agentdb is a real global
but pinned to ruflo's bundled version, so it is deliberately excluded from the
update banner and fell out of the array with it; aidefence ships inside ruflo
and has no global install at all. foldKnownVersions() adds all three from
structured probes — never scraped from a status row's prose — as outdated:false
so noticeHtml (which filters on outdated) still shows an empty banner. The host
probe costs ~300ms, so it sits behind a 5-minute in-process TTL rather than
riding every 30s poll. Now 9 of 9.

Windows CI: the new absolute-paths assertion compared a fixture path against
the raw JSON body. A Windows path carries backslashes, which JSON escapes on
the wire, so the check passed on POSIX and failed on all three windows-latest
legs for a payload that was correct — the sibling assertions that compare
parsed values were green throughout. Compares the encoded form now.

* fix(dashboard): correct foldKnownVersions' JSDoc so tsc --checkJs passes

The CI quality gate went red on the previous commit: the param was annotated
Array<{pkg:string}> while the function pushes {pkg, installed, latest,
outdated}, so tsc rejected the object literal. The annotation was simply
narrower than the array driftReport() and the selfDrift/brain/ruvector folds
have always produced.

The return type keeps those fields OPTIONAL rather than required, because
incoming entries are passed through untouched — promising them as present
would be a second wrong annotation in the other direction.

* test(ui): defuse the dated fixture corpus, and match the hero's new contract

The UI suite went from 241/0 to 183/25 with no code change between the runs.
Cause: the fixture corpus is pinned to 2026-07-24 and the panel requests a
14-day window, so at 00:00 on 2026-08-07 the whole corpus aged out of its own
window. Proof, straight from the index: days=14 -> 0 sessions, days=30 -> 3.
One data-fixture check failed and 23 session-view assertions cascaded off it.

The kit suites avoid this by pinning `now` (usage-index.test.mjs says so in its
header), but this harness drives a REAL server against the real clock, so it
cannot. extendedCorpus() already copies the fixtures into a temp dir, so the
copy is shifted forward instead — by a WHOLE number of days, which preserves
every relative fact the assertions rest on: the 85-minute idle gap separating
the three time tiers, the worktree session nested inside another's span, and
each turn's local time-of-day for the punchcard's hour buckets. The checked-in
fixtures keep their literal dates, because the kit suites pin `now` against
exactly those.

The remaining failure was a real contract change, not a bomb: the hero no
longer counts detections, so asserting it says "unknown" tested behaviour that
was deliberately removed. It now asserts the actual contract — the hero states
only what ak MANAGES, making no detection claim in either direction, because
each card's own chip already carries per-component state and an aggregate could
only restate it less precisely.

* feat(system): wire the footprint collectors, fix a hard hang, expand reclaimables

The collectors from the previous run were built and tested but never called —
index.mjs had no owner, so nothing composed them into the payload. Wiring them
up is most of this change; the rest is what wiring them exposed.

Now live (verified against GET /api/system, not asserted):
  - projects: 50 ever seen / 25 on disk / 21 git repos, de-duped across hosts by
    resolved real path. Was 4, because discovery reused discoverRuvfloProjects()
    — which requires .claude-flow/neural/ state and answers a different question
    that Intelligence still depends on, so it is left alone.
  - consumers: 80 roots, top 20 ranked, grouped by ecosystem. The panel called
    npx cache the #1 consumer at 6 GB; it is #12. The real leaders were entirely
    unscanned — Ollama 141 GB, LM Studio 49 GB, Hugging Face 36 GB, npm's
    _cacache 22 GB. Overlapping roots collapse by path so the list cannot go
    self-similar (~/.npm never appears beside its own _cacache).
  - snapshot persists consumers; runtime is still absent, and that allow-list is
    what structurally enforces the ephemeral-census invariant.
  - refreshDeep finally takes includeProjectTrees, which the caller was already
    passing into a zero-arity function. Sticky across rescans; default off,
    because one repository here is 175 GB and flattens every other row.

A HARD HANG, found only because the wider discovery reached it: the deep scan
parked forever at 0% CPU inside a Dropbox tree. Cloud providers leave evicted
placeholders — stat returns instantly, read blocks in the kernel until the
provider materializes the bytes, which never happens while it is signed out.
There is no timeout, so the scan never completed and the dashboard served a
17-hour-old snapshot. Worse, reading them silently pulls the file down. Guarded
by the allocated-blocks basis the DDD already names (blocks === 0 && size > 0):
placeholders are stat-ed, never opened. Zero false positives across 3,667 files
here, including sub-2KB files that APFS stores inline. The tree that hung
forever now returns in 50ms; a full scan takes 204s.

Also: Playwright was invisible on macOS (the scan looked only at the XDG and
Windows paths, so 1.86 GB read as a measured zero), and the RuvNet Brain was
under-reported by 85% because only kb/ was measured — the other 11 GB is five
dated kb.bak snapshots, now broken out rather than silently folded in.

Reclaimables grew from two detectors to cover those backups, npm's regenerable
cache, orphaned transcripts, and browser downloads — split into two safety
tiers that never sum: 'regenerable' for caches a tool refetches on demand, and
'review' for things like mise's 8 node versions, where recommending deletion of
a live runtime would be worse than saying nothing.

* feat(dashboard): one project census, current models, reworked System area

Addresses a review of the local diagnostic panel. Four themes.

Project counting. Overview/Usage/Observability/System each discovered
projects their own way and reported four different numbers for the same
machine (4, 14, another 14, ~48). ADR-0027 makes discoverProjectSources()
the single census with four named scopes, and no surface may render a
project count without the sentence explaining what it counted. The
Intelligence panel now asks whether memory/intelligence has been
ACTIVATED (.claude-flow, .agentic-qe or .swarm, any host) rather than
whether ruflo has trained: 4 projects becomes 17. The learning scope
folds a repo's sub-directories and agent worktrees onto one identity —
without that, keying the picker off identity left 7 of 24 rows
unreachable. Retires project-discovery.mjs; registryWorkspaces() is
module-private again.

Models. gpt-5.4/gpt-5.4-mini retire from Codex on 2026-08-31 and
gpt-5.3-codex is already withdrawn, so execution routes to gpt-5.6-terra
and mechanical work to gpt-5.6-luna. RETIRED_MODELS substitutes a
withdrawn model at the read boundary — the one place a user pin is
overridden, because honoring a pin into a dead model fails the run — and
ak sync rewrites seeded routes naming one. claude-opus-4-8 is
deliberately NOT listed: it carries no deprecation notice, so it is
divergence, not retirement.

System area. Seven sub-views: Advisory and Sessions split out of Storage,
Advisory because it is the only part of System that suggests an action.
Storage lifts learning stores (99% of retained bytes) onto their own card
so the donut is legible, restricts the per-host split to real hosts, and
gives growth five axed sparklines. Catalog covers project scope across
every project on disk and gains kind/host filters. Projects lists only
repositories with a remote that a host has recorded a session in.

Honest degradation. Removes the AI-worker budget tile: no code path could
ever populate it, so it was a permanent "unavailable" rather than a
degradation. ADR-0023 gains §9 (a permanently unmeasurable quantity is
deleted, not degraded) and §10 (an excluded figure is still stated).

Also fixes eslint linting gitignored .ui-artifacts/, which failed
`pnpm run check` for anyone who had run `pnpm test:ui`.

* fix(dashboard): loosen the System layout and plainen the Advisory copy

Follow-up to fdd7052, all presentation — no collector or payload change.

Advisory copy. "Two safety tiers that are reported separately and never
added" stated an accounting rule before the reader had met the tiers, so
they now introduce themselves by what they mean to you. On the card,
"no total — pointers, not a sum" told you a number was MISSING without
saying why you should be glad it is; it now reads as a deliberate choice.

Catalog KPI. "232" and "skills" were on separate lines, so the tile's one
fact read as two. "skills" moves up beside the number as a unit — the
same treatment GB already gets on the byte tiles — at 12px, so it does
not inherit the 27px odometer size.

Summary band. A margin-top:-5px was actively pulling the projects liner
up into the KPI cards (5px of separation), and the disk strip carried
5px of padding around a 12px meter. Now 16px, 18px and 13px. That liner
is also capped at 120ch: two lines of dense accounting prose run the full
1440px, well past a readable measure.

Largest consumers. 110px showed about four rows, too few to read as a
ranking — you compared the top of the list against nothing. Sized for the
denser by-ecosystem mode where every row also carries a note: 9 full rows
there, 13 ranked.

Reclaimable rows. The removal hint was a bare .why div flush against the
rationale, reading as its next clause. Its own class now, with real
separation and the command in high-contrast mono, so the thing you could
run is distinct from the thing that was found.

* feat(dashboard): sortable headers on the System project footprints table

Every column sorts, one at a time, announced through aria-sort — exactly one
<th> is ever anything but "none". Clicking the active column reverses it;
clicking another takes over. Opens on project name, ascending.

First click uses each column's NATURAL direction rather than always
ascending: nobody opens a size column wanting the smallest project first, or
a recency column wanting the stalest. Project and language open ascending;
lines, disk and last-active open descending.

An unmeasured figure sorts LAST in both directions. These cells are
Measurement wrappers, so a row can carry `unknown` rather than a number, and
letting it rank would present an absent figure as a small one — the same rule
ADR-0023 applies everywhere else in the System area. A deliberately
unmeasured fixture row pins it.

The whole header is the button, so the target is the column's width rather
than a glyph, and the arrows always render: a control that only appears on
hover is invisible to anyone who never hovers, and a column that changes
width when pointed at is worse than no affordance.

* fix(test): canonicalise fixture roots the way the collectors do, and stop
asserting a POSIX-only encoding on Windows

Windows CI has failed since 2387a35 with 13 failures across three footprint
test files. Two distinct causes, both in the harness rather than the product.

realpathSync vs realpathSync.native. The fixtures canonicalised with the JS
realpath; every collector canonicalises with the native one. On POSIX these
agree, so it passed everywhere else. On Windows the JS realpath leaves an 8.3
short name alone (C:\Users\RUNNER~1\...) while the native one resolves it to
the long form the code under test returns (C:\Users\runneradmin\...) — the
same directory in two spellings, compared against each other. Fixtures now use
the native variant with the same `?? realpathSync` fallback the product uses.
footprint-collectors did not canonicalise at all.

A POSIX-rooted encoding asserted on Windows. Two tests build a Claude
transcript-directory name with `path.sep`, which yields `-a-b-c` on POSIX and
`C:-Users-...` on Windows. decodeClaudeProjectDir documents a drive prefix as
undecodable and returns null, so those tests were asserting the platform, not
the decoder. They are POSIX-only now, and a new test asserts the refusal
contract — including the Windows shape — on every platform.

Also fixes a real over-claim this surfaced. Because nothing decodes on
Windows, System > Sessions would have labelled EVERY row "deleted project"
there. labelSessions now reports which reason applies — `gone` for a
POSIX-rooted name that no longer resolves, `encoding` for a name that was
never decodable — and the panel says "name not decodable" rather than
asserting a deletion that did not happen.

* fix(footprint): the cloud-placeholder rule condemned every file on Windows

`fs.Stats.blocks` is a POSIX field. On win32 Node reports it as 0 for every
file, and `isCloudPlaceholder` read that as "a provider has evicted this" — so
every file was treated as dataless. Manifests were never queued, every source
file was skipped, and a scan returned no lines, no dependencies and no stack.
Eight of the thirteen Windows CI failures were this one gate; two independent
symptoms pinned it (`manifestsRead` 0 where 2 were present, and a skip count
one higher than the fixture's only binary file).

win32 is excluded from the heuristic. The cost is real and stated in the code:
Windows is where OneDrive Files On-Demand actually lives, so it is the platform
that most needs this check and the one platform that cannot have it — detecting
a placeholder there means FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, which fs.Stats
does not surface. A Windows placeholder is therefore opened and may block,
exactly as before the heuristic existed. Reading one file slowly is
recoverable; measuring nothing at all is not.

`platform` is a parameter rather than a direct process.platform read, and the
predicate is exported, so the win32 branch is testable from any machine. The
end-to-end test shims lstatSync to report zero blocks — without that it would
pass with or without the fix, since real POSIX files have blocks — and an
anti-vacuity test asserts the damage is still reproducible on POSIX, so the
guard cannot quietly stop being load-bearing.

* fix(footprint): the same POSIX-blocks assumption in two more places

The previous commit fixed the cloud-placeholder rule in stack-detect but
missed a hand-rolled DUPLICATE of it in projectRemote, which is why Windows
CI went 13 → 8 → 2 rather than to zero. Both remaining failures were that
copy: `st.blocks === 0 && st.size > 0` on a .git/config, which on win32 is
true for every file, so every project reported status 'unknown' with no
remote URL. It now calls the shared exported predicate instead of restating
the rule — the duplication is what let the first fix look complete.

A third instance was latent rather than failing. measureAllocated derives
allocated bytes from per-file `blocks` and falls back to apparent size only
when the count is not finite. On win32 the count IS finite — it is 0 — so an
entire machine's allocated total came out as zero bytes beside a correct
apparent total. Its own doc comment already promised the Windows fallback
this commit actually implements. No test covered it, so CI was green on a
figure that would have been wrong on every Windows machine.

Each fix carries an anti-vacuity assertion: the identical zero-blocks input
on POSIX must still be read as a placeholder, and must still allocate zero.
Without those the platform argument could stop being load-bearing and the
tests would keep passing.
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.

1 participant