@mrclrchtr/supi-debug 4.10.0 → 6.0.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 +15 -7
- package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -6
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
- package/package.json +3 -2
- package/prompts/supi-tooling-retro.md +77 -0
- package/src/command.ts +132 -0
- package/src/config.ts +83 -0
- package/src/debug.ts +19 -309
- package/src/format-events.ts +101 -0
- package/src/query.ts +45 -0
- package/src/render-details.ts +336 -0
- package/src/renderer.ts +134 -59
- package/src/session-events.ts +88 -14
- package/src/tool/debug/execute.ts +20 -0
- package/src/tool/debug/guidance.ts +11 -0
- package/src/tool/debug/register.ts +16 -0
- package/src/tool/debug/render.ts +105 -0
- package/src/tool/debug/result.ts +113 -0
- package/src/tool/debug/spec.ts +35 -0
- package/src/tool/guidance.ts +0 -11
package/README.md
CHANGED
|
@@ -27,10 +27,10 @@ pi install ./packages/supi-debug
|
|
|
27
27
|
After install, this package wires the shared debug registry into three user-facing surfaces:
|
|
28
28
|
|
|
29
29
|
- `/supi-debug` — show recent debug events in a readable TUI report
|
|
30
|
-
- `
|
|
30
|
+
- `debug` — let the model query recent debug events during troubleshooting
|
|
31
31
|
- `/supi-settings` integration — configure whether events are captured and how much data is exposed
|
|
32
32
|
|
|
33
|
-
It also registers a **Debug** provider section for `/supi-context
|
|
33
|
+
It also registers a **Debug** provider section for `/supi-context`, and ships a `/supi-tooling-retro` prompt template for post-task retrospective feedback on the SuPi tooling used in the completed task.
|
|
34
34
|
|
|
35
35
|
## Event behavior
|
|
36
36
|
|
|
@@ -40,6 +40,12 @@ It also registers a **Debug** provider section for `/supi-context`.
|
|
|
40
40
|
- if debug capture is disabled, no events are retained
|
|
41
41
|
- agent-facing access is blocked, sanitized, or raw depending on settings
|
|
42
42
|
|
|
43
|
+
### Identity disclosure
|
|
44
|
+
|
|
45
|
+
Retained and persisted LSP debug events may identify local workspaces and files. Since the LSP telemetry expansion, LSP events can carry the absolute workspace root (`cwd`), the configured server name (`server`), workspace-relative file paths (`file`), exact LSP method names (`method`), and the server root (`root`). Identity strings are bounded to 512 UTF-16 code units and server lists to 16 entries.
|
|
46
|
+
|
|
47
|
+
Identity fields are **not** secret-redacted. The debug registry redacts secret keys and secret-looking values (tokens, passwords, API keys, authorization headers, URL credentials), but server names, file paths, method names, and workspace roots pass through unredacted by design, so local protocol failures stay diagnosable. Treat retained and persisted LSP events as potentially identifying your local machine, project layout, and file names.
|
|
48
|
+
|
|
43
49
|
## Rendering
|
|
44
50
|
|
|
45
51
|
`/supi-debug` uses a custom TUI message renderer that shows two levels of detail:
|
|
@@ -63,6 +69,7 @@ Rendered fields per event:
|
|
|
63
69
|
- optional `cwd`
|
|
64
70
|
- optional `data`
|
|
65
71
|
- optional `rawData`
|
|
72
|
+
- optional `operationId` for events directly owned by one public `code_*` call
|
|
66
73
|
|
|
67
74
|
### Why collapsed by default
|
|
68
75
|
|
|
@@ -71,23 +78,24 @@ keeps the conversation focused; expand only when you need the details.
|
|
|
71
78
|
|
|
72
79
|
### Seeing full details without expanding
|
|
73
80
|
|
|
74
|
-
The agent-facing `
|
|
81
|
+
The agent-facing `debug` tool returns the expanded plain-text
|
|
75
82
|
representation, subject to PI's standard tool-output truncation limits. This is
|
|
76
83
|
useful for automated troubleshooting flows while protecting the model context
|
|
77
84
|
from very large event payloads.
|
|
78
85
|
|
|
79
86
|
## Filters
|
|
80
87
|
|
|
81
|
-
Both `/supi-debug` and `
|
|
88
|
+
Both `/supi-debug` and `debug` support the same basic filters:
|
|
82
89
|
|
|
83
90
|
- `source`
|
|
84
91
|
- `level`
|
|
85
92
|
- `category`
|
|
93
|
+
- exact `operationId`
|
|
86
94
|
- `limit`
|
|
87
95
|
|
|
88
|
-
For historical sessions, pass `sessionFile` to `
|
|
96
|
+
For historical sessions, pass `sessionFile` to `debug`, or
|
|
89
97
|
`sessionFile=<path>` to `/supi-debug`. Historical sessions never retain raw data.
|
|
90
|
-
The tool also accepts `includeRaw` for live-session data when settings allow it.
|
|
98
|
+
The tool also accepts `includeRaw` for live-session data when settings allow it. A Debug Operation ID groups direct request ownership only. It is not a security identity, distributed trace, raw Pi Tool-call identity, or time-window correlation.
|
|
91
99
|
|
|
92
100
|
## Settings
|
|
93
101
|
|
|
@@ -100,7 +108,7 @@ Available settings:
|
|
|
100
108
|
- `maxEvents` — maximum retained events in memory
|
|
101
109
|
|
|
102
110
|
Historical inspection works for events captured after this version is loaded. For example, an
|
|
103
|
-
agent can call `
|
|
111
|
+
agent can call `debug` with `sessionFile` set to a PI session JSONL path.
|
|
104
112
|
|
|
105
113
|
Defaults come from the shared debug registry:
|
|
106
114
|
|
|
@@ -28,6 +28,7 @@ pnpm add @mrclrchtr/supi-core
|
|
|
28
28
|
- `loadSupiConfig()` — merged config with resolution order `defaults <- global <- project`
|
|
29
29
|
- `loadSupiConfigForScope()` — load one scope at a time for settings UIs
|
|
30
30
|
- `writeSupiConfig()` — persist values
|
|
31
|
+
- `replaceSupiConfigSection()` — replace one nested section while preserving other sections
|
|
31
32
|
- `removeSupiConfigKey()` — remove a key or override
|
|
32
33
|
|
|
33
34
|
Config file locations:
|
|
@@ -49,6 +50,7 @@ Config file locations:
|
|
|
49
50
|
|
|
50
51
|
- context-provider registry for `/supi-context`
|
|
51
52
|
- debug-event registry and monotonic phase timers for producers that want shared debug capture
|
|
53
|
+
- optional Debug Operation IDs for exact, directly owned public Tool-call correlation; ambient events stay uncorrelated
|
|
52
54
|
- settings registry used by `/supi-settings`
|
|
53
55
|
|
|
54
56
|
### Project and session helpers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Shared settings, configuration, reporting, and session infrastructure",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"./api": "./src/api.ts",
|
|
54
54
|
"./config": "./src/config.ts",
|
|
55
55
|
"./context": "./src/context.ts",
|
|
56
|
-
"./debug": "./src/debug
|
|
56
|
+
"./debug": "./src/debug.ts",
|
|
57
57
|
"./evidence-badge": "./src/evidence-badge.ts",
|
|
58
58
|
"./footer-registry": "./src/footer-registry.ts",
|
|
59
59
|
"./llm": "./src/llm.ts",
|
|
@@ -11,7 +11,7 @@ export * from "./config.ts";
|
|
|
11
11
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
12
12
|
export * from "./context.ts";
|
|
13
13
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
14
|
-
export * from "./debug
|
|
14
|
+
export * from "./debug.ts";
|
|
15
15
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
16
|
export * from "./evidence-badge.ts";
|
|
17
17
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
@@ -148,6 +148,37 @@ export function writeSupiConfig(
|
|
|
148
148
|
fs.writeFileSync(configPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Replace one complete config section while preserving other sections.
|
|
153
|
+
*
|
|
154
|
+
* This is useful for nested settings that must remove stale keys as part of
|
|
155
|
+
* one update. An empty section is removed from the config file.
|
|
156
|
+
*/
|
|
157
|
+
export function replaceSupiConfigSection(
|
|
158
|
+
loc: SupiConfigLocation,
|
|
159
|
+
value: Record<string, unknown>,
|
|
160
|
+
options?: SupiConfigOptions,
|
|
161
|
+
): void {
|
|
162
|
+
const configPath = getSupiConfigPath(loc.scope, loc.cwd, options);
|
|
163
|
+
const existing = readJsonFile(configPath) ?? {};
|
|
164
|
+
|
|
165
|
+
if (Object.keys(value).length > 0) existing[loc.section] = value;
|
|
166
|
+
else delete existing[loc.section];
|
|
167
|
+
|
|
168
|
+
const content = Object.keys(existing).length > 0 ? `${JSON.stringify(existing, null, 2)}\n` : "";
|
|
169
|
+
if (content) {
|
|
170
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
171
|
+
fs.writeFileSync(configPath, content, "utf-8");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
fs.unlinkSync(configPath);
|
|
177
|
+
} catch {
|
|
178
|
+
// File may not exist.
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
151
182
|
/**
|
|
152
183
|
* Remove a key from a config section.
|
|
153
184
|
* Used by `interval default` to remove the project override.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// supi-core config domain — config loading.
|
|
2
2
|
export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
|
|
3
3
|
export {
|
|
4
|
+
getSupiConfigPath,
|
|
4
5
|
loadSupiConfig,
|
|
5
6
|
loadSupiConfigForScope,
|
|
6
7
|
loadSupiConfigSectionForScope,
|
|
7
8
|
readJsonFile,
|
|
8
9
|
removeSupiConfigKey,
|
|
10
|
+
replaceSupiConfigSection,
|
|
9
11
|
writeSupiConfig,
|
|
10
12
|
} from "./config/config.ts";
|
|
@@ -4,9 +4,6 @@
|
|
|
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
|
-
|
|
10
7
|
export type DebugLevel = "debug" | "info" | "warning" | "error";
|
|
11
8
|
export type DebugAgentAccess = "off" | "sanitized" | "raw";
|
|
12
9
|
export interface DebugRegistryConfig {
|
|
@@ -25,6 +22,8 @@ export const DEBUG_REGISTRY_DEFAULTS: DebugRegistryConfig = {
|
|
|
25
22
|
};
|
|
26
23
|
|
|
27
24
|
export interface DebugEventInput {
|
|
25
|
+
/** Opaque identity for events directly owned by one public Tool call. */
|
|
26
|
+
operationId?: string;
|
|
28
27
|
source: string;
|
|
29
28
|
level: DebugLevel;
|
|
30
29
|
category: string;
|
|
@@ -42,6 +41,8 @@ export interface DebugEvent extends DebugEventInput {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
export interface DebugEventQuery {
|
|
44
|
+
/** Match one exact Debug Operation ID. */
|
|
45
|
+
operationId?: string;
|
|
45
46
|
source?: string;
|
|
46
47
|
level?: DebugLevel;
|
|
47
48
|
category?: string;
|
|
@@ -53,6 +54,7 @@ export interface DebugEventQuery {
|
|
|
53
54
|
export interface DebugEventView {
|
|
54
55
|
id: number;
|
|
55
56
|
timestamp: number;
|
|
57
|
+
operationId?: string;
|
|
56
58
|
source: string;
|
|
57
59
|
level: DebugLevel;
|
|
58
60
|
category: string;
|
|
@@ -84,6 +86,7 @@ interface DebugRegistryState {
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
const REGISTRY_KEY = Symbol.for("@mrclrchtr/supi-core/debug-registry");
|
|
89
|
+
const DEBUG_OPERATION_ID_RE = /^op-[A-Za-z0-9_-]{21}[AQgw]$/;
|
|
87
90
|
const SECRET_KEY_RE = /(?:token|password|passwd|secret|api[_-]?key|authorization|credential)/i;
|
|
88
91
|
const ENV_SECRET_RE =
|
|
89
92
|
/\b([A-Za-z0-9_]*(?:token|password|passwd|secret|api[_-]?key|authorization|credential)[A-Za-z0-9_]*)=(?:'[^']*'|"[^"]*"|\S+)/gi;
|
|
@@ -135,11 +138,17 @@ export function isDebugLevel(value: unknown): value is DebugLevel {
|
|
|
135
138
|
return value === "debug" || value === "info" || value === "warning" || value === "error";
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
/**
|
|
141
|
+
/** Return whether a value has the exact 16-byte base64url Debug Operation ID form. */
|
|
142
|
+
export function isDebugOperationId(value: unknown): value is string {
|
|
143
|
+
return typeof value === "string" && DEBUG_OPERATION_ID_RE.test(value);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Match a debug event against the supported exact filters. */
|
|
139
147
|
export function matchesDebugEventQuery(
|
|
140
|
-
event: Pick<DebugEventView, "source" | "level" | "category">,
|
|
141
|
-
query: Pick<DebugEventQuery, "source" | "level" | "category">,
|
|
148
|
+
event: Pick<DebugEventView, "operationId" | "source" | "level" | "category">,
|
|
149
|
+
query: Pick<DebugEventQuery, "operationId" | "source" | "level" | "category">,
|
|
142
150
|
): boolean {
|
|
151
|
+
if (query.operationId && event.operationId !== query.operationId) return false;
|
|
143
152
|
if (query.source && event.source !== query.source) return false;
|
|
144
153
|
if (query.level && event.level !== query.level) return false;
|
|
145
154
|
if (query.category && event.category !== query.category) return false;
|
|
@@ -197,6 +206,7 @@ function toSanitizedView(event: DebugEvent): DebugEventView {
|
|
|
197
206
|
return {
|
|
198
207
|
id: event.id,
|
|
199
208
|
timestamp: event.timestamp,
|
|
209
|
+
operationId: event.operationId,
|
|
200
210
|
source: event.source,
|
|
201
211
|
level: event.level,
|
|
202
212
|
category: event.category,
|
|
@@ -216,6 +226,9 @@ export function subscribeDebugEvents(listener: DebugEventListener): () => void {
|
|
|
216
226
|
/** Record a session-local debug event if debugging is enabled. */
|
|
217
227
|
export function recordDebugEvent(input: DebugEventInput): DebugEvent | null {
|
|
218
228
|
const state = getState();
|
|
229
|
+
if (input.operationId !== undefined && !isDebugOperationId(input.operationId)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
219
232
|
if (!state.config.enabled) {
|
|
220
233
|
return null;
|
|
221
234
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Debug domain entry for `@mrclrchtr/supi-core/debug`.
|
|
2
|
+
//
|
|
3
|
+
// Kept separate from debug-registry.ts so debug-timing.ts can import the
|
|
4
|
+
// registry without creating an import cycle through the barrel re-export.
|
|
5
|
+
|
|
6
|
+
// biome-ignore lint/performance/noReExportAll: preserve the stable debug domain entry point
|
|
7
|
+
export * from "./debug-registry.ts";
|
|
8
|
+
// biome-ignore lint/performance/noReExportAll: preserve the stable debug domain entry point
|
|
9
|
+
export * from "./debug-timing.ts";
|
|
@@ -11,7 +11,7 @@ export * from "./config.ts";
|
|
|
11
11
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
12
12
|
export * from "./context.ts";
|
|
13
13
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
14
|
-
export * from "./debug
|
|
14
|
+
export * from "./debug.ts";
|
|
15
15
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
16
16
|
export * from "./footer-registry.ts";
|
|
17
17
|
// biome-ignore lint/performance/noReExportAll: intentional convenience barrel
|
|
@@ -41,7 +41,10 @@ export interface SettingsApplyResult {
|
|
|
41
41
|
*/
|
|
42
42
|
export interface SettingsModule {
|
|
43
43
|
id: string;
|
|
44
|
+
/** Human-readable section label shown in the UI. */
|
|
44
45
|
label: string;
|
|
46
|
+
/** Optional label that groups this module within its section. */
|
|
47
|
+
subsection?: string;
|
|
45
48
|
read(context: SettingsContext): Promise<SettingsSnapshot>;
|
|
46
49
|
apply(request: SettingsActionRequest): Promise<SettingsApplyResult>;
|
|
47
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-debug",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Capture and inspect SuPi debug events",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
"type": "module",
|
|
29
29
|
"files": [
|
|
30
30
|
"src/**/*.ts",
|
|
31
|
+
"prompts",
|
|
31
32
|
"README.md"
|
|
32
33
|
],
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@mrclrchtr/supi-core": "
|
|
35
|
+
"@mrclrchtr/supi-core": "6.0.0"
|
|
35
36
|
},
|
|
36
37
|
"bundledDependencies": [
|
|
37
38
|
"@mrclrchtr/supi-core"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Retrospect on SuPi tooling used during the completed task
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Produce a compact, evidence-based retrospective of the task that just ended. Evaluate the SuPi surfaces that were available and relevant: tools, skills, prompts, extensions, injected context or guidance, documentation, settings, and human-only TUI commands. Do not evaluate the entire suite in the abstract. Do not evaluate pi-core behavior.
|
|
6
|
+
|
|
7
|
+
## Evidence rules
|
|
8
|
+
|
|
9
|
+
- Reconstruct the retrospective from this session only. Do not invent tool calls, outcomes, failures, token costs, or user impact.
|
|
10
|
+
- You may query the `debug` tool to verify event-level claims. You may query `cache_forensics` when it is available to verify cache-related claims. Use such queries only for verification, and keep them few. Do not perform any other tool calls or follow-up investigation. Stop after you write the retrospective.
|
|
11
|
+
- Tag every claim as `[observed]`, `[inferred]`, or `[unknown]`. An observed claim is supported by the transcript, a tool result, or an evidence query. An inferred claim is plausible but untested.
|
|
12
|
+
- Rate the impact of each surfaced item as `high`, `medium`, or `low`. The impact is the degree to which the item changed the task outcome or its cost.
|
|
13
|
+
- Use the exact surface name that appears in the session. Do not treat an available but unused tool as evidence of failure.
|
|
14
|
+
- If no SuPi surface was used or relevant, say so explicitly. Do not build a wishlist. List only task-specific, plausible missed help.
|
|
15
|
+
|
|
16
|
+
## Evaluate
|
|
17
|
+
|
|
18
|
+
- SuPi surfaces that were actually used: what they enabled, where they fell short, and any failure or friction.
|
|
19
|
+
- Failed or mis-parameterized tool calls: SuPi surface calls that failed or were rejected for wrong, missing, or invalid parameters, including calls corrected on retry. Name the cause: agent misuse, ambiguous tool description, missing guidance (promptSnippet or promptGuidelines), a schema gap, or a tool bug.
|
|
20
|
+
- Recognized problems or bugs: concrete defects in SuPi surfaces, such as crashes, wrong output, failed calls for valid usage, regressions, inconsistent behavior, or guidance that led to a wrong action. Do not report defects in the project's own code.
|
|
21
|
+
- Missed opportunities: a relevant unused tool (for example a supi-code-intelligence tool) or context source, and why it was missed. Name the cause: discoverability, guidance, timing, capability, or not applicable.
|
|
22
|
+
- Missing pieces: a concrete utility, capability, documentation page, example, or output improvement that would have changed this task.
|
|
23
|
+
- Noise: redundant instructions, repeated advice without added value, stale or irrelevant context, excessive output, unnecessary long paths, poor timing, or misleading guidance. Do not criticize necessary context without naming the avoidable cost.
|
|
24
|
+
- Keep SuPi or tooling recommendations separate from general code or project recommendations.
|
|
25
|
+
|
|
26
|
+
## Output rules
|
|
27
|
+
|
|
28
|
+
- Keep the result under about 600 words and specific to this task.
|
|
29
|
+
- Open with a one-line overall verdict on SuPi tooling for this task.
|
|
30
|
+
- Give each item a confidence tag and an impact rating. `None identified` lines need neither.
|
|
31
|
+
- Prefer concrete evidence and observed friction over generic praise or a feature wishlist.
|
|
32
|
+
- If a section has no supported item, write `None identified` and briefly explain the evidence limit.
|
|
33
|
+
- Include at most three recommendations. Each must name a changeable surface (tool, prompt, skill, docs, guidance, or feature), the proposed change, and the expected benefit. Recommended changes may target tool descriptions, parameter schemas, or guidance, especially when they address the Failed or mis-parameterized tool calls listed above. Do not recommend "use the tool more" unless discoverability is the identified cause.
|
|
34
|
+
- Do not edit files, open issues, update OpenSpec artifacts, or take any other follow-up action.
|
|
35
|
+
|
|
36
|
+
## Required output
|
|
37
|
+
|
|
38
|
+
## SuPi Tooling Retrospective
|
|
39
|
+
|
|
40
|
+
**Task completed**: <1–2 sentence summary>
|
|
41
|
+
|
|
42
|
+
**Verdict**: <one line: the overall state of SuPi tooling for this task and its biggest lever>
|
|
43
|
+
|
|
44
|
+
### Tools used
|
|
45
|
+
- `[observed · impact: high|medium|low]` **`<surface>`** — concrete help, friction, or failure.
|
|
46
|
+
- If none: `None identified — no SuPi surface materially participated in this task.`
|
|
47
|
+
|
|
48
|
+
### Failed or mis-parameterized tool calls
|
|
49
|
+
- `[observed|inferred · impact: high|medium|low]` **`<surface>`** — the call, the invalid or missing parameter, the outcome, and the cause: agent misuse, ambiguous description, missing guidance, schema gap, or tool bug. Include near misses: a call rejected once and corrected on retry is stronger evidence than a clean pass.
|
|
50
|
+
- If none: `None identified — every SuPi call used valid parameters.`
|
|
51
|
+
|
|
52
|
+
### Recognized problems or bugs
|
|
53
|
+
- `[observed|inferred · impact: high|medium|low]` **`<surface>`** — the concrete defect (crash, wrong behavior, regression, inconsistency) and how it affected the task. Do not repeat ordinary friction that belongs under Tools used, and do not repeat parameter-caused call failures that belong under Failed or mis-parameterized tool calls.
|
|
54
|
+
- If none: `None identified — no defect surfaced in this task.`
|
|
55
|
+
|
|
56
|
+
### Missed opportunities
|
|
57
|
+
- `[inferred · impact: high|medium|low]` **`<surface>`** — task-specific help it might have provided. Name the cause: discoverability, guidance, timing, capability, or not applicable.
|
|
58
|
+
- If none: `None identified — do not infer a gap from non-use alone.`
|
|
59
|
+
|
|
60
|
+
### Missing pieces
|
|
61
|
+
- `[observed|inferred · impact: high|medium|low]` **`<utility, feature, docs, example, or output change>`** — the concrete gap and how it affected this task.
|
|
62
|
+
- If none: `None identified.`
|
|
63
|
+
|
|
64
|
+
### Unhelpful or noisy context
|
|
65
|
+
- `[observed|inferred · impact: high|medium|low]` **`<instruction, context, or output>`** — what was unnecessary or costly, and how it could be reduced or better timed.
|
|
66
|
+
- If none: `None identified.`
|
|
67
|
+
|
|
68
|
+
### Prioritized recommendations
|
|
69
|
+
1. **`<named surface>`** — <specific change>; <expected benefit>; confidence: <high|medium|low>.
|
|
70
|
+
2. **`<named surface>`** — <specific change>; <expected benefit>; confidence: <high|medium|low>.
|
|
71
|
+
3. **`<named surface>`** — <specific change>; <expected benefit>; confidence: <high|medium|low>.
|
|
72
|
+
- Include only supported recommendations. If none are supported, write `None identified`.
|
|
73
|
+
|
|
74
|
+
### Confidence / evidence
|
|
75
|
+
- Direct evidence: <what the session and any evidence queries demonstrate>
|
|
76
|
+
- Inference: <what is plausible but untested>
|
|
77
|
+
- Limits: <what the session cannot establish>
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getDebugEvents } from "@mrclrchtr/supi-core/debug";
|
|
3
|
+
import { resolveToolPath } from "@mrclrchtr/supi-core/path";
|
|
4
|
+
import { formatDebugEvents, truncateDebugOutput } from "./format-events.ts";
|
|
5
|
+
import type { DebugToolParams } from "./query.ts";
|
|
6
|
+
import { parseDebugCommandArgs } from "./query.ts";
|
|
7
|
+
import { createDebugMessageDetails } from "./renderer.ts";
|
|
8
|
+
import { readSessionDebugEvents } from "./session-events.ts";
|
|
9
|
+
|
|
10
|
+
const DEBUG_REPORT_TYPE = "supi-debug-report";
|
|
11
|
+
|
|
12
|
+
interface DebugConfig {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface DebugCommandDependencies {
|
|
17
|
+
pi: ExtensionAPI;
|
|
18
|
+
applyConfig: (cwd: string) => DebugConfig;
|
|
19
|
+
normalizeLimit: (value: string) => number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function persistedDebugFilters(query: DebugToolParams) {
|
|
23
|
+
return {
|
|
24
|
+
operationId: query.operationId,
|
|
25
|
+
source: query.source,
|
|
26
|
+
level: query.level,
|
|
27
|
+
category: query.category,
|
|
28
|
+
limit: query.limit,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function sendPersistedDebugReport(
|
|
33
|
+
query: DebugToolParams,
|
|
34
|
+
ctx: ExtensionCommandContext,
|
|
35
|
+
pi: ExtensionAPI,
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const statusKey = "supi-debug";
|
|
38
|
+
const setStatus = ctx.ui?.setStatus;
|
|
39
|
+
try {
|
|
40
|
+
const filters = persistedDebugFilters(query);
|
|
41
|
+
if (!query.sessionFile)
|
|
42
|
+
throw new Error("A session file is required for historical debug events.");
|
|
43
|
+
const sessionFile = resolveToolPath(ctx.cwd, query.sessionFile);
|
|
44
|
+
const persisted =
|
|
45
|
+
ctx.signal || setStatus
|
|
46
|
+
? await readSessionDebugEvents(sessionFile, filters, {
|
|
47
|
+
signal: ctx.signal,
|
|
48
|
+
onProgress: (progress) => {
|
|
49
|
+
setStatus?.(
|
|
50
|
+
statusKey,
|
|
51
|
+
`Reading debug events: ${progress.scannedLines.toLocaleString("en-US")} lines · ${progress.matchedEvents.toLocaleString("en-US")} matches`,
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
: await readSessionDebugEvents(sessionFile, filters);
|
|
56
|
+
const output = truncateDebugOutput(
|
|
57
|
+
formatDebugEvents(persisted.events, false, false, persisted.persistedEventCount).join("\n"),
|
|
58
|
+
);
|
|
59
|
+
pi.sendMessage({
|
|
60
|
+
customType: DEBUG_REPORT_TYPE,
|
|
61
|
+
content: output.text,
|
|
62
|
+
display: true,
|
|
63
|
+
details: createDebugMessageDetails(persisted.events, {
|
|
64
|
+
sessionFile: query.sessionFile,
|
|
65
|
+
persistedEventCount: persisted.persistedEventCount,
|
|
66
|
+
eventCount: persisted.events.length,
|
|
67
|
+
emptyReason:
|
|
68
|
+
persisted.events.length === 0
|
|
69
|
+
? persisted.persistedEventCount === 0
|
|
70
|
+
? "no-persisted-events"
|
|
71
|
+
: "no-matches"
|
|
72
|
+
: undefined,
|
|
73
|
+
truncation: output.truncation,
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
} finally {
|
|
77
|
+
setStatus?.(statusKey, undefined);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sendLiveDebugReport(query: DebugToolParams, pi: ExtensionAPI): void {
|
|
82
|
+
const { events, rawAccessDenied } = getDebugEvents(query);
|
|
83
|
+
const output = truncateDebugOutput(formatDebugEvents(events, rawAccessDenied).join("\n"));
|
|
84
|
+
pi.sendMessage({
|
|
85
|
+
customType: DEBUG_REPORT_TYPE,
|
|
86
|
+
content: output.text,
|
|
87
|
+
display: true,
|
|
88
|
+
details: createDebugMessageDetails(events, {
|
|
89
|
+
rawAccessDenied,
|
|
90
|
+
eventCount: events.length,
|
|
91
|
+
emptyReason: events.length === 0 ? "no-matches" : undefined,
|
|
92
|
+
truncation: output.truncation,
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function handleDebugCommand(
|
|
98
|
+
args: string,
|
|
99
|
+
ctx: ExtensionCommandContext,
|
|
100
|
+
dependencies: DebugCommandDependencies,
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
const { pi, applyConfig, normalizeLimit } = dependencies;
|
|
103
|
+
const config = applyConfig(ctx.cwd);
|
|
104
|
+
const query = parseDebugCommandArgs(args, normalizeLimit);
|
|
105
|
+
if (!config.enabled && !query.sessionFile) {
|
|
106
|
+
pi.sendMessage({
|
|
107
|
+
customType: DEBUG_REPORT_TYPE,
|
|
108
|
+
content: "SuPi debug event capture is disabled. Enable Debug in /supi-settings.",
|
|
109
|
+
display: true,
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (query.sessionFile) {
|
|
115
|
+
await sendPersistedDebugReport(query, ctx, pi);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
sendLiveDebugReport(query, pi);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Register the user-facing debug command. */
|
|
122
|
+
export function registerDebugCommand(
|
|
123
|
+
pi: ExtensionAPI,
|
|
124
|
+
applyConfig: (cwd: string) => DebugConfig,
|
|
125
|
+
normalizeLimit: (value: string) => number,
|
|
126
|
+
): void {
|
|
127
|
+
const dependencies = { pi, applyConfig, normalizeLimit };
|
|
128
|
+
pi.registerCommand("supi-debug", {
|
|
129
|
+
description: "Show recent SuPi debug events",
|
|
130
|
+
handler: (args, ctx) => handleDebugCommand(args, ctx, dependencies),
|
|
131
|
+
});
|
|
132
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Debug config loading and live registry synchronization.
|
|
2
|
+
|
|
3
|
+
import { loadSupiConfig } from "@mrclrchtr/supi-core/config";
|
|
4
|
+
import {
|
|
5
|
+
clearDebugEvents,
|
|
6
|
+
configureDebugRegistry,
|
|
7
|
+
DEBUG_REGISTRY_DEFAULTS,
|
|
8
|
+
type DebugAgentAccess,
|
|
9
|
+
} from "@mrclrchtr/supi-core/debug";
|
|
10
|
+
|
|
11
|
+
export const DEBUG_SECTION = "debug";
|
|
12
|
+
|
|
13
|
+
export interface DebugConfig extends Record<string, unknown> {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
agentAccess: DebugAgentAccess;
|
|
16
|
+
maxEvents: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEBUG_DEFAULTS: DebugConfig = { ...DEBUG_REGISTRY_DEFAULTS };
|
|
20
|
+
|
|
21
|
+
function normalizeAgentAccess(value: string): DebugAgentAccess {
|
|
22
|
+
return value === "off" || value === "raw" ? value : "sanitized";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function normalizeMaxEvents(value: string | number): number {
|
|
26
|
+
const parsed = typeof value === "number" ? value : Number.parseInt(value, 10);
|
|
27
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : DEBUG_DEFAULTS.maxEvents;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeEnabled(value: unknown): boolean {
|
|
31
|
+
if (typeof value === "boolean") {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof value === "string") {
|
|
36
|
+
const normalized = value.trim().toLowerCase();
|
|
37
|
+
if (
|
|
38
|
+
normalized === "true" ||
|
|
39
|
+
normalized === "on" ||
|
|
40
|
+
normalized === "1" ||
|
|
41
|
+
normalized === "yes"
|
|
42
|
+
) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (
|
|
46
|
+
normalized === "false" ||
|
|
47
|
+
normalized === "off" ||
|
|
48
|
+
normalized === "0" ||
|
|
49
|
+
normalized === "no" ||
|
|
50
|
+
normalized === ""
|
|
51
|
+
) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return DEBUG_DEFAULTS.enabled;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (value === 1) return true;
|
|
58
|
+
if (value === 0) return false;
|
|
59
|
+
return DEBUG_DEFAULTS.enabled;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function loadDebugConfig(cwd: string): DebugConfig {
|
|
63
|
+
const config = loadSupiConfig(DEBUG_SECTION, cwd, DEBUG_DEFAULTS);
|
|
64
|
+
return {
|
|
65
|
+
enabled: normalizeEnabled(config.enabled),
|
|
66
|
+
agentAccess: normalizeAgentAccess(String(config.agentAccess)),
|
|
67
|
+
maxEvents: normalizeMaxEvents(config.maxEvents),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function applyDebugConfig(cwd: string): DebugConfig {
|
|
72
|
+
const config = loadDebugConfig(cwd);
|
|
73
|
+
configureDebugRegistry(config);
|
|
74
|
+
return config;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function syncLiveDebugRegistry(cwd: string): DebugConfig {
|
|
78
|
+
const config = applyDebugConfig(cwd);
|
|
79
|
+
if (!config.enabled) {
|
|
80
|
+
clearDebugEvents();
|
|
81
|
+
}
|
|
82
|
+
return config;
|
|
83
|
+
}
|