@mrclrchtr/supi-code-intelligence 2.1.0 → 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 (97) hide show
  1. package/README.md +17 -3
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +9 -8
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +13 -7
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +50 -309
  12. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  13. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +17 -1
  14. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  15. package/node_modules/@mrclrchtr/supi-core/README.md +9 -8
  16. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  17. package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  18. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  19. package/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  20. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  21. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +13 -7
  22. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  23. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  24. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +50 -309
  25. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  26. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +17 -1
  27. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +9 -8
  28. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  29. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  30. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  31. 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
  32. 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
  33. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +13 -7
  34. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  35. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +50 -309
  37. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  38. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +17 -1
  39. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  40. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +9 -8
  41. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  42. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  43. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  44. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  46. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +13 -7
  47. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +50 -309
  50. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  51. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings.ts +17 -1
  52. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  53. package/node_modules/@mrclrchtr/supi-lsp/src/config/lsp-settings.ts +1 -1
  54. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/README.md +9 -8
  55. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  56. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  57. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  58. 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
  59. 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
  60. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +13 -7
  61. 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
  62. 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
  63. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +50 -309
  64. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  65. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings.ts +17 -1
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +2 -2
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +9 -8
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -3
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +0 -5
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/scoped-settings-list.ts +372 -0
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-action-menu.ts +101 -0
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +13 -7
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +464 -0
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-submenus.ts +124 -0
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +50 -309
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +2 -5
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings.ts +17 -1
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +3 -3
  80. package/package.json +5 -5
  81. package/src/analysis/instruction-files.ts +194 -0
  82. package/src/app/app.ts +6 -1
  83. package/src/config.ts +42 -0
  84. package/src/extension.ts +8 -0
  85. package/src/session/session.ts +67 -0
  86. package/src/substrate/lsp/diagnostic-injection.ts +0 -15
  87. package/src/substrate/lsp/settings.ts +101 -179
  88. package/src/tool/guidance.ts +27 -38
  89. package/src/tool/orientation/mode.ts +53 -2
  90. package/src/tool/result/types.ts +3 -0
  91. package/src/tool/schemas.ts +22 -32
  92. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -207
  93. package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -207
  94. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -207
  95. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -207
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -207
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +0 -207
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,6 +39,7 @@ export default function codeIntelligenceExtension(
38
39
  const tsState = createTsAdapterState();
39
40
 
40
41
  // ── Substrate wiring ──────────────────────────────────────────────
42
+ registerCodeIntelligenceSettings(pi);
41
43
  registerLspSettings(pi);
42
44
  registerLspSessionLifecycle(pi, lspState);
43
45
  registerLspAwareToolOverrides(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",
@@ -15,6 +15,8 @@
15
15
  * @mrclrchtr/supi-code-intelligence — internal, not exported via api.ts
16
16
  */
17
17
 
18
+ import * as path from "node:path";
19
+ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
18
20
  import {
19
21
  getDefaultWorkspaceRuntime,
20
22
  type SemanticProvider,
@@ -104,6 +106,12 @@ export class WorkspaceCodeIntelligenceSession {
104
106
  */
105
107
  readonly coverageWarningState = new CoverageWarningState();
106
108
 
109
+ /** Absolute instruction files already loaded by PI's native context-file mechanism. */
110
+ readonly nativeInstructionPaths = new Set<string>();
111
+
112
+ /** Absolute directories whose instruction files were already surfaced by code_orientation. */
113
+ readonly surfacedInstructionDirs = new Set<string>();
114
+
107
115
  /**
108
116
  * Optional LSP controller reference — attached by LSP lifecycle module
109
117
  * for coverage evaluation and server management.
@@ -142,6 +150,38 @@ export class WorkspaceCodeIntelligenceSession {
142
150
  });
143
151
  }
144
152
 
153
+ // ── Instruction-file state ───────────────────────────────────────
154
+
155
+ /** Remember instruction/context file paths already loaded by PI natively. */
156
+ captureNativeInstructionPaths(files: Array<{ path: string }>): void {
157
+ for (const file of files) {
158
+ this.nativeInstructionPaths.add(path.resolve(this.cwd, file.path));
159
+ }
160
+ }
161
+
162
+ /** Mark directory-local instruction files as surfaced after a successful orientation render. */
163
+ markInstructionDirsSurfaced(directories: string[]): void {
164
+ for (const directory of directories) {
165
+ this.surfacedInstructionDirs.add(path.resolve(this.cwd, directory));
166
+ }
167
+ }
168
+
169
+ /** Clear instruction-file dedup state after compaction. */
170
+ resetSurfacedInstructionDirs(): void {
171
+ this.surfacedInstructionDirs.clear();
172
+ }
173
+
174
+ /** Reconstruct surfaced instruction directories from branch tool-result details after compaction. */
175
+ reconstructInstructionState(branch: SessionEntry[]): void {
176
+ this.surfacedInstructionDirs.clear();
177
+ const entries = entriesAfterLatestCompaction(branch);
178
+ for (const entry of entries) {
179
+ for (const directory of extractInstructionDirectories(entry)) {
180
+ this.surfacedInstructionDirs.add(path.resolve(this.cwd, directory));
181
+ }
182
+ }
183
+ }
184
+
145
185
  // ── Provider access ───────────────────────────────────────────────
146
186
 
147
187
  /**
@@ -290,7 +330,34 @@ export class WorkspaceCodeIntelligenceSession {
290
330
  clearStores(): void {
291
331
  this.#refactorPlans.clear();
292
332
  this.#workflowTargets.clear();
333
+ this.surfacedInstructionDirs.clear();
334
+ this.nativeInstructionPaths.clear();
335
+ }
336
+ }
337
+
338
+ function entriesAfterLatestCompaction(branch: SessionEntry[]): SessionEntry[] {
339
+ let start = 0;
340
+ for (let i = 0; i < branch.length; i++) {
341
+ if (branch[i]?.type === "compaction") start = i + 1;
293
342
  }
343
+ return branch.slice(start);
344
+ }
345
+
346
+ function extractInstructionDirectories(entry: SessionEntry): string[] {
347
+ if (entry.type !== "message" || entry.message.role !== "toolResult") return [];
348
+ if (entry.message.toolName !== "code_orientation") return [];
349
+
350
+ const details = entry.message.details as
351
+ | { type?: string; data?: { instructions?: { files?: Array<{ directory?: unknown }> } } }
352
+ | undefined;
353
+ if (details?.type !== "context") return [];
354
+
355
+ const files = details.data?.instructions?.files;
356
+ if (!Array.isArray(files)) return [];
357
+
358
+ return files
359
+ .map((file) => file.directory)
360
+ .filter((directory): directory is string => typeof directory === "string");
294
361
  }
295
362
 
296
363
  // ── Standalone target-id expansion helper ─────────────────────────────
@@ -12,9 +12,6 @@ import {
12
12
  import type { LspAdapterState } from "./state.ts";
13
13
  import { diagnosticMessageString } from "./utils.ts";
14
14
 
15
- // LSP_TOOL_NAMES removed — lsp_* tools are no longer registered (TNDM-A9AQF4)
16
- const LSP_TOOL_NAMES: readonly string[] = [];
17
-
18
15
  export function registerDiagnosticInjectionHandlers(
19
16
  pi: ExtensionAPI,
20
17
  state: LspAdapterState,
@@ -22,21 +19,9 @@ export function registerDiagnosticInjectionHandlers(
22
19
  pi.on("before_agent_start", async (_event, _ctx: ExtensionContext) => {
23
20
  const manager = state.controller?.manager;
24
21
  if (!manager || !state.lspActive) {
25
- const activeTools = pi.getActiveTools();
26
- const nextTools = activeTools.filter(
27
- (t: string) => !LSP_TOOL_NAMES.includes(t as (typeof LSP_TOOL_NAMES)[number]),
28
- );
29
- if (nextTools.length !== activeTools.length) {
30
- pi.setActiveTools(nextTools);
31
- }
32
22
  return;
33
23
  }
34
24
 
35
- const missing = LSP_TOOL_NAMES.filter((name) => !pi.getActiveTools().includes(name));
36
- if (missing.length > 0) {
37
- pi.setActiveTools([...pi.getActiveTools(), ...missing]);
38
- }
39
-
40
25
  // Sentinel refresh: detect lockfile/tsconfig/d.ts changes from outside write/edit
41
26
  applySentinelChanges(state, manager);
42
27
 
@@ -4,26 +4,21 @@
4
4
  // are deprecated and ignored. Per-language disable via
5
5
  // `lsp.servers.<language>.enabled: false` is the only supported opt-out.
6
6
  //
7
- // This settings section provides:
8
- // - severity control
9
- // - excluded patterns
10
- // - a "Disabled Servers" submenu that writes per-language disable config
7
+ // Uses the declarative settings schema with:
8
+ // - severity: number with labeled values
9
+ // - exclude: stringList
10
+ // - disabled_servers: custom submenu that writes per-language disable config
11
11
 
12
- import {
13
- CONFIG_DIR_NAME,
14
- type ExtensionAPI,
15
- getSettingsListTheme,
16
- } from "@earendil-works/pi-coding-agent";
17
- import type { SettingItem } from "@earendil-works/pi-tui";
12
+ import { type ExtensionAPI, getSettingsListTheme } from "@earendil-works/pi-coding-agent";
13
+ import type { Component, SettingItem } from "@earendil-works/pi-tui";
18
14
  import { Container, Key, matchesKey, SettingsList, Text } from "@earendil-works/pi-tui";
19
15
 
20
- type SettingsScope = "project" | "global";
21
-
16
+ import { loadSupiConfigSectionForScope, writeSupiConfig } from "@mrclrchtr/supi-core/config";
22
17
  import {
23
- loadSupiConfigForScope,
24
- registerConfigSettings,
25
- writeSupiConfig,
26
- } from "@mrclrchtr/supi-core/config";
18
+ registerDeclarativeSettings,
19
+ type SettingsScope,
20
+ type ValueSource,
21
+ } from "@mrclrchtr/supi-core/settings";
27
22
  import { type LspSettings, loadConfig } from "@mrclrchtr/supi-lsp/api";
28
23
 
29
24
  const LSP_DEFAULTS: LspSettings = {
@@ -33,21 +28,6 @@ const LSP_DEFAULTS: LspSettings = {
33
28
  exclude: [],
34
29
  };
35
30
 
36
- function severityLabel(severity: number): string {
37
- switch (severity) {
38
- case 1:
39
- return "errors";
40
- case 2:
41
- return "warnings";
42
- case 3:
43
- return "info";
44
- case 4:
45
- return "hints";
46
- default:
47
- return "errors";
48
- }
49
- }
50
-
51
31
  /** Discover configured servers from the defaults + effective LSP config. */
52
32
  function getConfiguredServers(cwd: string): string[] {
53
33
  try {
@@ -63,12 +43,10 @@ function getConfiguredServers(cwd: string): string[] {
63
43
  * configured disabled servers from the persisted config.
64
44
  */
65
45
  function getDisabledServersFromConfig(scope: SettingsScope, cwd: string): Set<string> {
66
- const section = loadSupiConfigForScope(
67
- "lsp",
68
- cwd,
69
- { servers: {} as Record<string, { enabled?: boolean }> },
70
- { scope },
71
- );
46
+ const section = loadSupiConfigSectionForScope("lsp", cwd, {
47
+ scope,
48
+ });
49
+ if (!section) return new Set();
72
50
  const servers = section.servers as Record<string, { enabled?: boolean }> | undefined;
73
51
  const disabled = new Set<string>();
74
52
  if (servers) {
@@ -79,75 +57,11 @@ function getDisabledServersFromConfig(scope: SettingsScope, cwd: string): Set<st
79
57
  return disabled;
80
58
  }
81
59
 
82
- export function registerLspSettings(pi: ExtensionAPI): void {
83
- registerConfigSettings(pi, {
84
- id: "lsp",
85
- label: "LSP",
86
- section: "lsp",
87
- defaults: LSP_DEFAULTS,
88
- buildItems: (settings, scope, cwd) => buildLspSettingItems(settings, scope, cwd),
89
- // biome-ignore lint/complexity/useMaxParams: registerConfigSettings callback signature
90
- persistChange: (_scope, _cwd, settingId, value, helpers) => {
91
- switch (settingId) {
92
- case "severity": {
93
- const num = Number.parseInt(value.split(" ")[0] ?? "1", 10);
94
- helpers.set("severity", Number.isNaN(num) ? 1 : num);
95
- break;
96
- }
97
- case "exclude": {
98
- const patterns = value
99
- .split(",")
100
- .map((s) => s.trim())
101
- .filter(Boolean);
102
- if (patterns.length > 0) helpers.set("exclude", patterns);
103
- else helpers.unset("exclude");
104
- break;
105
- }
106
- }
107
- },
108
- });
109
- }
110
-
111
- function buildLspSettingItems(
112
- settings: LspSettings,
113
- scope: "project" | "global",
114
- cwd: string,
115
- ): SettingItem[] {
116
- return [
117
- {
118
- id: "severity",
119
- label: "Inline Severity",
120
- description: "Minimum diagnostic severity to show inline (1=errors, 4=hints)",
121
- currentValue: `${settings.severity} (${severityLabel(settings.severity)})`,
122
- values: ["1 (errors)", "2 (warnings)", "3 (info)", "4 (hints)"],
123
- },
124
- {
125
- id: "disabled_servers",
126
- label: "Disabled Servers",
127
- description: "Press Enter to choose which language servers to disable",
128
- currentValue: renderDisabledServersLabel(scope, cwd),
129
- submenu: (_currentValue, done) => createDisabledServersSubmenu(scope, cwd, done),
130
- },
131
- {
132
- id: "exclude",
133
- label: "Exclude Patterns",
134
- description: "Gitignore patterns to suppress LSP diagnostics",
135
- currentValue: settings.exclude.length > 0 ? settings.exclude.join(", ") : "none",
136
- submenu: (_currentValue, done) => createExcludeSubmenu(scope, cwd, settings, done),
137
- },
138
- ];
139
- }
140
-
141
- function renderDisabledServersLabel(scope: SettingsScope, cwd: string): string {
142
- const disabled = getDisabledServersFromConfig(scope, cwd);
143
- return disabled.size > 0 ? [...disabled].sort().join(", ") : "none disabled";
144
- }
145
-
146
60
  function createDisabledServersSubmenu(
147
61
  scope: SettingsScope,
148
62
  cwd: string,
149
- done: (value?: string) => void,
150
- ) {
63
+ done: (selectedValue?: string) => void,
64
+ ): Component {
151
65
  const allServers = getConfiguredServers(cwd);
152
66
  const disabledServers = getDisabledServersFromConfig(scope, cwd);
153
67
 
@@ -160,14 +74,7 @@ function createDisabledServersSubmenu(
160
74
 
161
75
  let dirty = false;
162
76
  const container = new Container();
163
- const header = new Text("Disabled Servers — per-language opt-out", 0, 0);
164
- container.addChild(header);
165
- const footer = new Text(
166
- "Marking a server as disabled writes lsp.servers.<lang>.enabled: false for this scope",
167
- 0,
168
- 0,
169
- );
170
- container.addChild(footer);
77
+ container.addChild(new Text(" Disabled Servers — per-language opt-out", 1, 0));
171
78
 
172
79
  const settingsList = new SettingsList(
173
80
  items,
@@ -184,26 +91,22 @@ function createDisabledServersSubmenu(
184
91
  { enableSearch: true },
185
92
  );
186
93
  container.addChild(settingsList);
94
+ container.addChild(new Text(" esc save and close", 1, 0));
187
95
 
188
96
  return {
189
97
  render: (width: number) => container.render(width),
190
98
  invalidate: () => container.invalidate(),
191
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: single event handler dispatching keyboard shortcuts for the Disabled Servers submenu
99
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: single event handler for the submenu
192
100
  handleInput: (data: string) => {
193
101
  if (matchesKey(data, Key.escape)) {
194
102
  if (!dirty) {
195
103
  done();
196
- return true;
104
+ return;
197
105
  }
198
106
  // Write per-language enabled/disabled into lsp.servers
199
- const currentSection = loadSupiConfigForScope(
200
- "lsp",
201
- cwd,
202
- { servers: {} as Record<string, { enabled?: boolean }> },
203
- { scope },
204
- );
107
+ const currentSection = loadSupiConfigSectionForScope("lsp", cwd, { scope });
205
108
  const servers =
206
- (currentSection.servers as Record<string, Record<string, unknown>> | undefined) ?? {};
109
+ (currentSection?.servers as Record<string, Record<string, unknown>> | undefined) ?? {};
207
110
 
208
111
  for (const item of items) {
209
112
  if (item.currentValue === "disabled") {
@@ -212,7 +115,6 @@ function createDisabledServersSubmenu(
212
115
  const srv = servers[item.id];
213
116
  if (srv) {
214
117
  delete srv.enabled;
215
- // If only `enabled` was the property, remove the whole entry
216
118
  if (Object.keys(srv).length === 0) {
217
119
  delete servers[item.id];
218
120
  }
@@ -221,70 +123,90 @@ function createDisabledServersSubmenu(
221
123
  }
222
124
 
223
125
  writeSupiConfig({ section: "lsp", scope, cwd }, { servers });
224
- done();
225
- return true;
126
+ done("saved");
127
+ return;
226
128
  }
227
129
  settingsList.handleInput?.(data);
228
- return true;
229
130
  },
230
131
  };
231
132
  }
232
133
 
233
- function createExcludeSubmenu(
234
- _scope: "project" | "global",
235
- _cwd: string,
236
- settings: LspSettings,
237
- done: (value?: string) => void,
238
- ) {
239
- const items: SettingItem[] = settings.exclude.map((pattern) => ({
240
- id: pattern,
241
- label: pattern,
242
- currentValue: "enabled",
243
- values: ["enabled", "disabled"],
244
- }));
245
-
246
- let dirty = false;
247
- const container = new Container();
248
- const header = new Text("Exclude Patterns — toggle off to remove", 0, 0);
249
- container.addChild(header);
250
-
251
- const footer = new Text(
252
- `Add new patterns in ${CONFIG_DIR_NAME}/supi/config.json under lsp.exclude`,
253
- 0,
254
- 0,
255
- );
256
- container.addChild(footer);
134
+ export function registerLspSettings(pi: ExtensionAPI): void {
135
+ registerDeclarativeSettings(pi, {
136
+ id: "lsp",
137
+ label: "LSP",
138
+ section: "lsp",
139
+ defaults: LSP_DEFAULTS,
140
+ fields: [
141
+ {
142
+ kind: "number" as const,
143
+ key: "severity",
144
+ label: "Inline Severity",
145
+ description: "Minimum diagnostic severity to show inline (1=errors, 4=hints)",
146
+ values: ["1", "2", "3", "4"],
147
+ },
148
+ {
149
+ kind: "stringList" as const,
150
+ key: "exclude",
151
+ label: "Exclude Patterns",
152
+ description: "Gitignore patterns to suppress LSP diagnostics (comma-separated)",
153
+ },
154
+ {
155
+ kind: "custom" as const,
156
+ key: "disabled_servers",
157
+ label: "Disabled Servers",
158
+ description: "Press Enter to choose which language servers to disable",
159
+ resolve: (scope, cwd) => {
160
+ const scopedDisabled = getDisabledServersFromConfig(scope, cwd);
161
+ const otherScope = scope === "project" ? "global" : "project";
162
+ const otherDisabled =
163
+ scope === "project" ? getDisabledServersFromConfig("global", cwd) : new Set<string>();
164
+
165
+ // Effective disabled servers = union of both scopes (since either disables)
166
+ const effectiveDisabled = new Set([...scopedDisabled, ...otherDisabled]);
167
+ const label =
168
+ effectiveDisabled.size > 0 ? [...effectiveDisabled].sort().join(", ") : "none disabled";
169
+
170
+ // Source: where the override comes from
171
+ let source: ValueSource;
172
+ if (scopedDisabled.size > 0) {
173
+ source = scope;
174
+ } else if (otherDisabled.size > 0) {
175
+ source = otherScope as ValueSource;
176
+ } else {
177
+ source = "default";
178
+ }
257
179
 
258
- const settingsList = new SettingsList(
259
- items,
260
- Math.min(items.length + 3, 15),
261
- getSettingsListTheme(),
262
- (id, newValue) => {
263
- const idx = items.findIndex((i) => i.id === id);
264
- if (idx >= 0 && items[idx].currentValue !== newValue) {
265
- dirty = true;
266
- items[idx].currentValue = newValue;
267
- }
268
- },
269
- () => {},
270
- );
271
- container.addChild(settingsList);
180
+ let inheritanceSource: "global" | "default" | undefined;
181
+ if (scope === "project" && source === "project") {
182
+ inheritanceSource =
183
+ getDisabledServersFromConfig("global", cwd).size > 0 ? "global" : "default";
184
+ }
272
185
 
273
- return {
274
- render: (width: number) => container.render(width),
275
- invalidate: () => container.invalidate(),
276
- handleInput: (data: string) => {
277
- if (matchesKey(data, Key.escape)) {
278
- if (!dirty) {
279
- done();
280
- return true;
281
- }
282
- const enabled = items.filter((i) => i.currentValue === "enabled").map((i) => i.id);
283
- done(enabled.join(", ") || undefined);
284
- return true;
285
- }
286
- settingsList.handleInput?.(data);
287
- return true;
288
- },
289
- };
186
+ return { displayValue: label, source, inheritanceSource };
187
+ },
188
+ submenu: (_currentValue, done, scope, cwd) =>
189
+ createDisabledServersSubmenu(scope, cwd, done),
190
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: custom field persist handler orchestrates nested config writes
191
+ persist: (scope, cwd, action, _helpers) => {
192
+ if (action.kind === "inherit" || action.kind === "resetToDefault") {
193
+ // Clear all per-server enable/disable entries for this scope
194
+ const currentSection = loadSupiConfigSectionForScope("lsp", cwd, { scope });
195
+ const servers =
196
+ (currentSection?.servers as Record<string, Record<string, unknown>> | undefined) ??
197
+ {};
198
+ for (const name of Object.keys(servers)) {
199
+ const srv = servers[name];
200
+ if (srv) {
201
+ delete srv.enabled;
202
+ if (Object.keys(srv).length === 0) delete servers[name];
203
+ }
204
+ }
205
+ writeSupiConfig({ section: "lsp", scope, cwd }, { servers });
206
+ }
207
+ // Explicit set actions are handled entirely inside the submenu
208
+ },
209
+ },
210
+ ],
211
+ });
290
212
  }