@mrclrchtr/supi-code-intelligence 2.0.5 → 2.1.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 (121) hide show
  1. package/README.md +71 -1
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +29 -21
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +88 -69
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +51 -29
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +64 -18
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +11 -5
  12. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  13. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  14. package/node_modules/@mrclrchtr/supi-core/README.md +29 -21
  15. package/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  16. package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +88 -69
  17. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  18. package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  19. package/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  20. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  21. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +51 -29
  22. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +64 -18
  23. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +11 -5
  24. package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +29 -21
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  27. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +88 -69
  28. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  29. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  30. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  32. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +51 -29
  33. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +64 -18
  34. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +11 -5
  35. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  37. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +29 -21
  38. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  39. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +88 -69
  40. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  41. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  42. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  44. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +51 -29
  45. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +64 -18
  46. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings.ts +11 -5
  47. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  49. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +29 -21
  50. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  51. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +88 -69
  52. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  53. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +51 -29
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +64 -18
  58. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +11 -5
  59. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  61. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +29 -21
  62. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +88 -69
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +51 -29
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +64 -18
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings.ts +11 -5
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  73. package/package.json +5 -5
  74. package/src/analysis/evidence.ts +13 -0
  75. package/src/analysis/search/pattern.ts +24 -13
  76. package/src/analysis/search/ripgrep.ts +92 -7
  77. package/src/extension.ts +1 -1
  78. package/src/session/capability-adapter.ts +218 -0
  79. package/src/session/session.ts +39 -1
  80. package/src/session/target-workflow.ts +430 -0
  81. package/src/substrate/lsp/settings.ts +7 -3
  82. package/src/tool/find/execute.ts +23 -10
  83. package/src/tool/find/orchestrate.ts +67 -54
  84. package/src/tool/find/semantic-search.ts +30 -16
  85. package/src/tool/graph/collect-relation.ts +2 -1
  86. package/src/tool/graph/execute.ts +40 -12
  87. package/src/tool/graph/markdown-base.ts +5 -37
  88. package/src/tool/graph/orchestrate.ts +9 -35
  89. package/src/tool/graph/tui.ts +2 -0
  90. package/src/tool/guidance.ts +1 -1
  91. package/src/tool/health/markdown.ts +12 -70
  92. package/src/tool/health/orchestrate.ts +5 -13
  93. package/src/tool/impact/analysis.ts +0 -27
  94. package/src/tool/impact/change-set-impact.ts +3 -2
  95. package/src/tool/impact/execute.ts +27 -0
  96. package/src/tool/impact/file-level-impact.ts +3 -2
  97. package/src/tool/impact/result.ts +2 -21
  98. package/src/tool/impact/single-impact.ts +3 -2
  99. package/src/tool/infra/error-results.ts +18 -196
  100. package/src/tool/infra/pipeline.ts +24 -1
  101. package/src/tool/inspect/markdown.ts +4 -41
  102. package/src/tool/inspect/orchestrate.ts +27 -28
  103. package/src/tool/orientation/orchestrate.ts +5 -8
  104. package/src/tool/orientation/precise-target.ts +65 -69
  105. package/src/tool/refactor-apply/orchestrate.ts +2 -7
  106. package/src/tool/refactor-plan/orchestrate.ts +31 -39
  107. package/src/tool/resolve/markdown.ts +5 -6
  108. package/src/tool/resolve/orchestrate.ts +7 -103
  109. package/src/tool/result/errors.ts +199 -0
  110. package/src/tool/result/find.ts +29 -0
  111. package/src/tool/result/graph.ts +93 -0
  112. package/src/tool/result/health.ts +94 -0
  113. package/src/tool/result/impact.ts +50 -0
  114. package/src/tool/result/inspect.ts +60 -0
  115. package/src/tool/result/orientation.ts +30 -0
  116. package/src/tool/result/refactor.ts +35 -0
  117. package/src/tool/result/resolve.ts +114 -0
  118. package/src/tool/result/types.ts +188 -0
  119. package/src/tool/schemas.ts +22 -1
  120. package/src/types/details.ts +17 -185
  121. package/src/ui/tui/common.ts +45 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-tree-sitter",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
