@mrclrchtr/supi-code-intelligence 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +1 -1
  2. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  5. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  6. package/node_modules/@mrclrchtr/supi-lsp/README.md +21 -3
  7. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  8. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  9. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  11. package/node_modules/@mrclrchtr/supi-lsp/package.json +4 -5
  12. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +12 -7
  13. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +1 -1
  14. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +0 -5
  15. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +6 -3
  16. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +2 -1
  17. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +5 -2
  18. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +2 -1
  19. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +17 -5
  20. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +40 -18
  21. package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +164 -30
  22. package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +9 -87
  23. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +0 -1
  24. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +4 -5
  25. package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +0 -29
  26. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +7 -21
  27. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-severity.ts +6 -0
  28. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +11 -14
  29. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +0 -5
  30. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +0 -1
  31. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +29 -49
  32. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +1 -1
  33. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +4 -8
  34. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +1 -9
  35. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +6 -3
  36. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +9 -1
  37. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +26 -41
  38. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +275 -99
  39. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +4 -1
  40. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +59 -28
  41. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +27 -19
  42. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +26 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registration.ts +0 -17
  44. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +83 -19
  45. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +5 -2
  46. package/node_modules/@mrclrchtr/supi-lsp/src/session/scanner.ts +24 -21
  47. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +29 -3
  48. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +19 -40
  49. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +13 -19
  50. package/node_modules/@mrclrchtr/supi-lsp/src/workspace-path-policy.ts +287 -0
  51. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +6 -18
  52. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  53. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +5 -0
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -5
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -11
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +19 -0
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +5 -23
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/src/operation-support.ts +3 -21
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +5 -9
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +10 -20
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +7 -1
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +9 -5
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +134 -9
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-name.ts +96 -0
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +11 -12
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +39 -56
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +9 -7
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/js-binding-pattern.ts +26 -0
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +2 -21
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline-html-sql.ts +144 -12
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/outline.ts +50 -37
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/scope.ts +138 -0
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +0 -7
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +1 -14
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +0 -10
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +6 -8
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +34 -10
  80. package/package.json +5 -5
  81. package/src/analysis/capability/capability-warnings.ts +4 -22
  82. package/src/analysis/search/ast-scan-timing.ts +4 -2
  83. package/src/analysis/search/ast-scan.ts +1 -1
  84. package/src/api.ts +1 -0
  85. package/src/extension.ts +4 -2
  86. package/src/session/health-refresh.ts +24 -2
  87. package/src/session/health-types.ts +19 -0
  88. package/src/session/input/health-refactor.ts +16 -14
  89. package/src/session/refactor-types.ts +14 -1
  90. package/src/session/refactor-workflow.ts +21 -16
  91. package/src/substrate/lsp/lifecycle.ts +0 -3
  92. package/src/substrate/lsp/maintenance.ts +4 -5
  93. package/src/substrate/lsp/settings.ts +6 -9
  94. package/src/substrate/lsp/state.ts +0 -2
  95. package/src/substrate/workspace-provider-host.ts +2 -2
  96. package/src/tool/code_health/file-scope-markdown.ts +43 -0
  97. package/src/tool/code_health/markdown.ts +66 -66
  98. package/src/tool/code_health/refresh-outcome.ts +46 -0
  99. package/src/tool/code_health/refresh-status.ts +166 -23
  100. package/src/tool/code_health/result.ts +1 -0
  101. package/src/tool/code_health/tui.ts +18 -5
  102. package/src/tool/code_refactor_apply/guidance.ts +3 -2
  103. package/src/tool/code_refactor_plan/guidance.ts +1 -1
  104. package/src/tool/register.ts +5 -2
  105. package/src/tool/schemas.ts +21 -11
  106. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-actions.ts +0 -59
  107. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +0 -24
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +0 -132
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +0 -158
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +0 -167
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -42
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-registration.ts +0 -37
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +0 -30
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/structure.ts +0 -8
@@ -3,6 +3,7 @@ import type {
3
3
  ActiveCoverageSummaryEntry,
4
4
  OutstandingDiagnosticSummaryEntry,
5
5
  } from "./manager/manager-types.ts";
