@mrclrchtr/supi-code-intelligence 4.5.1 → 4.7.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.
Files changed (31) hide show
  1. package/README.md +1 -3
  2. package/node_modules/@mrclrchtr/supi-code-runtime/README.md +1 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +1 -3
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +49 -12
  6. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +3 -3
  7. package/node_modules/@mrclrchtr/supi-core/README.md +1 -3
  8. package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  9. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +49 -12
  10. package/node_modules/@mrclrchtr/supi-lsp/README.md +1 -3
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +1 -1
  12. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +1 -3
  13. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  14. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +49 -12
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +3 -3
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +1 -3
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +49 -12
  19. package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -4
  20. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +1 -3
  21. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +1 -1
  22. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +1 -3
  23. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  24. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +49 -12
  25. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +3 -3
  26. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +1 -3
  27. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  28. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +49 -12
  29. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +4 -4
  30. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +1 -1
  31. package/package.json +6 -6
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-code-intelligence">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-code-intelligence/assets/social-preview.png" alt="SuPi Code Intelligence" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-code-intelligence/assets/social-preview.png" alt="SuPi Code Intelligence" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  Shared workspace context, capability contracts, and canonical types for the SuPi code-understanding stack.
10
10
 
11
- This is a **library-only package** — it has no pi extension surface, no user-facing tools, and no UI. It provides the shared abstractions that `supi-lsp`, `supi-tree-sitter`, and `supi-code-intelligence` use to communicate capability availability.
11
+ This is a **library-only package** — it has no pi extension surface, no user-facing tools, and no UI. It is not installed directly `supi-lsp`, `supi-tree-sitter`, and `supi-code-intelligence` bundle it and use its shared abstractions to communicate capability availability.
12
12
 
13
13
  ## Package surfaces
14
14
 
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-core">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.5.1",
4
- "description": "SuPi core shared infrastructure for SuPi extensions (XML context tags, config system)",
3
+ "version": "4.7.0",
4
+ "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -64,6 +64,9 @@ export interface DebugEventQueryResult {
64
64
  rawAccessDenied: boolean;
65
65
  }
66
66
 
