@mrclrchtr/supi-skills 4.8.0 → 4.9.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.
@@ -48,7 +48,7 @@ Config file locations:
48
48
  ### Shared registries
49
49
 
50
50
  - context-provider registry for `/supi-context`
51
- - debug-event registry for producers that want shared debug capture
51
+ - debug-event registry and monotonic phase timers for producers that want shared debug capture
52
52
  - settings registry used by `/supi-settings`
53
53
 
54
54
  ### Project and session helpers
@@ -108,5 +108,7 @@ export default function myExtension(pi: ExtensionAPI) {
108
108
 
109
109
  - `src/api.ts` — exported library surface
110
110
  - `src/config.ts` — shared config loading and writing
111
+ - `src/debug-registry.ts` — Debug domain surface, event state, retention, redaction, listeners, and queries
112
+ - `src/debug-timing.ts` — monotonic total and phase timers for Debug Event Producers
111
113
  - `src/settings/` — settings registry, schema, scope resolution, and persistence
112
114
  - `src/report.ts` — shared text/report rendering helpers
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
4
4
  "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -4,6 +4,9 @@
4
4
  // supi-debug extension owns policy/configuration and exposes events through a
5
5
  // command/tool while this module stays dependency-free for producers.
6
6
 
7
+ // biome-ignore lint/performance/noReExportAll: preserve the stable debug domain entry point
8
+ export * from "./debug-timing.ts";
9
+
7
10
  export type DebugLevel = "debug" | "info" | "warning" | "error";
8
11
  export type DebugAgentAccess = "off" | "sanitized" | "raw";
9
12
  export interface DebugRegistryConfig {
@@ -180,6 +183,11 @@ export function getDebugRegistryConfig(): DebugRegistryConfig {
180
183
  return cloneConfig(getState().config);
181
184
  }
182
185
 
186
+ /** Return whether the Debug Registry currently retains producer events. */
187
+ export function isDebugRegistryEnabled(): boolean {
188
+ return getState().config.enabled;
189
+ }
190
+
183
191
  /** Best-effort redaction helper for data exposed through sanitized debug views. */
184
192
  export function redactDebugData<T>(value: T): T {
185
193
  return redactValue(value, 8) as T;
@@ -0,0 +1,107 @@
1
+ import { performance } from "node:perf_hooks";
2
+ import {
3
+ type DebugEvent,
4
+ type DebugEventInput,
5
+ isDebugRegistryEnabled,
6
+ recordDebugEvent,
7
+ } from "./debug-registry.ts";
8
+
9
+ /** Monotonic duration data added to a timed debug event. */
10
+ export interface DebugTiming {
11
+ readonly durationMs: number;
12
+ readonly phasesMs: Readonly<Record<string, number>>;
13
+ }
14
+
15
+ /** Debug event input whose data can receive the reserved `timing` field. */
16
+ export interface TimedDebugEventInput extends Omit<DebugEventInput, "data"> {
17
+ readonly data?: Readonly<Record<string, unknown>>;
18
+ }
19
+
20
+ /** Test seam for the monotonic clock used by a debug timer. */
21
+ export interface DebugTimerOptions {
22
+ readonly now?: () => number;
23
+ }
24
+
25
+ /** Lazy timed-event input that is not evaluated when Debug is disabled. */
26
+ export type TimedDebugEventFactory = () => TimedDebugEventInput;
27
+
28
+ /** One-shot debug timer with optional sequential phase measurements. */
29
+ export interface DebugTimer {
30
+ /** Whether this timer sampled Debug as enabled when it started. */
31
+ readonly enabled: boolean;
32
+ /** Finish the current phase and start the next unnamed interval. */
33
+ mark(phase: string): void;
34
+ /** Record one event. A second call returns `null` without recording another event. */
35
+ finish(
36
+ input: TimedDebugEventInput | TimedDebugEventFactory,
37
+ finalPhase?: string,
38
+ ): DebugEvent | null;
39
+ }
40
+
41
+ /**
42
+ * Start a monotonic timer for one debug event.
43
+ *
44
+ * Each `mark(name)` stores the interval since the previous mark. `finish()`
45
+ * stores the total duration and can name the final interval. Repeated phase
46
+ * names are accumulated. Event data reserves the `timing` field. When Debug is
47
+ * disabled at start, this returns a no-op timer and does not read the clock.
48
+ * Pass a factory to `finish()` to avoid event-data construction when disabled.
49
+ */
50
+ export function startDebugTimer(options: DebugTimerOptions = {}): DebugTimer {
51
+ if (!isDebugRegistryEnabled()) return DISABLED_DEBUG_TIMER;
52
+ const now = options.now ?? performance.now.bind(performance);
53
+ const startedAt = now();
54
+ let previousAt = startedAt;
55
+ let finished = false;
56
+ const phases = new Map<string, number>();
57
+
58
+ const markAt = (phase: string, current: number): void => {
59
+ const name = phase.trim();
60
+ if (!name) return;
61
+ phases.set(name, (phases.get(name) ?? 0) + Math.max(0, current - previousAt));
62
+ previousAt = current;
63
+ };
64
+
65
+ return {
66
+ enabled: true,
67
+ mark(phase) {
68
+ if (finished) return;
69
+ markAt(phase, now());
70
+ },
71
+ finish(input, finalPhase) {
72
+ if (finished) return null;
73
+ if (!isDebugRegistryEnabled()) {
74
+ finished = true;
75
+ return null;
76
+ }
77
+ const completedAt = now();
78
+ if (finalPhase) markAt(finalPhase, completedAt);
79
+ finished = true;
80
+ const phasesMs = Object.fromEntries(
81
+ [...phases.entries()].map(([name, value]) => [name, duration(value)]),
82
+ );
83
+ const timing: DebugTiming = {
84
+ durationMs: duration(completedAt - startedAt),
85
+ phasesMs,
86
+ };
87
+ const eventInput = typeof input === "function" ? input() : input;
88
+ return recordDebugEvent({
89
+ ...eventInput,
90
+ data: { ...eventInput.data, timing },
91
+ });
92
+ },
93
+ };
94
+ }
95
+
96
+ const DISABLED_DEBUG_TIMER: DebugTimer = Object.freeze({
97
+ enabled: false,
98
+ mark() {},
99
+ finish() {
100
+ return null;
101
+ },
102
+ });
103
+
104
+ function duration(value: number): number {
105
+ if (!Number.isFinite(value)) return 0;
106
+ return Math.round(Math.max(0, value) * 10) / 10;
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-skills",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
4
4
  "description": "Scoped skill controls and skill input shortcuts for PI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "README.md"
31
31
  ],
32
32
  "dependencies": {
33
- "@mrclrchtr/supi-core": "4.8.0"
33
+ "@mrclrchtr/supi-core": "4.9.0"
34
34
  },
35
35
  "bundledDependencies": [
36
36
  "@mrclrchtr/supi-core"