6
+ import type { AutomaticLspPathPolicy } from "./workspace-path-policy.ts";
6
7
 
7
8
  /**
8
9
  * Display form for a file path used both for human-readable LSP output and as
@@ -67,9 +68,14 @@ export function normalizeRelevantPaths(relevantPaths: string[]): string[] {
67
68
  * - candidate has no "/" and no ".": treat as a directory name anywhere in the path
68
69
  * - otherwise: treat as a filename and match the basename
69
70
  */
70
- export function isPathRelevant(filePath: string, relevantPaths: string[], cwd: string): boolean {
71
+ export function isPathRelevant(
72
+ filePath: string,
73
+ relevantPaths: string[],
74
+ cwd: string,
75
+ policy: AutomaticLspPathPolicy,
76
+ ): boolean {
71
77
  const normalizedFilePath = normalizeRelevantPath(filePath);
72
- if (shouldIgnoreLspPath(normalizedFilePath, cwd)) return false;
78
+ if (shouldIgnoreLspPath(normalizedFilePath, cwd, policy)) return false;
73
79
 
74
80
  return relevantPaths.some((candidate) => {
75
81
  if (normalizedFilePath === candidate) return true;
@@ -86,48 +92,21 @@ export function isPathRelevant(filePath: string, relevantPaths: string[], cwd: s
86
92
 
87
93
  import { isFileExcludedByTsconfig } from "./config/tsconfig-scope.ts";
88
94
 
89
- /** Check whether a file path is inside the project tree (within cwd, not node_modules/.pnpm/out-of-tree).
90
- * Does NOT check tsconfig exclusion use `shouldIgnoreLspPath` for diagnostics/guidance filtering. */
91
- export function isInProjectTree(filePath: string, cwd: string): boolean {
92
- const normalized = normalizeRelevantPath(filePath);
93
- if (
94
- normalized === "node_modules" ||
95
- normalized.startsWith("node_modules/") ||
96
- normalized.includes("/node_modules/") ||
97
- normalized === ".pnpm" ||
98
- normalized.startsWith(".pnpm/") ||
99
- normalized.includes("/.pnpm/")
100
- ) {
101
- return false;
102
- }
103
-
104
- const absolutePath = path.resolve(cwd, filePath);
105
- const relativePath = path.relative(cwd, absolutePath);
106
- return !(relativePath.startsWith(`..${path.sep}`) || relativePath === "..");
95
+ /** Check whether the supplied automatic path policy allows a file path. */
96
+ export function isInProjectTree(filePath: string, policy: AutomaticLspPathPolicy): boolean {
97
+ return policy.isEligible(filePath);
107
98
  }
108
99
 
109
- /** Check whether a file path is inside the current project (not ignored and within cwd). */
110
- export function isProjectSource(filePath: string, cwd: string): boolean {
111
- return isInProjectTree(filePath, cwd);
112
- }
113
-
114
- export function shouldIgnoreLspPath(filePath: string, cwd: string): boolean {
115
- if (!isInProjectTree(filePath, cwd)) return true;
100
+ /** Check automatic and tsconfig diagnostic scope for one file path. */
101
+ export function shouldIgnoreLspPath(
102
+ filePath: string,
103
+ cwd: string,
104
+ policy: AutomaticLspPathPolicy,
105
+ ): boolean {
106
+ if (!isInProjectTree(filePath, policy)) return true;
116
107
 
117
108
  const normalized = normalizeRelevantPath(filePath);
118
- if (
119
- normalized === "node_modules" ||
120
- normalized.startsWith("node_modules/") ||
121
- normalized.includes("/node_modules/") ||
122
- normalized === ".pnpm" ||
123
- normalized.startsWith(".pnpm/") ||
124
- normalized.includes("/.pnpm/") ||
125
- isFileExcludedByTsconfig(normalized, cwd)
126
- ) {
127
- return true;
128
- }
129
-
130
- return false;
109
+ return isFileExcludedByTsconfig(normalized, cwd);
131
110
  }
132
111
 
133
112
  function normalizeRelevantPath(filePath: string): string {
@@ -3,12 +3,6 @@
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
5
 
6
- export {
7
- fileToUri,
8
- resolveToolPath as resolveSessionPath,
9
- uriToFile,
10
- } from "@mrclrchtr/supi-core/path";
11
-
12
6
  // ── Language ID Detection ─────────────────────────────────────────────
13
7
 
14
8
  const EXT_TO_LANGUAGE: Record<string, string> = {
@@ -68,11 +62,6 @@ export function detectLanguageId(filePath: string): string {
68
62
  return EXT_TO_LANGUAGE[ext] ?? ext;
69
63
  }
70
64
 
71
- /** Get the file extension (without dot) from a file path. */
72
- export function getFileExtension(filePath: string): string {
73
- return path.extname(filePath).slice(1).toLowerCase();
74
- }
75
-
76
65
  // ── PATH Validation ───────────────────────────────────────────────────
77
66
 
78
67
  /**
@@ -80,9 +69,9 @@ export function getFileExtension(filePath: string): string {
80
69
  * Uses synchronous check to avoid complexity.
81
70
  */
82
71
  export function commandExists(command: string): boolean {
83
- // If it's an absolute path, check directly
72
+ // If it is an absolute path, validate the exact executable file.
84
73
  if (path.isAbsolute(command)) {
85
- return fs.existsSync(command);
74
+ return isExecutableFile(command);
86
75
  }
87
76
 
88
77
  const pathDirs = (process.env.PATH ?? "").split(path.delimiter);
@@ -92,13 +81,18 @@ export function commandExists(command: string): boolean {
92
81
  for (const dir of pathDirs) {
93
82
  for (const ext of extensions) {
94
83
  const fullPath = path.join(dir, command + ext);
95
- try {
96
- fs.accessSync(fullPath, fs.constants.X_OK);
97
- return true;
98
- } catch {
99
- // Not found here, continue
100
- }
84
+ if (isExecutableFile(fullPath)) return true;
101
85
  }
102
86
  }
103
87
  return false;
104
88
  }
89
+
90
+ function isExecutableFile(filePath: string): boolean {
91
+ try {
92
+ if (!fs.statSync(filePath).isFile()) return false;
93
+ fs.accessSync(filePath, fs.constants.X_OK);
94
+ return true;
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
@@ -0,0 +1,287 @@
1
+ import { type Dirent, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
+ import * as path from "node:path";
3
+ import ignore from "ignore";
4
+ import { loadLspSettings } from "./config/lsp-settings.ts";
5
+
6
+ /** Directory names that automatic LSP work never enters or selects. */
7
+ export const AUTOMATIC_LSP_EXCLUDED_DIRECTORIES: readonly string[] = Object.freeze([
8
+ ".git",
9
+ ".cache",
10
+ ".pi",
11
+ ".pnpm",
12
+ "node_modules",
13
+ "dist",
14
+ "build",
15
+ "out",
16
+ "coverage",
17
+ ".next",
18
+ ".nuxt",
19
+ ".turbo",
20
+ "__pycache__",
21
+ ]);
22
+
23
+ const BUILT_IN_EXCLUSIONS = new Set(AUTOMATIC_LSP_EXCLUDED_DIRECTORIES);
24
+
25
+ /** Automatic path rules for one Workspace LSP runtime. The rules do not change. */
26
+ export interface AutomaticLspPathPolicy {
27
+ /** Canonical absolute workspace root used for all matching. */
28
+ readonly workspaceRoot: string;
29
+ /** True when automatic LSP work can use the path. */
30
+ isEligible(candidate: string, kind?: "file" | "directory"): boolean;
31
+ }
32
+
33
+ /** Create the automatic path rules for one Workspace LSP runtime. */
34
+ export function createAutomaticLspPathPolicy(
35
+ workspaceRoot: string,
36
+ excludePatterns: readonly string[],
37
+ ): AutomaticLspPathPolicy {
38
+ const canonicalRoot = canonicalPath(workspaceRoot);
39
+ const configuredExclusions = createIgnoreMatcher([...excludePatterns]);
40
+ const repositoryRules = compileRepositoryRules(canonicalRoot, configuredExclusions);
41
+ return Object.freeze({
42
+ workspaceRoot: canonicalRoot,
43
+ isEligible(candidate: string, kind: "file" | "directory" = "file"): boolean {
44
+ const relativePath = normalizeCandidate(canonicalRoot, workspaceRoot, candidate);
45
+ if (
46
+ relativePath === null ||
47
+ !isAutomaticCandidateAllowed(workspaceRoot, relativePath, kind)
48
+ ) {
49
+ return false;
50
+ }
51
+ return isEligibleRelativePath(
52
+ relativePath,
53
+ kind === "directory",
54
+ configuredExclusions,
55
+ repositoryRules,
56
+ );
57
+ },
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Create path rules for an automatic LSP helper used outside a runtime.
63
+ * Runtime-owned operations should pass the rules created at startup instead.
64
+ */
65
+ export function createDefaultAutomaticLspPathPolicy(workspaceRoot: string): AutomaticLspPathPolicy {
66
+ return createAutomaticLspPathPolicy(workspaceRoot, loadLspSettings(workspaceRoot).exclude);
67
+ }
68
+
69
+ /**
70
+ * Walk paths allowed by the policy without visiting symbolic-link directories.
71
+ *
72
+ * The visitor receives only entries that the automatic LSP policy allows.
73
+ * Return `true` from the visitor to stop the walk after that directory.
74
+ */
75
+ export function walkAutomaticLspTree(
76
+ policy: AutomaticLspPathPolicy,
77
+ directory: string,
78
+ depth: number,
79
+ onDirectory: (directory: string, entries: readonly Dirent[]) => boolean | undefined,
80
+ ): void {
81
+ walkDirectoryTree({
82
+ directory,
83
+ depth,
84
+ includeEntry: (candidate, entry) =>
85
+ policy.isEligible(candidate, entry.isDirectory() ? "directory" : "file"),
86
+ onDirectory,
87
+ });
88
+ }
89
+
90
+ interface DirectoryWalkOptions {
91
+ directory: string;
92
+ depth: number;
93
+ includeEntry: (candidate: string, entry: Dirent) => boolean;
94
+ onDirectory: (directory: string, entries: readonly Dirent[]) => boolean | undefined;
95
+ beforeFilter?: (directory: string) => void;
96
+ }
97
+
98
+ function walkDirectoryTree(options: DirectoryWalkOptions): boolean {
99
+ const { directory, depth, includeEntry, onDirectory, beforeFilter } = options;
100
+ beforeFilter?.(directory);
101
+ let entries: Dirent[];
102
+ try {
103
+ entries = [...readdirSync(directory, { withFileTypes: true })].sort((a, b) =>
104
+ a.name.localeCompare(b.name),
105
+ );
106
+ } catch {
107
+ return false;
108
+ }
109
+
110
+ const eligibleEntries = entries.filter((entry) =>
111
+ includeEntry(path.join(directory, entry.name), entry),
112
+ );
113
+ if (onDirectory(directory, eligibleEntries) === true) return true;
114
+ if (depth <= 0) return false;
115
+
116
+ for (const entry of eligibleEntries) {
117
+ if (!entry.isDirectory()) continue;
118
+ if (
119
+ walkDirectoryTree({
120
+ ...options,
121
+ directory: path.join(directory, entry.name),
122
+ depth: depth - 1,
123
+ })
124
+ ) {
125
+ return true;
126
+ }
127
+ }
128
+ return false;
129
+ }
130
+
131
+ type IgnoreMatcher = ReturnType<typeof ignore>;
132
+
133
+ function createIgnoreMatcher(patterns: string | readonly string[]): IgnoreMatcher {
134
+ return ignore({ ignorecase: false }).add(patterns);
135
+ }
136
+
137
+ interface RepositoryIgnoreRules {
138
+ readonly base: string;
139
+ readonly matcher: IgnoreMatcher;
140
+ }
141
+
142
+ function compileRepositoryRules(
143
+ workspaceRoot: string,
144
+ configuredExclusions: IgnoreMatcher,
145
+ ): readonly RepositoryIgnoreRules[] {
146
+ const rules: RepositoryIgnoreRules[] = [];
147
+
148
+ walkDirectoryTree({
149
+ directory: workspaceRoot,
150
+ depth: Number.MAX_SAFE_INTEGER,
151
+ includeEntry: (candidate, entry) => {
152
+ if (entry.isSymbolicLink() || !entry.isDirectory()) return false;
153
+ const relativePath = path.relative(workspaceRoot, candidate).split(path.sep).join("/");
154
+ return isEligibleRelativePath(relativePath, true, configuredExclusions, rules);
155
+ },
156
+ onDirectory: () => undefined,
157
+ beforeFilter: (directory) => {
158
+ const relativeDirectory = path.relative(workspaceRoot, directory).split(path.sep).join("/");
159
+ const nestedRules = readRepositoryIgnore(directory, relativeDirectory);
160
+ if (nestedRules) rules.push(nestedRules);
161
+ },
162
+ });
163
+
164
+ return Object.freeze([...rules]);
165
+ }
166
+
167
+ function readRepositoryIgnore(
168
+ directory: string,
169
+ relativeDirectory: string,
170
+ ): RepositoryIgnoreRules | null {
171
+ try {
172
+ const matcher = createIgnoreMatcher(readFileSync(path.join(directory, ".gitignore"), "utf-8"));
173
+ return Object.freeze({ base: relativeDirectory, matcher });
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ function isEligibleRelativePath(
180
+ relativePath: string,
181
+ directory: boolean,
182
+ configuredExclusions: IgnoreMatcher,
183
+ repositoryRules: readonly RepositoryIgnoreRules[],
184
+ ): boolean {
185
+ const segments = relativePath.split("/");
186
+ if (
187
+ segments.some(
188
+ (segment, index) =>
189
+ BUILT_IN_EXCLUSIONS.has(segment) && (directory || index < segments.length - 1),
190
+ )
191
+ ) {
192
+ return false;
193
+ }
194
+ if (relativePath === "") return true;
195
+
196
+ const matchPath = directory ? `${relativePath}/` : relativePath;
197
+ if (configuredExclusions.ignores(matchPath)) return false;
198
+ return !isRepositoryIgnored(relativePath, directory, repositoryRules);
199
+ }
200
+
201
+ function isRepositoryIgnored(
202
+ relativePath: string,
203
+ directory: boolean,
204
+ repositoryRules: readonly RepositoryIgnoreRules[],
205
+ ): boolean {
206
+ let ignored = false;
207
+ for (const rules of repositoryRules) {
208
+ const localPath = relativeToRuleBase(rules.base, relativePath);
209
+ if (localPath === null || localPath === "") continue;
210
+ const result = rules.matcher.test(directory ? `${localPath}/` : localPath);
211
+ if (result.ignored) ignored = true;
212
+ else if (result.unignored) ignored = false;
213
+ }
214
+ return ignored;
215
+ }
216
+
217
+ function relativeToRuleBase(base: string, relativePath: string): string | null {
218
+ if (base === "") return relativePath;
219
+ if (relativePath === base) return "";
220
+ return relativePath.startsWith(`${base}/`) ? relativePath.slice(base.length + 1) : null;
221
+ }
222
+
223
+ function isAutomaticCandidateAllowed(
224
+ suppliedRoot: string,
225
+ relativePath: string,
226
+ kind: "file" | "directory",
227
+ ): boolean {
228
+ if (relativePath === "") return true;
229
+ let current = path.resolve(suppliedRoot);
230
+ const segments = relativePath.split("/");
231
+
232
+ for (const [index, segment] of segments.entries()) {
233
+ current = path.join(current, segment);
234
+ let link: ReturnType<typeof lstatSync>;
235
+ try {
236
+ link = lstatSync(current);
237
+ } catch {
238
+ return true;
239
+ }
240
+ if (!link.isSymbolicLink()) continue;
241
+ if (index < segments.length - 1) return false;
242
+
243
+ try {
244
+ const target = statSync(current);
245
+ return kind === "file" && target.isFile();
246
+ } catch {
247
+ return false;
248
+ }
249
+ }
250
+
251
+ return true;
252
+ }
253
+
254
+ function canonicalPath(candidate: string): string {
255
+ try {
256
+ return realpathSync(candidate);
257
+ } catch {
258
+ return path.resolve(candidate);
259
+ }
260
+ }
261
+
262
+ function normalizeCandidate(
263
+ canonicalRoot: string,
264
+ suppliedRoot: string,
265
+ candidate: string,
266
+ ): string | null {
267
+ const absoluteCandidate = path.isAbsolute(candidate)
268
+ ? mapSuppliedRootToCanonical(canonicalRoot, suppliedRoot, candidate)
269
+ : path.resolve(canonicalRoot, candidate);
270
+ const relativePath = path.relative(canonicalRoot, absoluteCandidate);
271
+ if (relativePath === "") return "";
272
+ if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`)) return null;
273
+ return relativePath.split(path.sep).join("/");
274
+ }
275
+
276
+ function mapSuppliedRootToCanonical(
277
+ canonicalRoot: string,
278
+ suppliedRoot: string,
279
+ candidate: string,
280
+ ): string {
281
+ const resolvedSuppliedRoot = path.resolve(suppliedRoot);
282
+ const relativePath = path.relative(resolvedSuppliedRoot, path.resolve(candidate));
283
+ if (relativePath === "" || (relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`))) {
284
+ return path.resolve(canonicalRoot, relativePath);
285
+ }
286
+ return path.resolve(candidate);
287
+ }
@@ -25,7 +25,6 @@ npm install @mrclrchtr/supi-tree-sitter
25
25
 
26
26
  This package provides the parser-backed structural substrate consumed by `@mrclrchtr/supi-code-intelligence`:
27
27
 
28
- - a shared session-scoped Tree-sitter service for structural analysis
29
28
  - an owned parsing session API for direct library consumers
30
29
  - a `StructuralProvider` adapter published through `./provider/tree-sitter-provider`
31
30
  - a long-lived owned Structural Worker that keeps Pi responsive during parser-backed work
@@ -59,18 +58,20 @@ Outline collection supports every listed family. HTML outlines contain elements
59
58
  `@mrclrchtr/supi-tree-sitter` is the **structural substrate** in SuPi's
60
59
  code-understanding stack. It depends on `@mrclrchtr/supi-core` and
61
60
  `@mrclrchtr/supi-code-runtime` for shared contracts, and provides structural
62
- analysis via a session-scoped Tree-sitter service that publishes its
63
- capabilities into the shared workspace runtime.
61
+ analysis via a lifecycle controller that publishes its capabilities into
62
+ the shared workspace runtime.
64
63
 
65
64
  ```text
66
65
  supi-code-runtime ← shared contracts + workspace runtime
67
66
 
68
- supi-tree-sitter ← one owned Structural Worker + session-scoped service + runtime capabilities
67
+ supi-tree-sitter ← one owned Structural Worker + runtime capabilities
69
68
  ```
70
69
 
71
70
  ## Package surfaces
72
71
 
73
- - `@mrclrchtr/supi-tree-sitter/api` reusable parsing session factory, shared session-scoped structural service access, and shared result types
72
+ There is no package-root export. Use one of these explicit subpaths:
73
+
74
+ - `@mrclrchtr/supi-tree-sitter/api` — reusable parsing session factory, lifecycle controller, language helpers, and shared result types
74
75
  - `@mrclrchtr/supi-tree-sitter/provider/tree-sitter-provider` — shared StructuralProvider adapter
75
76
 
76
77
  This is a **library-only** package. Public tool registration and pi event handlers belong to `@mrclrchtr/supi-code-intelligence`.
@@ -89,17 +90,6 @@ const callees = await session.calleesAt("src/index.ts", 42, 10);
89
90
  await session.dispose();
90
91
  ```
91
92
 
92
- Shared session-scoped service example:
93
-
94
- ```ts
95
- import { getSessionTreeSitterService } from "@mrclrchtr/supi-tree-sitter/api";
96
-
97
- const state = getSessionTreeSitterService("/project");
98
- if (state.kind === "ready") {
99
- const outline = await state.service.outline("src/index.ts");
100
- }
101
- ```
102
-
103
93
  ## Structural performance baseline
104
94
 
105
95
  Run the stable outline fixture on the same machine and dependency versions:
@@ -117,7 +107,6 @@ All structural service operations accept optional `CodeRequestControl`. The pare
117
107
  ## Source
118
108
 
119
109
  - `src/api.ts` — public library entrypoint
120
- - `src/index.ts` — re-export surface
121
110
  - `src/worker/bootstrap.mjs` — package-owned Worker bootstrap and direct `jiti` loader
122
111
  - `src/worker/runtime.ts` — Worker-only parser and query runtime
123
112
  - `src/worker/parsed-file-store.ts` — Worker-only parsed-file and compiled-query reuse
@@ -125,6 +114,5 @@ All structural service operations accept optional `CodeRequestControl`. The pare
125
114
  - `src/session/runtime-controller.ts` — generation-fenced shared Worker lifecycle
126
115
  - `src/session/session.ts` — asynchronous Worker-proxy service and owned session API
127
116
  - `src/operation-support.ts` — authoritative operation-specific extension support
128
- - `src/session/service-registry.ts` — shared session-scoped structural service registry
129
117
  - `src/provider/tree-sitter-provider.ts` — `StructuralProvider` adapter consumed by `@mrclrchtr/supi-code-intelligence`
130
118
  - `src/tool/outline.ts`, `src/tool/outline-*.ts`, `src/tool/imports.ts`, `src/tool/exports.ts`, `src/tool/node-at.ts`, `src/tool/callees.ts`, `src/tool/call-sites.ts` — Worker-internal structural analyses
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,11 @@
1
+ /** Maximum UTF-16 code-unit length of one debug identity string. */
2
+ export const MAX_DEBUG_IDENTITY_STRING = 512;
3
+
4
+ /** Marker appended to a truncated debug identity string. */
5
+ export const DEBUG_IDENTITY_TRUNCATION_MARKER = "…";
6
+
7
+ /** Bound one debug identity string, including its truncation marker. */
8
+ export function truncateDebugIdentity(value: string): string {
9
+ if (value.length <= MAX_DEBUG_IDENTITY_STRING) return value;
10
+ return `${value.slice(0, MAX_DEBUG_IDENTITY_STRING - 1)}${DEBUG_IDENTITY_TRUNCATION_MARKER}`;
11
+ }
@@ -3,6 +3,11 @@
3
3
  // Kept separate from debug-registry.ts so debug-timing.ts can import the
4
4
  // registry without creating an import cycle through the barrel re-export.
5
5
 
6
+ export {
7
+ DEBUG_IDENTITY_TRUNCATION_MARKER,
8
+ MAX_DEBUG_IDENTITY_STRING,
9
+ truncateDebugIdentity,
10
+ } from "./debug-identity.ts";
6
11
  // biome-ignore lint/performance/noReExportAll: preserve the stable debug domain entry point
7
12
  export * from "./debug-registry.ts";
8
13
  // biome-ignore lint/performance/noReExportAll: preserve the stable debug domain entry point
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "description": "Structural AST analysis for SuPi code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,14 +30,13 @@
30
30
  "src/**/*.mjs",
31
31
  "src/**/*.json",
32
32
  "resources",
33
- "scripts/*.mjs",
34
33
  "!__tests__"
35
34
  ],
36
35
  "dependencies": {
37
36
  "jiti": "^2.7.0",
38
37
  "web-tree-sitter": "^0.26.8",
39
- "@mrclrchtr/supi-code-runtime": "6.1.0",
40
- "@mrclrchtr/supi-core": "6.1.0"
38
+ "@mrclrchtr/supi-code-runtime": "6.2.0",
39
+ "@mrclrchtr/supi-core": "6.2.0"
41
40
  },
42
41
  "bundledDependencies": [
43
42
  "@mrclrchtr/supi-code-runtime",
@@ -45,7 +44,6 @@
45
44
  "jiti",
46
45
  "web-tree-sitter"
47
46
  ],
48
- "main": "src/api.ts",
49
47
  "exports": {
50
48
  "./api": "./src/api.ts",
51
49
  "./package.json": "./package.json",
@@ -1,4 +1,4 @@
1
- // Public tree-sitter session factory, shared session service access, and shared types.
1
+ // Public Tree-sitter session factory, lifecycle controller, and shared types.
2
2
  //
3
3
  // This package is library-only — no pi extension surface.
4
4
  // Exports structured runtime/service APIs only.
@@ -7,19 +7,11 @@
7
7
  // Language detection helpers
8
8
  export {
9
9
  detectGrammar,
10
- getSupportedExtension,
11
10
  getSupportedExtensions,
12
- isJsTsGrammar,
13
- isSupportedFile,
14
11
  } from "./language.ts";
15
12
  export type { StructuralSearchOperation } from "./operation-support.ts";
16
13
  export { getStructuralSearchSupportedExtensions } from "./operation-support.ts";
17
- export type {
18
- TsControllerState,
19
- TsStartResult,
20
- } from "./session/runtime-controller.ts";
21
14
  export { TreeSitterRuntimeController } from "./session/runtime-controller.ts";
22
- export { getSessionTreeSitterService } from "./session/service-registry.ts";
23
15
  export { createTreeSitterSession } from "./session/session.ts";
24
16
 
25
17
  // Shared types
@@ -32,8 +24,6 @@ export type {
32
24
  NodeAtResult,
33
25
  OutlineItem,
34
26
  QueryCapture,
35
- SessionTreeSitterService,
36
- SessionTreeSitterServiceState,
37
27
  SourceRange,
38
28
  SupportedExtension,
39
29
  TreeSitterResult,
@@ -81,6 +81,25 @@ export function nodeToRange(
81
81
  };
82
82
  }
83
83
 
84
+ /** Validate that a public position exists in the source bounds. */
85
+ export function validatePublicPositionBounds(
86
+ line: number,
87
+ character: number,
88
+ source: string,
89
+ ): { kind: "validation-error"; message: string } | null {
90
+ const lines = splitSourceLines(source);
91
+ if (line > lines.length) {
92
+ return { kind: "validation-error", message: "line is beyond end of file" };
93
+ }
94
+
95
+ const lineText = lines[line - 1] ?? "";
96
+ if (character > lineText.length + 1) {
97
+ return { kind: "validation-error", message: "character is beyond end of line" };
98
+ }
99
+
100
+ return null;
101
+ }
102
+
84
103
  /** Split source into logical lines without CRLF line-ending bytes. */
85
104
  export function splitSourceLines(source: string): string[] {
86
105
  return source.replace(/\r\n?/g, "\n").split("\n");