You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
"Ghost surface" detects skill / command names a session referenced but never invoked — i.e. the LLM saw a list of available tools/skills/commands but ignored most of them, paying tokens for unused surface area. Per-harness mining adapters (Claude / Codex / OpenCode) extract candidate names from session text, the detector cross-references with observed call patterns, and findings get emitted with savings estimates.
This is a single ~930 LoC chunk because the inputs file (ghost-surface-inputs.ts) is tightly coupled to the detector — it builds the per-source observed-name maps the detector consumes.
Scope
ghost_surface_inputs.rs (port of ghost-surface-inputs.ts, ~137 LoC)
Port buildObservedNamesBySource(turns), buildSessionCountBySource(turns), pickRepresentativeCacheReadRate(turns), buildGhostSurfaceInputs(turns).
Pure aggregations over EnrichedTurn[] — straightforward.
ghost_surface.rs (port of ghost-surface.ts, ~793 LoC)
Port GhostFindingKind (string-literal union — closed enum), GhostSurfaceFinding, GhostCandidate, GhostSurfaceInputs, GhostSurfaceAdapter, DetectGhostSurfaceOptions, GhostSurfaceFindingOptions types.
Port mineClaudeCommandNames(...), mineCodexSlashInvocations(...) — text-mining helpers that grep for /foo slash-command tokens in session bodies.
Port the three default adapters: claudeGhostAdapter, codexGhostAdapter, opencodeGhostAdapter. Each is a GhostSurfaceAdapter impl that knows how to mine its harness's session shape. Use a static &'static [&'static dyn GhostSurfaceAdapter] or an enum dispatch — Rust-idiomatic, no need to mirror the JS const adapter: GhostSurfaceAdapter = {...} literal shape.
Port DEFAULT_GHOST_ADAPTERS constant.
Port detectGhostSurface(inputs, options) — the main entry point.
Port ghostSurfaceToFinding(candidate, options) and ghostFindingsToWasteFindings(findings) adapters.
The fixture corpus exercises all three adapters across mixed-source sessions. Output ordering must be deterministic — sort findings by (severity desc, savings desc, kind, name).
Acceptance
cargo test -p relayburn-analyze green for ghost-surface.
Public surface: claudeGhostAdapter, codexGhostAdapter, opencodeGhostAdapter, DEFAULT_GHOST_ADAPTERS, detectGhostSurface, ghostSurfaceToFinding, ghostFindingsToWasteFindings, buildGhostSurfaceInputs, buildObservedNamesBySource, buildSessionCountBySource, pickRepresentativeCacheReadRate, plus all listed types.
Adapter trait shape allows downstream sub-issues (e.g. a future Cursor adapter) to plug in without modifying core detector logic.
Finding ordering matches TS on the fixture corpus.
Parent: #244
Depends on: #242, #266 (foundation), #268 (findings)
Context
"Ghost surface" detects skill / command names a session referenced but never invoked — i.e. the LLM saw a list of available tools/skills/commands but ignored most of them, paying tokens for unused surface area. Per-harness mining adapters (Claude / Codex / OpenCode) extract candidate names from session text, the detector cross-references with observed call patterns, and findings get emitted with savings estimates.
This is a single ~930 LoC chunk because the inputs file (
ghost-surface-inputs.ts) is tightly coupled to the detector — it builds the per-source observed-name maps the detector consumes.Scope
ghost_surface_inputs.rs(port ofghost-surface-inputs.ts, ~137 LoC)buildObservedNamesBySource(turns),buildSessionCountBySource(turns),pickRepresentativeCacheReadRate(turns),buildGhostSurfaceInputs(turns).EnrichedTurn[]— straightforward.ghost_surface.rs(port ofghost-surface.ts, ~793 LoC)GhostFindingKind(string-literal union — closed enum),GhostSurfaceFinding,GhostCandidate,GhostSurfaceInputs,GhostSurfaceAdapter,DetectGhostSurfaceOptions,GhostSurfaceFindingOptionstypes.mineClaudeCommandNames(...),mineCodexSlashInvocations(...)— text-mining helpers that grep for/fooslash-command tokens in session bodies.claudeGhostAdapter,codexGhostAdapter,opencodeGhostAdapter. Each is aGhostSurfaceAdapterimpl that knows how to mine its harness's session shape. Use a static&'static [&'static dyn GhostSurfaceAdapter]or an enum dispatch — Rust-idiomatic, no need to mirror the JSconst adapter: GhostSurfaceAdapter = {...}literal shape.DEFAULT_GHOST_ADAPTERSconstant.detectGhostSurface(inputs, options)— the main entry point.ghostSurfaceToFinding(candidate, options)andghostFindingsToWasteFindings(findings)adapters.Conformance gate
packages/analyze/src/ghost-surface.test.ts(564 lines)The fixture corpus exercises all three adapters across mixed-source sessions. Output ordering must be deterministic — sort findings by
(severity desc, savings desc, kind, name).Acceptance
cargo test -p relayburn-analyzegreen for ghost-surface.claudeGhostAdapter,codexGhostAdapter,opencodeGhostAdapter,DEFAULT_GHOST_ADAPTERS,detectGhostSurface,ghostSurfaceToFinding,ghostFindingsToWasteFindings,buildGhostSurfaceInputs,buildObservedNamesBySource,buildSessionCountBySource,pickRepresentativeCacheReadRate, plus all listed types.Files
packages/analyze/src/ghost-surface.ts+ghost-surface.test.tspackages/analyze/src/ghost-surface-inputs.ts