@mrclrchtr/supi-code-intelligence 1.4.0 → 1.6.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/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +42 -10
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +42 -10
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +2 -16
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +11 -184
- package/node_modules/@mrclrchtr/supi-lsp/src/session/service-registry.ts +5 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/guidance.ts +15 -75
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/register-tools.ts +13 -166
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/tool-specs.ts +248 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +5 -34
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +18 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +107 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +44 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +85 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/context/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/context/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/context/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +85 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +86 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +8 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +5 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +5 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +3 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +30 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +16 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/action-specs.ts +92 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/guidance.ts +12 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +111 -61
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +13 -2
- package/package.json +4 -4
- package/src/actions/brief-action.ts +5 -5
- package/src/code-intelligence.ts +3 -10
- package/src/pattern-structured.ts +1 -1
- package/src/providers/structural-provider.ts +15 -3
- package/src/search-helpers.ts +4 -15
- package/src/tool/action-specs.ts +66 -0
- package/src/tool/guidance.ts +4 -7
- package/src/tool-actions.ts +23 -40
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Settings registry for SuPi extensions.
|
|
2
|
+
//
|
|
3
|
+
// Extensions declare their settings via `registerSettings()` during their
|
|
4
|
+
// factory function. The generic settings UI reads them via `getRegisteredSettings()`.
|
|
5
|
+
|
|
6
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
7
|
+
import { createRegistry } from "../registry-utils.ts";
|
|
8
|
+
|
|
9
|
+
export type SettingsScope = "project" | "global";
|
|
10
|
+
|
|
11
|
+
export interface SettingsSection {
|
|
12
|
+
/** Extension identifier — e.g. "lsp", "claude-md" */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Human-readable label shown in the UI */
|
|
15
|
+
label: string;
|
|
16
|
+
/** Load current SettingItem[] for the given scope */
|
|
17
|
+
loadValues: (scope: SettingsScope, cwd: string) => SettingItem[];
|
|
18
|
+
/** Persist a change back to config */
|
|
19
|
+
persistChange: (scope: SettingsScope, cwd: string, settingId: string, value: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const registry = createRegistry<SettingsSection>("settings-registry");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Register a settings section for an extension.
|
|
26
|
+
* Call during the extension factory function (not async handlers).
|
|
27
|
+
* Duplicate ids replace the previous registration.
|
|
28
|
+
*/
|
|
29
|
+
export function registerSettings(section: SettingsSection): void {
|
|
30
|
+
registry.register(section.id, section);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Get all registered settings sections in registration order. */
|
|
34
|
+
export function getRegisteredSettings(): SettingsSection[] {
|
|
35
|
+
return registry.getAll();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Clear the registry — used by tests. */
|
|
39
|
+
export function clearRegisteredSettings(): void {
|
|
40
|
+
registry.clear();
|
|
41
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Generic settings overlay for SuPi extensions.
|
|
2
|
+
//
|
|
3
|
+
// Uses pi-tui's SettingsList with scope toggle (Tab), extension grouping,
|
|
4
|
+
// and search. Each extension declares its settings via registerSettings().
|
|
5
|
+
|
|
6
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import {
|
|
9
|
+
Container,
|
|
10
|
+
Input,
|
|
11
|
+
Key,
|
|
12
|
+
matchesKey,
|
|
13
|
+
type SettingItem,
|
|
14
|
+
SettingsList,
|
|
15
|
+
Text,
|
|
16
|
+
} from "@earendil-works/pi-tui";
|
|
17
|
+
import {
|
|
18
|
+
getRegisteredSettings,
|
|
19
|
+
type SettingsScope,
|
|
20
|
+
type SettingsSection,
|
|
21
|
+
} from "./settings-registry.ts";
|
|
22
|
+
|
|
23
|
+
// ── Input submenu component ──────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a pi-tui Input-backed submenu component with enter-to-confirm
|
|
27
|
+
* and escape-to-cancel handling.
|
|
28
|
+
*
|
|
29
|
+
* @param currentValue - Initial value for the text input.
|
|
30
|
+
* @param label - Label text displayed above the input.
|
|
31
|
+
* @param done - Callback invoked with the confirmed value, or undefined on cancel.
|
|
32
|
+
*/
|
|
33
|
+
export function createInputSubmenu(
|
|
34
|
+
currentValue: string,
|
|
35
|
+
label: string,
|
|
36
|
+
done: (selectedValue?: string) => void,
|
|
37
|
+
): {
|
|
38
|
+
render: (width: number) => string[];
|
|
39
|
+
invalidate: () => void;
|
|
40
|
+
handleInput: (data: string) => boolean;
|
|
41
|
+
} {
|
|
42
|
+
const input = new Input();
|
|
43
|
+
input.setValue(currentValue);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
render: (_width: number) => {
|
|
47
|
+
const lines = [` ${label}`];
|
|
48
|
+
lines.push(...input.render(_width));
|
|
49
|
+
lines.push(" enter confirm • esc cancel");
|
|
50
|
+
return lines;
|
|
51
|
+
},
|
|
52
|
+
invalidate: () => {
|
|
53
|
+
input.invalidate();
|
|
54
|
+
},
|
|
55
|
+
handleInput: (data: string) => {
|
|
56
|
+
if (matchesKey(data, Key.escape)) {
|
|
57
|
+
done();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (matchesKey(data, Key.enter)) {
|
|
61
|
+
done(input.getValue());
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
input.handleInput(data);
|
|
65
|
+
return true;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Types ────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
interface OverlayState {
|
|
73
|
+
scope: SettingsScope;
|
|
74
|
+
cwd: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Pure helpers ─────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
function getScopeLabel(scope: SettingsScope): string {
|
|
80
|
+
return scope === "project" ? "Project" : "Global";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildFlatItems(
|
|
84
|
+
sections: SettingsSection[],
|
|
85
|
+
scope: SettingsScope,
|
|
86
|
+
cwd: string,
|
|
87
|
+
): SettingItem[] {
|
|
88
|
+
const items: SettingItem[] = [];
|
|
89
|
+
for (const section of sections) {
|
|
90
|
+
const sectionItems = section.loadValues(scope, cwd);
|
|
91
|
+
for (const item of sectionItems) {
|
|
92
|
+
items.push({
|
|
93
|
+
...item,
|
|
94
|
+
id: `${section.id}.${item.id}`,
|
|
95
|
+
label: `${section.label}: ${item.label}`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return items;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function findSectionAndId(
|
|
103
|
+
sections: SettingsSection[],
|
|
104
|
+
flatId: string,
|
|
105
|
+
): { section: SettingsSection; itemId: string } | null {
|
|
106
|
+
const dotIndex = flatId.indexOf(".");
|
|
107
|
+
if (dotIndex === -1) return null;
|
|
108
|
+
const sectionId = flatId.slice(0, dotIndex);
|
|
109
|
+
const itemId = flatId.slice(dotIndex + 1);
|
|
110
|
+
const section = sections.find((s) => s.id === sectionId);
|
|
111
|
+
if (!section) return null;
|
|
112
|
+
return { section, itemId };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Component ────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
interface SettingsOverlayDeps {
|
|
118
|
+
state: OverlayState;
|
|
119
|
+
container: Container;
|
|
120
|
+
settingsList: SettingsList | null;
|
|
121
|
+
tui: Parameters<Parameters<ExtensionContext["ui"]["custom"]>[0]>[0];
|
|
122
|
+
theme: Parameters<Parameters<ExtensionContext["ui"]["custom"]>[0]>[1];
|
|
123
|
+
done: () => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function createSettingsList(deps: SettingsOverlayDeps): SettingsList {
|
|
127
|
+
const sections = getRegisteredSettings();
|
|
128
|
+
const items = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
129
|
+
const onChange = (flatId: string, newValue: string) => {
|
|
130
|
+
const found = findSectionAndId(sections, flatId);
|
|
131
|
+
if (found) {
|
|
132
|
+
found.section.persistChange(deps.state.scope, deps.state.cwd, found.itemId, newValue);
|
|
133
|
+
}
|
|
134
|
+
// Re-read all values to reflect persisted changes, but keep the list
|
|
135
|
+
// instance (and its selectedIndex) intact.
|
|
136
|
+
const updatedItems = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
137
|
+
for (const updated of updatedItems) {
|
|
138
|
+
const existing = items.find((i) => i.id === updated.id);
|
|
139
|
+
if (existing && existing.currentValue !== updated.currentValue) {
|
|
140
|
+
settingsList.updateValue(updated.id, updated.currentValue);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
deps.tui.requestRender();
|
|
144
|
+
};
|
|
145
|
+
const settingsList = new SettingsList(
|
|
146
|
+
items,
|
|
147
|
+
Math.min(items.length + 4, 20),
|
|
148
|
+
getSettingsListTheme(),
|
|
149
|
+
onChange,
|
|
150
|
+
() => deps.done(),
|
|
151
|
+
{ enableSearch: true },
|
|
152
|
+
);
|
|
153
|
+
return settingsList;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function rebuildSettingsList(deps: SettingsOverlayDeps): SettingsList {
|
|
157
|
+
const settingsList = createSettingsList(deps);
|
|
158
|
+
deps.settingsList = settingsList;
|
|
159
|
+
|
|
160
|
+
deps.container.clear();
|
|
161
|
+
deps.container.addChild(createHeaderComponent(deps));
|
|
162
|
+
deps.container.addChild(settingsList);
|
|
163
|
+
|
|
164
|
+
return settingsList;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createHeaderComponent(deps: SettingsOverlayDeps): Text {
|
|
168
|
+
const { theme, state } = deps;
|
|
169
|
+
const scopeLabel = getScopeLabel(state.scope);
|
|
170
|
+
const otherScope = state.scope === "project" ? "Global" : "Project";
|
|
171
|
+
const headerText = new Text(
|
|
172
|
+
`${theme.fg("accent", theme.bold("SuPi Settings"))} ${theme.fg("text", `Scope: ${scopeLabel}`)} ${theme.fg("dim", `(tab → ${otherScope})`)}`,
|
|
173
|
+
0,
|
|
174
|
+
0,
|
|
175
|
+
);
|
|
176
|
+
return headerText;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function handleScopeToggle(deps: SettingsOverlayDeps): void {
|
|
180
|
+
deps.state.scope = deps.state.scope === "project" ? "global" : "project";
|
|
181
|
+
rebuildSettingsList(deps);
|
|
182
|
+
deps.tui.requestRender();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Entry point ──────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
export function openSettingsOverlay(ctx: ExtensionContext): void {
|
|
188
|
+
const sections = getRegisteredSettings();
|
|
189
|
+
if (sections.length === 0) {
|
|
190
|
+
ctx.ui.notify("No settings registered by SuPi extensions", "info");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
void ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
195
|
+
const state: OverlayState = { scope: "project", cwd: ctx.cwd };
|
|
196
|
+
const container = new Container();
|
|
197
|
+
|
|
198
|
+
const deps: SettingsOverlayDeps = {
|
|
199
|
+
state,
|
|
200
|
+
container,
|
|
201
|
+
settingsList: null,
|
|
202
|
+
tui,
|
|
203
|
+
theme,
|
|
204
|
+
done,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
rebuildSettingsList(deps);
|
|
208
|
+
|
|
209
|
+
const component = {
|
|
210
|
+
render: (width: number) => container.render(width),
|
|
211
|
+
invalidate: () => container.invalidate(),
|
|
212
|
+
handleInput: (data: string) => {
|
|
213
|
+
if (matchesKey(data, Key.tab)) {
|
|
214
|
+
handleScopeToggle(deps);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
// Delegate input to the settings list (always set after rebuildSettingsList)
|
|
218
|
+
deps.settingsList?.handleInput?.(data);
|
|
219
|
+
deps.tui.requestRender();
|
|
220
|
+
return true;
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return component;
|
|
225
|
+
});
|
|
226
|
+
}
|
package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/terminal.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared terminal title formatting and signaling utilities.
|
|
3
|
+
*
|
|
4
|
+
* Centralized place for pi title convention (π prefix), completion (✓)
|
|
5
|
+
* and waiting (●) indicators, and the audible terminal bell.
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
/** Unicode checkmark shown when the agent finishes a turn. */
|
|
10
|
+
export const DONE_SYMBOL = "\u2713";
|
|
11
|
+
/** Unicode dot shown when waiting for user input. */
|
|
12
|
+
export const WAITING_SYMBOL = "\u25CF";
|
|
13
|
+
|
|
14
|
+
/** Minimal UI surface needed for title operations. */
|
|
15
|
+
export interface TitleTarget {
|
|
16
|
+
ui: {
|
|
17
|
+
setTitle?(title: string): void;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format pi's canonical terminal title from session name and cwd.
|
|
23
|
+
* Falls back gracefully when either is missing.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* formatTitle("my-session", "/home/projects/foo") // "π - my-session - foo"
|
|
27
|
+
* formatTitle(undefined, "/home/projects/foo") // "π - foo"
|
|
28
|
+
* formatTitle("my-session") // "π - my-session"
|
|
29
|
+
* formatTitle() // "π"
|
|
30
|
+
*/
|
|
31
|
+
export function formatTitle(sessionName?: string, cwd?: string): string {
|
|
32
|
+
const base = cwd ? path.basename(cwd) : undefined;
|
|
33
|
+
if (sessionName && base) return `π - ${sessionName} - ${base}`;
|
|
34
|
+
if (sessionName) return `π - ${sessionName}`;
|
|
35
|
+
if (base) return `π - ${base}`;
|
|
36
|
+
return "π";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Sound the audible terminal bell (ASCII BEL). */
|
|
40
|
+
export function signalBell(): void {
|
|
41
|
+
process.stdout.write("\x07");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Set the terminal title to indicate the agent is waiting for user input.
|
|
46
|
+
* Prefixes with ● and sounds the terminal bell.
|
|
47
|
+
*/
|
|
48
|
+
export function signalWaiting(ctx: TitleTarget, title: string): void {
|
|
49
|
+
ctx.ui.setTitle?.(`${WAITING_SYMBOL} ${title}`);
|
|
50
|
+
signalBell();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set the terminal title to indicate the agent turn has completed.
|
|
55
|
+
* Prefixes with ✓ and sounds the terminal bell.
|
|
56
|
+
*/
|
|
57
|
+
export function signalDone(ctx: TitleTarget, title: string): void {
|
|
58
|
+
ctx.ui.setTitle?.(`${DONE_SYMBOL} ${title}`);
|
|
59
|
+
signalBell();
|
|
60
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-tree-sitter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "SuPi Tree-sitter extension — structural AST analysis for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"!__tests__"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"web-tree-sitter": "^0.26.8"
|
|
26
|
+
"web-tree-sitter": "^0.26.8",
|
|
27
|
+
"@mrclrchtr/supi-core": "1.6.0"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
30
|
"@earendil-works/pi-ai": "*",
|
|
@@ -43,9 +44,13 @@
|
|
|
43
44
|
},
|
|
44
45
|
"pi": {
|
|
45
46
|
"extensions": [
|
|
46
|
-
"./src/extension.ts"
|
|
47
|
+
"./src/extension.ts",
|
|
48
|
+
"node_modules/@mrclrchtr/supi-core/src/extension.ts"
|
|
47
49
|
]
|
|
48
50
|
},
|
|
51
|
+
"bundledDependencies": [
|
|
52
|
+
"@mrclrchtr/supi-core"
|
|
53
|
+
],
|
|
49
54
|
"main": "src/api.ts",
|
|
50
55
|
"exports": {
|
|
51
56
|
"./api": "./src/api.ts",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Public tree-sitter session factory and shared types.
|
|
1
|
+
// Public tree-sitter session factory, shared session service access, and shared types.
|
|
2
2
|
|
|
3
|
+
export { getSessionTreeSitterService } from "./session/service-registry.ts";
|
|
3
4
|
export { createTreeSitterSession } from "./session/session.ts";
|
|
4
5
|
export type {
|
|
5
6
|
CalleesAtResult,
|
|
@@ -9,8 +10,11 @@ export type {
|
|
|
9
10
|
NodeAtResult,
|
|
10
11
|
OutlineItem,
|
|
11
12
|
QueryCapture,
|
|
13
|
+
SessionTreeSitterService,
|
|
14
|
+
SessionTreeSitterServiceState,
|
|
12
15
|
SourceRange,
|
|
13
16
|
SupportedExtension,
|
|
14
17
|
TreeSitterResult,
|
|
18
|
+
TreeSitterService,
|
|
15
19
|
TreeSitterSession,
|
|
16
20
|
} from "./types.ts";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Public session factory and re-exports for @mrclrchtr/supi-tree-sitter.
|
|
1
|
+
// Public session factory, shared session service access, and re-exports for @mrclrchtr/supi-tree-sitter.
|
|
2
2
|
|
|
3
|
+
export { getSessionTreeSitterService } from "./session/service-registry.ts";
|
|
3
4
|
export { createTreeSitterSession } from "./session/session.ts";
|
|
4
5
|
export type {
|
|
5
6
|
CalleesAtResult,
|
|
@@ -9,8 +10,11 @@ export type {
|
|
|
9
10
|
NodeAtResult,
|
|
10
11
|
OutlineItem,
|
|
11
12
|
QueryCapture,
|
|
13
|
+
SessionTreeSitterService,
|
|
14
|
+
SessionTreeSitterServiceState,
|
|
12
15
|
SourceRange,
|
|
13
16
|
SupportedExtension,
|
|
14
17
|
TreeSitterResult,
|
|
18
|
+
TreeSitterService,
|
|
15
19
|
TreeSitterSession,
|
|
16
20
|
} from "./types.ts";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { resolveToolPath } from "@mrclrchtr/supi-core/api";
|
|
5
6
|
import type { Language, Parser, Tree } from "web-tree-sitter";
|
|
6
7
|
import { nodeToRange } from "../coordinates.ts";
|
|
7
8
|
import { detectGrammar, resolveGrammarWasmPath } from "../language.ts";
|
|
@@ -113,7 +114,7 @@ export class TreeSitterRuntime {
|
|
|
113
114
|
grammarId: GrammarId;
|
|
114
115
|
}>
|
|
115
116
|
> {
|
|
116
|
-
const resolvedPath =
|
|
117
|
+
const resolvedPath = resolveToolPath(this.cwd, filePath);
|
|
117
118
|
|
|
118
119
|
// Check language support first
|
|
119
120
|
const grammarId = detectGrammar(filePath);
|
|
@@ -212,7 +213,7 @@ export class TreeSitterRuntime {
|
|
|
212
213
|
|
|
213
214
|
/** Resolve a file path from cwd. */
|
|
214
215
|
resolvePath(filePath: string): string {
|
|
215
|
-
return
|
|
216
|
+
return resolveToolPath(this.cwd, filePath);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
/** Dispose all held parser resources. */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Shared session-scoped Tree-sitter service registry.
|
|
2
|
+
// Peer extensions can import `getSessionTreeSitterService` from the package API
|
|
3
|
+
// to reuse the active structural runtime without creating duplicate sessions.
|
|
4
|
+
|
|
5
|
+
import { createSessionStateRegistry } from "@mrclrchtr/supi-core/api";
|
|
6
|
+
import type { SessionTreeSitterService, SessionTreeSitterServiceState } from "../types.ts";
|
|
7
|
+
|
|
8
|
+
const registry = createSessionStateRegistry<SessionTreeSitterServiceState>(
|
|
9
|
+
"supi-tree-sitter/session-registry",
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
/** Publish the shared Tree-sitter service for one session cwd. */
|
|
13
|
+
export function setSessionTreeSitterService(cwd: string, service: SessionTreeSitterService): void {
|
|
14
|
+
registry.set(cwd, { kind: "ready", service });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Acquire the shared Tree-sitter service state for one session cwd. */
|
|
18
|
+
export function getSessionTreeSitterService(cwd: string): SessionTreeSitterServiceState {
|
|
19
|
+
return (
|
|
20
|
+
registry.get(cwd) ?? {
|
|
21
|
+
kind: "unavailable",
|
|
22
|
+
reason: "No Tree-sitter session initialized for this workspace",
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Remove the shared Tree-sitter service for one session cwd. */
|
|
28
|
+
export function clearSessionTreeSitterService(cwd: string): void {
|
|
29
|
+
registry.clear(cwd);
|
|
30
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Session factory — creates
|
|
1
|
+
// Session factory — creates runtime-backed Tree-sitter services and owned sessions.
|
|
2
2
|
|
|
3
3
|
import { detectGrammar, isJsTsGrammar } from "../language.ts";
|
|
4
4
|
import {
|
|
@@ -16,17 +16,13 @@ import type {
|
|
|
16
16
|
OutlineItem,
|
|
17
17
|
QueryCapture,
|
|
18
18
|
TreeSitterResult,
|
|
19
|
+
TreeSitterService,
|
|
19
20
|
TreeSitterSession,
|
|
20
21
|
} from "../types.ts";
|
|
21
22
|
import { TreeSitterRuntime } from "./runtime.ts";
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
* The session owns parser/grammar reuse and must be disposed when done.
|
|
26
|
-
*/
|
|
27
|
-
export function createTreeSitterSession(cwd: string): TreeSitterSession {
|
|
28
|
-
const runtime = new TreeSitterRuntime(cwd);
|
|
29
|
-
|
|
24
|
+
/** Create a runtime-backed structural service without taking ownership of disposal. */
|
|
25
|
+
export function createTreeSitterService(runtime: TreeSitterRuntime): TreeSitterService {
|
|
30
26
|
return {
|
|
31
27
|
async canParse(file: string) {
|
|
32
28
|
const result = await runtime.parseFile(file);
|
|
@@ -104,7 +100,19 @@ export function createTreeSitterSession(cwd: string): TreeSitterSession {
|
|
|
104
100
|
): Promise<TreeSitterResult<CalleesAtResult>> {
|
|
105
101
|
return lookupCalleesAt(runtime, file, line, character);
|
|
106
102
|
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
107
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Create a new Tree-sitter session bound to the given working directory.
|
|
108
|
+
* The session owns parser/grammar reuse and must be disposed when done.
|
|
109
|
+
*/
|
|
110
|
+
export function createTreeSitterSession(cwd: string): TreeSitterSession {
|
|
111
|
+
const runtime = new TreeSitterRuntime(cwd);
|
|
112
|
+
const service = createTreeSitterService(runtime);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...service,
|
|
108
116
|
dispose() {
|
|
109
117
|
runtime.dispose();
|
|
110
118
|
},
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the public `tree_sitter` action surface.
|
|
3
|
+
*
|
|
4
|
+
* Tool registration, validation, and prompt guidance should derive from these
|
|
5
|
+
* specs so the action list and per-action requirements do not drift apart.
|
|
6
|
+
*/
|
|
7
|
+
export const TREE_SITTER_ACTION_SPECS = [
|
|
8
|
+
{
|
|
9
|
+
name: "outline",
|
|
10
|
+
guidanceGroup: "js-ts-structure",
|
|
11
|
+
languageScope: "js-ts-only",
|
|
12
|
+
requiresPosition: false,
|
|
13
|
+
requiresQuery: false,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "imports",
|
|
17
|
+
guidanceGroup: "js-ts-structure",
|
|
18
|
+
languageScope: "js-ts-only",
|
|
19
|
+
requiresPosition: false,
|
|
20
|
+
requiresQuery: false,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "exports",
|
|
24
|
+
guidanceGroup: "js-ts-structure",
|
|
25
|
+
languageScope: "js-ts-only",
|
|
26
|
+
requiresPosition: false,
|
|
27
|
+
requiresQuery: false,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "node_at",
|
|
31
|
+
guidanceGroup: "node-at",
|
|
32
|
+
languageScope: "all-supported",
|
|
33
|
+
requiresPosition: true,
|
|
34
|
+
requiresQuery: false,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "query",
|
|
38
|
+
guidanceGroup: "query",
|
|
39
|
+
languageScope: "all-supported",
|
|
40
|
+
requiresPosition: false,
|
|
41
|
+
requiresQuery: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "callees",
|
|
45
|
+
guidanceGroup: "callees",
|
|
46
|
+
languageScope: "many-supported",
|
|
47
|
+
requiresPosition: true,
|
|
48
|
+
requiresQuery: false,
|
|
49
|
+
},
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
export type TreeSitterAction = (typeof TREE_SITTER_ACTION_SPECS)[number]["name"];
|
|
53
|
+
export type TreeSitterActionSpec = (typeof TREE_SITTER_ACTION_SPECS)[number];
|
|
54
|
+
export type TreeSitterGuidanceGroup = TreeSitterActionSpec["guidanceGroup"];
|
|
55
|
+
|
|
56
|
+
/** Ordered action names for schemas, validation messages, and docs. */
|
|
57
|
+
export const TREE_SITTER_ACTION_NAMES = TREE_SITTER_ACTION_SPECS.map(
|
|
58
|
+
(spec) => spec.name,
|
|
59
|
+
) as readonly TreeSitterAction[];
|
|
60
|
+
|
|
61
|
+
const TREE_SITTER_ACTION_NAME_SET = new Set<string>(TREE_SITTER_ACTION_NAMES);
|
|
62
|
+
const TREE_SITTER_ACTION_SPEC_MAP = new Map<TreeSitterAction, TreeSitterActionSpec>(
|
|
63
|
+
TREE_SITTER_ACTION_SPECS.map((spec) => [spec.name, spec]),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
/** Check whether a runtime string is a supported `tree_sitter` action. */
|
|
67
|
+
export function isTreeSitterAction(action: string): action is TreeSitterAction {
|
|
68
|
+
return TREE_SITTER_ACTION_NAME_SET.has(action);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Look up the spec for one supported `tree_sitter` action. */
|
|
72
|
+
export function getTreeSitterActionSpec(action: TreeSitterAction): TreeSitterActionSpec {
|
|
73
|
+
const spec = TREE_SITTER_ACTION_SPEC_MAP.get(action);
|
|
74
|
+
if (!spec) {
|
|
75
|
+
throw new Error(`Unknown tree_sitter action: ${action}`);
|
|
76
|
+
}
|
|
77
|
+
return spec;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Get the ordered action names that belong to one prompt-guidance group. */
|
|
81
|
+
export function getTreeSitterActionNamesByGuidanceGroup(
|
|
82
|
+
group: TreeSitterGuidanceGroup,
|
|
83
|
+
): TreeSitterAction[] {
|
|
84
|
+
return TREE_SITTER_ACTION_SPECS.filter((spec) => spec.guidanceGroup === group).map(
|
|
85
|
+
(spec) => spec.name,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Format the public action list for validation messages and docs. */
|
|
90
|
+
export function formatTreeSitterActionList(): string {
|
|
91
|
+
return TREE_SITTER_ACTION_NAMES.join(", ");
|
|
92
|
+
}
|
|
@@ -3,14 +3,23 @@
|
|
|
3
3
|
// Note: We intentionally do NOT include cross-tool routing (e.g., "use lsp for
|
|
4
4
|
// type info") because this package can be installed standalone without supi-lsp.
|
|
5
5
|
|
|
6
|
+
import {
|
|
7
|
+
formatTreeSitterActionList,
|
|
8
|
+
getTreeSitterActionNamesByGuidanceGroup,
|
|
9
|
+
} from "./action-specs.ts";
|
|
10
|
+
|
|
11
|
+
const jsTsStructureActions = getTreeSitterActionNamesByGuidanceGroup("js-ts-structure")
|
|
12
|
+
.map((action) => `tree_sitter.${action}(file)`)
|
|
13
|
+
.join(", ");
|
|
14
|
+
|
|
6
15
|
export const toolDescription = `Tree-sitter tool — parser-level structure and syntax queries for supported files.
|
|
7
16
|
|
|
8
|
-
Actions:
|
|
17
|
+
Actions: ${formatTreeSitterActionList()}.
|
|
9
18
|
|
|
10
|
-
Use tree_sitter for exact syntax nodes, shallow structure, parsed imports/exports, outgoing calls, or custom AST queries within one file. file is required for all actions. line and character are 1-based UTF-16 coordinates for node_at and callees. query is required for query. outline, imports, and exports are JavaScript/TypeScript-only; node_at and query work across supported grammars; callees works for many grammars. Relative paths resolve from the session working directory.`;
|
|
19
|
+
Use tree_sitter for exact syntax nodes, shallow structure, parsed imports/exports, outgoing calls, or custom AST queries within one file. file is required for all actions. line and character are 1-based UTF-16 coordinates for node_at and callees. query is required for query. outline, imports, and exports are JavaScript/TypeScript-only; node_at and query work across supported grammars; callees works for many grammars. Relative paths resolve from the session working directory, and a leading @ on file paths is stripped.`;
|
|
11
20
|
|
|
12
21
|
export const promptGuidelines = [
|
|
13
|
-
|
|
22
|
+
`Use ${jsTsStructureActions} for shallow JavaScript or TypeScript structure without reading the whole file.`,
|
|
14
23
|
"Use tree_sitter.node_at(file, line, character) for the exact syntax node and ancestry at a known position.",
|
|
15
24
|
"Use tree_sitter.callees(file, line, character) for outgoing calls from the enclosing function or method at a known position.",
|
|
16
25
|
"Use tree_sitter.query(file, query) for custom Tree-sitter patterns when the built-in actions are not specific enough.",
|