@mrclrchtr/supi-code-intelligence 0.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 (146) hide show
  1. package/README.md +212 -0
  2. package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
  3. package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
  4. package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
  5. package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
  6. package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
  7. package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
  11. package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
  13. package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
  14. package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
  15. package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
  16. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
  17. package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/README.md +112 -0
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +90 -0
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +30 -0
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
  23. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
  24. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
  27. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
  28. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
  30. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
  32. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
  34. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  35. package/node_modules/@mrclrchtr/supi-lsp/package.json +45 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/capabilities.ts +62 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +229 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +545 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +192 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/config.ts +143 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/defaults.json +82 -0
  42. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +82 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +68 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +73 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +98 -0
  46. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +47 -0
  47. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +58 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +359 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +163 -0
  50. package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +17 -0
  51. package/node_modules/@mrclrchtr/supi-lsp/src/lsp-state.ts +82 -0
  52. package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +470 -0
  53. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +34 -0
  54. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +139 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +39 -0
  56. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +46 -0
  57. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-types.ts +39 -0
  58. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +83 -0
  59. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +18 -0
  60. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +550 -0
  61. package/node_modules/@mrclrchtr/supi-lsp/src/overrides.ts +173 -0
  62. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +197 -0
  63. package/node_modules/@mrclrchtr/supi-lsp/src/renderer.ts +120 -0
  64. package/node_modules/@mrclrchtr/supi-lsp/src/scanner.ts +153 -0
  65. package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +98 -0
  66. package/node_modules/@mrclrchtr/supi-lsp/src/service-registry.ts +153 -0
  67. package/node_modules/@mrclrchtr/supi-lsp/src/settings-registration.ts +292 -0
  68. package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +153 -0
  69. package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +430 -0
  70. package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +48 -0
  71. package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +156 -0
  72. package/node_modules/@mrclrchtr/supi-lsp/src/types.ts +409 -0
  73. package/node_modules/@mrclrchtr/supi-lsp/src/ui.ts +358 -0
  74. package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +122 -0
  75. package/node_modules/@mrclrchtr/supi-lsp/src/workspace-sentinels.ts +114 -0
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +97 -0
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +67 -0
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/.gitkeep +0 -0
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm +0 -0
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm +0 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm +0 -0
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm +0 -0
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm +0 -0
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm +0 -0
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +126 -0
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +144 -0
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +151 -0
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/callees.ts +343 -0
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +108 -0
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/exports.ts +315 -0
  115. package/node_modules/@mrclrchtr/supi-tree-sitter/src/formatting.ts +104 -0
  116. package/node_modules/@mrclrchtr/supi-tree-sitter/src/imports.ts +42 -0
  117. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +16 -0
  118. package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +116 -0
  119. package/node_modules/@mrclrchtr/supi-tree-sitter/src/node-at.ts +96 -0
  120. package/node_modules/@mrclrchtr/supi-tree-sitter/src/outline.ts +287 -0
  121. package/node_modules/@mrclrchtr/supi-tree-sitter/src/runtime.ts +237 -0
  122. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session.ts +112 -0
  123. package/node_modules/@mrclrchtr/supi-tree-sitter/src/structure.ts +7 -0
  124. package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +13 -0
  125. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +306 -0
  126. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +146 -0
  127. package/package.json +47 -0
  128. package/src/actions/affected-action.ts +310 -0
  129. package/src/actions/brief-action.ts +242 -0
  130. package/src/actions/callees-action.ts +134 -0
  131. package/src/actions/callers-action.ts +215 -0
  132. package/src/actions/implementations-action.ts +190 -0
  133. package/src/actions/index-action.ts +187 -0
  134. package/src/actions/pattern-action.ts +232 -0
  135. package/src/architecture.ts +367 -0
  136. package/src/brief-focused.ts +383 -0
  137. package/src/brief.ts +228 -0
  138. package/src/code-intelligence.ts +122 -0
  139. package/src/git-context.ts +65 -0
  140. package/src/guidance.ts +39 -0
  141. package/src/index.ts +28 -0
  142. package/src/resolve-target.ts +104 -0
  143. package/src/search-helpers.ts +283 -0
  144. package/src/target-resolution.ts +368 -0
  145. package/src/tool-actions.ts +109 -0
  146. package/src/types.ts +57 -0
