@mrclrchtr/supi-code-intelligence 2.0.6 → 2.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 (169) hide show
  1. package/README.md +88 -4
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +32 -23
  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.ts +22 -8
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  12. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +55 -27
  13. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  14. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  15. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +72 -285
  16. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  17. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +27 -5
  18. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  19. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  20. package/node_modules/@mrclrchtr/supi-core/README.md +32 -23
  21. package/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  22. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  23. package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  24. package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  25. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  26. package/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  27. package/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  28. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  29. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  30. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +55 -27
  31. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  32. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  33. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +72 -285
  34. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  35. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +27 -5
  36. package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +32 -23
  38. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  39. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  40. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  42. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  43. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  46. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  47. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +55 -27
  48. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  50. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +72 -285
  51. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  52. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +27 -5
  53. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  54. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  55. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +32 -23
  56. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  57. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  58. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  59. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  60. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  61. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  62. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  63. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  64. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  65. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +55 -27
  66. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  67. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  68. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +72 -285
  69. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  70. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings.ts +27 -5
  71. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  72. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  73. package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +1 -1
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +32 -23
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  77. 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
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  83. 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
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +55 -27
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +72 -285
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +27 -5
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +32 -23
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -1
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +22 -8
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +357 -0
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +1 -1
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +55 -27
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +72 -285
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings.ts +27 -5
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +10 -0
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  110. package/package.json +5 -5
  111. package/src/analysis/evidence.ts +13 -0
  112. package/src/analysis/instruction-files.ts +194 -0
  113. package/src/analysis/search/pattern.ts +24 -13
  114. package/src/analysis/search/ripgrep.ts +92 -7
  115. package/src/app/app.ts +6 -1
  116. package/src/config.ts +42 -0
  117. package/src/extension.ts +9 -1
  118. package/src/session/capability-adapter.ts +218 -0
  119. package/src/session/session.ts +106 -1
  120. package/src/session/target-workflow.ts +430 -0
  121. package/src/substrate/lsp/diagnostic-injection.ts +0 -15
  122. package/src/substrate/lsp/settings.ts +101 -175
  123. package/src/tool/find/execute.ts +23 -10
  124. package/src/tool/find/orchestrate.ts +67 -54
  125. package/src/tool/find/semantic-search.ts +30 -16
  126. package/src/tool/graph/collect-relation.ts +2 -1
  127. package/src/tool/graph/execute.ts +40 -12
  128. package/src/tool/graph/markdown-base.ts +5 -37
  129. package/src/tool/graph/orchestrate.ts +9 -35
  130. package/src/tool/graph/tui.ts +2 -0
  131. package/src/tool/guidance.ts +27 -38
  132. package/src/tool/health/markdown.ts +12 -70
  133. package/src/tool/health/orchestrate.ts +5 -13
  134. package/src/tool/impact/analysis.ts +0 -27
  135. package/src/tool/impact/change-set-impact.ts +3 -2
  136. package/src/tool/impact/execute.ts +27 -0
  137. package/src/tool/impact/file-level-impact.ts +3 -2
  138. package/src/tool/impact/result.ts +2 -21
  139. package/src/tool/impact/single-impact.ts +3 -2
  140. package/src/tool/infra/error-results.ts +18 -196
  141. package/src/tool/infra/pipeline.ts +24 -1
  142. package/src/tool/inspect/markdown.ts +4 -41
  143. package/src/tool/inspect/orchestrate.ts +27 -28
  144. package/src/tool/orientation/mode.ts +53 -2
  145. package/src/tool/orientation/orchestrate.ts +5 -8
  146. package/src/tool/orientation/precise-target.ts +65 -69
  147. package/src/tool/refactor-apply/orchestrate.ts +2 -7
  148. package/src/tool/refactor-plan/orchestrate.ts +31 -39
  149. package/src/tool/resolve/markdown.ts +5 -6
  150. package/src/tool/resolve/orchestrate.ts +7 -103
  151. package/src/tool/result/errors.ts +199 -0
  152. package/src/tool/result/find.ts +29 -0
  153. package/src/tool/result/graph.ts +93 -0
  154. package/src/tool/result/health.ts +94 -0
  155. package/src/tool/result/impact.ts +50 -0
  156. package/src/tool/result/inspect.ts +60 -0
  157. package/src/tool/result/orientation.ts +30 -0
  158. package/src/tool/result/refactor.ts +35 -0
  159. package/src/tool/result/resolve.ts +114 -0
  160. package/src/tool/result/types.ts +191 -0
  161. package/src/tool/schemas.ts +39 -28
  162. package/src/types/details.ts +17 -185
  163. package/src/ui/tui/common.ts +45 -1
  164. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -188
  165. package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -188
  166. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -188
  167. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -188
  168. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -188
  169. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -188
