@mrclrchtr/supi-tree-sitter 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -39
- package/node_modules/@mrclrchtr/supi-core/README.md +107 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +44 -0
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +85 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +85 -0
- package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +86 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/package.json +8 -3
- package/src/api.ts +6 -2
- package/src/index.ts +6 -2
- package/src/{runtime.ts → session/runtime.ts} +6 -5
- package/src/session/service-registry.ts +30 -0
- package/src/{session.ts → session/session.ts} +20 -12
- package/src/tool/action-specs.ts +92 -0
- package/src/{callees.ts → tool/callees.ts} +3 -3
- package/src/{exports.ts → tool/exports.ts} +4 -4
- package/src/{formatting.ts → tool/formatting.ts} +1 -1
- package/src/tool/guidance.ts +31 -0
- package/src/{imports.ts → tool/imports.ts} +4 -4
- package/src/{node-at.ts → tool/node-at.ts} +3 -3
- package/src/{outline.ts → tool/outline.ts} +3 -3
- package/src/tree-sitter.ts +118 -91
- package/src/types.ts +13 -2
- /package/src/{structure.ts → tool/structure.ts} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 {
|
|
3
|
+
export { getSessionTreeSitterService } from "./session/service-registry.ts";
|
|
4
|
+
export { createTreeSitterSession } from "./session/session.ts";
|
|
4
5
|
export type {
|
|
5
6
|
CalleesAtResult,
|
|
6
7
|
ExportRecord,
|
|
@@ -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,10 +2,11 @@
|
|
|
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
|
-
import { nodeToRange } from "
|
|
7
|
-
import { detectGrammar, resolveGrammarWasmPath } from "
|
|
8
|
-
import type { GrammarId, QueryCapture, TreeSitterResult } from "
|
|
7
|
+
import { nodeToRange } from "../coordinates.ts";
|
|
8
|
+
import { detectGrammar, resolveGrammarWasmPath } from "../language.ts";
|
|
9
|
+
import type { GrammarId, QueryCapture, TreeSitterResult } from "../types.ts";
|
|
9
10
|
|
|
10
11
|
interface ParserEntry {
|
|
11
12
|
parser: Parser;
|
|
@@ -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,14 +1,13 @@
|
|
|
1
|
-
// Session factory — creates
|
|
1
|
+
// Session factory — creates runtime-backed Tree-sitter services and owned sessions.
|
|
2
2
|
|
|
3
|
-
import { detectGrammar, isJsTsGrammar } from "
|
|
4
|
-
import { TreeSitterRuntime } from "./runtime.ts";
|
|
3
|
+
import { detectGrammar, isJsTsGrammar } from "../language.ts";
|
|
5
4
|
import {
|
|
6
5
|
extractExports,
|
|
7
6
|
extractImports,
|
|
8
7
|
extractOutline,
|
|
9
8
|
lookupCalleesAt,
|
|
10
9
|
lookupNodeAt,
|
|
11
|
-
} from "
|
|
10
|
+
} from "../tool/structure.ts";
|
|
12
11
|
import type {
|
|
13
12
|
CalleesAtResult,
|
|
14
13
|
ExportRecord,
|
|
@@ -17,16 +16,13 @@ import type {
|
|
|
17
16
|
OutlineItem,
|
|
18
17
|
QueryCapture,
|
|
19
18
|
TreeSitterResult,
|
|
19
|
+
TreeSitterService,
|
|
20
20
|
TreeSitterSession,
|
|
21
|
-
} from "
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Create a new Tree-sitter session bound to the given working directory.
|
|
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);
|
|
21
|
+
} from "../types.ts";
|
|
22
|
+
import { TreeSitterRuntime } from "./runtime.ts";
|
|
29
23
|
|
|
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
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Structural callee extraction — enclosing-scope lookup with per-language queries.
|
|
2
2
|
|
|
3
|
-
import { detectGrammar } from "
|
|
4
|
-
import type { TreeSitterRuntime } from "
|
|
5
|
-
import type { GrammarId, SourceRange, TreeSitterResult } from "
|
|
3
|
+
import { detectGrammar } from "../language.ts";
|
|
4
|
+
import type { TreeSitterRuntime } from "../session/runtime.ts";
|
|
5
|
+
import type { GrammarId, SourceRange, TreeSitterResult } from "../types.ts";
|
|
6
6
|
|
|
7
7
|
/** Result shape returned by lookupCalleesAt. */
|
|
8
8
|
export interface CalleesAtResult {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Export extraction for supported files.
|
|
2
2
|
|
|
3
|
-
import { nodeToRange } from "
|
|
4
|
-
import type { TreeSitterRuntime } from "
|
|
5
|
-
import type { SyntaxNodeLike } from "
|
|
6
|
-
import type { ExportRecord, TreeSitterResult } from "
|
|
3
|
+
import { nodeToRange } from "../coordinates.ts";
|
|
4
|
+
import type { TreeSitterRuntime } from "../session/runtime.ts";
|
|
5
|
+
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
6
|
+
import type { ExportRecord, TreeSitterResult } from "../types.ts";
|
|
7
7
|
|
|
8
8
|
/** Extract export records from a supported file. */
|
|
9
9
|
export async function extractExports(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Prompt guidance and tool description for the tree_sitter tool.
|
|
2
|
+
//
|
|
3
|
+
// Note: We intentionally do NOT include cross-tool routing (e.g., "use lsp for
|
|
4
|
+
// type info") because this package can be installed standalone without supi-lsp.
|
|
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
|
+
|
|
15
|
+
export const toolDescription = `Tree-sitter tool — parser-level structure and syntax queries for supported files.
|
|
16
|
+
|
|
17
|
+
Actions: ${formatTreeSitterActionList()}.
|
|
18
|
+
|
|
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.`;
|
|
20
|
+
|
|
21
|
+
export const promptGuidelines = [
|
|
22
|
+
`Use ${jsTsStructureActions} for shallow JavaScript or TypeScript structure without reading the whole file.`,
|
|
23
|
+
"Use tree_sitter.node_at(file, line, character) for the exact syntax node and ancestry at a known position.",
|
|
24
|
+
"Use tree_sitter.callees(file, line, character) for outgoing calls from the enclosing function or method at a known position.",
|
|
25
|
+
"Use tree_sitter.query(file, query) for custom Tree-sitter patterns when the built-in actions are not specific enough.",
|
|
26
|
+
"Use tree_sitter for syntax, node types, source ranges, and other parser-backed structure within one supported file.",
|
|
27
|
+
"Do not use tree_sitter for type information, cross-file references, semantic renames, or codebase-wide orientation.",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const promptSnippet =
|
|
31
|
+
"tree_sitter — parser-backed single-file structure, node lookup, callees, and AST queries";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Import extraction for supported files.
|
|
2
2
|
|
|
3
|
-
import { nodeToRange } from "
|
|
4
|
-
import type { TreeSitterRuntime } from "
|
|
5
|
-
import type { SyntaxNodeLike } from "
|
|
6
|
-
import type { ImportRecord, TreeSitterResult } from "
|
|
3
|
+
import { nodeToRange } from "../coordinates.ts";
|
|
4
|
+
import type { TreeSitterRuntime } from "../session/runtime.ts";
|
|
5
|
+
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
6
|
+
import type { ImportRecord, TreeSitterResult } from "../types.ts";
|
|
7
7
|
|
|
8
8
|
/** Extract import records from a supported file. */
|
|
9
9
|
export async function extractImports(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Node-at-position lookup.
|
|
2
2
|
|
|
3
|
-
import { nodeToRange, publicToTreeSitter, splitSourceLines } from "
|
|
4
|
-
import type { TreeSitterRuntime } from "
|
|
5
|
-
import type { NodeAtResult, SourceRange, TreeSitterResult } from "
|
|
3
|
+
import { nodeToRange, publicToTreeSitter, splitSourceLines } from "../coordinates.ts";
|
|
4
|
+
import type { TreeSitterRuntime } from "../session/runtime.ts";
|
|
5
|
+
import type { NodeAtResult, SourceRange, TreeSitterResult } from "../types.ts";
|
|
6
6
|
|
|
7
7
|
const MAX_ANCESTRY = 10;
|
|
8
8
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Outline extraction for supported files.
|
|
2
2
|
|
|
3
|
-
import { nodeToRange } from "
|
|
4
|
-
import type { SyntaxNodeLike } from "
|
|
5
|
-
import type { OutlineItem } from "
|
|
3
|
+
import { nodeToRange } from "../coordinates.ts";
|
|
4
|
+
import type { SyntaxNodeLike } from "../syntax-node.ts";
|
|
5
|
+
import type { OutlineItem } from "../types.ts";
|
|
6
6
|
|
|
7
7
|
/** Node types that can be extracted directly as outline items. */
|
|
8
8
|
const OUTLINE_DECLARATION_NODE_TYPES = new Set([
|
package/src/tree-sitter.ts
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
4
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
|
+
import { detectGrammar, isJsTsGrammar } from "./language.ts";
|
|
7
|
+
import { TreeSitterRuntime } from "./session/runtime.ts";
|
|
8
|
+
import {
|
|
9
|
+
clearSessionTreeSitterService,
|
|
10
|
+
setSessionTreeSitterService,
|
|
11
|
+
} from "./session/service-registry.ts";
|
|
12
|
+
import { createTreeSitterService } from "./session/session.ts";
|
|
13
|
+
import {
|
|
14
|
+
formatTreeSitterActionList,
|
|
15
|
+
getTreeSitterActionSpec,
|
|
16
|
+
isTreeSitterAction,
|
|
17
|
+
TREE_SITTER_ACTION_NAMES,
|
|
18
|
+
type TreeSitterAction,
|
|
19
|
+
} from "./tool/action-specs.ts";
|
|
6
20
|
import {
|
|
7
21
|
formatNonSuccess,
|
|
8
22
|
formatOutlineItemsCapped,
|
|
@@ -11,56 +25,35 @@ import {
|
|
|
11
25
|
truncatedNotice,
|
|
12
26
|
truncateText,
|
|
13
27
|
validationError,
|
|
14
|
-
} from "./formatting.ts";
|
|
15
|
-
import {
|
|
16
|
-
import { collectOutline } from "./outline.ts";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const TreeSitterActionEnum = StringEnum([
|
|
21
|
-
"outline",
|
|
22
|
-
"imports",
|
|
23
|
-
"exports",
|
|
24
|
-
"node_at",
|
|
25
|
-
"query",
|
|
26
|
-
"callees",
|
|
27
|
-
] as const);
|
|
28
|
-
|
|
29
|
-
const toolDescription = `Tree-sitter tool — provides structural AST analysis for supported files.
|
|
30
|
-
|
|
31
|
-
Actions:
|
|
32
|
-
- outline: Extract structural declarations (functions, classes, interfaces, etc.). JavaScript/TypeScript only.
|
|
33
|
-
- imports: List import statements with module specifiers. JavaScript/TypeScript only.
|
|
34
|
-
- exports: List export declarations, re-exports, and export assignments with names and kinds. JavaScript/TypeScript only.
|
|
35
|
-
- node_at: Find the syntax node at a position. Params: file, line, character
|
|
36
|
-
- query: Run a Tree-sitter query. Params: file, query
|
|
37
|
-
- callees: Find outgoing function/method calls from a position. Params: file, line, character. Supported for most grammars.
|
|
38
|
-
|
|
39
|
-
Coordinates are 1-based (line, character), compatible with the lsp tool convention.
|
|
40
|
-
Character is a UTF-16 code-unit column.
|
|
41
|
-
Relative file paths resolve from the session working directory.
|
|
42
|
-
|
|
43
|
-
Supported extensions: .ts, .tsx, .js, .jsx, .mts, .cts, .mjs, .cjs, .py, .pyi, .rs, .go, .mod, .c, .h, .cpp, .hpp, .cc, .cxx, .hxx, .c++, .h++, .java, .kt, .kts, .rb, .sh, .bash, .zsh, .html, .htm, .xhtml, .r, .sql`;
|
|
44
|
-
|
|
45
|
-
const promptGuidelines = [
|
|
46
|
-
"Use tree_sitter for structural syntax-tree analysis: extracting declarations, imports, exports, node-at-position lookup, and custom queries.",
|
|
47
|
-
"Prefer tree_sitter when you need AST node types, exact source ranges, or parser-level structure that semantic language-server tooling does not expose.",
|
|
48
|
-
"tree_sitter is a standalone structural analysis tool; use semantic language-server features separately when they are available for hover, definitions, references, or diagnostics.",
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
const promptSnippet = `Use the tree_sitter tool for structural code analysis — outline, imports, exports, node-at-position lookup, and custom queries.`;
|
|
28
|
+
} from "./tool/formatting.ts";
|
|
29
|
+
import { promptGuidelines, promptSnippet, toolDescription } from "./tool/guidance.ts";
|
|
30
|
+
import { collectOutline } from "./tool/outline.ts";
|
|
31
|
+
import { extractExports, extractImports, lookupCalleesAt, lookupNodeAt } from "./tool/structure.ts";
|
|
32
|
+
|
|
33
|
+
const TreeSitterActionEnum = StringEnum(TREE_SITTER_ACTION_NAMES);
|
|
52
34
|
|
|
53
35
|
export default function treeSitterExtension(pi: ExtensionAPI) {
|
|
54
36
|
let runtime: TreeSitterRuntime | undefined;
|
|
37
|
+
let activeCwd: string | null = null;
|
|
55
38
|
|
|
56
39
|
pi.on("session_start", (_event, ctx) => {
|
|
57
|
-
runtime
|
|
40
|
+
if (runtime && activeCwd) {
|
|
41
|
+
clearSessionTreeSitterService(activeCwd);
|
|
42
|
+
runtime.dispose();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
activeCwd = ctx.cwd;
|
|
58
46
|
runtime = new TreeSitterRuntime(ctx.cwd);
|
|
47
|
+
setSessionTreeSitterService(ctx.cwd, createTreeSitterService(runtime));
|
|
59
48
|
});
|
|
60
49
|
|
|
61
50
|
pi.on("session_shutdown", () => {
|
|
51
|
+
if (activeCwd) {
|
|
52
|
+
clearSessionTreeSitterService(activeCwd);
|
|
53
|
+
}
|
|
62
54
|
runtime?.dispose();
|
|
63
55
|
runtime = undefined;
|
|
56
|
+
activeCwd = null;
|
|
64
57
|
});
|
|
65
58
|
|
|
66
59
|
pi.registerTool({
|
|
@@ -96,8 +89,6 @@ export default function treeSitterExtension(pi: ExtensionAPI) {
|
|
|
96
89
|
});
|
|
97
90
|
}
|
|
98
91
|
|
|
99
|
-
type TreeSitterAction = "outline" | "imports" | "exports" | "node_at" | "query" | "callees";
|
|
100
|
-
|
|
101
92
|
type ToolParams = {
|
|
102
93
|
action?: string;
|
|
103
94
|
file?: string;
|
|
@@ -106,17 +97,47 @@ type ToolParams = {
|
|
|
106
97
|
query?: string;
|
|
107
98
|
};
|
|
108
99
|
|
|
100
|
+
interface ValidatedToolParams {
|
|
101
|
+
action: TreeSitterAction;
|
|
102
|
+
file: string;
|
|
103
|
+
line?: number;
|
|
104
|
+
character?: number;
|
|
105
|
+
query?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const SUPPORTED_ACTIONS_TEXT = formatTreeSitterActionList();
|
|
109
|
+
|
|
110
|
+
const ACTION_HANDLERS: Record<
|
|
111
|
+
TreeSitterAction,
|
|
112
|
+
(runtime: TreeSitterRuntime, params: ValidatedToolParams) => Promise<string>
|
|
113
|
+
> = {
|
|
114
|
+
outline: (runtime, params) => handleOutline(runtime, params.file),
|
|
115
|
+
imports: (runtime, params) => handleImports(runtime, params.file),
|
|
116
|
+
exports: (runtime, params) => handleExports(runtime, params.file),
|
|
117
|
+
node_at: (runtime, params) =>
|
|
118
|
+
handleNodeAt(runtime, params.file, params.line as number, params.character as number),
|
|
119
|
+
query: (runtime, params) => handleQuery(runtime, params.file, params.query as string),
|
|
120
|
+
callees: (runtime, params) =>
|
|
121
|
+
handleCallees(runtime, params.file, params.line as number, params.character as number),
|
|
122
|
+
};
|
|
123
|
+
|
|
109
124
|
async function executeToolAction(runtime: TreeSitterRuntime, params: ToolParams): Promise<string> {
|
|
125
|
+
const validated = validateToolParams(params);
|
|
126
|
+
if (typeof validated === "string") {
|
|
127
|
+
return validated;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return ACTION_HANDLERS[validated.action](runtime, validated);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function validateToolParams(params: ToolParams): ValidatedToolParams | string {
|
|
110
134
|
if (!params.action) {
|
|
111
|
-
return validationError(
|
|
112
|
-
"`action` is required. Supported: outline, imports, exports, node_at, query, callees.",
|
|
113
|
-
);
|
|
135
|
+
return validationError(`\`action\` is required. Supported: ${SUPPORTED_ACTIONS_TEXT}.`);
|
|
114
136
|
}
|
|
115
137
|
|
|
116
|
-
|
|
117
|
-
if (!action) {
|
|
138
|
+
if (!isTreeSitterAction(params.action)) {
|
|
118
139
|
return validationError(
|
|
119
|
-
`Unknown action: ${params.action}. Supported:
|
|
140
|
+
`Unknown action: ${params.action}. Supported: ${SUPPORTED_ACTIONS_TEXT}`,
|
|
120
141
|
);
|
|
121
142
|
}
|
|
122
143
|
|
|
@@ -124,12 +145,37 @@ async function executeToolAction(runtime: TreeSitterRuntime, params: ToolParams)
|
|
|
124
145
|
return validationError("`file` is required for all actions.");
|
|
125
146
|
}
|
|
126
147
|
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
const spec = getTreeSitterActionSpec(params.action);
|
|
149
|
+
if (spec.requiresPosition) {
|
|
150
|
+
const lineError = validatePositiveInteger("line", params.line, params.action);
|
|
151
|
+
if (lineError) return lineError;
|
|
152
|
+
|
|
153
|
+
const characterError = validatePositiveInteger("character", params.character, params.action);
|
|
154
|
+
if (characterError) return characterError;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (spec.requiresQuery && (!params.query || params.query.trim().length === 0)) {
|
|
158
|
+
return validationError("`query` is required and must be non-empty.");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
action: params.action,
|
|
163
|
+
file: params.file,
|
|
164
|
+
line: params.line,
|
|
165
|
+
character: params.character,
|
|
166
|
+
query: params.query,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function validatePositiveInteger(
|
|
171
|
+
field: "line" | "character",
|
|
172
|
+
value: number | undefined,
|
|
173
|
+
action: TreeSitterAction,
|
|
174
|
+
): string | null {
|
|
175
|
+
if (value === undefined || !Number.isInteger(value) || value < 1) {
|
|
176
|
+
return validationError(`\`${field}\` must be a positive 1-based integer for ${action} action.`);
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
133
179
|
}
|
|
134
180
|
|
|
135
181
|
async function handleOutline(runtime: TreeSitterRuntime, file: string): Promise<string> {
|
|
@@ -198,18 +244,13 @@ async function handleExports(runtime: TreeSitterRuntime, file: string): Promise<
|
|
|
198
244
|
return lines.join("\n");
|
|
199
245
|
}
|
|
200
246
|
|
|
201
|
-
async function handleNodeAt(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const file = params.file;
|
|
210
|
-
const line = params.line as number;
|
|
211
|
-
const character = params.character as number;
|
|
212
|
-
const result = await lookupNodeAt(runtime, file as string, line, character);
|
|
247
|
+
async function handleNodeAt(
|
|
248
|
+
runtime: TreeSitterRuntime,
|
|
249
|
+
file: string,
|
|
250
|
+
line: number,
|
|
251
|
+
character: number,
|
|
252
|
+
): Promise<string> {
|
|
253
|
+
const result = await lookupNodeAt(runtime, file, line, character);
|
|
213
254
|
if (result.kind !== "success") return formatNonSuccess(result);
|
|
214
255
|
|
|
215
256
|
const { data } = result;
|
|
@@ -235,13 +276,12 @@ async function handleNodeAt(runtime: TreeSitterRuntime, params: ToolParams): Pro
|
|
|
235
276
|
return lines.join("\n");
|
|
236
277
|
}
|
|
237
278
|
|
|
238
|
-
async function handleQuery(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
const result = await runtime.queryFile(file, params.query);
|
|
279
|
+
async function handleQuery(
|
|
280
|
+
runtime: TreeSitterRuntime,
|
|
281
|
+
file: string,
|
|
282
|
+
query: string,
|
|
283
|
+
): Promise<string> {
|
|
284
|
+
const result = await runtime.queryFile(file, query);
|
|
245
285
|
if (result.kind !== "success") return formatNonSuccess(result);
|
|
246
286
|
|
|
247
287
|
const { data: captures } = result;
|
|
@@ -260,25 +300,12 @@ async function handleQuery(runtime: TreeSitterRuntime, params: ToolParams): Prom
|
|
|
260
300
|
return lines.join("\n");
|
|
261
301
|
}
|
|
262
302
|
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
async function handleCallees(runtime: TreeSitterRuntime, params: ToolParams): Promise<string> {
|
|
271
|
-
if (!Number.isInteger(params.line) || (params.line as number) < 1) {
|
|
272
|
-
return validationError("`line` must be a positive 1-based integer for callees action.");
|
|
273
|
-
}
|
|
274
|
-
if (!Number.isInteger(params.character) || (params.character as number) < 1) {
|
|
275
|
-
return validationError("`character` must be a positive 1-based integer for callees action.");
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const file = params.file as string;
|
|
279
|
-
const line = params.line as number;
|
|
280
|
-
const character = params.character as number;
|
|
281
|
-
|
|
303
|
+
async function handleCallees(
|
|
304
|
+
runtime: TreeSitterRuntime,
|
|
305
|
+
file: string,
|
|
306
|
+
line: number,
|
|
307
|
+
character: number,
|
|
308
|
+
): Promise<string> {
|
|
282
309
|
const result = await lookupCalleesAt(runtime, file, line, character);
|
|
283
310
|
if (result.kind !== "success") return formatNonSuccess(result);
|
|
284
311
|
|
package/src/types.ts
CHANGED
|
@@ -66,8 +66,8 @@ export interface QueryCapture {
|
|
|
66
66
|
text: string;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/**
|
|
70
|
-
export interface
|
|
69
|
+
/** Shared Tree-sitter service surface, independent of lifecycle ownership. */
|
|
70
|
+
export interface TreeSitterService {
|
|
71
71
|
/** Validate that a supported file can be read and parsed; does not expose the raw tree. */
|
|
72
72
|
canParse(file: string): Promise<TreeSitterResult<{ file: string; language: string }>>;
|
|
73
73
|
/** Run a Tree-sitter query and return all captures. */
|
|
@@ -86,10 +86,21 @@ export interface TreeSitterSession {
|
|
|
86
86
|
line: number,
|
|
87
87
|
character: number,
|
|
88
88
|
): Promise<TreeSitterResult<CalleesAtResult>>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Owned Tree-sitter session that must release its runtime resources. */
|
|
92
|
+
export interface TreeSitterSession extends TreeSitterService {
|
|
89
93
|
/** Release parser and grammar resources owned by this session. */
|
|
90
94
|
dispose(): void;
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
/** Session-scoped shared structural service published by the extension runtime. */
|
|
98
|
+
export type SessionTreeSitterService = TreeSitterService;
|
|
99
|
+
|
|
100
|
+
export type SessionTreeSitterServiceState =
|
|
101
|
+
| { kind: "ready"; service: SessionTreeSitterService }
|
|
102
|
+
| { kind: "unavailable"; reason: string };
|
|
103
|
+
|
|
93
104
|
/** Supported grammar identifiers. */
|
|
94
105
|
export type GrammarId =
|
|
95
106
|
| "javascript"
|
|
File without changes
|