@@ -0,0 +1,54 @@
1
+ // Shared registry utility for SuPi extensions.
2
+ //
3
+ // Provides a globalThis-backed registry pattern so that all jiti module instances
4
+ // (resolved through different node_modules symlinks) share the same Map.
5
+ // Without this, each symlink path gets its own module copy and its own Map,
6
+ // so registrations from one instance are invisible to consumers in another.
7
+
8
+ const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
9
+
10
+ /**
11
+ * Create a named registry backed by `globalThis` + `Symbol.for`.
12
+ *
13
+ * The registry is lazily initialized on first access and shared across all
14
+ * jiti module instances via the global symbol namespace.
15
+ *
16
+ * @typeParam T - The value type stored in the registry.
17
+ * @param name - Unique registry name (used to construct the `Symbol.for` key).
18
+ * @returns An object with `register`, `getAll`, and `clear` functions.
19
+ */
20
+ export function createRegistry<T>(name: string) {
21
+ const key = Symbol.for(SYMBOL_PREFIX + name);
22
+
23
+ const getMap = (): Map<string, T> => {
24
+ let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
25
+ if (!map) {
26
+ map = new Map<string, T>();
27
+ (globalThis as Record<symbol, unknown>)[key] = map;
28
+ }
29
+ return map;
30
+ };
31
+
32
+ return {
33
+ /**
34
+ * Register a value by id. Duplicate ids silently replace the previous registration.
35
+ */
36
+ register: (id: string, value: T): void => {
37
+ getMap().set(id, value);
38
+ },
39
+
40
+ /**
41
+ * Get all registered values in registration order.
42
+ */
43
+ getAll: (): T[] => {
44
+ return Array.from(getMap().values());
45
+ },
46
+
47
+ /**
48
+ * Clear all entries from the registry (primarily for tests).
49
+ */
50
+ clear: (): void => {
51
+ getMap().clear();
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,29 @@
1
+ // Generic session-file tree-walking utilities.
2
+
3
+ import type { FileEntry, SessionEntry } from "@earendil-works/pi-coding-agent";
4
+
5
+ /**
6
+ * Resolve the active branch path using PI's append-only tree semantics.
7
+ *
8
+ * The active branch is the path from the **last entry** (current leaf)
9
+ * back to the root via `parentId`. This follows PI's tree structure where
10
+ * entries are append-only and the last entry in the file is always the
11
+ * current leaf of the active branch.
12
+ */
13
+ export function getActiveBranchEntries(entries: FileEntry[]): SessionEntry[] {
14
+ const sessionEntries = entries.filter((e): e is SessionEntry => e.type !== "session");
15
+ const byId = new Map(sessionEntries.map((entry) => [entry.id, entry]));
16
+ const leaf = sessionEntries.at(-1);
17
+ if (!leaf) return [];
18
+
19
+ const path: SessionEntry[] = [];
20
+ const visited = new Set<string>();
21
+ let current: SessionEntry | undefined = leaf;
22
+ while (current) {
23
+ if (visited.has(current.id)) break;
24
+ visited.add(current.id);
25
+ path.unshift(current);
26
+ current = current.parentId ? byId.get(current.parentId) : undefined;
27
+ }
28
+ return path;
29
+ }
@@ -0,0 +1,15 @@
1
+ // /supi-settings command registration.
2
+ //
3
+ // Thin wrapper that registers the command and delegates to openSettingsOverlay.
4
+
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { openSettingsOverlay } from "./settings-ui.ts";
7
+
8
+ export function registerSettingsCommand(pi: ExtensionAPI): void {
9
+ pi.registerCommand("supi-settings", {
10
+ description: "Manage SuPi extension settings",
11
+ handler: async (_args, ctx) => {
12
+ openSettingsOverlay(ctx);
13
+ },
14
+ });
15
+ }
@@ -0,0 +1,41 @@
1
+ // Settings registry for SuPi extensions.
2
+ //
3
+ // Extensions declare their settings via `registerSettings()` during their
4
+ // factory function. The generic settings UI reads them via `getRegisteredSettings()`.
5
+
6
+ import type { SettingItem } from "@earendil-works/pi-tui";
7
+ import { createRegistry } from "./registry-utils.ts";
8
+
9
+ export type SettingsScope = "project" | "global";
10
+
11
+ export interface SettingsSection {
12
+ /** Extension identifier — e.g. "lsp", "claude-md" */
13
+ id: string;
14
+ /** Human-readable label shown in the UI */
15
+ label: string;
16
+ /** Load current SettingItem[] for the given scope */
17
+ loadValues: (scope: SettingsScope, cwd: string) => SettingItem[];
18
+ /** Persist a change back to config */
19
+ persistChange: (scope: SettingsScope, cwd: string, settingId: string, value: string) => void;
20
+ }
21
+
22
+ const registry = createRegistry<SettingsSection>("settings-registry");
23
+
24
+ /**
25
+ * Register a settings section for an extension.
26
+ * Call during the extension factory function (not async handlers).
27
+ * Duplicate ids replace the previous registration.
28
+ */
29
+ export function registerSettings(section: SettingsSection): void {
30
+ registry.register(section.id, section);
31
+ }
32
+
33
+ /** Get all registered settings sections in registration order. */
34
+ export function getRegisteredSettings(): SettingsSection[] {
35
+ return registry.getAll();
36
+ }
37
+
38
+ /** Clear the registry — used by tests. */
39
+ export function clearRegisteredSettings(): void {
40
+ registry.clear();
41
+ }
@@ -0,0 +1,226 @@
1
+ // Generic settings overlay for SuPi extensions.
2
+ //
3
+ // Uses pi-tui's SettingsList with scope toggle (Tab), extension grouping,
4
+ // and search. Each extension declares its settings via registerSettings().
5
+
6
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
7
+ import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
8
+ import {
9
+ Container,
10
+ Input,
11
+ Key,
12
+ matchesKey,
13
+ type SettingItem,
14
+ SettingsList,
15
+ Text,
16
+ } from "@earendil-works/pi-tui";
17
+ import {
18
+ getRegisteredSettings,
19
+ type SettingsScope,
20
+ type SettingsSection,
21
+ } from "./settings-registry.ts";
22
+
23
+ // ── Input submenu component ──────────────────────────────────
24
+
25
+ /**
26
+ * Creates a pi-tui Input-backed submenu component with enter-to-confirm
27
+ * and escape-to-cancel handling.
28
+ *
29
+ * @param currentValue - Initial value for the text input.
30
+ * @param label - Label text displayed above the input.
31
+ * @param done - Callback invoked with the confirmed value, or undefined on cancel.
32
+ */
33
+ export function createInputSubmenu(
34
+ currentValue: string,
35
+ label: string,
36
+ done: (selectedValue?: string) => void,
37
+ ): {
38
+ render: (width: number) => string[];
39
+ invalidate: () => void;
40
+ handleInput: (data: string) => boolean;
41
+ } {
42
+ const input = new Input();
43
+ input.setValue(currentValue);
44
+
45
+ return {
46
+ render: (_width: number) => {
47
+ const lines = [` ${label}`];
48
+ lines.push(...input.render(_width));
49
+ lines.push(" enter confirm • esc cancel");
50
+ return lines;
51
+ },
52
+ invalidate: () => {
53
+ input.invalidate();
54
+ },
55
+ handleInput: (data: string) => {
56
+ if (matchesKey(data, Key.escape)) {
57
+ done();
58
+ return true;
59
+ }
60
+ if (matchesKey(data, Key.enter)) {
61
+ done(input.getValue());
62
+ return true;
63
+ }
64
+ input.handleInput(data);
65
+ return true;
66
+ },
67
+ };
68
+ }
69
+
70
+ // ── Types ────────────────────────────────────────────────────
71
+
72
+ interface OverlayState {
73
+ scope: SettingsScope;
74
+ cwd: string;
75
+ }
76
+
77
+ // ── Pure helpers ─────────────────────────────────────────────
78
+
79
+ function getScopeLabel(scope: SettingsScope): string {
80
+ return scope === "project" ? "Project" : "Global";
81
+ }
82
+
83
+ function buildFlatItems(
84
+ sections: SettingsSection[],
85
+ scope: SettingsScope,
86
+ cwd: string,
87
+ ): SettingItem[] {
88
+ const items: SettingItem[] = [];
89
+ for (const section of sections) {
90
+ const sectionItems = section.loadValues(scope, cwd);
91
+ for (const item of sectionItems) {
92
+ items.push({
93
+ ...item,
94
+ id: `${section.id}.${item.id}`,
95
+ label: `${section.label}: ${item.label}`,
96
+ });
97
+ }
98
+ }
99
+ return items;
100
+ }
101
+
102
+ function findSectionAndId(
103
+ sections: SettingsSection[],
104
+ flatId: string,
105
+ ): { section: SettingsSection; itemId: string } | null {
106
+ const dotIndex = flatId.indexOf(".");
107
+ if (dotIndex === -1) return null;
108
+ const sectionId = flatId.slice(0, dotIndex);
109
+ const itemId = flatId.slice(dotIndex + 1);
110
+ const section = sections.find((s) => s.id === sectionId);
111
+ if (!section) return null;
112
+ return { section, itemId };
113
+ }
114
+
115
+ // ── Component ────────────────────────────────────────────────
116
+
117
+ interface SettingsOverlayDeps {
118
+ state: OverlayState;
119
+ container: Container;
120
+ settingsList: SettingsList | null;
121
+ tui: Parameters<Parameters<ExtensionContext["ui"]["custom"]>[0]>[0];
122
+ theme: Parameters<Parameters<ExtensionContext["ui"]["custom"]>[0]>[1];
123
+ done: () => void;
124
+ }
125
+
126
+ function createSettingsList(deps: SettingsOverlayDeps): SettingsList {
127
+ const sections = getRegisteredSettings();
128
+ const items = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
129
+ const onChange = (flatId: string, newValue: string) => {
130
+ const found = findSectionAndId(sections, flatId);
131
+ if (found) {
132
+ found.section.persistChange(deps.state.scope, deps.state.cwd, found.itemId, newValue);
133
+ }
134
+ // Re-read all values to reflect persisted changes, but keep the list
135
+ // instance (and its selectedIndex) intact.
136
+ const updatedItems = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
137
+ for (const updated of updatedItems) {
138
+ const existing = items.find((i) => i.id === updated.id);
139
+ if (existing && existing.currentValue !== updated.currentValue) {
140
+ settingsList.updateValue(updated.id, updated.currentValue);
141
+ }
142
+ }
143
+ deps.tui.requestRender();
144
+ };
145
+ const settingsList = new SettingsList(
146
+ items,
147
+ Math.min(items.length + 4, 20),
148
+ getSettingsListTheme(),
149
+ onChange,
150
+ () => deps.done(),
151
+ { enableSearch: true },
152
+ );
153
+ return settingsList;
154
+ }
155
+
156
+ function rebuildSettingsList(deps: SettingsOverlayDeps): SettingsList {
157
+ const settingsList = createSettingsList(deps);
158
+ deps.settingsList = settingsList;
159
+
160
+ deps.container.clear();
161
+ deps.container.addChild(createHeaderComponent(deps));
162
+ deps.container.addChild(settingsList);
163
+
164
+ return settingsList;
165
+ }
166
+
167
+ function createHeaderComponent(deps: SettingsOverlayDeps): Text {
168
+ const { theme, state } = deps;
169
+ const scopeLabel = getScopeLabel(state.scope);
170
+ const otherScope = state.scope === "project" ? "Global" : "Project";
171
+ const headerText = new Text(
172
+ `${theme.fg("accent", theme.bold("SuPi Settings"))} ${theme.fg("text", `Scope: ${scopeLabel}`)} ${theme.fg("dim", `(tab → ${otherScope})`)}`,
173
+ 0,
174
+ 0,
175
+ );
176
+ return headerText;
177
+ }
178
+
179
+ function handleScopeToggle(deps: SettingsOverlayDeps): void {
180
+ deps.state.scope = deps.state.scope === "project" ? "global" : "project";
181
+ rebuildSettingsList(deps);
182
+ deps.tui.requestRender();
183
+ }
184
+
185
+ // ── Entry point ──────────────────────────────────────────────
186
+
187
+ export function openSettingsOverlay(ctx: ExtensionContext): void {
188
+ const sections = getRegisteredSettings();
189
+ if (sections.length === 0) {
190
+ ctx.ui.notify("No settings registered by SuPi extensions", "info");
191
+ return;
192
+ }
193
+
194
+ void ctx.ui.custom<void>((tui, theme, _kb, done) => {
195
+ const state: OverlayState = { scope: "project", cwd: ctx.cwd };
196
+ const container = new Container();
197
+
198
+ const deps: SettingsOverlayDeps = {
199
+ state,
200
+ container,
201
+ settingsList: null,
202
+ tui,
203
+ theme,
204
+ done,
205
+ };
206
+
207
+ rebuildSettingsList(deps);
208
+
209
+ const component = {
210
+ render: (width: number) => container.render(width),
211
+ invalidate: () => container.invalidate(),
212
+ handleInput: (data: string) => {
213
+ if (matchesKey(data, Key.tab)) {
214
+ handleScopeToggle(deps);
215
+ return true;
216
+ }
217
+ // Delegate input to the settings list (always set after rebuildSettingsList)
218
+ deps.settingsList?.handleInput?.(data);
219
+ deps.tui.requestRender();
220
+ return true;
221
+ },
222
+ };
223
+
224
+ return component;
225
+ });
226
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Shared terminal title formatting and signaling utilities.
3
+ *
4
+ * Centralized place for pi title convention (π prefix), completion (✓)
5
+ * and waiting (●) indicators, and the audible terminal bell.
6
+ */
7
+ import path from "node:path";
8
+
9
+ /** Unicode checkmark shown when the agent finishes a turn. */
10
+ export const DONE_SYMBOL = "\u2713";
11
+ /** Unicode dot shown when waiting for user input. */
12
+ export const WAITING_SYMBOL = "\u25CF";
13
+
14
+ /** Minimal UI surface needed for title operations. */
15
+ export interface TitleTarget {
16
+ ui: {
17
+ setTitle?(title: string): void;
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Format pi's canonical terminal title from session name and cwd.
23
+ * Falls back gracefully when either is missing.
24
+ *
25
+ * @example
26
+ * formatTitle("my-session", "/home/projects/foo") // "π - my-session - foo"
27
+ * formatTitle(undefined, "/home/projects/foo") // "π - foo"
28
+ * formatTitle("my-session") // "π - my-session"
29
+ * formatTitle() // "π"
30
+ */
31
+ export function formatTitle(sessionName?: string, cwd?: string): string {
32
+ const base = cwd ? path.basename(cwd) : undefined;
33
+ if (sessionName && base) return `π - ${sessionName} - ${base}`;
34
+ if (sessionName) return `π - ${sessionName}`;
35
+ if (base) return `π - ${base}`;
36
+ return "π";
37
+ }
38
+
39
+ /** Sound the audible terminal bell (ASCII BEL). */
40
+ export function signalBell(): void {
41
+ process.stdout.write("\x07");
42
+ }
43
+
44
+ /**
45
+ * Set the terminal title to indicate the agent is waiting for user input.
46
+ * Prefixes with ● and sounds the terminal bell.
47
+ */
48
+ export function signalWaiting(ctx: TitleTarget, title: string): void {
49
+ ctx.ui.setTitle?.(`${WAITING_SYMBOL} ${title}`);
50
+ signalBell();
51
+ }
52
+
53
+ /**
54
+ * Set the terminal title to indicate the agent turn has completed.
55
+ * Prefixes with ✓ and sounds the terminal bell.
56
+ */
57
+ export function signalDone(ctx: TitleTarget, title: string): void {
58
+ ctx.ui.setTitle?.(`${DONE_SYMBOL} ${title}`);
59
+ signalBell();
60
+ }
@@ -0,0 +1,112 @@
1
+ # @mrclrchtr/supi-lsp
2
+
3
+ Language Server Protocol integration for the [pi coding agent](https://github.com/earendil-works/pi).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pi install npm:@mrclrchtr/supi-lsp
9
+ ```
10
+
11
+ ## What it adds
12
+
13
+ - `lsp` tool with `hover`, `definition`, `references`, `diagnostics`, `symbols`, `rename`, `code_actions`, `workspace_symbol`, `search`, `symbol_hover`, and `recover`
14
+ - Stable system-prompt guidance that tells the agent to prefer LSP over grep/rg for code navigation
15
+ - Proactive project scanning and eager startup of detected language servers
16
+ - Automatic stale-diagnostic recovery when workspace sentinels change (`package.json`, root lockfiles, `tsconfig*`, generated `*.d.ts` files`) before the next agent turn, plus immediate recovery after successful `write` or `edit` calls for those paths
17
+ - Inline diagnostic surfacing around reads, writes, and edits
18
+ - Compact diagnostic context injection when outstanding diagnostics change, with stale-diagnostic warnings when needed
19
+ - `/lsp-status` status overlay
20
+
21
+ ## Public library API
22
+
23
+ In addition to the extension entrypoint, `@mrclrchtr/supi-lsp` exports a reusable session-scoped service API for peer extensions:
24
+
25
+ ```ts
26
+ import { getSessionLspService, SessionLspService } from "@mrclrchtr/supi-lsp";
27
+
28
+ const state = getSessionLspService("/project");
29
+
30
+ if (state.kind === "ready") {
31
+ const service = state.service;
32
+ const hover = await service.hover("src/index.ts", { line: 5, character: 10 });
33
+ const defs = await service.definition("src/index.ts", { line: 5, character: 10 });
34
+ const refs = await service.references("src/index.ts", { line: 5, character: 10 });
35
+ const impls = await service.implementation("src/index.ts", { line: 5, character: 10 });
36
+ const symbols = await service.documentSymbols("src/index.ts");
37
+ const projectServers = service.getProjectServers();
38
+ }
39
+ ```
40
+
41
+ Peer extensions can import from the package root without reaching into private files.
42
+
43
+ ## Tool actions
44
+
45
+ The `lsp` tool supports these actions:
46
+
47
+ - `hover`: type info at a position
48
+ - `definition`: go to definition
49
+ - `references`: find all references
50
+ - `diagnostics`: per-file or project-wide diagnostics
51
+ - `symbols`: document symbols
52
+ - `rename`: workspace-wide rename
53
+ - `code_actions`: quick fixes at a position
54
+ - `workspace_symbol`: fuzzy symbol search across the project
55
+ - `search`: symbol search with text fallback
56
+ - `symbol_hover`: hover by symbol name
57
+ - `recover`: refresh diagnostics after workspace-wide dependency, config, or generated-type changes
58
+
59
+ Line and character positions are **1-based**.
60
+
61
+ Example:
62
+
63
+ ```json
64
+ {
65
+ "action": "definition",
66
+ "file": "src/index.ts",
67
+ "line": 12,
68
+ "character": 8
69
+ }
70
+ ```
71
+
72
+ ## Configuration
73
+
74
+ If your install surface includes `/supi-settings` (for example via `@mrclrchtr/supi`), this package contributes an LSP settings section there. Use that panel to:
75
+
76
+ - enable or disable LSP globally
77
+ - control the severity threshold
78
+ - select active language servers
79
+ - configure file exclusion patterns
80
+
81
+ ## Commands
82
+
83
+ `/lsp-status` toggles an overlay showing active language servers and outstanding diagnostics:
84
+
85
+ ```text
86
+ λ LSP inspector /lsp-status toggles
87
+ 3 servers running • 12 open files • 5 errors • 2 warnings
88
+
89
+ Servers
90
+ typescript running 24 open files
91
+ python running 8 open files
92
+ bash running 0 open files
93
+
94
+ Problems
95
+ src/lsp.ts:42 Cannot find name 'foo' ts(2304)
96
+ src/manager.ts:108 Property 'bar' does not exist ts(2339)
97
+ ```
98
+
99
+ When no servers are available, the overlay shows `no LSP servers available for this project`. A compact status summary is always visible in the pi status bar.
100
+
101
+ ## Requirements
102
+
103
+ - `@earendil-works/pi-coding-agent`
104
+ - `@earendil-works/pi-tui`
105
+ - `typebox`
106
+ - relevant language servers installed and available on `PATH`
107
+ - `@mrclrchtr/supi-core`
108
+
109
+ ## Source
110
+
111
+ - Extension entrypoint: `lsp.ts`
112
+ - Public library surface: `index.ts`
@@ -0,0 +1,90 @@
1
+ # @mrclrchtr/supi-core
2
+
3
+ Shared infrastructure for SuPi packages.
4
+
5
+ ## Install
6
+
7
+ Use it as a dependency in another extension package:
8
+
9
+ ```bash
10
+ pnpm add @mrclrchtr/supi-core
11
+ ```
12
+
13
+ ## Package role
14
+
15
+ `@mrclrchtr/supi-core` is a library package. It does **not** register a pi extension and is not meant to be installed as a standalone pi package.
16
+
17
+ ## What it provides
18
+
19
+ Current exports cover:
20
+
21
+ - shared config loading, scoped reads, writes, and key removal
22
+ - config-backed settings registration helpers for `/supi-settings`
23
+ - the shared settings registry, overlay UI, and `registerSettingsCommand()` helper
24
+ - XML `<extension-context>` wrapping plus context-message utilities
25
+ - context-provider and debug-event registries reused across SuPi packages
26
+ - project root and path helpers reused by packages such as `supi-lsp`
27
+
28
+ ## Config system
29
+
30
+ Config resolution order:
31
+
32
+ ```text
33
+ defaults <- global <- project
34
+ ```
35
+
36
+ Config file locations:
37
+
38
+ - global: `~/.pi/agent/supi/config.json`
39
+ - project: `.pi/supi/config.json`
40
+
41
+ Main helpers:
42
+
43
+ - `loadSupiConfig()` — effective merged config (`defaults <- global <- project`)
44
+ - `loadSupiConfigForScope()` — raw single-scope config for settings UIs (`defaults <- selected scope`)
45
+ - `writeSupiConfig()`
46
+ - `removeSupiConfigKey()`
47
+ - `registerConfigSettings()`
48
+
49
+ ## Context and settings helpers
50
+
51
+ - `wrapExtensionContext()`
52
+ - `findLastUserMessageIndex()`
53
+ - `getContextToken()`
54
+ - `pruneAndReorderContextMessages()`
55
+ - `registerSettings()`
56
+ - `registerSettingsCommand()`
57
+ - `openSettingsOverlay()`
58
+
59
+ ## Example
60
+
61
+ ```ts
62
+ import { loadSupiConfig, registerConfigSettings, wrapExtensionContext } from "@mrclrchtr/supi-core";
63
+
64
+ const config = loadSupiConfig("my-extension", process.cwd(), {
65
+ enabled: true,
66
+ });
67
+
68
+ registerConfigSettings({
69
+ id: "my-extension",
70
+ label: "My Extension",
71
+ section: "my-extension",
72
+ defaults: { enabled: true },
73
+ buildItems: () => [],
74
+ persistChange: () => {},
75
+ });
76
+
77
+ const message = wrapExtensionContext("my-extension", "hello", {
78
+ turn: 1,
79
+ file: "CLAUDE.md",
80
+ });
81
+ ```
82
+
83
+ ## Requirements
84
+
85
+ - `@earendil-works/pi-coding-agent`
86
+ - `@earendil-works/pi-tui`
87
+
88
+ ## Source
89
+
90
+ - Main exports: `src/index.ts`
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@mrclrchtr/supi-core",
3
+ "version": "0.1.0",
4
+ "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/mrclrchtr/supi.git"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "keywords": [
14
+ "pi",
15
+ "pi-coding-agent"
16
+ ],
17
+ "files": [
18
+ "src/**/*.ts",
19
+ "!__tests__"
20
+ ],
21
+ "peerDependencies": {
22
+ "@earendil-works/pi-coding-agent": "*",
23
+ "@earendil-works/pi-tui": "*"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.6.0",
27
+ "vitest": "^4.1.4"
28
+ },
29
+ "main": "src/index.ts"
30
+ }