decorated-pi 0.4.1 → 0.5.1
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 +76 -59
- package/extensions/index.ts +130 -8
- package/extensions/io.ts +5 -3
- package/extensions/lsp/servers.ts +63 -3
- package/extensions/mcp/builtin.ts +260 -44
- package/extensions/mcp/client.ts +28 -19
- package/extensions/mcp/index.ts +336 -80
- package/extensions/model-integration.ts +6 -3
- package/extensions/rtk.ts +219 -0
- package/extensions/settings.ts +31 -1
- package/extensions/slash.ts +198 -66
- package/extensions/smart-at.ts +27 -1
- package/extensions/wakatime.ts +403 -0
- package/package.json +4 -5
- package/extensions/guidance.ts +0 -23
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# decorated-pi
|
|
2
2
|
|
|
3
|
-
`decorated-pi` is a practical enhancement pack for [Pi](https://github.com/earendil-works/pi).
|
|
3
|
+
`decorated-pi` is a practical enhancement pack for [Pi](https://github.com/earendil-works/pi) — smarter tools that are token efficient and cache friendly.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -14,67 +14,60 @@ pi install /path/to/decorated-pi
|
|
|
14
14
|
|
|
15
15
|
### 1. Patch Tool
|
|
16
16
|
|
|
17
|
-
Replaces Pi's built-in `edit` / `write` with a stronger `patch` tool
|
|
17
|
+
Replaces Pi's built-in `edit` / `write` with a stronger `patch` tool that adds unique safety and usability improvements on top of the native tools.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
| Capability | Pi native `edit` | `patch` |
|
|
20
|
+
| ------ | :---: | :---: |
|
|
21
|
+
| Exact string replacement | ✅ `oldText` | ✅ `old_str` |
|
|
22
|
+
| Atomic overwrite | ✅ `write` | ✅ `overwrite` |
|
|
23
|
+
| Syntax‑highlighted overwrite | ✅ streaming | ✅ incremental |
|
|
24
|
+
| **Anchor‑based search** | ❌ extending `oldText` for uniqueness | ✅ `anchor` bounds scope for precise matching |
|
|
25
|
+
| **Fuzzy whitespace match** | ❌ only reports "not found" | ✅ auto‑corrects tab↔space / trailing whitespace mismatches |
|
|
26
|
+
| **Edit fault diagnostics** | ❌ only reports "not found" | ✅ pinpoint faults for LLM comprehension |
|
|
27
|
+
| **Stale‑read protection** | ❌ Blind to external changes | ✅ `read` captures mtime, `patch` rejects stale targets |
|
|
22
28
|
|
|
23
|
-
### 2.
|
|
29
|
+
### 2. Smarter `@` File Search
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### 3. Built-in MCP Client
|
|
31
|
+
Replaces Pi's built-in `@` file completion with smarter matching and noise filtering:
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
| Aspect | Pi native `@` | `decorated-pi` |
|
|
34
|
+
| ------ | :---: | :---: |
|
|
35
|
+
| **Speed** | ❌ re‑scans filesystem on every trigger | ✅ caches once per `@` trigger |
|
|
36
|
+
| **Noise filtering** | ❌ no penalty system, shows hidden files | ✅ tiered penalty auto‑filters clutter |
|
|
37
|
+
| **Default suggestions** | ❌ all files visible on empty query | ✅ only visible project files |
|
|
38
|
+
| **Match precision** | ❌ case‑insensitive simple scoring | ✅ multi‑level case‑sensitive scoring |
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
| --- | --- | --- |
|
|
33
|
-
| Context7 | `context7_*` | `https://mcp.context7.com/mcp` |
|
|
34
|
-
| Exa | `exa_*` | `https://mcp.exa.ai/mcp` |
|
|
40
|
+
### 3. Secret redaction
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
{
|
|
40
|
-
"mcpServers": {
|
|
41
|
-
"my-server": {
|
|
42
|
-
"url": "https://my-mcp.example.com/mcp",
|
|
43
|
-
"enabled": true
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
42
|
+
Three-layer detection: high-confidence known-format patterns (AWS, GitHub, OpenAI, etc.), config-key regex matching, and adjusted Shannon entropy heuristics for unknown secret-like values.
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
Example redaction on a `read` / `bash` output:
|
|
50
45
|
|
|
51
46
|
```json
|
|
52
47
|
{
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
"aws_access_key_id": "AKI**************PLE",
|
|
49
|
+
"github_token": "ghp***************def",
|
|
50
|
+
"database_password": "Sup#######t99",
|
|
51
|
+
"api_key": "sk_**************f5a",
|
|
52
|
+
"random_secret": "a1b??????5f5"
|
|
56
53
|
}
|
|
57
54
|
```
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Use `/mcp` to view connection status and registered tools.
|
|
56
|
+
> `*` = known pattern, `#` = config key regex, `?` = entropy heuristic.
|
|
62
57
|
|
|
63
|
-
### 4. Auxiliary Models
|
|
58
|
+
### 4. Auxiliary Models
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
Offloads auxiliary ops to cheaper models, reducing cost on every session. Configured via `/dp-model`:
|
|
66
61
|
|
|
67
62
|
- **Image read fallback** — when the model reads an image file, detects type via magic bytes, calls a configured vision-capable model, and replaces the read result with image analysis text (jpeg, png, gif, webp)
|
|
68
|
-
- **Compact model** — uses a configured model for context compaction (instead of the main model)
|
|
63
|
+
- **Compact model** — uses a configured model for context compaction (instead of the main model).
|
|
69
64
|
|
|
70
|
-
### 5.
|
|
65
|
+
### 5. Progressive Context from `AGENTS.md` / `CLAUDE.md`
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
Extension capability: context is disclosed progressively as the agent works across different parts of the project.
|
|
73
68
|
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
- Reduces clutter from hidden, cache, and build directories
|
|
77
|
-
- Keeps default suggestions focused on visible project files
|
|
69
|
+
- When reading or editing a file, discovers `AGENTS.md` / `CLAUDE.md` in that file's directory and ancestor directories
|
|
70
|
+
- Newly discovered guidance is injected into tool results, scoped to the current context
|
|
78
71
|
|
|
79
72
|
### 6. LSP Tool Suite
|
|
80
73
|
|
|
@@ -85,12 +78,39 @@ A cleaned-up, minimal LSP toolset. The extension keeps only the two LSP tools th
|
|
|
85
78
|
|
|
86
79
|
Supported languages: c/cpp, go, java, lua, json, python, ruby, rust, svelte, typescript
|
|
87
80
|
|
|
88
|
-
### 7.
|
|
81
|
+
### 7. Built-in MCP Client
|
|
89
82
|
|
|
90
|
-
|
|
83
|
+
Zero-config MCP client with built-in servers:
|
|
91
84
|
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
| Server | Tool Prefix | Source |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| Context7 | `context7_*` | `https://mcp.context7.com/mcp` |
|
|
88
|
+
| Exa | `exa_*` | `https://mcp.exa.ai/mcp` |
|
|
89
|
+
|
|
90
|
+
**Custom servers** in `.pi/agent/mcp.json` (project) or `~/.pi/agent/decorated-pi.json` (global). Project overrides global.
|
|
91
|
+
Tool prompts and schemas are cached locally so MCP tools are available immediately on startup, even before servers connect.
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"my-server": {
|
|
97
|
+
"url": "https://my-mcp.example.com/mcp",
|
|
98
|
+
"enabled": true
|
|
99
|
+
},
|
|
100
|
+
"my-sse": {
|
|
101
|
+
"url": "https://my-mcp.example.com/sse",
|
|
102
|
+
"enabled": false
|
|
103
|
+
},
|
|
104
|
+
"my-stdio": {
|
|
105
|
+
"command": "npx",
|
|
106
|
+
"args": ["-y", "my-mcp-server"],
|
|
107
|
+
"env": { "DEBUG": "1" }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use `/mcp` to view connection status and registered tools.
|
|
94
114
|
|
|
95
115
|
### 8. Extend Providers
|
|
96
116
|
|
|
@@ -102,6 +122,11 @@ Extend providers are registered via `/login` → "Use a subscription":
|
|
|
102
122
|
| Baidu Qianfan | `qianfan.baidubce.com/v2/coding` |
|
|
103
123
|
| ARK Coding | `ark.cn-beijing.volces.com/api/coding/v3` |
|
|
104
124
|
|
|
125
|
+
### 9. Other
|
|
126
|
+
|
|
127
|
+
- **RTK** — integrates [RTK](https://github.com/rtk-ai/rtk) for token-efficient command output.
|
|
128
|
+
- **WakaTime** — tracks coding activity via [WakaTime](https://wakatime.com).
|
|
129
|
+
|
|
105
130
|
## Configuration
|
|
106
131
|
|
|
107
132
|
Runtime settings are stored in:
|
|
@@ -112,26 +137,18 @@ Runtime settings are stored in:
|
|
|
112
137
|
|
|
113
138
|
### Module Loading
|
|
114
139
|
|
|
115
|
-
Modules can be toggled on/off
|
|
116
|
-
|
|
117
|
-
| Module | Default | Effect when disabled |
|
|
118
|
-
| -------- | --------- | --------------------- |
|
|
119
|
-
| `patch` | `true` | Reverts to Pi's built-in `edit` / `write` tools |
|
|
120
|
-
| `safety` | `true` | No secret redaction on `read` / `bash` output |
|
|
121
|
-
| `lsp` | `true` | All `lsp_*` tools unregistered — no diagnostics, hover, etc. |
|
|
122
|
-
| `smart-at` | `true` | Fallback to Pi's built-in `@` file completion |
|
|
123
|
-
| `mcp` | `true` | All `{server}_*` MCP tools unregistered |
|
|
124
|
-
|
|
125
|
-
Use `/dp-settings` to toggle, or edit the config file directly:
|
|
140
|
+
Modules can be toggled on/off by `/dp-settings`. Changes take effect after `/reload`.
|
|
126
141
|
|
|
127
142
|
```json
|
|
128
143
|
{
|
|
129
144
|
"modules": {
|
|
130
145
|
"patch": true,
|
|
131
146
|
"safety": true,
|
|
132
|
-
"
|
|
147
|
+
"rtk": true,
|
|
148
|
+
"lsp": true,
|
|
133
149
|
"smart-at": true,
|
|
134
|
-
"mcp": true
|
|
150
|
+
"mcp": true,
|
|
151
|
+
"wakatime": true
|
|
135
152
|
}
|
|
136
153
|
}
|
|
137
154
|
```
|
package/extensions/index.ts
CHANGED
|
@@ -3,19 +3,138 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
|
|
7
|
+
// Extend prompt cache TTL for all providers: Anthropic 1h, OpenAI 24h
|
|
8
|
+
process.env.PI_CACHE_RETENTION ??= "long";
|
|
6
9
|
import { setupSafety } from "./safety/index.js";
|
|
7
10
|
import { setupModelIntegration } from "./model-integration";
|
|
8
11
|
import { setupSlash } from "./slash";
|
|
9
12
|
import { setupSubdirAgents } from "./subdir-agents";
|
|
10
13
|
import { setupSessionTitle } from "./session-title";
|
|
11
14
|
import { setupIO } from "./io";
|
|
12
|
-
import { setupGuidance } from "./guidance";
|
|
13
15
|
import { setupLsp } from "./lsp/index";
|
|
16
|
+
import { collectLspDependencyStatuses } from "./lsp/servers";
|
|
14
17
|
import { setupProviders } from "./providers/index";
|
|
15
|
-
import { setupSmartAt } from "./smart-at";
|
|
18
|
+
import { getSmartAtDependencyStatuses, setupSmartAt } from "./smart-at";
|
|
16
19
|
import { setupMcp } from "./mcp/index.js";
|
|
20
|
+
import { collectMcpDependencyStatuses } from "./mcp/builtin";
|
|
21
|
+
import { setupWakatime } from "./wakatime";
|
|
22
|
+
import { findSystemRtk, getRtkDependencyStatuses, setupRtkIntegration, type DependencyStatus } from "./rtk";
|
|
17
23
|
import { isModuleEnabled } from "./settings";
|
|
18
24
|
|
|
25
|
+
function collectDependencyStatuses(cwd: string): DependencyStatus[] {
|
|
26
|
+
const statuses: DependencyStatus[] = [];
|
|
27
|
+
if (isModuleEnabled("rtk")) statuses.push(...getRtkDependencyStatuses());
|
|
28
|
+
if (isModuleEnabled("smart-at")) statuses.push(...getSmartAtDependencyStatuses(cwd));
|
|
29
|
+
if (isModuleEnabled("lsp")) statuses.push(...collectLspDependencyStatuses(cwd));
|
|
30
|
+
if (isModuleEnabled("mcp")) statuses.push(...collectMcpDependencyStatuses(cwd));
|
|
31
|
+
return statuses;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatDependencyLines(statuses: DependencyStatus[]): string[] {
|
|
35
|
+
const missing = statuses.filter((item) => item.state === "missing");
|
|
36
|
+
const grouped = new Map<string, string[]>();
|
|
37
|
+
|
|
38
|
+
for (const item of missing) {
|
|
39
|
+
const labels = grouped.get(item.module) ?? [];
|
|
40
|
+
labels.push(item.label);
|
|
41
|
+
grouped.set(item.module, labels);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const lines = ["[decorated-pi] missing dependencies:"];
|
|
45
|
+
for (const [module, labels] of grouped) {
|
|
46
|
+
lines.push(` [${module}] ${labels.join(", ")}`);
|
|
47
|
+
}
|
|
48
|
+
return lines;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function setupDependencyReminders(pi: ExtensionAPI) {
|
|
52
|
+
let notifyTimer: ReturnType<typeof setTimeout> | undefined;
|
|
53
|
+
|
|
54
|
+
pi.on("session_start", async (event, ctx) => {
|
|
55
|
+
if (!ctx.hasUI) return;
|
|
56
|
+
if (event.reason !== "startup" && event.reason !== "reload") return;
|
|
57
|
+
|
|
58
|
+
const statuses = collectDependencyStatuses(ctx.cwd);
|
|
59
|
+
const missing = statuses.filter((item) => item.state === "missing");
|
|
60
|
+
if (missing.length === 0) return;
|
|
61
|
+
|
|
62
|
+
if (notifyTimer) clearTimeout(notifyTimer);
|
|
63
|
+
const message = formatDependencyLines(statuses).join("\n");
|
|
64
|
+
|
|
65
|
+
// Defer until after pi finishes startup/reload UI rebuild, otherwise
|
|
66
|
+
// notify() is appended to the chat and then wiped by rebuildChatFromMessages().
|
|
67
|
+
notifyTimer = setTimeout(() => {
|
|
68
|
+
notifyTimer = undefined;
|
|
69
|
+
try {
|
|
70
|
+
ctx.ui.notify(message, "info");
|
|
71
|
+
} catch {
|
|
72
|
+
// Extension context may be stale if another reload/session switch happened.
|
|
73
|
+
}
|
|
74
|
+
}, 0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
pi.on("session_shutdown", async () => {
|
|
78
|
+
if (!notifyTimer) return;
|
|
79
|
+
clearTimeout(notifyTimer);
|
|
80
|
+
notifyTimer = undefined;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const DECORATED_PI_GUIDANCE_MARKER = "## Decorated Pi Guidance";
|
|
85
|
+
|
|
86
|
+
function setupGuidance(pi: ExtensionAPI) {
|
|
87
|
+
pi.on("before_agent_start", async (event) => {
|
|
88
|
+
// Remove "Current date: YYYY-MM-DD" from system prompt to improve cache stability
|
|
89
|
+
let prompt = event.systemPrompt.replace(/\nCurrent date: \d{4}-\d{2}-\d{2}/, "");
|
|
90
|
+
|
|
91
|
+
if (!prompt.includes(DECORATED_PI_GUIDANCE_MARKER)) {
|
|
92
|
+
const guidance = [
|
|
93
|
+
DECORATED_PI_GUIDANCE_MARKER,
|
|
94
|
+
"",
|
|
95
|
+
"- Before acting on a user's prompt, ensure you fully understand their needs. If the intent is ambiguous, ask clarifying questions. Proceed only when the intent is clear.",
|
|
96
|
+
"- Look before you leap! Ensure you have conducted thorough research before taking any action.",
|
|
97
|
+
"- Exercise caution when performing any **write** operations, especially when you are in a research or exploration phase.",
|
|
98
|
+
"- You don't need to read **AGENTS.md** or **CLAUDE.md** files unless you're explicitly asked to, these files will loaded automatically if neccessary.",
|
|
99
|
+
"- CAUTION: Do not perform write operations in the following directories unless explicitly instructed: `node_modules`, `venv`, `env`, `__pycache__`, `.git` or any other hidden directories.",
|
|
100
|
+
"",
|
|
101
|
+
"### Secret Redaction",
|
|
102
|
+
"",
|
|
103
|
+
"- When you see masked secret values (e.g. `sk-***...***` where `*`, `#`, or `?` are mask characters), the real value has been redacted by the system. Do not attempt to read or guess it. If you need the secret, use tools like `jq` or `grep` to extract it from the original source file.",
|
|
104
|
+
].join("\n");
|
|
105
|
+
|
|
106
|
+
prompt = `${prompt}\n\n${guidance}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
sortSystemPromptOptions(event.systemPromptOptions);
|
|
110
|
+
return { systemPrompt: prompt };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Sort all fields in systemPromptOptions alphabetically for stable system prompt. */
|
|
115
|
+
export function sortSystemPromptOptions(opts: {
|
|
116
|
+
toolSnippets?: Record<string, string>;
|
|
117
|
+
selectedTools?: string[];
|
|
118
|
+
promptGuidelines?: string[];
|
|
119
|
+
skills?: Array<{ name: string; description: string; filePath: string }>;
|
|
120
|
+
}) {
|
|
121
|
+
const sortedToolNames = Object.keys(opts.toolSnippets ?? {}).sort((a, b) => a.localeCompare(b));
|
|
122
|
+
const sortedToolSnippets: Record<string, string> = {};
|
|
123
|
+
for (const name of sortedToolNames) {
|
|
124
|
+
sortedToolSnippets[name] = opts.toolSnippets![name];
|
|
125
|
+
}
|
|
126
|
+
opts.toolSnippets = sortedToolSnippets;
|
|
127
|
+
if (opts.selectedTools) {
|
|
128
|
+
opts.selectedTools = sortedToolNames;
|
|
129
|
+
}
|
|
130
|
+
if (opts.promptGuidelines) {
|
|
131
|
+
opts.promptGuidelines = [...opts.promptGuidelines].sort((a, b) => a.localeCompare(b));
|
|
132
|
+
}
|
|
133
|
+
if (opts.skills) {
|
|
134
|
+
opts.skills = [...opts.skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
19
138
|
export default function (pi: ExtensionAPI) {
|
|
20
139
|
// Always loaded — core commands and providers
|
|
21
140
|
setupSlash(pi);
|
|
@@ -24,11 +143,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
24
143
|
setupSubdirAgents(pi);
|
|
25
144
|
setupSessionTitle(pi);
|
|
26
145
|
setupGuidance(pi);
|
|
146
|
+
setupDependencyReminders(pi);
|
|
27
147
|
|
|
28
148
|
// Configurable modules
|
|
29
|
-
if (isModuleEnabled("patch"))
|
|
30
|
-
if (isModuleEnabled("safety"))
|
|
31
|
-
if (isModuleEnabled("lsp"))
|
|
32
|
-
if (isModuleEnabled("smart-at"))
|
|
33
|
-
if (isModuleEnabled("mcp"))
|
|
34
|
-
|
|
149
|
+
if (isModuleEnabled("patch")) setupIO(pi);
|
|
150
|
+
if (isModuleEnabled("safety")) setupSafety(pi);
|
|
151
|
+
if (isModuleEnabled("lsp")) setupLsp(pi);
|
|
152
|
+
if (isModuleEnabled("smart-at")) setupSmartAt(pi);
|
|
153
|
+
if (isModuleEnabled("mcp")) setupMcp(pi);
|
|
154
|
+
if (isModuleEnabled("wakatime")) setupWakatime(pi);
|
|
155
|
+
if (isModuleEnabled("rtk") && findSystemRtk()) setupRtkIntegration(pi);
|
|
156
|
+
}
|
package/extensions/io.ts
CHANGED
|
@@ -193,7 +193,7 @@ function normalizeDisplayText(text: string): string {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function replaceTabs(text: string): string {
|
|
196
|
-
return text.replace(/\t/g, "
|
|
196
|
+
return text.replace(/\t/g, " ");
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
function highlightSingleLine(line: string, lang: string | undefined): string {
|
|
@@ -302,7 +302,7 @@ function appendPatchDiffChildren(parent: Box, body: string, theme: any): void {
|
|
|
302
302
|
|
|
303
303
|
const flush = () => {
|
|
304
304
|
if (buffer.length === 0) return;
|
|
305
|
-
parent.addChild(new Text(renderDiff(buffer.join("\n")), 0, 0));
|
|
305
|
+
parent.addChild(new Text(renderDiff(replaceTabs(buffer.join("\n"))), 0, 0));
|
|
306
306
|
buffer = [];
|
|
307
307
|
};
|
|
308
308
|
|
|
@@ -336,7 +336,7 @@ export function buildPatchCallComponent(component: PatchCallComponent, args: any
|
|
|
336
336
|
if (args?.path) {
|
|
337
337
|
label = theme.fg("accent", args.path);
|
|
338
338
|
if (args.overwrite) {
|
|
339
|
-
label += theme.fg("
|
|
339
|
+
label += theme.fg("error", " [overwrite]");
|
|
340
340
|
} else if (args.edits?.length > 0) {
|
|
341
341
|
label += theme.fg("dim", ` (${args.edits.length} edit${args.edits.length > 1 ? "s" : ""})`);
|
|
342
342
|
}
|
|
@@ -447,6 +447,8 @@ export function setupIO(pi: ExtensionAPI) {
|
|
|
447
447
|
promptGuidelines: [
|
|
448
448
|
"Always prefer modifying files with PATCH tool over bash commands or python scripts.",
|
|
449
449
|
"For full-file replacement, always use patch tool to prevent unintended edits or data loss.",
|
|
450
|
+
"To prevent hallucinations: 1. Keep each edit batch ≤ 5 changes; 2. Process remaining revisions in sequential steps",
|
|
451
|
+
"On repeated failures: read the file first to confirm information accuracy.",
|
|
450
452
|
],
|
|
451
453
|
parameters: PatchSchema,
|
|
452
454
|
renderShell: "self",
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LSP Server Config — language detection, server commands, workspace roots.
|
|
3
3
|
*/
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
4
|
+
import { existsSync, readdirSync, type Dirent } from "node:fs";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
5
6
|
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import type { DependencyStatus } from "../rtk";
|
|
6
8
|
|
|
7
9
|
// ─── File extension → language mapping ────────────────────────────────────
|
|
8
10
|
|
|
@@ -39,8 +41,8 @@ const LANGUAGE_SERVERS: Record<string, Omit<LanguageConfig, "is_project_local">>
|
|
|
39
41
|
install_hint: "Install clangd and ensure the clangd binary is on PATH.",
|
|
40
42
|
},
|
|
41
43
|
python: {
|
|
42
|
-
language: "python", command: "
|
|
43
|
-
install_hint: "Install Python LSP
|
|
44
|
+
language: "python", command: "pyright-langserver", args: ["--stdio"],
|
|
45
|
+
install_hint: "Install a Python LSP and ensure the pyright-langserver binary is on PATH.",
|
|
44
46
|
},
|
|
45
47
|
rust: {
|
|
46
48
|
language: "rust", command: "rust-analyzer", args: [],
|
|
@@ -110,6 +112,54 @@ export function languageIdForFile(filePath: string): string | undefined {
|
|
|
110
112
|
return detectLanguage(filePath);
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
export function detectProjectLanguages(cwd: string, limit = 1500): Set<string> {
|
|
116
|
+
const found = new Set<string>();
|
|
117
|
+
let seen = 0;
|
|
118
|
+
const skipDirs = new Set([".git", "node_modules", ".pi", "dist", "build", "coverage"]);
|
|
119
|
+
|
|
120
|
+
function walk(dir: string): void {
|
|
121
|
+
if (seen >= limit) return;
|
|
122
|
+
let entries: Dirent[] = [];
|
|
123
|
+
try {
|
|
124
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
125
|
+
} catch {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (seen >= limit) return;
|
|
130
|
+
const full = join(dir, entry.name);
|
|
131
|
+
if (entry.isDirectory()) {
|
|
132
|
+
if (skipDirs.has(entry.name) || entry.name.startsWith('.')) continue;
|
|
133
|
+
walk(full);
|
|
134
|
+
} else if (entry.isFile()) {
|
|
135
|
+
seen++;
|
|
136
|
+
const language = detectLanguage(full);
|
|
137
|
+
if (language) found.add(language);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
walk(cwd);
|
|
143
|
+
return found;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function collectLspDependencyStatuses(cwd: string): DependencyStatus[] {
|
|
147
|
+
const statuses: DependencyStatus[] = [];
|
|
148
|
+
const seen = new Set<string>();
|
|
149
|
+
for (const language of listSupportedLanguages()) {
|
|
150
|
+
const cfg = getServerConfig(language, cwd);
|
|
151
|
+
if (!cfg || seen.has(cfg.command)) continue;
|
|
152
|
+
seen.add(cfg.command);
|
|
153
|
+
statuses.push({
|
|
154
|
+
module: "lsp",
|
|
155
|
+
label: cfg.command,
|
|
156
|
+
state: commandExists(cfg.command) ? "ok" : "missing",
|
|
157
|
+
detail: cfg.install_hint,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return statuses;
|
|
161
|
+
}
|
|
162
|
+
|
|
113
163
|
export function findWorkspaceRoot(
|
|
114
164
|
filePath: string,
|
|
115
165
|
fallback = process.cwd(),
|
|
@@ -124,6 +174,16 @@ export function findWorkspaceRoot(
|
|
|
124
174
|
|
|
125
175
|
// ─── Internal helpers ─────────────────────────────────────────────────────
|
|
126
176
|
|
|
177
|
+
function commandExists(command: string): boolean {
|
|
178
|
+
if (isAbsolute(command) || command.includes("/") || command.includes("\\")) {
|
|
179
|
+
return existsSync(command);
|
|
180
|
+
}
|
|
181
|
+
const result = process.platform === "win32"
|
|
182
|
+
? spawnSync("where", [command], { encoding: "utf-8" })
|
|
183
|
+
: spawnSync(process.env.SHELL || "sh", ["-lc", `command -v '${command.replace(/'/g, `'"'"'`)}'`], { encoding: "utf-8" });
|
|
184
|
+
return result.status === 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
127
187
|
function resolveLocalBinary(
|
|
128
188
|
command: string,
|
|
129
189
|
cwd: string,
|