4
4
  "description": "SuPi Tree-sitter extension — structural AST analysis for pi",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,8 +35,8 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "web-tree-sitter": "^0.26.8",
38
- "@mrclrchtr/supi-code-runtime": "2.0.5",
39
- "@mrclrchtr/supi-core": "2.0.5"
38
+ "@mrclrchtr/supi-code-runtime": "2.1.0",
39
+ "@mrclrchtr/supi-core": "2.1.0"
40
40
  },
41
41
  "bundledDependencies": [
42
42
  "@mrclrchtr/supi-code-runtime",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-intelligence",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
4
4
  "description": "SuPi Code Intelligence extension — architecture briefs, caller/callee analysis, impact assessment, and pattern search for pi",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,10 +34,10 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "ts-pattern": "^5.9.0",
37
- "@mrclrchtr/supi-code-runtime": "2.0.5",
38
- "@mrclrchtr/supi-lsp": "2.0.5",
39
- "@mrclrchtr/supi-core": "2.0.5",
40
- "@mrclrchtr/supi-tree-sitter": "2.0.5"
37
+ "@mrclrchtr/supi-code-runtime": "2.1.0",
38
+ "@mrclrchtr/supi-core": "2.1.0",
39
+ "@mrclrchtr/supi-lsp": "2.1.0",
40
+ "@mrclrchtr/supi-tree-sitter": "2.1.0"
41
41
  },
