@mrclrchtr/supi-cache 1.3.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mrclrchtr/supi-cache
2
2
 
3
- Prompt cache health monitoring and cross-session forensics for the [pi coding agent](https://github.com/earendil-works/pi).
3
+ Adds prompt-cache monitoring and cache-regression forensics to the [pi coding agent](https://github.com/earendil-works/pi).
4
4
 
5
5
  ## Install
6
6
 
@@ -8,8 +8,7 @@ Prompt cache health monitoring and cross-session forensics for the [pi coding ag
8
8
  pi install npm:@mrclrchtr/supi-cache
9
9
  ```
10
10
 
11
- > **🧪 Beta package** not included in the `@mrclrchtr/supi` meta-package.
12
- > Install directly when you need cache forensics.
11
+ This is a **beta** package. It is not bundled in `@mrclrchtr/supi`.
13
12
 
14
13
  For local development:
15
14
 
@@ -17,60 +16,72 @@ For local development:
17
16
  pi install ./packages/supi-cache
18
17
  ```
19
18
 
20
- Edit the source and `/reload` to pick up changes.
19
+ After editing the source, run `/reload`.
21
20
 
22
- ## What it adds
21
+ ## What you get
23
22
 
24
- **Real-time monitoring** — tracks per-turn cache hit rates and shows a compact footer status (`cache: 80% ↑`). When the hit rate drops below the configured threshold, a warning notification includes the likely cause (compaction, model change, system prompt change, or idle).
23
+ After install, the package does two things:
25
24
 
26
- **Cross-session forensics** scans past session files to answer investigative questions across sessions with four query patterns:
25
+ 1. **Monitor the current session**
26
+ - records per-turn cache usage from assistant messages
27
+ - updates a footer status for cache health
28
+ - warns when the cache hit rate drops enough to count as a regression
29
+ - tries to explain the drop as compaction, model change, prompt change, or unknown
27
30
 
28
- - **Hotspots** worst hit-rate drops across all sessions, ranked by magnitude
29
- - **Breakdown** tally of regression causes (compaction, model change, prompt change, unknown, idle)
30
- - **Tool correlation** which tool calls preceded each regression drop
31
- - **Idle-time detection** gaps between turns that correlate with cache expiry
31
+ 2. **Investigate past sessions**
32
+ - scans session files for cache regressions across time
33
+ - groups findings into a few built-in query patterns
34
+ - keeps agent-facing results redacted to structural fingerprints instead of raw command text or file paths
32
35
 
33
36
  ## Commands and tool
34
37
 
35
- | Surface | Name | Description |
36
- |----------|------|-------------|
37
- | Command | `/supi-cache-history` | Per-turn cache metrics table for the current session, with annotated regression details and fingerprint diffs |
38
- | Command | `/supi-cache-forensics` | Cross-session investigation with themed TUI report. Accepts `--pattern`, `--since`, `--min-drop` filters |
39
- | Tool | `supi_cache_forensics` | Agent-callable — returns structured JSON with shape fingerprints (param types and lengths, no raw content) |
38
+ ### `/supi-cache-history`
40
39
 
41
- ### Command examples
40
+ Shows cache history for the current session.
42
41
 
43
- ```text
44
- # Show per-turn history for the current session
45
- /supi-cache-history
42
+ The report includes per-turn values for:
46
43
 
47
- # Cause breakdown for the last 7 days
48
- /supi-cache-forensics
44
+ - input tokens
45
+ - cache read tokens
46
+ - cache write tokens
47
+ - hit rate
48
+ - notes about detected regressions
49
49
 
50
- # Worst drops from the last 3 days
51
- /supi-cache-forensics --pattern hotspots --since 3d --min-drop 20
50
+ ### `/supi-cache-forensics`
52
51
 
53
- # Idle-time detection
54
- /supi-cache-forensics --pattern idle
55
- ```
52
+ Runs a cross-session investigation.
56
53
 
57
- ### Agent tool example
54
+ Supported patterns:
58
55
 
59
- ```json
60
- { "pattern": "hotspots", "since": "7d", "minDrop": 20 }
61
- { "pattern": "breakdown" }
62
- { "pattern": "correlate", "since": "24h" }
63
- { "pattern": "idle", "since": "30d" }
64
- ```
56
+ - `breakdown` — count regressions by cause
57
+ - `hotspots` show the largest drops
58
+ - `correlate` show which preceding tool calls correlate with drops
59
+ - `idle` show drops after long gaps between turns
60
+
61
+ Useful flags:
62
+
63
+ - `--since 7d`
64
+ - `--pattern breakdown`
65
+ - `--min-drop 20`
66
+
67
+ ### `supi_cache_forensics`
68
+
69
+ Adds one model-callable tool with the same four patterns: `hotspots`, `breakdown`, `correlate`, and `idle`.
70
+
71
+ The tool returns JSON text. Before results are returned to the model, human-only details such as `_pathsInvolved` and `_commandSummaries` are stripped out.
72
+
73
+ ## Settings
74
+
75
+ This package registers a **Cache** section in `/supi-settings`.
65
76
 
66
- ## Configuration
77
+ Available settings:
67
78
 
68
- Config files (project overrides global):
79
+ - `enabled` turn monitoring on or off
80
+ - `notifications` — show warning notifications for regressions
81
+ - `regressionThreshold` — percentage-point drop that counts as a regression warning
82
+ - `idleThresholdMinutes` — inactivity gap used to classify idle-time regressions
69
83
 
70
- | Scope | Path |
71
- |-------|------|
72
- | Global | `~/.pi/agent/supi/config.json` |
73
- | Project | `.pi/supi/config.json` |
84
+ Defaults:
74
85
 
75
86
  ```json
76
87
  {
@@ -83,37 +94,11 @@ Config files (project overrides global):
83
94
  }
84
95
  ```
85
96
 
86
- | Setting | Description | Default |
87
- |---------|-------------|---------|
88
- | `enabled` | Enable/disable monitoring | `true` |
89
- | `notifications` | Show regression warning notifications | `true` |
90
- | `regressionThreshold` | Percentage-point drop that triggers a warning and gates forensics findings | `25` |
91
- | `idleThresholdMinutes` | Minutes of inactivity to classify as idle-time regression | `5` |
92
-
93
- Upgrades from the old `cache-monitor` config section are handled automatically.
94
-
95
- If you have `/supi-settings` available (for example when also installing the `@mrclrchtr/supi` meta-package), the **Cache** section also appears there with editable fields.
96
-
97
- ## Provider notes
98
-
99
- Some providers do not report cache write tokens in their usage metadata. For example, Anthropic's API returns `cache_read_input_tokens` and `input_tokens` but does not expose cache writes. When using such a provider, the `CacheW` column in `/supi-cache-history` will always show `0`. Providers that do report cache writes (e.g. Google Gemini) will populate the column normally.
100
-
101
- ## Agent safety
102
-
103
- The `supi_cache_forensics` tool returns shape fingerprints, not raw content:
104
-
105
- | If the agent sees | It does NOT see |
106
- |---|---|
107
- | `{ "toolName": "bash", "paramKeys": ["command"], "paramShapes": { "command": { "kind": "string", "len": 340, "multiline": true } } }` | The 340-char command text |
108
- | `{ "toolName": "write", "paramKeys": ["file_path", "content"] }` | The file content or exact path |
109
-
110
- Human-only detail (`_pathsInvolved`, `_commandSummaries`) is stripped before returning to the agent — the TUI renderer shows richer information.
111
-
112
- ## Requirements
113
-
114
- - `@earendil-works/pi-coding-agent`
115
- - `@mrclrchtr/supi-core`
97
+ The config loader also reads the legacy `cache-monitor` section for upgrades, but `supi-cache` is the current config section.
116
98
 
117
99
  ## Source
118
100
 
119
- Extension entrypoint: `src/index.ts` → `src/monitor/monitor.ts`
101
+ - `src/monitor/monitor.ts` — live monitoring, commands, and tool registration
102
+ - `src/forensics/forensics.ts` — cross-session scan pipeline
103
+ - `src/report/history.ts` — current-session history report
104
+ - `src/report/forensics.ts` — cross-session forensics report
@@ -1,65 +1,78 @@
1
1
  # @mrclrchtr/supi-core
2
2
 
3
- Shared infrastructure for SuPi packages.
3
+ Shared infrastructure for SuPi extensions.
4
+
5
+ This package is mainly for extension authors. It gives you a common config system, settings plumbing, context helpers, registries, and a small extension surface that registers `/supi-settings`.
4
6
 
5
7
  ## Install
6
8
 
7
- Use it as a dependency in another extension package:
9
+ ### As a dependency for another extension
8
10
 
9
11
  ```bash
10
12
  pnpm add @mrclrchtr/supi-core
11
13
  ```
12
14
 
13
- ## Package role
14
-
15
- `@mrclrchtr/supi-core` now has two explicit surfaces:
15
+ ### As a pi package
16
16
 
17
- - `@mrclrchtr/supi-core/api` — shared library helpers for other SuPi packages
18
- - `@mrclrchtr/supi-core/extension` — a minimal pi extension that registers `/supi-settings`
19
-
20
- `pi.extensions` still points at the real file path `./src/extension.ts` inside the package. The `/api` and `/extension` paths are consumer-facing package exports, not manifest aliases.
17
+ ```bash
18
+ pi install npm:@mrclrchtr/supi-core
19
+ ```
21
20
 
22
- ## What it provides
21
+ Installing it as a pi package adds the minimal `/supi-settings` extension surface.
23
22
 
24
- Current exports cover:
23
+ ## Package surfaces
25
24
 
26
- - shared config loading, scoped reads, writes, and key removal
27
- - config-backed settings registration helpers for `/supi-settings`
28
- - the shared settings registry, overlay UI, and `registerSettingsCommand()` helper
29
- - XML `<extension-context>` wrapping plus context-message utilities
30
- - context-provider and debug-event registries reused across SuPi packages
31
- - project root and path helpers reused by packages such as `supi-lsp`
25
+ - `@mrclrchtr/supi-core/api` reusable helpers for other packages and extensions
26
+ - `@mrclrchtr/supi-core/extension` minimal pi extension that registers `/supi-settings`
32
27
 
33
- ## Config system
28
+ ## What you get from the API
34
29
 
35
- Config resolution order:
30
+ ### Config helpers
36
31
 
37
- ```text
38
- defaults <- global <- project
39
- ```
32
+ - `loadSupiConfig()` — merged config with resolution order `defaults <- global <- project`
33
+ - `loadSupiConfigForScope()` load one scope at a time for settings UIs
34
+ - `writeSupiConfig()` — persist values
35
+ - `removeSupiConfigKey()` — remove a key or override
40
36
 
41
37
  Config file locations:
42
38
 
43
39
  - global: `~/.pi/agent/supi/config.json`
44
40
  - project: `.pi/supi/config.json`
45
41
 
46
- Main helpers:
42
+ ### Settings helpers
43
+
44
+ - `registerSettings()` — register an arbitrary settings section
45
+ - `registerConfigSettings()` — register a config-backed settings section with scoped persistence helpers
46
+ - `registerSettingsCommand()` — register `/supi-settings`
47
+ - `openSettingsOverlay()` — open the shared settings UI directly
48
+ - `createInputSubmenu()` — helper for simple text-entry submenus
49
+
50
+ The built-in settings UI supports:
47
51
 
48
- - `loadSupiConfig()` — effective merged config (`defaults <- global <- project`)
49
- - `loadSupiConfigForScope()` raw single-scope config for settings UIs (`defaults <- selected scope`)
50
- - `writeSupiConfig()`
51
- - `removeSupiConfigKey()`
52
- - `registerConfigSettings()`
52
+ - project/global scope toggle
53
+ - grouped extension sections
54
+ - searchable setting lists
53
55
 
54
- ## Context and settings helpers
56
+ ### Context helpers
55
57
 
56
- - `wrapExtensionContext()`
58
+ - `wrapExtensionContext()` — wrap injected text in SuPi's `<extension-context>` tag
57
59
  - `findLastUserMessageIndex()`
58
60
  - `getContextToken()`
61
+ - `getPromptContent()`
59
62
  - `pruneAndReorderContextMessages()`
60
- - `registerSettings()`
61
- - `registerSettingsCommand()`
62
- - `openSettingsOverlay()`
63
+ - `restorePromptContent()`
64
+
65
+ ### Shared registries
66
+
67
+ - context-provider registry for `/supi-context`
68
+ - debug-event registry for producers that want shared debug capture
69
+ - settings registry used by `/supi-settings`
70
+
71
+ ### Project and session helpers
72
+
73
+ - project-root detection and directory walking helpers such as `findProjectRoot()` and `walkProject()`
74
+ - active-branch session helper: `getActiveBranchEntries()`
75
+ - terminal helpers such as `formatTitle()`, `signalWaiting()`, and `signalDone()`
63
76
 
64
77
  ## Example
65
78
 
@@ -80,17 +93,15 @@ registerConfigSettings({
80
93
  });
81
94
 
82
95
  const message = wrapExtensionContext("my-extension", "hello", {
83
- turn: 1,
84
96
  file: "CLAUDE.md",
97
+ turn: 1,
85
98
  });
86
99
  ```
87
100
 
88
- ## Requirements
89
-
90
- - `@earendil-works/pi-coding-agent`
91
- - `@earendil-works/pi-tui`
92
-
93
101
  ## Source
94
102
 
95
- - Library surface: `src/api.ts`
96
- - Extension surface: `src/extension.ts`
103
+ - `src/api.ts` — exported library surface
104
+ - `src/extension.ts` — minimal `/supi-settings` entrypoint
105
+ - `src/config.ts` — shared config loading and writing
106
+ - `src/config-settings.ts` — config-backed settings registration helper
107
+ - `src/settings-ui.ts` — shared settings overlay
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -2,30 +2,30 @@
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // and settings registry for supi-wide TUI settings.
4
4
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config.ts";
5
+ export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
6
  export {
7
7
  loadSupiConfig,
8
8
  loadSupiConfigForScope,
9
9
  removeSupiConfigKey,
10
10
  writeSupiConfig,
11
- } from "./config.ts";
12
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config-settings.ts";
13
- export { registerConfigSettings } from "./config-settings.ts";
14
- export type { ContextMessageLike } from "./context-messages.ts";
11
+ } from "./config/config.ts";
12
+ export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
13
+ export { registerConfigSettings } from "./config/config-settings.ts";
14
+ export type { ContextMessageLike } from "./context/context-messages.ts";
15
15
  export {
16
16
  findLastUserMessageIndex,
17
17
  getContextToken,
18
18
  getPromptContent,
19
19
  pruneAndReorderContextMessages,
20
20
  restorePromptContent,
21
- } from "./context-messages.ts";
22
- export type { ContextProvider } from "./context-provider-registry.ts";
21
+ } from "./context/context-messages.ts";
22
+ export type { ContextProvider } from "./context/context-provider-registry.ts";
23
23
  export {
24
24
  clearRegisteredContextProviders,
25
25
  getRegisteredContextProviders,
26
26
  registerContextProvider,
27
- } from "./context-provider-registry.ts";
28
- export { wrapExtensionContext } from "./context-tag.ts";
27
+ } from "./context/context-provider-registry.ts";
28
+ export { wrapExtensionContext } from "./context/context-tag.ts";
29
29
  export type {
30
30
  DebugAgentAccess,
31
31
  DebugEvent,
@@ -49,6 +49,7 @@ export {
49
49
  redactDebugData,
50
50
  resetDebugRegistry,
51
51
  } from "./debug-registry.ts";
52
+ export { fileToUri, resolveToolPath, stripToolPathPrefix, uriToFile } from "./path-utils.ts";
52
53
  export type { KnownRootEntry } from "./project-roots.ts";
53
54
  export {
54
55
  buildKnownRootsMap,
@@ -63,15 +64,16 @@ export {
63
64
  sortRootsBySpecificity,
64
65
  walkProject,
65
66
  } from "./project-roots.ts";
67
+ export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
66
68
  export { getActiveBranchEntries } from "./session-utils.ts";
67
- export { registerSettingsCommand } from "./settings-command.ts";
68
- export type { SettingsScope, SettingsSection } from "./settings-registry.ts";
69
+ export { registerSettingsCommand } from "./settings/settings-command.ts";
70
+ export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
69
71
  export {
70
72
  clearRegisteredSettings,
71
73
  getRegisteredSettings,
72
74
  registerSettings,
73
- } from "./settings-registry.ts";
74
- export { createInputSubmenu, openSettingsOverlay } from "./settings-ui.ts";
75
+ } from "./settings/settings-registry.ts";
76
+ export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
75
77
  export type { TitleTarget } from "./terminal.ts";
76
78
  export {
77
79
  DONE_SYMBOL,
@@ -2,9 +2,9 @@
2
2
  // Wraps registerSettings() and centralizes selected-scope loading + scoped persistence.
3
3
 
4
4
  import type { SettingItem } from "@earendil-works/pi-tui";
5
+ import type { SettingsScope } from "../settings/settings-registry.ts";
6
+ import { registerSettings } from "../settings/settings-registry.ts";
5
7
  import { loadSupiConfigForScope, removeSupiConfigKey, writeSupiConfig } from "./config.ts";
6
- import type { SettingsScope } from "./settings-registry.ts";
7
- import { registerSettings } from "./settings-registry.ts";
8
8
 
9
9
  export interface ConfigSettingsHelpers {
10
10
  /** Write a key to the selected scope's config section. */
@@ -3,7 +3,7 @@
3
3
  // Extensions declare context data providers via `registerContextProvider()` during their
4
4
  // factory function. The `/supi-context` command reads them via `getRegisteredContextProviders()`.
5
5
 
6
- import { createRegistry } from "./registry-utils.ts";
6
+ import { createRegistry } from "../registry-utils.ts";
7
7
 
8
8
  export interface ContextProvider {
9
9
  /** Unique identifier — e.g. "rtk" */
@@ -1 +1 @@
1
- export { registerSettingsCommand as default } from "./settings-command.ts";
1
+ export { registerSettingsCommand as default } from "./settings/settings-command.ts";
@@ -2,30 +2,30 @@
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // and settings registry for supi-wide TUI settings.
4
4
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config.ts";
5
+ export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
6
  export {
7
7
  loadSupiConfig,
8
8
  loadSupiConfigForScope,
9
9
  removeSupiConfigKey,
10
10
  writeSupiConfig,
11
- } from "./config.ts";
12
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config-settings.ts";
13
- export { registerConfigSettings } from "./config-settings.ts";
14
- export type { ContextMessageLike } from "./context-messages.ts";
11
+ } from "./config/config.ts";
12
+ export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
13
+ export { registerConfigSettings } from "./config/config-settings.ts";
14
+ export type { ContextMessageLike } from "./context/context-messages.ts";
15
15
  export {
16
16
  findLastUserMessageIndex,
17
17
  getContextToken,
18
18
  getPromptContent,
19
19
  pruneAndReorderContextMessages,
20
20
  restorePromptContent,
21
- } from "./context-messages.ts";
22
- export type { ContextProvider } from "./context-provider-registry.ts";
21
+ } from "./context/context-messages.ts";
22
+ export type { ContextProvider } from "./context/context-provider-registry.ts";
23
23
  export {
24
24
  clearRegisteredContextProviders,
25
25
  getRegisteredContextProviders,
26
26
  registerContextProvider,
27
- } from "./context-provider-registry.ts";
28
- export { wrapExtensionContext } from "./context-tag.ts";
27
+ } from "./context/context-provider-registry.ts";
28
+ export { wrapExtensionContext } from "./context/context-tag.ts";
29
29
  export type {
30
30
  DebugAgentAccess,
31
31
  DebugEvent,
@@ -49,6 +49,7 @@ export {
49
49
  redactDebugData,
50
50
  resetDebugRegistry,
51
51
  } from "./debug-registry.ts";
52
+ export { fileToUri, resolveToolPath, stripToolPathPrefix, uriToFile } from "./path-utils.ts";
52
53
  export type { KnownRootEntry } from "./project-roots.ts";
53
54
  export {
54
55
  buildKnownRootsMap,
@@ -63,15 +64,16 @@ export {
63
64
  sortRootsBySpecificity,
64
65
  walkProject,
65
66
  } from "./project-roots.ts";
67
+ export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
66
68
  export { getActiveBranchEntries } from "./session-utils.ts";
67
- export { registerSettingsCommand } from "./settings-command.ts";
68
- export type { SettingsScope, SettingsSection } from "./settings-registry.ts";
69
+ export { registerSettingsCommand } from "./settings/settings-command.ts";
70
+ export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
69
71
  export {
70
72
  clearRegisteredSettings,
71
73
  getRegisteredSettings,
72
74
  registerSettings,
73
- } from "./settings-registry.ts";
74
- export { createInputSubmenu, openSettingsOverlay } from "./settings-ui.ts";
75
+ } from "./settings/settings-registry.ts";
76
+ export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
75
77
  export type { TitleTarget } from "./terminal.ts";
76
78
  export {
77
79
  DONE_SYMBOL,
@@ -0,0 +1,40 @@
1
+ import * as path from "node:path";
2
+
3
+ /** Strip pi's optional leading `@` file-path prefix from a tool input. */
4
+ export function stripToolPathPrefix(target: string): string {
5
+ return target.startsWith("@") ? target.slice(1) : target;
6
+ }
7
+
8
+ /**
9
+ * Resolve a tool-style file path from a session cwd.
10
+ *
11
+ * Built-in pi file tools accept a leading `@` prefix in path arguments, so
12
+ * shared SuPi path helpers normalize that prefix before resolving relative
13
+ * paths.
14
+ */
15
+ export function resolveToolPath(cwd: string, target: string): string {
16
+ return path.resolve(cwd, stripToolPathPrefix(target));
17
+ }
18
+
19
+ /** Convert a file path to a file:// URI. */
20
+ export function fileToUri(filePath: string): string {
21
+ const resolved = path.resolve(filePath);
22
+ if (process.platform === "win32") {
23
+ return `file:///${resolved.replace(/\\/g, "/")}`;
24
+ }
25
+ return `file://${resolved}`;
26
+ }
27
+
28
+ /** Convert a file:// URI to a file path. */
29
+ export function uriToFile(uri: string): string {
30
+ if (!uri.startsWith("file://")) return uri;
31
+ let filePath = decodeURIComponent(uri.slice(7));
32
+ if (
33
+ process.platform === "win32" &&
34
+ filePath.startsWith("/") &&
35
+ /^[A-Za-z]:/.test(filePath.slice(1))
36
+ ) {
37
+ filePath = filePath.slice(1);
38
+ }
39
+ return filePath;
40
+ }
@@ -5,8 +5,20 @@
5
5
  // Without this, each symlink path gets its own module copy and its own Map,
6
6
  // so registrations from one instance are invisible to consumers in another.
7
7
 
8
+ import * as path from "node:path";
9
+
8
10
  const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
9
11
 
12
+ function getGlobalRegistryMap<T>(name: string): Map<string, T> {
13
+ const key = Symbol.for(SYMBOL_PREFIX + name);
14
+ let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
15
+ if (!map) {
16
+ map = new Map<string, T>();
17
+ (globalThis as Record<symbol, unknown>)[key] = map;
18
+ }
19
+ return map;
20
+ }
21
+
10
22
  /**
11
23
  * Create a named registry backed by `globalThis` + `Symbol.for`.
12
24
  *
@@ -18,16 +30,7 @@ const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
18
30
  * @returns An object with `register`, `getAll`, and `clear` functions.
19
31
  */
20
32
  export function createRegistry<T>(name: string) {
21
- const key = Symbol.for(SYMBOL_PREFIX + name);
22
-
23
- const getMap = (): Map<string, T> => {
24
- let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
25
- if (!map) {
26
- map = new Map<string, T>();
27
- (globalThis as Record<symbol, unknown>)[key] = map;
28
- }
29
- return map;
30
- };
33
+ const getMap = (): Map<string, T> => getGlobalRegistryMap<T>(name);
31
34
 
32
35
  return {
33
36
  /**
@@ -52,3 +55,32 @@ export function createRegistry<T>(name: string) {
52
55
  },
53
56
  };
54
57
  }
58
+
59
+ /**
60
+ * Create a named session-state registry keyed by normalized cwd.
61
+ *
62
+ * This helper is intended for session-scoped runtime services that should be
63
+ * shared across duplicate jiti module instances while keeping package-specific
64
+ * state unions and convenience wrappers local to the calling package.
65
+ */
66
+ export function createSessionStateRegistry<TState>(name: string) {
67
+ const getMap = (): Map<string, TState> => getGlobalRegistryMap<TState>(name);
68
+ const normalizeCwd = (cwd: string): string => path.resolve(cwd);
69
+
70
+ return {
71
+ /** Get the current state for one session cwd. */
72
+ get: (cwd: string): TState | undefined => {
73
+ return getMap().get(normalizeCwd(cwd));
74
+ },
75
+
76
+ /** Store the current state for one session cwd. */
77
+ set: (cwd: string, state: TState): void => {
78
+ getMap().set(normalizeCwd(cwd), state);
79
+ },
80
+
81
+ /** Clear the current state for one session cwd. */
82
+ clear: (cwd: string): void => {
83
+ getMap().delete(normalizeCwd(cwd));
84
+ },
85
+ };
86
+ }
@@ -4,7 +4,7 @@
4
4
  // factory function. The generic settings UI reads them via `getRegisteredSettings()`.
5
5
 
6
6
  import type { SettingItem } from "@earendil-works/pi-tui";
7
- import { createRegistry } from "./registry-utils.ts";
7
+ import { createRegistry } from "../registry-utils.ts";
8
8
 
9
9
  export type SettingsScope = "project" | "global";
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-cache",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "SuPi Cache — prompt cache health monitoring and cross-session forensics",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,7 +20,7 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@mrclrchtr/supi-core": "1.3.1"
23
+ "@mrclrchtr/supi-core": "1.5.0"
24
24
  },
25
25
  "bundledDependencies": [
26
26
  "@mrclrchtr/supi-core"
@@ -14,6 +14,7 @@ import { stripHumanDetail } from "../forensics/redact.ts";
14
14
  import { formatForensicsReport } from "../report/forensics.ts";
15
15
  import { type CacheReportSnapshot, formatCacheReport } from "../report/history.ts";
16
16
  import { registerCacheMonitorSettings } from "../settings-registration.ts";
17
+ import { promptGuidelines, promptSnippet, toolDescription } from "../tool/guidance.ts";
17
18
  import { CacheMonitorState, type RegressionResult } from "./state.ts";
18
19
  import { formatCacheStatus } from "./status.ts";
19
20
 
@@ -226,11 +227,8 @@ export default function cacheMonitorExtension(pi: ExtensionAPI) {
226
227
  pi.registerTool({
227
228
  name: "supi_cache_forensics",
228
229
  label: "Cache Forensics",
229
- description:
230
- "Investigate prompt cache regressions across historical PI sessions. " +
231
- "Provides four query patterns: hotspots (worst drops), breakdown (cause tally), " +
232
- "correlate (tools before regressions), and idle (long-gap regressions). " +
233
- 'Example: {"pattern": "hotspots", "since": "7d", "minDrop": 20}',
230
+ description: toolDescription,
231
+ promptSnippet,
234
232
  parameters: Type.Object({
235
233
  pattern: StringEnum(["hotspots", "breakdown", "correlate", "idle"], {
236
234
  description: "Query pattern",
@@ -254,14 +252,7 @@ export default function cacheMonitorExtension(pi: ExtensionAPI) {
254
252
  }),
255
253
  ),
256
254
  }),
257
- promptGuidelines: [
258
- "Use `supi_cache_forensics` when the user asks about cache performance patterns, suspects idle-time cache expiry, or wants to understand what preceded a cache drop.",
259
- "Prefer `pattern: 'breakdown'` for a quick overview of regression causes.",
260
- "Use `pattern: 'hotspots'` with `minDrop: 20` or higher to surface the worst regressions.",
261
- "Use `pattern: 'idle'` to detect cache drops caused by long gaps between turns.",
262
- "Use `pattern: 'correlate'` to see which tool calls preceded regressions.",
263
- "The tool returns shape fingerprints (param types and lengths), not raw file paths or command text.",
264
- ],
255
+ promptGuidelines,
265
256
  // biome-ignore lint/complexity/useMaxParams: pi tool execute signature
266
257
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
267
258
  const config = loadCacheMonitorConfig(ctx.cwd);
@@ -0,0 +1,16 @@
1
+ // Prompt guidance and tool description for the supi_cache_forensics tool.
2
+
3
+ export const toolDescription =
4
+ 'Investigate prompt cache regressions across historical PI sessions. Query patterns: hotspots (worst drops), breakdown (cause tally), correlate (tools before regressions), and idle (long-gap regressions). Results redact raw file paths and command text into shape fingerprints. Example: {"pattern": "hotspots", "since": "7d", "minDrop": 20}';
5
+
6
+ export const promptSnippet =
7
+ "supi_cache_forensics — investigate historical prompt cache regressions, causes, idle drops, and preceding tool activity";
8
+
9
+ export const promptGuidelines = [
10
+ "Use supi_cache_forensics when the user asks about cache performance patterns, suspects idle-time cache expiry, or wants to understand what preceded a cache drop.",
11
+ 'Use supi_cache_forensics with `pattern: "breakdown"` for a quick overview of regression causes.',
12
+ 'Use supi_cache_forensics with `pattern: "hotspots"` and `minDrop: 20` or higher to surface the worst regressions.',
13
+ 'Use supi_cache_forensics with `pattern: "idle"` to detect cache drops caused by long gaps between turns.',
14
+ 'Use supi_cache_forensics with `pattern: "correlate"` to see which tool-call shapes preceded regressions.',
15
+ "supi_cache_forensics returns shape fingerprints and parameter summaries, not raw file paths or command text.",
16
+ ];