@@ -0,0 +1,194 @@
1
+ // Directory-local instruction file discovery and rendering for code_orientation.
2
+
3
+ import { readFileSync, statSync } from "node:fs";
4
+ import * as path from "node:path";
5
+ import { isWithinOrEqual } from "@mrclrchtr/supi-core/project";
6
+
7
+ export const INSTRUCTION_FILE_LINE_LIMIT = 200;
8
+
9
+ /** Directory-local instruction file selected for orientation output. */
10
+ export interface InstructionFileMatch {
11
+ /** Absolute file path. */
12
+ absolutePath: string;
13
+ /** Workspace-relative file path. */
14
+ relativePath: string;
15
+ /** Absolute containing directory. */
16
+ directory: string;
17
+ /** Workspace-relative containing directory. */
18
+ relativeDirectory: string;
19
+ }
20
+
21
+ /** Agent-visible instruction file snippet plus metadata for details. */
22
+ export interface RenderedInstructionFile {
23
+ path: string;
24
+ directory: string;
25
+ shownLines: number;
26
+ totalLines: number;
27
+ truncated: boolean;
28
+ content: string;
29
+ }
30
+
31
+ /** Structured metadata stored in code_orientation details. */
32
+ export interface InstructionFilesMetadata {
33
+ files: Array<Omit<RenderedInstructionFile, "content">>;
34
+ }
35
+
36
+ /**
37
+ * Find configured instruction files for a directory focus.
38
+ *
39
+ * Walks from cwd to the focused directory (shallowest first, deepest last),
40
+ * selecting the first configured file name that exists in each directory.
41
+ */
42
+ export function findInstructionFilesForDirectory(options: {
43
+ directory: string;
44
+ cwd: string;
45
+ fileNames: string[];
46
+ nativeContextPaths: Set<string>;
47
+ surfacedDirectories: Set<string>;
48
+ }): InstructionFileMatch[] {
49
+ const cwd = path.resolve(options.cwd);
50
+ const directory = path.resolve(options.directory);
51
+ const fileNames = options.fileNames.filter((name) => name.trim().length > 0);
52
+ if (fileNames.length === 0) return [];
53
+ if (!isDirectoryWithinCwd(directory, cwd)) return [];
54
+
55
+ const dirs = collectDirsShallowestFirst(directory, cwd);
56
+ const matches: InstructionFileMatch[] = [];
57
+
58
+ for (const dir of dirs) {
59
+ if (options.surfacedDirectories.has(dir)) continue;
60
+ const match = findFirstInstructionFile(dir, cwd, fileNames);
61
+ if (!match) continue;
62
+ if (options.nativeContextPaths.has(match.absolutePath)) continue;
63
+ matches.push(match);
64
+ }
65
+
66
+ return matches;
67
+ }
68
+
69
+ function isDirectoryWithinCwd(directory: string, cwd: string): boolean {
70
+ try {
71
+ return statSync(directory).isDirectory() && isWithinOrEqual(cwd, directory);
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ function collectDirsShallowestFirst(directory: string, cwd: string): string[] {
78
+ const dirs: string[] = [];
79
+ let current = directory;
80
+
81
+ while (isWithinOrEqual(cwd, current)) {
82
+ dirs.push(current);
83
+ if (current === cwd) break;
84
+ const parent = path.dirname(current);
85
+ if (parent === current) break;
86
+ current = parent;
87
+ }
88
+
89
+ return dirs.reverse();
90
+ }
91
+
92
+ function findFirstInstructionFile(
93
+ dir: string,
94
+ cwd: string,
95
+ fileNames: string[],
96
+ ): InstructionFileMatch | null {
97
+ for (const fileName of fileNames) {
98
+ const candidate = path.join(dir, fileName);
99
+ if (!isFile(candidate)) continue;
100
+ return {
101
+ absolutePath: candidate,
102
+ relativePath: path.relative(cwd, candidate),
103
+ directory: dir,
104
+ relativeDirectory: path.relative(cwd, dir) || ".",
105
+ };
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /** Render instruction files as a markdown section and structured metadata. */
111
+ export function renderInstructionFiles(
112
+ files: InstructionFileMatch[],
113
+ lineLimit = INSTRUCTION_FILE_LINE_LIMIT,
114
+ ): { markdown: string; metadata: InstructionFilesMetadata } | null {
115
+ const rendered = files.map((file) => renderInstructionFile(file, lineLimit)).filter(isRendered);
116
+ if (rendered.length === 0) return null;
117
+
118
+ const lines: string[] = [];
119
+ lines.push("## Instructions");
120
+ lines.push("");
121
+ for (const file of rendered) {
122
+ lines.push(`### ${file.path}`);
123
+ lines.push("");
124
+ lines.push(file.content);
125
+ if (file.truncated) {
126
+ lines.push("");
127
+ lines.push(
128
+ `_Instruction file truncated to ${file.shownLines} of ${file.totalLines} lines. Use \`read\` on \`${file.path}\` for the full file._`,
129
+ );
130
+ }
131
+ lines.push("");
132
+ }
133
+
134
+ return {
135
+ markdown: lines.join("\n").trimEnd(),
136
+ metadata: {
137
+ files: rendered.map(({ content: _content, ...metadata }) => metadata),
138
+ },
139
+ };
140
+ }
141
+
142
+ function isFile(candidate: string): boolean {
143
+ try {
144
+ return statSync(candidate).isFile();
145
+ } catch {
146
+ return false;
147
+ }
148
+ }
149
+
150
+ function renderInstructionFile(
151
+ file: InstructionFileMatch,
152
+ lineLimit: number,
153
+ ): RenderedInstructionFile | null {
154
+ try {
155
+ const content = readFileSync(file.absolutePath, "utf-8").trim();
156
+ if (!content) return null;
157
+
158
+ const lines = content.split("\n");
159
+ const shown = lines.slice(0, lineLimit);
160
+ return {
161
+ path: file.relativePath,
162
+ directory: file.relativeDirectory,
163
+ shownLines: shown.length,
164
+ totalLines: lines.length,
165
+ truncated: lines.length > shown.length,
166
+ content: shown.join("\n"),
167
+ };
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ function isRendered(value: RenderedInstructionFile | null): value is RenderedInstructionFile {
174
+ return value !== null;
175
+ }
176
+
177
+ /** Insert instruction markdown after the opening title/summary block of a brief. */
178
+ export function insertInstructionsNearTop(content: string, instructionsMarkdown: string): string {
179
+ const lines = content.split("\n");
180
+ const insertAt = findFirstSectionIndexAfterTitle(lines);
181
+ const before = lines.slice(0, insertAt).join("\n").trimEnd();
182
+ const after = lines.slice(insertAt).join("\n").trimStart();
183
+
184
+ if (!before) return `${instructionsMarkdown}\n\n${after}`.trimEnd();
185
+ if (!after) return `${before}\n\n${instructionsMarkdown}`.trimEnd();
186
+ return `${before}\n\n${instructionsMarkdown}\n\n${after}`.trimEnd();
187
+ }
188
+
189
+ function findFirstSectionIndexAfterTitle(lines: string[]): number {
190
+ for (let i = 1; i < lines.length; i++) {
191
+ if (lines[i].startsWith("## ")) return i;
192
+ }
193
+ return lines.length;
194
+ }
@@ -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/app/app.ts CHANGED
@@ -77,8 +77,9 @@ export function createCodeIntelligenceApp(pi: ExtensionAPI): CodeIntelligenceApp
77
77
  pi.on("session_start", (_event, ctx) => {
78
78
  const session = manager.getOrCreateSession(ctx.cwd);
79
79
 
80
- // Check if the branch already contains an overview
80
+ // Check if the branch already contains an overview and restore branch-local instruction state.
81
81
  const branch = ctx.sessionManager.getBranch();
82
+ session.reconstructInstructionState(branch);
82
83
  for (const entry of branch) {
83
84
  if (
84
85
  entry.type === "custom_message" &&
@@ -90,6 +91,10 @@ export function createCodeIntelligenceApp(pi: ExtensionAPI): CodeIntelligenceApp
90
91
  }
91
92
  });
92
93
 
94
+ pi.on("session_compact", (_event, ctx) => {
95
+ manager.getSession(ctx.cwd)?.resetSurfacedInstructionDirs();
96
+ });
97
+
93
98
  pi.on("session_shutdown", () => {
94
99
  // Clear per-session stores before releasing sessions
95
100
  for (const session of manager.allSessions()) {
package/src/config.ts ADDED
@@ -0,0 +1,42 @@
1
+ // Code Intelligence configuration and settings registration.
2
+
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ import { loadSupiConfig } from "@mrclrchtr/supi-core/config";
5
+ import { registerDeclarativeSettings } from "@mrclrchtr/supi-core/settings";
6
+
7
+ const CODE_INTELLIGENCE_SECTION = "code-intelligence";
8
+
9
+ /** User-facing configuration for the code-intelligence extension. */
10
+ export interface CodeIntelligenceConfig extends Record<string, unknown> {
11
+ /** Ordered directory-local instruction file names to surface during directory orientation. */
12
+ instructionFileNames: string[];
13
+ }
14
+
15
+ /** Default code-intelligence configuration. */
16
+ export const CODE_INTELLIGENCE_DEFAULTS: CodeIntelligenceConfig = {
17
+ instructionFileNames: ["CLAUDE.md", "AGENTS.md"],
18
+ };
19
+
20
+ /** Load merged code-intelligence configuration for a workspace. */
21
+ export function loadCodeIntelligenceConfig(cwd: string, homeDir?: string): CodeIntelligenceConfig {
22
+ return loadSupiConfig(CODE_INTELLIGENCE_SECTION, cwd, CODE_INTELLIGENCE_DEFAULTS, { homeDir });
23
+ }
24
+
25
+ /** Register code-intelligence settings with the shared SuPi settings registry. */
26
+ export function registerCodeIntelligenceSettings(pi: ExtensionAPI, homeDir?: string): void {
27
+ registerDeclarativeSettings(pi, {
28
+ id: CODE_INTELLIGENCE_SECTION,
29
+ label: "Code Intelligence",
30
+ section: CODE_INTELLIGENCE_SECTION,
31
+ defaults: CODE_INTELLIGENCE_DEFAULTS,
32
+ fields: [
33
+ {
34
+ kind: "stringList" as const,
35
+ key: "instructionFileNames",
36
+ label: "Instruction File Names",
37
+ description: "Directory-local instruction file names shown by directory orientation",
38
+ },
39
+ ],
40
+ ...(homeDir ? { homeDir } : {}),
41
+ });
42
+ }
package/src/extension.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  gatherCoverageEvalInput,
9
9
  } from "./analysis/coverage/coverage-warnings.ts";
10
10
  import { createCodeIntelligenceApp } from "./app/app.ts";
11
+ import { registerCodeIntelligenceSettings } from "./config.ts";
11
12
  import type { WorkspaceCodeIntelligenceSession } from "./session/session.ts";
12
13
  import { registerDiagnosticInjectionHandlers } from "./substrate/lsp/diagnostic-injection.ts";
13
14
  import { registerLspSessionLifecycle } from "./substrate/lsp/lifecycle.ts";
@@ -38,7 +39,8 @@ export default function codeIntelligenceExtension(
38
39
  const tsState = createTsAdapterState();
39
40
 
40
41
  // ── Substrate wiring ──────────────────────────────────────────────
41
- registerLspSettings();
42
+ registerCodeIntelligenceSettings(pi);
43
+ registerLspSettings(pi);
42
44
  registerLspSessionLifecycle(pi, lspState);
43
45
  registerLspAwareToolOverrides(pi, lspState);
44
46
  registerDiagnosticInjectionHandlers(pi, lspState);
@@ -64,6 +66,12 @@ export default function codeIntelligenceExtension(
64
66
  registerCiStatusCommand(pi);
65
67
  registerLspFooterContribution(lspState);
66
68
 
69
+ // ── Native context path capture for instruction-file dedup ─────────
70
+ pi.on("before_agent_start", (event, ctx) => {
71
+ const session = app.getSession(ctx.cwd) ?? app.createSession(ctx.cwd);
72
+ session.captureNativeInstructionPaths(event.systemPromptOptions.contextFiles ?? []);
73
+ });
74
+
67
75
  // ── Coverage warning emission ─────────────────────────────────────
68
76
  pi.on(
69
77
  "before_agent_start",