42
42
  "bundledDependencies": [
43
43
  "@mrclrchtr/supi-code-runtime",
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Evidence-list helpers — the shared leaf utility for bounded tool-evidence
3
+ * collections with explicit completeness metadata.
4
+ *
5
+ * These helpers (types, builders, disclosure renderers) are consumed by both
6
+ * the `analysis/` layer (brief/reference/git formatters that produce markdown
7
+ * fragments) and the `tool/` layer (markdown/TUI renderers and tool-result
8
+ * assembly). They live here, at the lower layer, so `analysis/` never imports
9
+ * upward into `tool/`. The tool-result *assembly* (`assemble*` functions and
10
+ * details types) lives in `src/tool/result/`; this module is the shared
11
+ * evidence-list primitive beneath it.
12
+ */
13
+
1
14
  export type EvidencePartialReason = "timeout" | "safety-limit" | "interrupted" | "provider-limited";
2
15
 
3
16
  export interface EvidenceListMetadata {
@@ -64,7 +64,7 @@ export function isStructuredPatternKind(kind: string | undefined): kind is Struc
64
64
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: enumeration-interrupted, cap, timeout, and error branches are each necessary
65
65
  export async function getStructuredPatternMatches(
66
66
  params: StructuredPatternParams,
67
- scopePath: string,
67
+ scopePath: string | readonly string[],
68
68
  cwd: string,
69
69
  relScope: string,
70
70
  structural: StructuralSubstrate,
@@ -143,7 +143,10 @@ interface FileEnumeration {
143
143
  * Enumerate all tree-sitter-supported source files in scopePath using rg --files.
144
144
  * Returns null if ripgrep is not available.
145
145
  */
146
- function enumerateSourceFiles(scopePath: string, cwd: string): FileEnumeration | null {
146
+ function enumerateSourceFiles(
147
+ scopePath: string | readonly string[],
148
+ cwd: string,
149
+ ): FileEnumeration | null {
147
150
  const extensions = getSupportedExtensions();
148
151
  // Strip leading dots — ripgrep -g expects ".ext" without the dot prefix
149
152
  const globPattern = `*.{${Array.from(extensions)
@@ -151,18 +154,14 @@ function enumerateSourceFiles(scopePath: string, cwd: string): FileEnumeration |
151
154
  .join(",")}}`;
152
155
 
153
156
  try {
154
- const output = execFileSync("rg", ["--files", "-g", globPattern, scopePath], {
157
+ const output = execFileSync("rg", ["--files", "-g", globPattern, ...scopePathArgs(scopePath)], {
155
158
  encoding: "utf-8",
156
159
  cwd,
157
160
  timeout: 5000,
158
161
  stdio: ["pipe", "pipe", "pipe"],
159
162
  });
160
163
  return {
161
- files: output
162
- .split("\n")
163
- .map((line) => line.trim())
164
- .filter((line) => line.length > 0)
165
- .sort((a, b) => a.localeCompare(b)),
164
+ files: normalizeEnumeratedFiles(output),
166
165
  interrupted: false,
167
166
  };
168
167
  } catch (err: unknown) {
@@ -174,11 +173,7 @@ function enumerateSourceFiles(scopePath: string, cwd: string): FileEnumeration |
174
173
  const stdout = typeof err.stdout === "string" ? err.stdout : "";
175
174
  const wasKilled = (err as { killed?: boolean }).killed === true;
176
175
  return {
177
- files: stdout
178
- .split("\n")
179
- .map((line) => line.trim())
180
- .filter((line) => line.length > 0)
181
- .sort((a, b) => a.localeCompare(b)),
176
+ files: normalizeEnumeratedFiles(stdout),
182
177
  interrupted: wasKilled,
183
178
  };
184
179
  }
@@ -186,6 +181,22 @@ function enumerateSourceFiles(scopePath: string, cwd: string): FileEnumeration |
186
181
  }
187
182
  }
188
183
 
184
+ function scopePathArgs(scopePath: string | readonly string[]): string[] {
185
+ const paths = Array.isArray(scopePath) ? scopePath : [scopePath];
186
+ return paths.length > 0 ? [...paths] : ["."];
187
+ }
188
+
189
+ function normalizeEnumeratedFiles(output: string): string[] {
190
+ return [
191
+ ...new Set(
192
+ output
193
+ .split("\n")
194
+ .map((line) => line.trim())
195
+ .filter((line) => line.length > 0),
196
+ ),
197
+ ].sort((a, b) => a.localeCompare(b));
198
+ }
199
+
189
200
  // biome-ignore lint/complexity/useMaxParams: helper takes explicit collection inputs to avoid intermediate objects in the hot path
190
201
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: kind-specific tree-sitter matching is clearest as one helper
191
202
  async function collectMatchesForFile(
@@ -47,6 +47,11 @@ export function isInProjectPath(filePath: string, cwd: string): boolean {
47
47
  /** Result of resolving a user-provided `scope` value to an absolute path. */
48
48
  export type ScopeResolution = { kind: "ok"; path: string } | { kind: "error"; reason: string };
49
49
 
50
+ /** Result of resolving a code_find `scope` value to one or more absolute paths. */
51
+ export type ScopeSetResolution =
52
+ | { kind: "ok"; paths: string[]; display: string | null }
53
+ | { kind: "error"; reason: string };
54
+
50
55
  /**
51
56
  * Resolve a `scope` value to an absolute path using pi scope semantics:
52
57
  * - strip leading `@`
@@ -61,6 +66,55 @@ export function resolveScope(scope: string | undefined, cwd: string): ScopeResol
61
66
  reason: `Scope accepts a single directory or file path, not multiple: \`${scope}\``,
62
67
  };
63
68
  }
69
+ return resolveSingleScope(scope, cwd);
70
+ }
71
+
72
+ /**
73
+ * Resolve a code_find scope input to one or more absolute paths.
74
+ *
75
+ * `code_find` is intentionally more flexible than other code-intelligence tools:
76
+ * it accepts `scope: ["docs", "packages"]` and also splits delimiter-separated
77
+ * strings such as `"docs packages"` only when no exact path by that name exists.
78
+ */
79
+ export function resolveScopeSet(
80
+ scope: string | readonly string[] | undefined,
81
+ cwd: string,
82
+ ): ScopeSetResolution {
83
+ if (scope === undefined) {
84
+ return { kind: "ok", paths: [cwd], display: null };
85
+ }
86
+
87
+ const labels = typeof scope === "string" ? splitScopeString(scope, cwd) : [...scope];
88
+ if (labels.length === 0) {
89
+ return { kind: "error", reason: "Scope must include at least one directory or file path." };
90
+ }
91
+
92
+ const resolved: Array<{ label: string; path: string }> = [];
93
+ for (const label of labels) {
94
+ const trimmed = label.trim();
95
+ if (!trimmed) {
96
+ return { kind: "error", reason: "Scope must not include empty path entries." };
97
+ }
98
+ const single = resolveSingleScope(trimmed, cwd);
99
+ if (single.kind === "error") return single;
100
+ resolved.push({ label: trimmed, path: single.path });
101
+ }
102
+
103
+ const byPath = new Map<string, string>();
104
+ for (const entry of resolved) {
105
+ if (!byPath.has(entry.path)) byPath.set(entry.path, entry.label);
106
+ }
107
+ const paths = [...byPath.keys()];
108
+ const uniqueLabels = paths.map((p) => byPath.get(p) ?? p);
109
+
110
+ return {
111
+ kind: "ok",
112
+ paths,
113
+ display: uniqueLabels.length > 0 ? uniqueLabels.join(", ") : null,
114
+ };
115
+ }
116
+
117
+ function resolveSingleScope(scope: string, cwd: string): ScopeResolution {
64
118
  const resolved = normalizePath(scope, cwd);
65
119
  if (!existsSync(resolved)) {
66
120
  return { kind: "error", reason: `Scope path not found: \`${scope}\`` };
@@ -68,6 +122,20 @@ export function resolveScope(scope: string | undefined, cwd: string): ScopeResol
68
122
  return { kind: "ok", path: resolved };
69
123
  }
70
124
 
125
+ function splitScopeString(scope: string, cwd: string): string[] {
126
+ const trimmed = scope.trim();
127
+ if (!trimmed) return [];
128
+
129
+ const exact = resolveSingleScope(trimmed, cwd);
130
+ if (exact.kind === "ok") return [trimmed];
131
+
132
+ const parts = trimmed
133
+ .split(/[,;\s]+/)
134
+ .map((part) => part.trim())
135
+ .filter((part) => part.length > 0);
136
+ return parts.length > 1 ? parts : [trimmed];
137
+ }
138
+
71
139
  /** Escape regex special characters. */
72
140
  export function escapeRegex(s: string): string {
73
141
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -152,7 +220,7 @@ export class RipgrepAbortedError extends Error {
152
220
  */
153
221
  export async function runRipgrep(
154
222
  pattern: string,
155
- scopePath: string,
223
+ scopePath: string | readonly string[],
156
224
  cwd: string,
157
225
  opts?: RipgrepOptions,
158
226
  ): Promise<RgMatch[]> {
@@ -166,7 +234,7 @@ export async function runRipgrep(
166
234
  */
167
235
  export async function runRipgrepDetailed(
168
236
  pattern: string,
169
- scopePath: string,
237
+ scopePath: string | readonly string[],
170
238
  cwd: string,
171
239
  opts?: RipgrepOptions,
172
240
  ): Promise<RipgrepRunResult> {
@@ -195,16 +263,16 @@ export async function runRipgrepDetailed(
195
263
  };
196
264
  case "ok":
197
265
  case "nomatch":
198
- return { matches: parseRgJson(proc.stdout, filter) };
266
+ return { matches: dedupeMatches(parseRgJson(proc.stdout, filter)) };
199
267
  case "timeout":
200
268
  // Timeout: yield any partial stdout matches with no error surfaced — an
201
269
  // improvement over the prior execFileSync path, which discarded partial
202
270
  // output on timeout (the timeout error lacked a `status`, so the old
203
271
  // handleRipgrepError returned an empty match set).
204
- return { matches: proc.stdout ? parseRgJson(proc.stdout, filter) : [] };
272
+ return { matches: proc.stdout ? dedupeMatches(parseRgJson(proc.stdout, filter)) : [] };
205
273
  case "error":
206
274
  return {
207
- matches: proc.stdout ? parseRgJson(proc.stdout, filter) : [],
275
+ matches: proc.stdout ? dedupeMatches(parseRgJson(proc.stdout, filter)) : [],
208
276
  ...(proc.stderr.trim() ? { error: proc.stderr.trim() } : {}),
209
277
  };
210
278
  }
@@ -212,7 +280,7 @@ export async function runRipgrepDetailed(
212
280
 
213
281
  function buildRipgrepArgs(
214
282
  pattern: string,
215
- scopePath: string,
283
+ scopePath: string | readonly string[],
216
284
  opts?: { maxMatches?: number; contextLines?: number; literal?: boolean },
217
285
  ): string[] {
218
286
  const args = ["--json", "-m", String(opts?.maxMatches ?? 30)];
@@ -222,10 +290,15 @@ function buildRipgrepArgs(
222
290
  if (opts?.literal) {
223
291
  args.push("-F");
224
292
  }
225
- args.push("-e", pattern, scopePath);
293
+ args.push("-e", pattern, ...normalizeScopePathArgs(scopePath));
226
294
  return args;
227
295
  }
228
296
 
297
+ function normalizeScopePathArgs(scopePath: string | readonly string[]): string[] {
298
+ const paths = Array.isArray(scopePath) ? scopePath : [scopePath];
299
+ return paths.length > 0 ? [...paths] : ["."];
300
+ }
301
+
229
302
  /** Discriminated result of a single ripgrep process run (see {@link runRgProcess}). */
230
303
  type RgProcessResult =
231
304
  | { kind: "ok"; stdout: string; stderr: string; status: number }
@@ -432,6 +505,18 @@ function parseRgJson(output: string, filterLowSignal: boolean): RgMatch[] {
432
505
  return matches;
433
506
  }
434
507
 
508
+ function dedupeMatches(matches: RgMatch[]): RgMatch[] {
509
+ const seen = new Set<string>();
510
+ const deduped: RgMatch[] = [];
511
+ for (const match of matches) {
512
+ const key = `${match.file}\u0000${match.line}\u0000${match.text}`;
513
+ if (seen.has(key)) continue;
514
+ seen.add(key);
515
+ deduped.push(match);
516
+ }
517
+ return deduped;
518
+ }
519
+
435
520
  /**
436
521
  * Minimal interface for a reference-like object with a URI and range start.
437
522
  * Used by {@link filterOutDeclaration} to avoid coupling to a specific LspRef type.
package/src/extension.ts CHANGED
@@ -38,7 +38,7 @@ export default function codeIntelligenceExtension(
38
38
  const tsState = createTsAdapterState();
39
39
 
40
40
  // ── Substrate wiring ──────────────────────────────────────────────
41
- registerLspSettings();
41
+ registerLspSettings(pi);
42
42
  registerLspSessionLifecycle(pi, lspState);
43
43
  registerLspAwareToolOverrides(pi, lspState);
44
44
  registerDiagnosticInjectionHandlers(pi, lspState);
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Capability adapter — injected dependency for WorkspaceCodeIntelligenceSession.
3
+ *
4
+ * Encapsulates provider access and readiness so the session target
5
+ * workflow can be tested with an in-memory adapter. Two adapters
6
+ * (production and test) justify the seam.
7
+ *
8
+ * @mrclrchtr/supi-code-intelligence — internal, not exported via api.ts
9
+ */
10
+
11
+ import type { SemanticProvider, StructuralProvider } from "@mrclrchtr/supi-code-runtime/api";
12
+ import { getDefaultWorkspaceRuntime } from "@mrclrchtr/supi-code-runtime/api";
13
+ import type { SessionLspServiceState } from "@mrclrchtr/supi-lsp/api";
14
+ import {
15
+ type CodeProvider,
16
+ type CodeProviderState,
17
+ getCodeProviderState,
18
+ } from "../analysis/provider.ts";
19
+ import { ensureSemanticReadiness } from "../analysis/readiness.ts";
20
+
21
+ // ── Interface ─────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Readiness scope for semantic provider gating.
25
+ * `workspace` waits for all servers; `file` waits for a specific file's server.
26
+ */
27
+ export type ReadinessScope = { kind: "workspace" } | { kind: "file"; file: string };
28
+
29
+ /** Outcome of a readiness wait. */
30
+ export type ReadinessOutcome =
31
+ | { kind: "ready" }
32
+ | { kind: "timeout" }
33
+ | { kind: "unavailable"; reason: string };
34
+
35
+ /**
36
+ * Injected capability adapter.
37
+ *
38
+ * Production adapter reads the global workspace runtime. Test adapter
39
+ * provides semantic/structural providers in memory without global state.
40
+ */
41
+ export interface CapabilityAdapter {
42
+ /** Get the composite code provider state for a workspace. */
43
+ getProviderState(cwd: string): CodeProviderState;
44
+
45
+ /** Convenience: get the ready composite provider, or null. */
46
+ getProvider(cwd: string): CodeProvider | null;
47
+
48
+ /** Get the raw semantic provider, or null. */
49
+ getSemanticProvider(cwd: string): SemanticProvider | null;
50
+
51
+ /** Get the raw structural provider, or null. */
52
+ getStructuralProvider(cwd: string): StructuralProvider | null;
53
+
54
+ /**
55
+ * Wait for semantic (LSP) readiness. Returns a typed outcome so
56
+ * the target workflow can decide whether to proceed, return a
57
+ * timeout note, or return an unavailable result.
58
+ */
59
+ ensureSemanticReadiness(cwd: string, scope: ReadinessScope): Promise<ReadinessOutcome>;
60
+ }
61
+
62
+ // ── Production adapter ────────────────────────────────────────────────
63
+
64
+ /**
65
+ * Production capability adapter that reads from the global workspace
66
+ * runtime. Used in the real pi session.
67
+ */
68
+ export class WorkspaceCapabilityAdapter implements CapabilityAdapter {
69
+ getProviderState(cwd: string): CodeProviderState {
70
+ return getCodeProviderState(cwd);
71
+ }
72
+
73
+ getProvider(cwd: string): CodeProvider | null {
74
+ const state = this.getProviderState(cwd);
75
+ return state.kind === "ready" ? state.provider : null;
76
+ }
77
+
78
+ getSemanticProvider(cwd: string): SemanticProvider | null {
79
+ const runtime = getDefaultWorkspaceRuntime();
80
+ const ws = runtime.getWorkspace(cwd);
81
+ if (
82
+ (ws.semantic.state.kind === "ready" || ws.semantic.state.kind === "pending") &&
83
+ ws.semantic.provider !== null
84
+ ) {
85
+ return ws.semantic.provider as SemanticProvider;
86
+ }
87
+ return null;
88
+ }
89
+
90
+ getStructuralProvider(cwd: string): StructuralProvider | null {
91
+ const runtime = getDefaultWorkspaceRuntime();
92
+ const ws = runtime.getWorkspace(cwd);
93
+ if (ws.structural.state.kind === "ready" && ws.structural.provider !== null) {
94
+ return ws.structural.provider as StructuralProvider;
95
+ }
96
+ return null;
97
+ }
98
+
99
+ async ensureSemanticReadiness(cwd: string, scope: ReadinessScope): Promise<ReadinessOutcome> {
100
+ const result = await ensureSemanticReadiness(cwd, scope);
101
+ return result;
102
+ }
103
+
104
+ /** Convenience: LSP service state for impact/health consumers still using it directly. */
105
+ getLspService(cwd: string): SessionLspServiceState {
106
+ const state = this.getProviderState(cwd);
107
+ return state.kind === "ready"
108
+ ? state.lspService
109
+ : { kind: "unavailable" as const, reason: "No provider" };
110
+ }
111
+ }
112
+
113
+ // ── Test adapter ──────────────────────────────────────────────────────
114
+
115
+ /**
116
+ * In-memory capability adapter for tests.
117
+ *
118
+ * Pass explicit providers and readiness outcomes without touching
119
+ * the global workspace runtime.
120
+ */
121
+ export class TestCapabilityAdapter implements CapabilityAdapter {
122
+ readonly #semantic: SemanticProvider | null;
123
+ readonly #structural: StructuralProvider | null;
124
+ readonly #readiness: ReadinessOutcome;
125
+ readonly #lspService: SessionLspServiceState;
126
+
127
+ constructor(options: {
128
+ semantic?: SemanticProvider | null;
129
+ structural?: StructuralProvider | null;
130
+ readiness?: ReadinessOutcome;
131
+ lspService?: SessionLspServiceState;
132
+ }) {
133
+ this.#semantic = options.semantic ?? null;
134
+ this.#structural = options.structural ?? null;
135
+ this.#readiness = options.readiness ?? { kind: "ready" };
136
+ this.#lspService = options.lspService ?? {
137
+ kind: "unavailable" as const,
138
+ reason: "no test LSP",
139
+ };
140
+ }
141
+
142
+ getProviderState(_cwd: string): CodeProviderState {
143
+ if (!this.#semantic && !this.#structural) {
144
+ return { kind: "unavailable", reason: "No providers in test adapter" };
145
+ }
146
+ return {
147
+ kind: "ready",
148
+ provider: this.getProvider(_cwd) as CodeProvider,
149
+ lspService: this.#lspService,
150
+ };
151
+ }
152
+
153
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: adapter maps many provider methods; splitting would degrade readability
154
+ getProvider(_cwd: string): CodeProvider | null {
155
+ if (!this.#semantic && !this.#structural) return null;
156
+ return {
157
+ // Semantic pass-through
158
+ references: this.#semantic?.references
159
+ ? (...args: Parameters<SemanticProvider["references"]>) =>
160
+ this.#semantic?.references?.(...args)
161
+ : async () => null,
162
+ implementation: this.#semantic?.implementation
163
+ ? (...args) => this.#semantic?.implementation?.(...args)
164
+ : async () => null,
165
+ documentSymbols: this.#semantic?.documentSymbols
166
+ ? (...args) => this.#semantic?.documentSymbols?.(...args)
167
+ : async () => [],
168
+ workspaceSymbols: this.#semantic?.workspaceSymbols
169
+ ? (...args) => this.#semantic?.workspaceSymbols?.(...args)
170
+ : async () => [],
171
+ hover: this.#semantic?.hover ? (...args) => this.#semantic?.hover?.(...args) : undefined,
172
+ definition: this.#semantic?.definition
173
+ ? (...args) => this.#semantic?.definition?.(...args)
174
+ : undefined,
175
+ codeActions: this.#semantic?.codeActions
176
+ ? (...args) => this.#semantic?.codeActions?.(...args)
177
+ : undefined,
178
+ codeActionTitles: this.#semantic?.codeActionTitles
179
+ ? (...args) => this.#semantic?.codeActionTitles?.(...args)
180
+ : undefined,
181
+ rename: this.#semantic?.rename ? (...args) => this.#semantic?.rename?.(...args) : undefined,
182
+ refactor: this.#semantic?.refactor
183
+ ? (...args) => this.#semantic?.refactor?.(...args)
184
+ : undefined,
185
+ // Structural pass-through
186
+ calleesAt: this.#structural?.calleesAt
187
+ ? (...args) => this.#structural?.calleesAt?.(...args)
188
+ : async () => ({ kind: "unavailable" as const, message: "no test structural" }),
189
+ exports: this.#structural?.exports
190
+ ? (...args) => this.#structural?.exports?.(...args)
191
+ : async () => ({ kind: "unavailable" as const, message: "no test structural" }),
192
+ outline: this.#structural?.outline
193
+ ? (...args) => this.#structural?.outline?.(...args)
194
+ : async () => ({ kind: "unavailable" as const, message: "no test structural" }),
195
+ imports: this.#structural?.imports
196
+ ? (...args) => this.#structural?.imports?.(...args)
197
+ : async () => ({ kind: "unavailable" as const, message: "no test structural" }),
198
+ nodeAt: this.#structural?.nodeAt
199
+ ? (...args) => this.#structural?.nodeAt?.(...args)
200
+ : async () => ({ kind: "unavailable" as const, message: "no test structural" }),
201
+ callSites: this.#structural?.callSites
202
+ ? (...args) => this.#structural?.callSites?.(...args)
203
+ : async () => ({ kind: "unavailable" as const, message: "no test structural" }),
204
+ } as CodeProvider;
205
+ }
206
+
207
+ getSemanticProvider(_cwd: string): SemanticProvider | null {
208
+ return this.#semantic;
209
+ }
210
+
211
+ getStructuralProvider(_cwd: string): StructuralProvider | null {
212
+ return this.#structural;
213
+ }
214
+
215
+ async ensureSemanticReadiness(_cwd: string, _scope: ReadinessScope): Promise<ReadinessOutcome> {
216
+ return this.#readiness;
217
+ }
218
+ }
@@ -24,6 +24,7 @@ import type { LspRuntimeController } from "@mrclrchtr/supi-lsp/api";
24
24
  import { CoverageWarningState } from "../analysis/coverage/coverage-warnings.ts";
25
25
  import type { CodeProvider } from "../analysis/provider.ts";
26
26
  import { getCodeProvider } from "../analysis/provider.ts";
27
+ import { type CapabilityAdapter, WorkspaceCapabilityAdapter } from "./capability-adapter.ts";
27
28
  import { getPlan, type RefactorPlan, removePlan, storePlan } from "./refactor-plans.ts";
28
29
  import {
29
30
  getWorkflowTarget,
@@ -33,11 +34,18 @@ import {
33
34
  type TargetRegistrationOutput,
34
35
  type TargetStoreEntry,
35
36
  } from "./target-store.ts";
37
+ import {
38
+ resolveTargetWorkflow,
39
+ type TargetWorkflowInput,
40
+ type TargetWorkflowOutcome,
41
+ type TargetWorkflowPolicy,
42
+ } from "./target-workflow.ts";
36
43
 
37
44
  // ── Re-export types consumed by callers ───────────────────────────────
38
45
 
39
46
  export type { CoverageWarningState } from "../analysis/coverage/coverage-warnings.ts";
40
47
  export type { CodeProvider, CodeProviderState } from "../analysis/provider.ts";
48
+ export type { CapabilityAdapter } from "./capability-adapter.ts";
41
49
  export type { RefactorPlan } from "./refactor-plans.ts";
42
50
  export type {
43
51
  TargetLookupResult,
@@ -45,6 +53,11 @@ export type {
45
53
  TargetRegistrationOutput,
46
54
  TargetStoreEntry,
47
55
  } from "./target-store.ts";
56
+ export type {
57
+ TargetWorkflowInput,
58
+ TargetWorkflowOutcome,
59
+ TargetWorkflowPolicy,
60
+ } from "./target-workflow.ts";
48
61
 
49
62
  // ── Target ID expansion result ────────────────────────────────────────
50
63
 
@@ -74,6 +87,9 @@ export class WorkspaceCodeIntelligenceSession {
74
87
  /** Canonical workspace directory for this session. */
75
88
  readonly cwd: string;
76
89
 
90
+ /** Injected capability adapter — two adapters (production/test) justify the seam. */
91
+ readonly #capability: CapabilityAdapter;
92
+
77
93
  /** Whether the hidden architecture overview has been injected. */
78
94
  hasInjectedOverview = false;
79
95
 
@@ -100,8 +116,30 @@ export class WorkspaceCodeIntelligenceSession {
100
116
  */
101
117
  tsController: import("@mrclrchtr/supi-tree-sitter/api").TreeSitterRuntimeController | null = null;
102
118
 
103
- constructor(cwd: string) {
119
+ constructor(cwd: string, capability?: CapabilityAdapter) {
104
120
  this.cwd = cwd;
121
+ this.#capability = capability ?? new WorkspaceCapabilityAdapter();
122
+ }
123
+
124
+ // ── Target workflow (deep seam) ───────────────────────────────────
125
+
126
+ /**
127
+ * Resolve target input into an immutable resolved target.
128
+ *
129
+ * This is the deep module interface. Tool executors call this instead
130
+ * of `expandTargetId` or direct `resolveTarget`/`resolveAnchoredSymbolTarget`
131
+ * calls. Returns typed outcomes — no markdown, no mutated params.
132
+ */
133
+ async resolveTarget(
134
+ input: TargetWorkflowInput,
135
+ policy: TargetWorkflowPolicy,
136
+ ): Promise<TargetWorkflowOutcome> {
137
+ return resolveTargetWorkflow(input, policy, {
138
+ cwd: this.cwd,
139
+ capability: this.#capability,
140
+ lookupTargetId: (id) => this.lookupTargetId(id),
141
+ registerTarget: (inp) => this.registerTarget(inp),
142
+ });
105
143
  }
106
144
 
107
145
  // ── Provider access ───────────────────────────────────────────────