@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.
- package/README.md +212 -0
- package/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/README.md +112 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +90 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +30 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +54 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +45 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/capabilities.ts +62 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +229 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +545 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +192 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config.ts +143 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/defaults.json +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +68 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +73 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +47 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +58 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +359 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +163 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +17 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp-state.ts +82 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +470 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +34 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +139 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +46 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-types.ts +39 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +83 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +18 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +550 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/overrides.ts +173 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +197 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/renderer.ts +120 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/scanner.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +98 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/service-registry.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/settings-registration.ts +292 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +153 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +430 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +48 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +156 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/types.ts +409 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/ui.ts +358 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +122 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/workspace-sentinels.ts +114 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +97 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +67 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/.gitkeep +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/bash/tree-sitter-bash.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/c/tree-sitter-c.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/cpp/tree-sitter-cpp.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/go/tree-sitter-go.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/html/tree-sitter-html.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/java/tree-sitter-java.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/javascript/tree-sitter-javascript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +12 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/python/tree-sitter-python.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/r/tree-sitter-r.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/ruby/tree-sitter-ruby.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/rust/tree-sitter-rust.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +19 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/tsx/tree-sitter-tsx.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/typescript/tree-sitter-typescript.wasm.json +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-kotlin-wasm.mjs +126 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/generate-sql-wasm.mjs +144 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/scripts/vendor-wasm.mjs +151 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/callees.ts +343 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/coordinates.ts +108 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/exports.ts +315 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/formatting.ts +104 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/imports.ts +42 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +16 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/language.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/node-at.ts +96 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/outline.ts +287 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/runtime.ts +237 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session.ts +112 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/structure.ts +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/syntax-node.ts +13 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +306 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +146 -0
- package/package.json +47 -0
- package/src/actions/affected-action.ts +310 -0
- package/src/actions/brief-action.ts +242 -0
- package/src/actions/callees-action.ts +134 -0
- package/src/actions/callers-action.ts +215 -0
- package/src/actions/implementations-action.ts +190 -0
- package/src/actions/index-action.ts +187 -0
- package/src/actions/pattern-action.ts +232 -0
- package/src/architecture.ts +367 -0
- package/src/brief-focused.ts +383 -0
- package/src/brief.ts +228 -0
- package/src/code-intelligence.ts +122 -0
- package/src/git-context.ts +65 -0
- package/src/guidance.ts +39 -0
- package/src/index.ts +28 -0
- package/src/resolve-target.ts +104 -0
- package/src/search-helpers.ts +283 -0
- package/src/target-resolution.ts +368 -0
- package/src/tool-actions.ts +109 -0
- package/src/types.ts +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# @mrclrchtr/supi-code-intelligence
|
|
2
|
+
|
|
3
|
+
SuPi Code Intelligence extension — the main agent-facing code understanding tool for [pi](https://github.com/earendil-works/pi).
|
|
4
|
+
|
|
5
|
+
Registers the `code_intel` tool with seven high-level actions and injects structural project context into the agent's first turn.
|
|
6
|
+
|
|
7
|
+
## Injection Points
|
|
8
|
+
|
|
9
|
+
The extension hooks into pi's lifecycle at six points:
|
|
10
|
+
|
|
11
|
+
| # | Injection Point | What It Does |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| 1 | `package.json` → `pi.extensions` | Manifest entry that tells pi to load the extension at startup |
|
|
14
|
+
| 2 | `pi.registerTool({ name: "code_intel", ... })` | Makes the `code_intel` tool callable from agent turns |
|
|
15
|
+
| 3 | `promptGuidelines` (7 guidelines) | Flattened into the system prompt's `Guidelines:` section — teaches the agent *when* to use each action |
|
|
16
|
+
| 4 | `promptSnippet` (1 line) | Injected near the tool definition in context — short reminder to use `code_intel` over broad file reads |
|
|
17
|
+
| 5 | `pi.on("session_start", ...)` | Resets injection dedup state and scans the active branch to avoid re-injecting on reload/resume |
|
|
18
|
+
| 6 | `pi.on("before_agent_start", ...)` | On the first agent turn, builds an architecture model and injects a compact Markdown overview as a custom message (`customType: "code-intelligence-overview"`, `display: false`) — the agent sees it in conversation context without UI clutter |
|
|
19
|
+
|
|
20
|
+
### First-Turn Overview Flow
|
|
21
|
+
|
|
22
|
+
1. `session_start` fires → resets `hasInjectedOverview`, scans branch for existing `code-intelligence-overview` custom message
|
|
23
|
+
2. First `before_agent_start` fires → calls `buildArchitectureModel(ctx.cwd)` to parse the project
|
|
24
|
+
3. If modules are found, `generateOverview(model)` produces a dense Markdown summary (~500 tokens, max 8 modules) with git context when available
|
|
25
|
+
4. Returns a `BeforeAgentStartEventResult` with a `customMessage`; pi places it in the agent's context
|
|
26
|
+
5. Subsequent turns skip injection entirely
|
|
27
|
+
|
|
28
|
+
## Tool Actions
|
|
29
|
+
|
|
30
|
+
### `brief` — Architecture overviews and focused briefs
|
|
31
|
+
|
|
32
|
+
Scopes: project (no params), package/directory (`path`), file (`file`), or anchored symbol (`file`, `line`, and `character`).
|
|
33
|
+
|
|
34
|
+
- Project-level brief: module listing, dependency graph, "start here" recommendations, suggested next queries
|
|
35
|
+
- Focused brief (`path` or anchored symbol): stripped-down version with a single module or symbol focus
|
|
36
|
+
- Now includes git context (branch, dirty files, last commit) when inside a git repository
|
|
37
|
+
- Metadata returned: `BriefDetails` with confidence, focus target, public surfaces, dependency summary
|
|
38
|
+
|
|
39
|
+
### `callers` — Find call sites for a symbol
|
|
40
|
+
|
|
41
|
+
- LSP-first (references query), falls back to heuristic text search (word-boundary ripgrep)
|
|
42
|
+
- Results grouped by file with ranked, contextual call sites
|
|
43
|
+
- Confidence labeling: `semantic` (LSP), `heuristic` (text search)
|
|
44
|
+
|
|
45
|
+
### `callees` — Structural outgoing call map
|
|
46
|
+
|
|
47
|
+
- Structural tree-sitter analysis for all grammars configured in `supi-tree-sitter`
|
|
48
|
+
- Finds outgoing function/method calls from an anchored position
|
|
49
|
+
- Supports JavaScript/TypeScript, Python, Rust, Go, C/C++, Java, Kotlin, Ruby, Bash, and R
|
|
50
|
+
|
|
51
|
+
### `implementations` — Find concrete implementations
|
|
52
|
+
|
|
53
|
+
- Resolves interface/abstract method implementations via LSP
|
|
54
|
+
- Falls back to heuristic text search for `implements`/`extends` patterns
|
|
55
|
+
|
|
56
|
+
### `affected` — Blast-radius analysis
|
|
57
|
+
|
|
58
|
+
Before changing exported APIs, shared helpers, config surfaces, or cross-package contracts:
|
|
59
|
+
- Direct references (callers/importers)
|
|
60
|
+
- Downstream dependents (transitive)
|
|
61
|
+
- Risk level: `low` | `medium` | `high`
|
|
62
|
+
- Likely test files
|
|
63
|
+
- Returns `AffectedDetails` metadata
|
|
64
|
+
|
|
65
|
+
### `index` — Factual project map
|
|
66
|
+
|
|
67
|
+
A non-interpretive project overview for quick orientation:
|
|
68
|
+
|
|
69
|
+
- File counts by language/extension
|
|
70
|
+
- Top-level directory tree with file counts
|
|
71
|
+
- Landmark config files detected (package.json, tsconfig.json, Makefile, etc.)
|
|
72
|
+
- Skips low-signal directories (`node_modules`, `dist`, `build`, `.git`)
|
|
73
|
+
|
|
74
|
+
Use when you need to understand "what's here?" before diving into specific files.
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{ "action": "index" }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `pattern` — Bounded, scope-aware text search
|
|
81
|
+
|
|
82
|
+
Optimized for common agent lookups:
|
|
83
|
+
|
|
84
|
+
- `pattern` is treated as a **literal string by default**
|
|
85
|
+
- Set `regex: true` to opt into raw ripgrep regex semantics
|
|
86
|
+
- Malformed regex input returns an explicit error instead of a misleading "No matches found"
|
|
87
|
+
- Nearby matches in the same file deduplicate overlapping context lines to reduce token waste
|
|
88
|
+
- Results grouped with file and context lines
|
|
89
|
+
- `summary: true` returns aggregate counts by directory instead of line-level matches (useful for "how common is this pattern?")
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{ "action": "pattern", "pattern": "sendMessage({", "path": "packages/" }
|
|
95
|
+
{ "action": "pattern", "pattern": "register(Settings|Config)", "path": "packages/", "regex": true }
|
|
96
|
+
{ "action": "pattern", "pattern": "createServerFn", "summary": true }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Language & File Type Support
|
|
100
|
+
|
|
101
|
+
| Feature / Language | JS/TS | Python | Rust | Go | Java/Kotlin | Ruby | PHP | Swift | C/C++ | Other text |
|
|
102
|
+
|---|---|---|---|---|---|---|---|---|---|---|
|
|
103
|
+
| **`index`** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
104
|
+
| **`brief` (project)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅¹ |
|
|
105
|
+
| **`brief` (directory/file)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
106
|
+
| **`callers`** | ✅ | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ⚠️³ |
|
|
107
|
+
| **`callees`** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
|
|
108
|
+
| **`implementations`** | ✅ | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ⚠️³ |
|
|
109
|
+
| **`affected`** | ✅ | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ✅² | ⚠️³ |
|
|
110
|
+
| **`pattern`** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅⁴ |
|
|
111
|
+
|
|
112
|
+
**Legend:**
|
|
113
|
+
- **✅** Fully supported for that action.
|
|
114
|
+
- **⚠️** Partial or best-effort support (see footnotes).
|
|
115
|
+
- **❌** Not supported for that action.
|
|
116
|
+
- **¹** Project-level brief works for any project with a recognized manifest (`package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`, etc.).
|
|
117
|
+
- **²** Requires an active LSP server for semantic resolution; falls back to heuristic text search otherwise.
|
|
118
|
+
- **³** Heuristic text-search fallback only; no semantic or structural resolution.
|
|
119
|
+
- **⁴** `pattern` works on any text file. Binary files (`.png`, `.jpg`, `.zip`, `.pdf`, etc.) are explicitly rejected.
|
|
120
|
+
|
|
121
|
+
## Confidence Labeling
|
|
122
|
+
|
|
123
|
+
Every result carries a `confidence` label from the result metadata:
|
|
124
|
+
|
|
125
|
+
| Label | Meaning |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `semantic` | Truth from LSP (definitions, references, diagnostics) |
|
|
128
|
+
| `structural` | From tree-sitter AST (outlines, imports/exports, syntax) |
|
|
129
|
+
| `heuristic` | Text search / best-effort inference |
|
|
130
|
+
| `unavailable` | No data could be produced |
|
|
131
|
+
|
|
132
|
+
## Result Metadata
|
|
133
|
+
|
|
134
|
+
**Contract:** `details` is returned for every action handler execution. It is
|
|
135
|
+
`undefined` *only* when the request is rejected before any handler runs
|
|
136
|
+
(parameter validation errors, unknown action, missing required `pattern`).
|
|
137
|
+
|
|
138
|
+
For no-result and error states, `details` carries `confidence: "unavailable"` or
|
|
139
|
+
`confidence: "heuristic"` with appropriately zeroed counts, so consumers always
|
|
140
|
+
get structured metadata back.
|
|
141
|
+
|
|
142
|
+
- **`brief`** → `BriefDetails` (confidence, focus target, start-here suggestions, public surfaces, dependency summary, omitted count, next queries)
|
|
143
|
+
- **`search`** → `SearchDetails` (callers/callees/implementations/pattern: confidence, scope, candidate count, omitted count)
|
|
144
|
+
- **`affected`** → `AffectedDetails` (direct count, downstream count, risk level, likely tests, check-next list)
|
|
145
|
+
|
|
146
|
+
## Parameter Validation
|
|
147
|
+
|
|
148
|
+
The tool enforces these rules and returns explicit error messages:
|
|
149
|
+
|
|
150
|
+
- `line`/`character` require `file`, not `path` — `path` is for scope/focus, `file` anchors a position
|
|
151
|
+
- `file` that points to a directory is rejected — use `path` for directory scoping
|
|
152
|
+
- Unknown actions are rejected with a list of supported actions
|
|
153
|
+
|
|
154
|
+
## Architecture
|
|
155
|
+
|
|
156
|
+
Each action employs an appropriate fallback chain from the available services:
|
|
157
|
+
|
|
158
|
+
- **`@mrclrchtr/supi-lsp`** — Semantic truth via LSP (references, symbols, implementations, diagnostics)
|
|
159
|
+
- **`@mrclrchtr/supi-tree-sitter`** — Structural extraction (outlines, imports/exports, callees, syntax context)
|
|
160
|
+
- **`@mrclrchtr/supi-core`** — Project/root utilities (root walking, known-root mapping, path containment)
|
|
161
|
+
|
|
162
|
+
**Per-action fallback chains:** `callers`, `implementations`, and `affected` use LSP → ripgrep text search; `callees` uses tree-sitter AST analysis; `brief` uses the architecture model (plus tree-sitter outline for anchored briefs); `pattern` and `index` use filesystem and text-search primitives.
|
|
163
|
+
|
|
164
|
+
### Programmatic API
|
|
165
|
+
|
|
166
|
+
The package exports its internal APIs for use by peer extensions:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
// Architecture model
|
|
170
|
+
export type { ArchitectureModel, DependencyEdge, ModuleInfo } from "./architecture.ts";
|
|
171
|
+
export { buildArchitectureModel, findModuleForPath, getDependencies, getDependents } from "./architecture.ts";
|
|
172
|
+
|
|
173
|
+
// Brief generation
|
|
174
|
+
export { generateFocusedBrief, generateOverview, generateProjectBrief } from "./brief.ts";
|
|
175
|
+
|
|
176
|
+
// Target resolution
|
|
177
|
+
export type { ResolvedTarget, TargetResolutionResult } from "./target-resolution.ts";
|
|
178
|
+
export { normalizePath, resolveAnchoredTarget, resolveSymbolTarget, toZeroBased } from "./target-resolution.ts";
|
|
179
|
+
|
|
180
|
+
// Result types
|
|
181
|
+
export type { AffectedDetails, BriefDetails, CodeIntelResult, ConfidenceMode, DisambiguationCandidate, SearchDetails } from "./types.ts";
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Session Integration
|
|
185
|
+
|
|
186
|
+
- The overview custom message type (`code-intelligence-overview`) uses `display: false` so it appears in the LLM context but not in the TUI message log
|
|
187
|
+
- On `session_start`, the extension scans the session branch for an existing overview to avoid re-injecting on `/reload` or session resume
|
|
188
|
+
- The `hasInjectedOverview` flag is per-session, reset each `session_start`
|
|
189
|
+
|
|
190
|
+
## Prompt Guidelines (full text)
|
|
191
|
+
|
|
192
|
+
These seven guidelines are injected into the system prompt:
|
|
193
|
+
|
|
194
|
+
> - Use `code_intel brief` before editing an unfamiliar package, directory, or file to get architecture context and reduce blind reads.
|
|
195
|
+
> - Use `code_intel affected` before changing exported APIs, shared helpers, config surfaces, or cross-package contracts to check blast radius and risk.
|
|
196
|
+
> - Use `code_intel callers` before modifying a function to verify all call sites; use `callees` and `implementations` for dependency and interface analysis.
|
|
197
|
+
> - Use `code_intel pattern` for bounded, scope-aware text search when the question is textual rather than semantic; it treats patterns as literal strings by default and supports `regex: true` when needed.
|
|
198
|
+
> - Use `code_intel index` for a factual project map (file counts, directory structure, landmark files) when you need to orient yourself in a new codebase.
|
|
199
|
+
> - After `code_intel` narrows the target, use raw `lsp` and `tree_sitter` tools for precise drill-down on exact symbols, types, or AST nodes.
|
|
200
|
+
> - Do not prefer `code_intel` over direct file reads or lower-level tools for trivial, already-localized edits or exact symbol/AST drill-down tasks.
|
|
201
|
+
|
|
202
|
+
## Install
|
|
203
|
+
|
|
204
|
+
Included in the `@mrclrchtr/supi` meta-package, or install standalone:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
pi install npm:@mrclrchtr/supi-code-intelligence
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Config-aware settings helper for SuPi config-backed settings sections.
|
|
2
|
+
// Wraps registerSettings() and centralizes selected-scope loading + scoped persistence.
|
|
3
|
+
|
|
4
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
5
|
+
import { loadSupiConfigForScope, removeSupiConfigKey, writeSupiConfig } from "./config.ts";
|
|
6
|
+
import type { SettingsScope } from "./settings-registry.ts";
|
|
7
|
+
import { registerSettings } from "./settings-registry.ts";
|
|
8
|
+
|
|
9
|
+
export interface ConfigSettingsHelpers {
|
|
10
|
+
/** Write a key to the selected scope's config section. */
|
|
11
|
+
set(key: string, value: unknown): void;
|
|
12
|
+
/** Remove a key from the selected scope's config section. */
|
|
13
|
+
unset(key: string): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ConfigSettingsOptions<T> {
|
|
17
|
+
/** Extension identifier — e.g. "lsp", "claude-md" */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Human-readable label shown in the UI */
|
|
20
|
+
label: string;
|
|
21
|
+
/** SuPi config section name — e.g. "lsp", "claude-md" */
|
|
22
|
+
section: string;
|
|
23
|
+
/** Default config values */
|
|
24
|
+
defaults: T;
|
|
25
|
+
/** Build SettingItem[] from scoped config. Called by loadValues. */
|
|
26
|
+
buildItems: (settings: T, scope: SettingsScope, cwd: string) => SettingItem[];
|
|
27
|
+
/** Handle a settings change with scoped persistence helpers. */
|
|
28
|
+
persistChange: (
|
|
29
|
+
scope: SettingsScope,
|
|
30
|
+
cwd: string,
|
|
31
|
+
settingId: string,
|
|
32
|
+
value: string,
|
|
33
|
+
helpers: ConfigSettingsHelpers,
|
|
34
|
+
) => void;
|
|
35
|
+
/** Optional home directory for config resolution (testing). */
|
|
36
|
+
homeDir?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Register a config-backed settings section.
|
|
41
|
+
*
|
|
42
|
+
* Loads display values from the selected scope only (`defaults <- selected scope`)
|
|
43
|
+
* instead of merged effective runtime config. Provides scoped `set` / `unset`
|
|
44
|
+
* persistence helpers so extensions don't need to wire `writeSupiConfig` /
|
|
45
|
+
* `removeSupiConfigKey` by hand.
|
|
46
|
+
*/
|
|
47
|
+
export function registerConfigSettings<T>(options: ConfigSettingsOptions<T>): void {
|
|
48
|
+
registerSettings({
|
|
49
|
+
id: options.id,
|
|
50
|
+
label: options.label,
|
|
51
|
+
loadValues: (scope, cwd) => {
|
|
52
|
+
const settings = loadSupiConfigForScope(options.section, cwd, options.defaults, {
|
|
53
|
+
scope,
|
|
54
|
+
homeDir: options.homeDir,
|
|
55
|
+
});
|
|
56
|
+
return options.buildItems(settings, scope, cwd);
|
|
57
|
+
},
|
|
58
|
+
persistChange: (scope, cwd, settingId, value) => {
|
|
59
|
+
const helpers: ConfigSettingsHelpers = {
|
|
60
|
+
set: (key, val) => {
|
|
61
|
+
writeSupiConfig(
|
|
62
|
+
{ section: options.section, scope, cwd },
|
|
63
|
+
{ [key]: val },
|
|
64
|
+
{ homeDir: options.homeDir },
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
unset: (key) => {
|
|
68
|
+
removeSupiConfigKey({ section: options.section, scope, cwd }, key, {
|
|
69
|
+
homeDir: options.homeDir,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
options.persistChange(scope, cwd, settingId, value, helpers);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Shared config system for SuPi extensions.
|
|
2
|
+
//
|
|
3
|
+
// Global config: ~/.pi/agent/supi/config.json
|
|
4
|
+
// Project config: .pi/supi/config.json (relative to cwd)
|
|
5
|
+
// Resolution: hardcoded defaults ← global ← project
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
|
|
11
|
+
const GLOBAL_CONFIG_DIR = ".pi/agent/supi";
|
|
12
|
+
const PROJECT_CONFIG_DIR = ".pi/supi";
|
|
13
|
+
const CONFIG_FILE = "config.json";
|
|
14
|
+
|
|
15
|
+
function getGlobalConfigPath(homeDir?: string): string {
|
|
16
|
+
return path.join(homeDir ?? os.homedir(), GLOBAL_CONFIG_DIR, CONFIG_FILE);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getProjectConfigPath(cwd: string): string {
|
|
20
|
+
return path.join(cwd, PROJECT_CONFIG_DIR, CONFIG_FILE);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readJsonFile(filePath: string): Record<string, unknown> | null {
|
|
24
|
+
let content: string;
|
|
25
|
+
try {
|
|
26
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
27
|
+
} catch {
|
|
28
|
+
// ENOENT or permission error — silent, file may not exist
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let parsed: unknown;
|
|
33
|
+
try {
|
|
34
|
+
parsed = JSON.parse(content);
|
|
35
|
+
} catch {
|
|
36
|
+
// biome-ignore lint/suspicious/noConsole: deliberate config parse warning
|
|
37
|
+
console.warn(`[supi-core] Failed to parse config file, ignoring: ${filePath}`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
42
|
+
return parsed as Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// biome-ignore lint/suspicious/noConsole: deliberate config parse warning
|
|
46
|
+
console.warn(`[supi-core] Config file root is not an object, ignoring: ${filePath}`);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function shallowMerge<T>(base: T, ...overrides: Array<Record<string, unknown> | null>): T {
|
|
51
|
+
let result = { ...base };
|
|
52
|
+
for (const override of overrides) {
|
|
53
|
+
if (!override) continue;
|
|
54
|
+
result = { ...result, ...override };
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SupiConfigOptions {
|
|
60
|
+
homeDir?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Load and merge config for a given extension section.
|
|
65
|
+
*
|
|
66
|
+
* Resolution order: defaults ← global ← project
|
|
67
|
+
*/
|
|
68
|
+
export function loadSupiConfig<T>(
|
|
69
|
+
section: string,
|
|
70
|
+
cwd: string,
|
|
71
|
+
defaults: T,
|
|
72
|
+
options?: SupiConfigOptions,
|
|
73
|
+
): T {
|
|
74
|
+
const globalConfig = readJsonFile(getGlobalConfigPath(options?.homeDir));
|
|
75
|
+
const projectConfig = readJsonFile(getProjectConfigPath(cwd));
|
|
76
|
+
|
|
77
|
+
const globalSection = extractSection(globalConfig, section);
|
|
78
|
+
const projectSection = extractSection(projectConfig, section);
|
|
79
|
+
|
|
80
|
+
return shallowMerge(defaults, globalSection, projectSection);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Load config for a single scope only.
|
|
85
|
+
*
|
|
86
|
+
* Resolution order: defaults ← selected scope
|
|
87
|
+
*
|
|
88
|
+
* This is useful for settings UIs that need to show the raw values stored in
|
|
89
|
+
* one scope, rather than the effective merged config.
|
|
90
|
+
*/
|
|
91
|
+
export function loadSupiConfigForScope<T>(
|
|
92
|
+
section: string,
|
|
93
|
+
cwd: string,
|
|
94
|
+
defaults: T,
|
|
95
|
+
options: { scope: "global" | "project" } & SupiConfigOptions,
|
|
96
|
+
): T {
|
|
97
|
+
const config =
|
|
98
|
+
options.scope === "global"
|
|
99
|
+
? readJsonFile(getGlobalConfigPath(options.homeDir))
|
|
100
|
+
: readJsonFile(getProjectConfigPath(cwd));
|
|
101
|
+
|
|
102
|
+
const scopedSection = extractSection(config, section);
|
|
103
|
+
return shallowMerge(defaults, scopedSection);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SupiConfigLocation {
|
|
107
|
+
section: string;
|
|
108
|
+
scope: "global" | "project";
|
|
109
|
+
cwd: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Write config values for a given extension section.
|
|
114
|
+
*/
|
|
115
|
+
export function writeSupiConfig(
|
|
116
|
+
loc: SupiConfigLocation,
|
|
117
|
+
value: Record<string, unknown>,
|
|
118
|
+
options?: SupiConfigOptions,
|
|
119
|
+
): void {
|
|
120
|
+
const configPath =
|
|
121
|
+
loc.scope === "global" ? getGlobalConfigPath(options?.homeDir) : getProjectConfigPath(loc.cwd);
|
|
122
|
+
|
|
123
|
+
const dir = path.dirname(configPath);
|
|
124
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
125
|
+
|
|
126
|
+
const existing = readJsonFile(configPath) ?? {};
|
|
127
|
+
existing[loc.section] = {
|
|
128
|
+
...((existing[loc.section] as Record<string, unknown>) ?? {}),
|
|
129
|
+
...value,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
fs.writeFileSync(configPath, `${JSON.stringify(existing, null, 2)}\n`, "utf-8");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Remove a key from a config section.
|
|
137
|
+
* Used by `interval default` to remove the project override.
|
|
138
|
+
*/
|
|
139
|
+
export function removeSupiConfigKey(
|
|
140
|
+
loc: SupiConfigLocation,
|
|
141
|
+
key: string,
|
|
142
|
+
options?: SupiConfigOptions,
|
|
143
|
+
): void {
|
|
144
|
+
const configPath =
|
|
145
|
+
loc.scope === "global" ? getGlobalConfigPath(options?.homeDir) : getProjectConfigPath(loc.cwd);
|
|
146
|
+
|
|
147
|
+
const existing = readJsonFile(configPath);
|
|
148
|
+
if (!existing) return;
|
|
149
|
+
|
|
150
|
+
const sectionData = existing[loc.section] as Record<string, unknown> | undefined;
|
|
151
|
+
if (!sectionData) return;
|
|
152
|
+
|
|
153
|
+
delete sectionData[key];
|
|
154
|
+
|
|
155
|
+
if (Object.keys(sectionData).length === 0) {
|
|
156
|
+
delete existing[loc.section];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const dir = path.dirname(configPath);
|
|
160
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
161
|
+
|
|
162
|
+
const content = Object.keys(existing).length > 0 ? `${JSON.stringify(existing, null, 2)}\n` : "";
|
|
163
|
+
|
|
164
|
+
if (content) {
|
|
165
|
+
// Directory guaranteed to exist since we just read from it
|
|
166
|
+
fs.writeFileSync(configPath, content, "utf-8");
|
|
167
|
+
} else {
|
|
168
|
+
try {
|
|
169
|
+
fs.unlinkSync(configPath);
|
|
170
|
+
} catch {
|
|
171
|
+
// File may not exist
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function extractSection(
|
|
177
|
+
config: Record<string, unknown> | null,
|
|
178
|
+
section: string,
|
|
179
|
+
): Record<string, unknown> | null {
|
|
180
|
+
if (!config) return null;
|
|
181
|
+
const data = config[section];
|
|
182
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
|
|
183
|
+
return data as Record<string, unknown>;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|