@mrclrchtr/supi-tree-sitter 11.2.0 → 11.3.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 CHANGED
@@ -82,11 +82,11 @@ The `type` filter accepts `class`, `interface`, `type`, `enum`, `struct`, `union
82
82
  ### Extraction limits
83
83
 
84
84
  - TS/JS, Python, Java, Go, Rust, and C/C++ outlines include nested named declarations and members. They are not complete indexes of local variables, parameters, or anonymous expressions. Other languages retain their existing extraction depth. HTML outlines list only elements with non-empty `id` attributes. SQL outlines list supported `CREATE` declarations and first-level table/type members. LaTeX outlines list section headings and selected command, environment, label, acronym, glossary-entry, and color definitions.
85
- - TypeScript and TSX use [checked build-time grammar patches](scripts/generate-typescript-wasm.mjs) for type-only star re-exports and arrays of qualified import types such as `import("node:fs").Dirent[]`. Runtime parsing retains the original source and coordinates; other grammar gaps still report recovered syntax.
85
+ - TypeScript and TSX use [checked build-time grammar patches](scripts/generate-typescript-wasm.mjs) for type-only star re-exports, arrays of qualified import types such as `import("node:fs").Dirent[]`, and import types in generic calls such as `read<import("./api").Value>()` or `read<typeof import("./api")>()`. Runtime parsing retains the original source and coordinates; other grammar gaps still report recovered syntax.
86
86
  - JavaScript-family imports collect `import` statements, not CommonJS `require()` calls. Exports collect file-level `export` statements, including TypeScript `export =`; they do not collect `module.exports` assignments or nested namespace exports. An eligible `.cjs` file can therefore return no imports or exports.
87
87
  - Python imports retain `import`/`from` module targets, including relative prefixes. Java imports retain ordinary/static targets. Go imports retain single/grouped paths. Rust imports retain explicit `use` paths, `extern crate`, and external `mod` names; inline module bodies are not imports. C/C++ imports retain literal quoted and angle-bracket include targets. Grouped paths use explicit syntax only; no files or bindings are resolved. Conditional directives are written evidence, not proof of active dependencies. Dynamic loaders, computed targets, and C++20 module imports remain outside extraction support.
88
88
  - LaTeX imports include package, class, source, and bibliography directives. Bibliography directives can refer to `.bib` files, but the grammar does not parse those files.
89
- - Call-site and callee queries use syntax, not resolved symbol identity. Distinct sites have source ranges; repeated names on one line are not merged. Rust macro invocations are syntax evidence, not expanded calls. JSX tags are not call expressions. Library callee queries work without LSP, but new `code_graph` targets require semantic readiness in Code Intelligence.
89
+ - Call-site and callee queries use syntax, not resolved symbol identity. Distinct sites have source ranges; repeated names on one line are not merged. Rust macro invocations are syntax evidence, not expanded calls. JSX tags and TypeScript/TSX import references in type syntax are excluded; dynamic import expressions remain call sites. Library callee queries work without LSP, but new `code_graph` targets require semantic readiness in Code Intelligence.
90
90
  - `.jsx` uses JavaScript; `.tsx` uses the separate TSX grammar. `.h` uses C by default. An explicit session association can select C++ without changing the supported suffix set. `.zsh` and `.ksh` use Bash; this is not complete support for those shell dialects.
91
91
  - Go module manifests (`.mod`), Ruby ERB templates (`.erb`), and BibTeX (`.bib`) have [built-in LSP routes](../supi-lsp/README.md#built-in-route-matrix), but no AST grammar. Unlisted extensions, including JSON, YAML, TOML, and CSS, have no grammar. Custom LSP configuration does not add one.
92
92
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "11.2.0",
3
+ "version": "11.3.0",
4
4
  "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -77,7 +77,9 @@ export interface SemanticProvider {
77
77
  ): Promise<CodeQueryResult<DocumentCodeSymbol[]>>;
78
78
  /**
79
79
  * Collect workspace symbols from routes that can contribute within the
80
- * optional canonical path scopes.
80
+ * optional path scopes. Completeness is relative to those scopes, but data
81
+ * may include out-of-scope rows; consumers must filter rows themselves.
82
+ * Policy exclusions proven outside every scope do not make the query partial.
81
83
  */
82
84
  workspaceSymbols(
83
85
  query: string,
@@ -3,8 +3,17 @@ export interface CodeQueryPartialMetadata {
3
3
  /** Automatic path-policy evidence removed from a collected result. */
4
4
  readonly automaticPathExclusion?: {
5
5
  readonly excludedCount: number;
6
+ /** Observed policy paths, not canonical navigation paths; retain aliases when presenting exclusions. */
6
7
  readonly examples: readonly string[];
7
8
  };
9
+ /**
10
+ * True when automatic path-policy exclusions are the only reason the result
11
+ * is partial: every contributing provider branch completed and no coverage
12
+ * gap was observed. Excluded rows matched paths the workspace policy treats
13
+ * as out of automatic scope, so the retained rows are the complete in-policy
14
+ * answer.
15
+ */
16
+ readonly exclusionOnly?: boolean;
8
17
  }
9
18
 
10
19
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "11.2.0",
3
+ "version": "11.3.0",
4
4
  "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,110 @@
1
+ // Session-provided Web tools for SuPi child sessions (Agent Runs and Reviewer Sessions).
2
+ //
3
+ // The loaded supi-web extension answers a synchronous request on the parent event bus.
4
+ // Consumers never depend on supi-web; when it is absent or provides no tools, the
5
+ // request returns undefined and the child simply lacks Web tools.
6
+
7
+ import type { ExtensionAPI, ExtensionFactory } from "@earendil-works/pi-coding-agent";
8
+
9
+ /** Web tools that child sessions may receive when the containing session provides them. */
10
+ export const CHILD_WEB_TOOL_NAMES = [
11
+ "web_fetch_md",
12
+ "web_search",
13
+ "web_docs_search",
14
+ "web_docs_fetch",
15
+ ] as const;
16
+
17
+ /** One session-provided Web tool name. */
18
+ export type ChildWebToolName = (typeof CHILD_WEB_TOOL_NAMES)[number];
19
+
20
+ /**
21
+ * Web tools currently callable in the containing session plus a child-only extension
22
+ * factory that registers them. The factory must not capture the parent session; it may
23
+ * capture plain data such as the containing session's configuration directory.
24
+ */
25
+ export interface ChildWebToolProvision {
26
+ readonly toolNames: readonly ChildWebToolName[];
27
+ /** Register the requested subset of `toolNames` in a child session. */
28
+ readonly createFactory: (toolNames: readonly ChildWebToolName[]) => ExtensionFactory;
29
+ }
30
+
31
+ /** Plain containing-session data supplied with a child Web tool request. */
32
+ export interface ChildWebToolsRequestContext {
33
+ /**
34
+ * Working directory whose SuPi configuration governs Web settings such as Firecrawl.
35
+ * Children with a different cwd (for example a frozen Review Workspace) keep this policy.
36
+ */
37
+ readonly configCwd: string;
38
+ }
39
+
40
+ type EventBusLike = Pick<ExtensionAPI["events"], "on" | "emit">;
41
+
42
+ const REQUEST_CHANNEL = "supi:child-web-tools:request";
43
+ const RESPONSE_CHANNEL = "supi:child-web-tools:response";
44
+
45
+ interface ChildWebToolsRequest extends ChildWebToolsRequestContext {
46
+ readonly requestId: string;
47
+ }
48
+
49
+ interface ChildWebToolsResponse {
50
+ readonly requestId: string;
51
+ readonly provision: ChildWebToolProvision;
52
+ }
53
+
54
+ let nextRequestId = 0;
55
+
56
+ /**
57
+ * Answer child Web tool requests on the parent event bus. The callback must be synchronous
58
+ * because Pi's event bus does not await listeners. Returns an unsubscribe function.
59
+ */
60
+ export function provideChildWebTools(
61
+ events: EventBusLike,
62
+ resolve: (context: ChildWebToolsRequestContext) => ChildWebToolProvision | undefined,
63
+ ): () => void {
64
+ return events.on(REQUEST_CHANNEL, (data) => {
65
+ const request = data as Partial<ChildWebToolsRequest> | undefined;
66
+ if (typeof request?.requestId !== "string" || typeof request.configCwd !== "string") return;
67
+ const { requestId, configCwd } = request;
68
+ const provision = resolve({ configCwd });
69
+ if (!provision || provision.toolNames.length === 0) return;
70
+ events.emit(RESPONSE_CHANNEL, { requestId, provision } satisfies ChildWebToolsResponse);
71
+ });
72
+ }
73
+
74
+ /** Capture the Web tools the containing session currently provides, if any. */
75
+ export function requestChildWebTools(
76
+ events: EventBusLike,
77
+ context: ChildWebToolsRequestContext,
78
+ ): ChildWebToolProvision | undefined {
79
+ const requestId = `child-web-${process.pid}-${++nextRequestId}`;
80
+ let provision: ChildWebToolProvision | undefined;
81
+ const unsubscribe = events.on(RESPONSE_CHANNEL, (data) => {
82
+ const response = data as Partial<ChildWebToolsResponse> | undefined;
83
+ if (response?.requestId === requestId && response.provision) provision = response.provision;
84
+ });
85
+ try {
86
+ events.emit(REQUEST_CHANNEL, { requestId, ...context } satisfies ChildWebToolsRequest);
87
+ } finally {
88
+ unsubscribe();
89
+ }
90
+ return provision;
91
+ }
92
+
93
+ /**
94
+ * Return the Web tools callable in a session under Pi's exposure rules: active `direct`
95
+ * tools, plus registered `codemode` and `deferred` tools. `model-only` and `hidden`
96
+ * tools are not callable.
97
+ */
98
+ export function callableChildWebToolNames(
99
+ pi: Pick<ExtensionAPI, "getAllTools" | "getActiveTools">,
100
+ ): ChildWebToolName[] {
101
+ const active = new Set(pi.getActiveTools());
102
+ const exposures = new Map(
103
+ pi.getAllTools().map((tool) => [tool.name, tool.exposure ?? "direct"] as const),
104
+ );
105
+ return CHILD_WEB_TOOL_NAMES.filter((name) => {
106
+ const exposure = exposures.get(name);
107
+ if (exposure === "direct") return active.has(name);
108
+ return exposure === "codemode" || exposure === "deferred";
109
+ });
110
+ }
@@ -1,5 +1,16 @@
1
1
  // supi-core session domain — session utilities and registries.
2
2
 
3
+ export type {
4
+ ChildWebToolName,
5
+ ChildWebToolProvision,
6
+ ChildWebToolsRequestContext,
7
+ } from "./child-web-tools.ts";
8
+ export {
9
+ CHILD_WEB_TOOL_NAMES,
10
+ callableChildWebToolNames,
11
+ provideChildWebTools,
12
+ requestChildWebTools,
13
+ } from "./child-web-tools.ts";
3
14
  export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
4
15
  export type {
5
16
  SessionCapabilitySkill,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "11.2.0",
3
+ "version": "11.3.0",
4
4
  "description": "Structural AST analysis for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,8 +36,8 @@
36
36
  "dependencies": {
37
37
  "jiti": "^2.7.0",
38
38
  "web-tree-sitter": "^0.27.0",
39
- "@mrclrchtr/supi-core": "11.2.0",
40
- "@mrclrchtr/supi-code-runtime": "11.2.0"
39
+ "@mrclrchtr/supi-code-runtime": "11.3.0",
40
+ "@mrclrchtr/supi-core": "11.3.0"
41
41
  },
42
42
  "bundledDependencies": [
43
43
  "@mrclrchtr/supi-code-runtime",
@@ -18,7 +18,11 @@
18
18
  {
19
19
  "id": "import-types-as-primary-types-v1",
20
20
  "file": "common/define-grammar.js"
21
+ },
22
+ {
23
+ "id": "import-type-generic-calls-v1",
24
+ "file": "common/define-grammar.js"
21
25
  }
22
26
  ],
23
- "sha256": "c5101ee00725c52a8aa083afa4749b598a5ff884482f80ed114fca18e88629a6"
27
+ "sha256": "aaa2904a00000d8e058708845172bd3ce7836566c7e16e9ee594c43f4ba4f451"
24
28
  }
@@ -18,7 +18,11 @@
18
18
  {
19
19
  "id": "import-types-as-primary-types-v1",
20
20
  "file": "common/define-grammar.js"
21
+ },
22
+ {
23
+ "id": "import-type-generic-calls-v1",
24
+ "file": "common/define-grammar.js"
21
25
  }
22
26
  ],
23
- "sha256": "db071e260bcfec92f4b11997516dec49393c5086775eb8f090217d38de300c0d"
27
+ "sha256": "86ba3af6f8214c90f32af1ea82214a0c1da87c9e2f5ea47c19577ba5b6c78bfb"
24
28
  }
@@ -2,7 +2,8 @@
2
2
 
3
3
  import type { CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
4
4
  import type { CallSiteMatch, GrammarId, TreeSitterResult } from "../types.ts";
5
- import type { TreeSitterRuntime } from "../worker/runtime.ts";
5
+ import { isTypeOnlyImportCapture } from "../worker/call-context.ts";
6
+ import { queryParsedFile, type TreeSitterRuntime } from "../worker/runtime.ts";
6
7
  import { normalizeCallName } from "./call-name.ts";
7
8
 
8
9
  // ── Per-grammar call-site queries ─────────────────────────────────────
@@ -66,33 +67,47 @@ export async function extractCallSites(
66
67
  };
67
68
  }
68
69
 
69
- const queryResult = await runtime.queryFile(filePath, queryStr, control);
70
- if (queryResult.kind !== "success" && queryResult.kind !== "partial") return queryResult;
70
+ const parsed = await runtime.parseFile(filePath, control);
71
+ if (parsed.kind !== "success") return parsed;
72
+ const { tree, source } = parsed.data;
73
+ try {
74
+ const queryResult = await queryParsedFile(runtime, {
75
+ file: filePath,
76
+ grammarId,
77
+ tree,
78
+ source,
79
+ queryString: queryStr,
80
+ control,
81
+ });
82
+ if (queryResult.kind !== "success" && queryResult.kind !== "partial") return queryResult;
71
83
 
72
- const captures = queryResult.data;
73
- const seen = new Set<string>();
74
- const matches: CallSiteMatch[] = [];
84
+ const seen = new Set<string>();
85
+ const matches: CallSiteMatch[] = [];
75
86
 
76
- for (const capture of captures) {
77
- // Normalize: trim whitespace and collapse internal whitespace to single space
78
- const name = normalizeCallName(capture.text, grammarId, capture.nodeType);
79
- if (name.length === 0) continue;
87
+ for (const capture of queryResult.data) {
88
+ if (isTypeOnlyImportCapture(tree.rootNode, capture, grammarId)) continue;
89
+ // Normalize: trim whitespace and collapse internal whitespace to single space
90
+ const name = normalizeCallName(capture.text, grammarId, capture.nodeType);
91
+ if (name.length === 0) continue;
80
92
 
81
- // Collapse duplicate captures only when they identify the same source range.
82
- const key = `${capture.range.startLine}:${capture.range.startCharacter}:${capture.range.endLine}:${capture.range.endCharacter}`;
83
- if (seen.has(key)) continue;
84
- seen.add(key);
93
+ // Collapse duplicate captures only when they identify the same source range.
94
+ const key = `${capture.range.startLine}:${capture.range.startCharacter}:${capture.range.endLine}:${capture.range.endCharacter}`;
95
+ if (seen.has(key)) continue;
96
+ seen.add(key);
85
97
 
86
- matches.push({
87
- name,
88
- startLine: capture.range.startLine,
89
- startCharacter: capture.range.startCharacter,
90
- endLine: capture.range.endLine,
91
- endCharacter: capture.range.endCharacter,
92
- });
93
- }
98
+ matches.push({
99
+ name,
100
+ startLine: capture.range.startLine,
101
+ startCharacter: capture.range.startCharacter,
102
+ endLine: capture.range.endLine,
103
+ endCharacter: capture.range.endCharacter,
104
+ });
105
+ }
94
106
 
95
- return queryResult.kind === "partial"
96
- ? { ...queryResult, data: matches }
97
- : { kind: "success", data: matches };
107
+ return queryResult.kind === "partial"
108
+ ? { ...queryResult, data: matches }
109
+ : { kind: "success", data: matches };
110
+ } finally {
111
+ tree.delete();
112
+ }
98
113
  }
@@ -6,6 +6,7 @@ import { validatePublicPositionBounds } from "../coordinates.ts";
6
6
  import { withSyntaxCompleteness } from "../syntax-errors.ts";
7
7
  import type { SyntaxNodeLike } from "../syntax-node.ts";
8
8
  import type { GrammarId, SourceRange, TreeSitterResult } from "../types.ts";
9
+ import { isTypeOnlyImportCapture } from "../worker/call-context.ts";
9
10
  import { queryParsedFile, type TreeSitterRuntime } from "../worker/runtime.ts";
10
11
  import { normalizeCallName } from "./call-name.ts";
11
12
  import { createCalleeDisplayName, createSourceLineStarts } from "./callee-display-name.ts";
@@ -384,6 +385,7 @@ function filterCalleeCaptures(
384
385
  if (isInInner) continue;
385
386
  }
386
387
 
388
+ if (isTypeOnlyImportCapture(enclosingNode, capture, grammarId)) continue;
387
389
  const name = normalizeCallName(capture.text, grammarId, capture.nodeType);
388
390
  const rangeKey = sourceRangeKey(capture.range);
389
391
  if (name.length === 0 || seenRanges.has(rangeKey)) continue;
@@ -0,0 +1,44 @@
1
+ import type { Node } from "web-tree-sitter";
2
+ import type { GrammarId, QueryCapture } from "../types.ts";
3
+
4
+ const TYPE_ONLY_CONTEXTS = new Set([
5
+ "type_annotation",
6
+ "type_arguments",
7
+ "type_parameters",
8
+ "type_alias_declaration",
9
+ "type_query",
10
+ "type_predicate",
11
+ "interface_declaration",
12
+ "implements_clause",
13
+ ]);
14
+
15
+ /**
16
+ * Tree-sitter aliases TypeScript import types to call-expression nodes.
17
+ * Exclude those erased references without dropping dynamic import expressions.
18
+ */
19
+ export function isTypeOnlyImportCapture(
20
+ root: Node,
21
+ capture: Pick<QueryCapture, "nodeType" | "range">,
22
+ grammarId: GrammarId,
23
+ ): boolean {
24
+ if (capture.nodeType !== "import" || (grammarId !== "typescript" && grammarId !== "tsx")) {
25
+ return false;
26
+ }
27
+
28
+ let node = root.descendantForPosition(
29
+ { row: capture.range.startLine - 1, column: capture.range.startCharacter - 1 },
30
+ { row: capture.range.endLine - 1, column: capture.range.endCharacter - 1 },
31
+ );
32
+ while (node?.parent) {
33
+ const parent = node.parent;
34
+ if (TYPE_ONLY_CONTEXTS.has(parent.type)) return true;
35
+ if (parent.type === "as_expression" || parent.type === "satisfies_expression") {
36
+ const value = parent.firstNamedChild;
37
+ if (value && (node.startIndex !== value.startIndex || node.endIndex !== value.endIndex)) {
38
+ return true;
39
+ }
40
+ }
41
+ node = parent;
42
+ }
43
+ return false;
44
+ }