67
+ /** Receives a sanitized event whenever the registry records one. */
68
+ export type DebugEventListener = (event: DebugEventView) => void;
69
+
67
70
  export interface DebugSummary {
68
71
  total: number;
69
72
  byLevel: Partial<Record<DebugLevel, number>>;
@@ -73,6 +76,7 @@ export interface DebugSummary {
73
76
  interface DebugRegistryState {
74
77
  config: DebugRegistryConfig;
75
78
  events: DebugEvent[];
79
+ listeners: Set<DebugEventListener>;
76
80
  nextId: number;
77
81
  }
78
82
 
@@ -97,10 +101,14 @@ function getState(): DebugRegistryState {
97
101
  state = {
98
102
  config: cloneConfig(DEBUG_REGISTRY_DEFAULTS),
99
103
  events: [],
104
+ listeners: new Set(),
100
105
  nextId: 1,
101
106
  };
102
107
  (globalThis as Record<symbol, unknown>)[REGISTRY_KEY] = state;
103
108
  }
109
+ // Keep the shared registry compatible with extension reloads that reuse an
110
+ // instance created before listeners existed.
111
+ if (!state.listeners) state.listeners = new Set();
104
112
  return state;
105
113
  }
106
114
 
@@ -119,7 +127,16 @@ function trimToMaxEvents(state: DebugRegistryState): void {
119
127
  state.events.splice(0, state.events.length - maxEvents);
120
128
  }
121
129
 
122
- function matchesQuery(event: DebugEvent, query: DebugEventQuery): boolean {
130
+ /** Return whether a debug level is recognized by the registry. */
131
+ export function isDebugLevel(value: unknown): value is DebugLevel {
132
+ return value === "debug" || value === "info" || value === "warning" || value === "error";
133
+ }
134
+
135
+ /** Match a debug event against the supported source, level, and category filters. */
136
+ export function matchesDebugEventQuery(
137
+ event: Pick<DebugEventView, "source" | "level" | "category">,
138
+ query: Pick<DebugEventQuery, "source" | "level" | "category">,
139
+ ): boolean {
123
140
  if (query.source && event.source !== query.source) return false;
124
141
  if (query.level && event.level !== query.level) return false;
125
142
  if (query.category && event.category !== query.category) return false;
@@ -168,6 +185,26 @@ export function redactDebugData<T>(value: T): T {
168
185
  return redactValue(value, 8) as T;
169
186
  }
170
187
 
188
+ function toSanitizedView(event: DebugEvent): DebugEventView {
189
+ return {
190
+ id: event.id,
191
+ timestamp: event.timestamp,
192
+ source: event.source,
193
+ level: event.level,
194
+ category: event.category,
195
+ message: event.message,
196
+ cwd: event.cwd,
197
+ data: event.data,
198
+ };
199
+ }
200
+
201
+ /** Subscribe to sanitized events. Listeners are isolated so diagnostics cannot disrupt producers. */
202
+ export function subscribeDebugEvents(listener: DebugEventListener): () => void {
203
+ const state = getState();
204
+ state.listeners.add(listener);
205
+ return () => state.listeners.delete(listener);
206
+ }
207
+
171
208
  /** Record a session-local debug event if debugging is enabled. */
172
209
  export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
173
210
  const state = getState();
@@ -183,6 +220,14 @@ export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
183
220
  };
184
221
  state.events.push(event);
185
222
  trimToMaxEvents(state);
223
+ const view = toSanitizedView(event);
224
+ for (const listener of state.listeners) {
225
+ try {
226
+ listener(view);
227
+ } catch {
228
+ // Debug-event consumers must not alter producer behavior.
229
+ }
230
+ }
186
231
  return { ...event };
187
232
  }
188
233
 
@@ -196,21 +241,12 @@ export function getDebugEvents(query: DebugEventQuery = {}): DebugEventQueryResu
196
241
  const limit = query.limit && query.limit > 0 ? Math.floor(query.limit) : state.config.maxEvents;
197
242
 
198
243
  const events = state.events
199
- .filter((event) => matchesQuery(event, query))
244
+ .filter((event) => matchesDebugEventQuery(event, query))
200
245
  .slice()
201
246
  .reverse()
202
247
  .slice(0, limit)
203
248
  .map((event): DebugEventView => {
204
- const view: DebugEventView = {
205
- id: event.id,
206
- timestamp: event.timestamp,
207
- source: event.source,
208
- level: event.level,
209
- category: event.category,
210
- message: event.message,
211
- cwd: event.cwd,
212
- data: event.data,
213
- };
249
+ const view = toSanitizedView(event);
214
250
  if (allowRaw && event.rawData !== undefined) {
215
251
  view.rawData = event.rawData;
216
252
  }
@@ -246,5 +282,6 @@ export function resetDebugRegistry(): void {
246
282
  const state = getState();
247
283
  state.config = cloneConfig(DEBUG_REGISTRY_DEFAULTS);
248
284
  state.events = [];
285
+ state.listeners.clear();
249
286
  state.nextId = 1;
250
287
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "4.5.1",
4
- "description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
3
+ "version": "4.7.0",
4
+ "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  "!__tests__"
30
30
  ],
31
31
  "dependencies": {
32
- "@mrclrchtr/supi-core": "4.5.1"
32
+ "@mrclrchtr/supi-core": "4.7.0"
33
33
  },
34
34
  "bundledDependencies": [
35
35
  "@mrclrchtr/supi-core"
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-core">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.5.1",
4
- "description": "SuPi core shared infrastructure for SuPi extensions (XML context tags, config system)",
3
+ "version": "4.7.0",
4
+ "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -64,6 +64,9 @@ export interface DebugEventQueryResult {
64
64
  rawAccessDenied: boolean;
65
65
  }
66
66
 
67
+ /** Receives a sanitized event whenever the registry records one. */
68
+ export type DebugEventListener = (event: DebugEventView) => void;
69
+
67
70
  export interface DebugSummary {
68
71
  total: number;
69
72
  byLevel: Partial<Record<DebugLevel, number>>;
@@ -73,6 +76,7 @@ export interface DebugSummary {
73
76
  interface DebugRegistryState {
74
77
  config: DebugRegistryConfig;
75
78
  events: DebugEvent[];
79
+ listeners: Set<DebugEventListener>;
76
80
  nextId: number;
77
81
  }
78
82
 
@@ -97,10 +101,14 @@ function getState(): DebugRegistryState {
97
101
  state = {
98
102
  config: cloneConfig(DEBUG_REGISTRY_DEFAULTS),
99
103
  events: [],
104
+ listeners: new Set(),
100
105
  nextId: 1,
101
106
  };
102
107
  (globalThis as Record<symbol, unknown>)[REGISTRY_KEY] = state;
103
108
  }
109
+ // Keep the shared registry compatible with extension reloads that reuse an
110
+ // instance created before listeners existed.
111
+ if (!state.listeners) state.listeners = new Set();
104
112
  return state;
105
113
  }
106
114
 
@@ -119,7 +127,16 @@ function trimToMaxEvents(state: DebugRegistryState): void {
119
127
  state.events.splice(0, state.events.length - maxEvents);
120
128
  }
121
129
 
122
- function matchesQuery(event: DebugEvent, query: DebugEventQuery): boolean {
130
+ /** Return whether a debug level is recognized by the registry. */
131
+ export function isDebugLevel(value: unknown): value is DebugLevel {
132
+ return value === "debug" || value === "info" || value === "warning" || value === "error";
133
+ }
134
+
135
+ /** Match a debug event against the supported source, level, and category filters. */
136
+ export function matchesDebugEventQuery(
137
+ event: Pick<DebugEventView, "source" | "level" | "category">,
138
+ query: Pick<DebugEventQuery, "source" | "level" | "category">,
139
+ ): boolean {
123
140
  if (query.source && event.source !== query.source) return false;
124
141
  if (query.level && event.level !== query.level) return false;
125
142
  if (query.category && event.category !== query.category) return false;
@@ -168,6 +185,26 @@ export function redactDebugData<T>(value: T): T {
168
185
  return redactValue(value, 8) as T;
169
186
  }
170
187
 
188
+ function toSanitizedView(event: DebugEvent): DebugEventView {
189
+ return {
190
+ id: event.id,
191
+ timestamp: event.timestamp,
192
+ source: event.source,
193
+ level: event.level,
194
+ category: event.category,
195
+ message: event.message,
196
+ cwd: event.cwd,
197
+ data: event.data,
198
+ };
199
+ }
200
+
201
+ /** Subscribe to sanitized events. Listeners are isolated so diagnostics cannot disrupt producers. */
202
+ export function subscribeDebugEvents(listener: DebugEventListener): () => void {
203
+ const state = getState();
204
+ state.listeners.add(listener);
205
+ return () => state.listeners.delete(listener);
206
+ }
207
+
171
208
  /** Record a session-local debug event if debugging is enabled. */
172
209
  export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
173
210
  const state = getState();
@@ -183,6 +220,14 @@ export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
183
220
  };
184
221
  state.events.push(event);
185
222
  trimToMaxEvents(state);
223
+ const view = toSanitizedView(event);
224
+ for (const listener of state.listeners) {
225
+ try {
226
+ listener(view);
227
+ } catch {
228
+ // Debug-event consumers must not alter producer behavior.
229
+ }
230
+ }
186
231
  return { ...event };
187
232
  }
188
233
 
@@ -196,21 +241,12 @@ export function getDebugEvents(query: DebugEventQuery = {}): DebugEventQueryResu
196
241
  const limit = query.limit && query.limit > 0 ? Math.floor(query.limit) : state.config.maxEvents;
197
242
 
198
243
  const events = state.events
199
- .filter((event) => matchesQuery(event, query))
244
+ .filter((event) => matchesDebugEventQuery(event, query))
200
245
  .slice()
201
246
  .reverse()
202
247
  .slice(0, limit)
203
248
  .map((event): DebugEventView => {
204
- const view: DebugEventView = {
205
- id: event.id,
206
- timestamp: event.timestamp,
207
- source: event.source,
208
- level: event.level,
209
- category: event.category,
210
- message: event.message,
211
- cwd: event.cwd,
212
- data: event.data,
213
- };
249
+ const view = toSanitizedView(event);
214
250
  if (allowRaw && event.rawData !== undefined) {
215
251
  view.rawData = event.rawData;
216
252
  }
@@ -246,5 +282,6 @@ export function resetDebugRegistry(): void {
246
282
  const state = getState();
247
283
  state.config = cloneConfig(DEBUG_REGISTRY_DEFAULTS);
248
284
  state.events = [];
285
+ state.listeners.clear();
249
286
  state.nextId = 1;
250
287
  }
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-lsp">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-lsp/assets/social-preview.png" alt="SuPi LSP" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-lsp/assets/social-preview.png" alt="SuPi LSP" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  Shared workspace context, capability contracts, and canonical types for the SuPi code-understanding stack.
10
10
 
11
- This is a **library-only package** — it has no pi extension surface, no user-facing tools, and no UI. It provides the shared abstractions that `supi-lsp`, `supi-tree-sitter`, and `supi-code-intelligence` use to communicate capability availability.
11
+ This is a **library-only package** — it has no pi extension surface, no user-facing tools, and no UI. It is not installed directly `supi-lsp`, `supi-tree-sitter`, and `supi-code-intelligence` bundle it and use its shared abstractions to communicate capability availability.
12
12
 
13
13
  ## Package surfaces
14
14
 
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-core">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.5.1",
4
- "description": "SuPi core shared infrastructure for SuPi extensions (XML context tags, config system)",
3
+ "version": "4.7.0",
4
+ "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -64,6 +64,9 @@ export interface DebugEventQueryResult {
64
64
  rawAccessDenied: boolean;
65
65
  }
66
66
 
67
+ /** Receives a sanitized event whenever the registry records one. */
68
+ export type DebugEventListener = (event: DebugEventView) => void;
69
+
67
70
  export interface DebugSummary {
68
71
  total: number;
69
72
  byLevel: Partial<Record<DebugLevel, number>>;
@@ -73,6 +76,7 @@ export interface DebugSummary {
73
76
  interface DebugRegistryState {
74
77
  config: DebugRegistryConfig;
75
78
  events: DebugEvent[];
79
+ listeners: Set<DebugEventListener>;
76
80
  nextId: number;
77
81
  }
78
82
 
@@ -97,10 +101,14 @@ function getState(): DebugRegistryState {
97
101
  state = {
98
102
  config: cloneConfig(DEBUG_REGISTRY_DEFAULTS),
99
103
  events: [],
104
+ listeners: new Set(),
100
105
  nextId: 1,
101
106
  };
102
107
  (globalThis as Record<symbol, unknown>)[REGISTRY_KEY] = state;
103
108
  }
109
+ // Keep the shared registry compatible with extension reloads that reuse an
110
+ // instance created before listeners existed.
111
+ if (!state.listeners) state.listeners = new Set();
104
112
  return state;
105
113
  }
106
114
 
@@ -119,7 +127,16 @@ function trimToMaxEvents(state: DebugRegistryState): void {
119
127
  state.events.splice(0, state.events.length - maxEvents);
120
128
  }
121
129
 
122
- function matchesQuery(event: DebugEvent, query: DebugEventQuery): boolean {
130
+ /** Return whether a debug level is recognized by the registry. */
131
+ export function isDebugLevel(value: unknown): value is DebugLevel {
132
+ return value === "debug" || value === "info" || value === "warning" || value === "error";
133
+ }
134
+
135
+ /** Match a debug event against the supported source, level, and category filters. */
136
+ export function matchesDebugEventQuery(
137
+ event: Pick<DebugEventView, "source" | "level" | "category">,
138
+ query: Pick<DebugEventQuery, "source" | "level" | "category">,
139
+ ): boolean {
123
140
  if (query.source && event.source !== query.source) return false;
124
141
  if (query.level && event.level !== query.level) return false;
125
142
  if (query.category && event.category !== query.category) return false;
@@ -168,6 +185,26 @@ export function redactDebugData<T>(value: T): T {
168
185
  return redactValue(value, 8) as T;
169
186
  }
170
187
 
188
+ function toSanitizedView(event: DebugEvent): DebugEventView {
189
+ return {
190
+ id: event.id,
191
+ timestamp: event.timestamp,
192
+ source: event.source,
193
+ level: event.level,
194
+ category: event.category,
195
+ message: event.message,
196
+ cwd: event.cwd,
197
+ data: event.data,
198
+ };
199
+ }
200
+
201
+ /** Subscribe to sanitized events. Listeners are isolated so diagnostics cannot disrupt producers. */
202
+ export function subscribeDebugEvents(listener: DebugEventListener): () => void {
203
+ const state = getState();
204
+ state.listeners.add(listener);
205
+ return () => state.listeners.delete(listener);
206
+ }
207
+
171
208
  /** Record a session-local debug event if debugging is enabled. */
172
209
  export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
173
210
  const state = getState();
@@ -183,6 +220,14 @@ export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
183
220
  };
184
221
  state.events.push(event);
185
222
  trimToMaxEvents(state);
223
+ const view = toSanitizedView(event);
224
+ for (const listener of state.listeners) {
225
+ try {
226
+ listener(view);
227
+ } catch {
228
+ // Debug-event consumers must not alter producer behavior.
229
+ }
230
+ }
186
231
  return { ...event };
187
232
  }
188
233
 
@@ -196,21 +241,12 @@ export function getDebugEvents(query: DebugEventQuery = {}): DebugEventQueryResu
196
241
  const limit = query.limit && query.limit > 0 ? Math.floor(query.limit) : state.config.maxEvents;
197
242
 
198
243
  const events = state.events
199
- .filter((event) => matchesQuery(event, query))
244
+ .filter((event) => matchesDebugEventQuery(event, query))
200
245
  .slice()
201
246
  .reverse()
202
247
  .slice(0, limit)
203
248
  .map((event): DebugEventView => {
204
- const view: DebugEventView = {
205
- id: event.id,
206
- timestamp: event.timestamp,
207
- source: event.source,
208
- level: event.level,
209
- category: event.category,
210
- message: event.message,
211
- cwd: event.cwd,
212
- data: event.data,
213
- };
249
+ const view = toSanitizedView(event);
214
250
  if (allowRaw && event.rawData !== undefined) {
215
251
  view.rawData = event.rawData;
216
252
  }
@@ -246,5 +282,6 @@ export function resetDebugRegistry(): void {
246
282
  const state = getState();
247
283
  state.config = cloneConfig(DEBUG_REGISTRY_DEFAULTS);
248
284
  state.events = [];
285
+ state.listeners.clear();
249
286
  state.nextId = 1;
250
287
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "4.5.1",
4
- "description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
3
+ "version": "4.7.0",
4
+ "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  "!__tests__"
30
30
  ],
31
31
  "dependencies": {
32
- "@mrclrchtr/supi-core": "4.5.1"
32
+ "@mrclrchtr/supi-core": "4.7.0"
33
33
  },
34
34
  "bundledDependencies": [
35
35
  "@mrclrchtr/supi-core"
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-core">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.5.1",
4
- "description": "SuPi core shared infrastructure for SuPi extensions (XML context tags, config system)",
3
+ "version": "4.7.0",
4
+ "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -64,6 +64,9 @@ export interface DebugEventQueryResult {
64
64
  rawAccessDenied: boolean;
65
65
  }
66
66
 
67
+ /** Receives a sanitized event whenever the registry records one. */
68
+ export type DebugEventListener = (event: DebugEventView) => void;
69
+
67
70
  export interface DebugSummary {
68
71
  total: number;
69
72
  byLevel: Partial<Record<DebugLevel, number>>;
@@ -73,6 +76,7 @@ export interface DebugSummary {
73
76
  interface DebugRegistryState {
74
77
  config: DebugRegistryConfig;
75
78
  events: DebugEvent[];
79
+ listeners: Set<DebugEventListener>;
76
80
  nextId: number;
77
81
  }
78
82
 
@@ -97,10 +101,14 @@ function getState(): DebugRegistryState {
97
101
  state = {
98
102
  config: cloneConfig(DEBUG_REGISTRY_DEFAULTS),
99
103
  events: [],
104
+ listeners: new Set(),
100
105
  nextId: 1,
101
106
  };
102
107
  (globalThis as Record<symbol, unknown>)[REGISTRY_KEY] = state;
103
108
  }
109
+ // Keep the shared registry compatible with extension reloads that reuse an
110
+ // instance created before listeners existed.
111
+ if (!state.listeners) state.listeners = new Set();
104
112
  return state;
105
113
  }
106
114
 
@@ -119,7 +127,16 @@ function trimToMaxEvents(state: DebugRegistryState): void {
119
127
  state.events.splice(0, state.events.length - maxEvents);
120
128
  }
121
129
 
122
- function matchesQuery(event: DebugEvent, query: DebugEventQuery): boolean {
130
+ /** Return whether a debug level is recognized by the registry. */
131
+ export function isDebugLevel(value: unknown): value is DebugLevel {
132
+ return value === "debug" || value === "info" || value === "warning" || value === "error";
133
+ }
134
+
135
+ /** Match a debug event against the supported source, level, and category filters. */
136
+ export function matchesDebugEventQuery(
137
+ event: Pick<DebugEventView, "source" | "level" | "category">,
138
+ query: Pick<DebugEventQuery, "source" | "level" | "category">,
139
+ ): boolean {
123
140
  if (query.source && event.source !== query.source) return false;
124
141
  if (query.level && event.level !== query.level) return false;
125
142
  if (query.category && event.category !== query.category) return false;
@@ -168,6 +185,26 @@ export function redactDebugData<T>(value: T): T {
168
185
  return redactValue(value, 8) as T;
169
186
  }
170
187
 
188
+ function toSanitizedView(event: DebugEvent): DebugEventView {
189
+ return {
190
+ id: event.id,
191
+ timestamp: event.timestamp,
192
+ source: event.source,
193
+ level: event.level,
194
+ category: event.category,
195
+ message: event.message,
196
+ cwd: event.cwd,
197
+ data: event.data,
198
+ };
199
+ }
200
+
201
+ /** Subscribe to sanitized events. Listeners are isolated so diagnostics cannot disrupt producers. */
202
+ export function subscribeDebugEvents(listener: DebugEventListener): () => void {
203
+ const state = getState();
204
+ state.listeners.add(listener);
205
+ return () => state.listeners.delete(listener);
206
+ }
207
+
171
208
  /** Record a session-local debug event if debugging is enabled. */
172
209
  export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
173
210
  const state = getState();
@@ -183,6 +220,14 @@ export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
183
220
  };
184
221
  state.events.push(event);
185
222
  trimToMaxEvents(state);
223
+ const view = toSanitizedView(event);
224
+ for (const listener of state.listeners) {
225
+ try {
226
+ listener(view);
227
+ } catch {
228
+ // Debug-event consumers must not alter producer behavior.
229
+ }
230
+ }
186
231
  return { ...event };
187
232
  }
188
233
 
@@ -196,21 +241,12 @@ export function getDebugEvents(query: DebugEventQuery = {}): DebugEventQueryResu
196
241
  const limit = query.limit && query.limit > 0 ? Math.floor(query.limit) : state.config.maxEvents;
197
242
 
198
243
  const events = state.events
199
- .filter((event) => matchesQuery(event, query))
244
+ .filter((event) => matchesDebugEventQuery(event, query))
200
245
  .slice()
201
246
  .reverse()
202
247
  .slice(0, limit)
203
248
  .map((event): DebugEventView => {
204
- const view: DebugEventView = {
205
- id: event.id,
206
- timestamp: event.timestamp,
207
- source: event.source,
208
- level: event.level,
209
- category: event.category,
210
- message: event.message,
211
- cwd: event.cwd,
212
- data: event.data,
213
- };
249
+ const view = toSanitizedView(event);
214
250
  if (allowRaw && event.rawData !== undefined) {
215
251
  view.rawData = event.rawData;
216
252
  }
@@ -246,5 +282,6 @@ export function resetDebugRegistry(): void {
246
282
  const state = getState();
247
283
  state.config = cloneConfig(DEBUG_REGISTRY_DEFAULTS);
248
284
  state.events = [];
285
+ state.listeners.clear();
249
286
  state.nextId = 1;
250
287
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-lsp",
3
- "version": "4.5.1",
4
- "description": "SuPi LSP runtime — Language Server Protocol integration library for pi",
3
+ "version": "4.7.0",
4
+ "description": "Language Server Protocol runtime for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -37,8 +37,8 @@
37
37
  "vscode-jsonrpc": "^9.0.0",
38
38
  "vscode-languageserver-protocol": "^3.17.5",
39
39
  "vscode-languageserver-types": "^3.17.5",
40
- "@mrclrchtr/supi-code-runtime": "4.5.1",
41
- "@mrclrchtr/supi-core": "4.5.1"
40
+ "@mrclrchtr/supi-code-runtime": "4.7.0",
41
+ "@mrclrchtr/supi-core": "4.7.0"
42
42
  },
43
43
  "bundledDependencies": [
44
44
  "@mrclrchtr/supi-code-runtime",
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-tree-sitter">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-tree-sitter/assets/social-preview.png" alt="SuPi Tree-sitter" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-tree-sitter/assets/social-preview.png" alt="SuPi Tree-sitter" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  Shared workspace context, capability contracts, and canonical types for the SuPi code-understanding stack.
10
10
 
11
- This is a **library-only package** — it has no pi extension surface, no user-facing tools, and no UI. It provides the shared abstractions that `supi-lsp`, `supi-tree-sitter`, and `supi-code-intelligence` use to communicate capability availability.
11
+ This is a **library-only package** — it has no pi extension surface, no user-facing tools, and no UI. It is not installed directly `supi-lsp`, `supi-tree-sitter`, and `supi-code-intelligence` bundle it and use its shared abstractions to communicate capability availability.
12
12
 
13
13
  ## Package surfaces
14
14
 
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-core">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.5.1",
4
- "description": "SuPi core shared infrastructure for SuPi extensions (XML context tags, config system)",
3
+ "version": "4.7.0",
4
+ "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -64,6 +64,9 @@ export interface DebugEventQueryResult {
64
64
  rawAccessDenied: boolean;
65
65
  }
66
66
 
67
+ /** Receives a sanitized event whenever the registry records one. */
68
+ export type DebugEventListener = (event: DebugEventView) => void;
69
+
67
70
  export interface DebugSummary {
68
71
  total: number;
69
72
  byLevel: Partial<Record<DebugLevel, number>>;
@@ -73,6 +76,7 @@ export interface DebugSummary {
73
76
  interface DebugRegistryState {
74
77
  config: DebugRegistryConfig;
75
78
  events: DebugEvent[];
79
+ listeners: Set<DebugEventListener>;
76
80
  nextId: number;
77
81
  }
78
82
 
@@ -97,10 +101,14 @@ function getState(): DebugRegistryState {
97
101
  state = {
98
102
  config: cloneConfig(DEBUG_REGISTRY_DEFAULTS),
99
103
  events: [],
104
+ listeners: new Set(),
100
105
  nextId: 1,
101
106
  };
102
107
  (globalThis as Record<symbol, unknown>)[REGISTRY_KEY] = state;
103
108
  }
109
+ // Keep the shared registry compatible with extension reloads that reuse an
110
+ // instance created before listeners existed.
111
+ if (!state.listeners) state.listeners = new Set();
104
112
  return state;
105
113
  }
106
114
 
@@ -119,7 +127,16 @@ function trimToMaxEvents(state: DebugRegistryState): void {
119
127
  state.events.splice(0, state.events.length - maxEvents);
120
128
  }
121
129
 
122
- function matchesQuery(event: DebugEvent, query: DebugEventQuery): boolean {
130
+ /** Return whether a debug level is recognized by the registry. */
131
+ export function isDebugLevel(value: unknown): value is DebugLevel {
132
+ return value === "debug" || value === "info" || value === "warning" || value === "error";
133
+ }
134
+
135
+ /** Match a debug event against the supported source, level, and category filters. */
136
+ export function matchesDebugEventQuery(
137
+ event: Pick<DebugEventView, "source" | "level" | "category">,
138
+ query: Pick<DebugEventQuery, "source" | "level" | "category">,
139
+ ): boolean {
123
140
  if (query.source && event.source !== query.source) return false;
124
141
  if (query.level && event.level !== query.level) return false;
125
142
  if (query.category && event.category !== query.category) return false;
@@ -168,6 +185,26 @@ export function redactDebugData<T>(value: T): T {
168
185
  return redactValue(value, 8) as T;
169
186
  }
170
187
 
188
+ function toSanitizedView(event: DebugEvent): DebugEventView {
189
+ return {
190
+ id: event.id,
191
+ timestamp: event.timestamp,
192
+ source: event.source,
193
+ level: event.level,
194
+ category: event.category,
195
+ message: event.message,
196
+ cwd: event.cwd,
197
+ data: event.data,
198
+ };
199
+ }
200
+
201
+ /** Subscribe to sanitized events. Listeners are isolated so diagnostics cannot disrupt producers. */
202
+ export function subscribeDebugEvents(listener: DebugEventListener): () => void {
203
+ const state = getState();
204
+ state.listeners.add(listener);
205
+ return () => state.listeners.delete(listener);
206
+ }
207
+
171
208
  /** Record a session-local debug event if debugging is enabled. */
172
209
  export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
173
210
  const state = getState();
@@ -183,6 +220,14 @@ export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
183
220
  };
184
221
  state.events.push(event);
185
222
  trimToMaxEvents(state);
223
+ const view = toSanitizedView(event);
224
+ for (const listener of state.listeners) {
225
+ try {
226
+ listener(view);
227
+ } catch {
228
+ // Debug-event consumers must not alter producer behavior.
229
+ }
230
+ }
186
231
  return { ...event };
187
232
  }
188
233
 
@@ -196,21 +241,12 @@ export function getDebugEvents(query: DebugEventQuery = {}): DebugEventQueryResu
196
241
  const limit = query.limit && query.limit > 0 ? Math.floor(query.limit) : state.config.maxEvents;
197
242
 
198
243
  const events = state.events
199
- .filter((event) => matchesQuery(event, query))
244
+ .filter((event) => matchesDebugEventQuery(event, query))
200
245
  .slice()
201
246
  .reverse()
202
247
  .slice(0, limit)
203
248
  .map((event): DebugEventView => {
204
- const view: DebugEventView = {
205
- id: event.id,
206
- timestamp: event.timestamp,
207
- source: event.source,
208
- level: event.level,
209
- category: event.category,
210
- message: event.message,
211
- cwd: event.cwd,
212
- data: event.data,
213
- };
249
+ const view = toSanitizedView(event);
214
250
  if (allowRaw && event.rawData !== undefined) {
215
251
  view.rawData = event.rawData;
216
252
  }
@@ -246,5 +282,6 @@ export function resetDebugRegistry(): void {
246
282
  const state = getState();
247
283
  state.config = cloneConfig(DEBUG_REGISTRY_DEFAULTS);
248
284
  state.events = [];
285
+ state.listeners.clear();
249
286
  state.nextId = 1;
250
287
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "4.5.1",
4
- "description": "SuPi code-runtime — shared workspace context, capability contracts, and canonical types for the code-understanding stack",
3
+ "version": "4.7.0",
4
+ "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  "!__tests__"
30
30
  ],
31
31
  "dependencies": {
32
- "@mrclrchtr/supi-core": "4.5.1"
32
+ "@mrclrchtr/supi-core": "4.7.0"
33
33
  },
34
34
  "bundledDependencies": [
35
35
  "@mrclrchtr/supi-core"
@@ -1,8 +1,6 @@
1
1
  <div align="center">
2
2
  <a href="https://github.com/mrclrchtr/supi/tree/main/packages/supi-core">
3
- <picture>
4
- <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
5
- </picture>
3
+ <img src="https://raw.githubusercontent.com/mrclrchtr/supi/main/packages/supi-core/assets/social-preview.png" alt="SuPi Core" width="100%">
6
4
  </a>
7
5
  </div>
8
6
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.5.1",
4
- "description": "SuPi core shared infrastructure for SuPi extensions (XML context tags, config system)",
3
+ "version": "4.7.0",
4
+ "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -64,6 +64,9 @@ export interface DebugEventQueryResult {
64
64
  rawAccessDenied: boolean;
65
65
  }
66
66
 
67
+ /** Receives a sanitized event whenever the registry records one. */
68
+ export type DebugEventListener = (event: DebugEventView) => void;
69
+
67
70
  export interface DebugSummary {
68
71
  total: number;
69
72
  byLevel: Partial<Record<DebugLevel, number>>;
@@ -73,6 +76,7 @@ export interface DebugSummary {
73
76
  interface DebugRegistryState {
74
77
  config: DebugRegistryConfig;
75
78
  events: DebugEvent[];
79
+ listeners: Set<DebugEventListener>;
76
80
  nextId: number;
77
81
  }
78
82
 
@@ -97,10 +101,14 @@ function getState(): DebugRegistryState {
97
101
  state = {
98
102
  config: cloneConfig(DEBUG_REGISTRY_DEFAULTS),
99
103
  events: [],
104
+ listeners: new Set(),
100
105
  nextId: 1,
101
106
  };
102
107
  (globalThis as Record<symbol, unknown>)[REGISTRY_KEY] = state;
103
108
  }
109
+ // Keep the shared registry compatible with extension reloads that reuse an
110
+ // instance created before listeners existed.
111
+ if (!state.listeners) state.listeners = new Set();
104
112
  return state;
105
113
  }
106
114
 
@@ -119,7 +127,16 @@ function trimToMaxEvents(state: DebugRegistryState): void {
119
127
  state.events.splice(0, state.events.length - maxEvents);
120
128
  }
121
129
 
122
- function matchesQuery(event: DebugEvent, query: DebugEventQuery): boolean {
130
+ /** Return whether a debug level is recognized by the registry. */
131
+ export function isDebugLevel(value: unknown): value is DebugLevel {
132
+ return value === "debug" || value === "info" || value === "warning" || value === "error";
133
+ }
134
+
135
+ /** Match a debug event against the supported source, level, and category filters. */
136
+ export function matchesDebugEventQuery(
137
+ event: Pick<DebugEventView, "source" | "level" | "category">,
138
+ query: Pick<DebugEventQuery, "source" | "level" | "category">,
139
+ ): boolean {
123
140
  if (query.source && event.source !== query.source) return false;
124
141
  if (query.level && event.level !== query.level) return false;
125
142
  if (query.category && event.category !== query.category) return false;
@@ -168,6 +185,26 @@ export function redactDebugData<T>(value: T): T {
168
185
  return redactValue(value, 8) as T;
169
186
  }
170
187
 
188
+ function toSanitizedView(event: DebugEvent): DebugEventView {
189
+ return {
190
+ id: event.id,
191
+ timestamp: event.timestamp,
192
+ source: event.source,
193
+ level: event.level,
194
+ category: event.category,
195
+ message: event.message,
196
+ cwd: event.cwd,
197
+ data: event.data,
198
+ };
199
+ }
200
+
201
+ /** Subscribe to sanitized events. Listeners are isolated so diagnostics cannot disrupt producers. */
202
+ export function subscribeDebugEvents(listener: DebugEventListener): () => void {
203
+ const state = getState();
204
+ state.listeners.add(listener);
205
+ return () => state.listeners.delete(listener);
206
+ }
207
+
171
208
  /** Record a session-local debug event if debugging is enabled. */
172
209
  export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
173
210
  const state = getState();
@@ -183,6 +220,14 @@ export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
183
220
  };
184
221
  state.events.push(event);
185
222
  trimToMaxEvents(state);
223
+ const view = toSanitizedView(event);
224
+ for (const listener of state.listeners) {
225
+ try {
226
+ listener(view);
227
+ } catch {
228
+ // Debug-event consumers must not alter producer behavior.
229
+ }
230
+ }
186
231
  return { ...event };
187
232
  }
188
233
 
@@ -196,21 +241,12 @@ export function getDebugEvents(query: DebugEventQuery = {}): DebugEventQueryResu
196
241
  const limit = query.limit && query.limit > 0 ? Math.floor(query.limit) : state.config.maxEvents;
197
242
 
198
243
  const events = state.events
199
- .filter((event) => matchesQuery(event, query))
244
+ .filter((event) => matchesDebugEventQuery(event, query))
200
245
  .slice()
201
246
  .reverse()
202
247
  .slice(0, limit)
203
248
  .map((event): DebugEventView => {
204
- const view: DebugEventView = {
205
- id: event.id,
206
- timestamp: event.timestamp,
207
- source: event.source,
208
- level: event.level,
209
- category: event.category,
210
- message: event.message,
211
- cwd: event.cwd,
212
- data: event.data,
213
- };
249
+ const view = toSanitizedView(event);
214
250
  if (allowRaw && event.rawData !== undefined) {
215
251
  view.rawData = event.rawData;
216
252
  }
@@ -246,5 +282,6 @@ export function resetDebugRegistry(): void {
246
282
  const state = getState();
247
283
  state.config = cloneConfig(DEBUG_REGISTRY_DEFAULTS);
248
284
  state.events = [];
285
+ state.listeners.clear();
249
286
  state.nextId = 1;
250
287
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "4.5.1",
4
- "description": "SuPi Tree-sitter library — structural AST analysis for pi",
3
+ "version": "4.7.0",
4
+ "description": "Structural AST analysis for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -34,8 +34,8 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "web-tree-sitter": "^0.26.8",
37
- "@mrclrchtr/supi-code-runtime": "4.5.1",
38
- "@mrclrchtr/supi-core": "4.5.1"
37
+ "@mrclrchtr/supi-code-runtime": "4.7.0",
38
+ "@mrclrchtr/supi-core": "4.7.0"
39
39
  },
40
40
  "bundledDependencies": [
41
41
  "@mrclrchtr/supi-code-runtime",
@@ -60,7 +60,7 @@ function htmlId(tag: SyntaxNodeLike): string | null {
60
60
  if (attribute.type !== "attribute") continue;
61
61
  const name = directChild(attribute, "attribute_name");
62
62
  if (name?.text.toLowerCase() !== "id") continue;
63
- return firstDescendant(attribute, "attribute_value")?.text || null;
63
+ return firstDescendant(attribute, "attribute_value")?.text ?? null;
64
64
  }
65
65
  return null;
66
66
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-intelligence",
3
- "version": "4.5.1",
4
- "description": "LSP and Tree-sitter AST code intelligence for the Pi coding agent — semantic navigation, structural search, diagnostics, and safe refactoring",
3
+ "version": "4.7.0",
4
+ "description": "LSP and AST navigation, search, diagnostics, and refactoring",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -35,10 +35,10 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "yaml": "^2.9.0",
38
- "@mrclrchtr/supi-core": "4.5.1",
39
- "@mrclrchtr/supi-lsp": "4.5.1",
40
- "@mrclrchtr/supi-code-runtime": "4.5.1",
41
- "@mrclrchtr/supi-tree-sitter": "4.5.1"
38
+ "@mrclrchtr/supi-code-runtime": "4.7.0",
39
+ "@mrclrchtr/supi-lsp": "4.7.0",
40
+ "@mrclrchtr/supi-tree-sitter": "4.7.0",
41
+ "@mrclrchtr/supi-core": "4.7.0"
42
42
  },
43
43
  "bundledDependencies": [
44
44
  "@mrclrchtr/supi-code-runtime",