decorated-pi 0.2.2 → 0.4.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 +82 -74
- package/extensions/file-times.ts +124 -0
- package/extensions/guidance.ts +5 -3
- package/extensions/index.ts +6 -2
- package/extensions/io.ts +587 -0
- package/extensions/lsp/client.ts +181 -428
- package/extensions/lsp/env.ts +45 -12
- package/extensions/lsp/format.ts +102 -237
- package/extensions/lsp/index.ts +8 -11
- package/extensions/lsp/manager.ts +249 -0
- package/extensions/lsp/prompt.ts +3 -42
- package/extensions/lsp/protocol.ts +219 -0
- package/extensions/lsp/servers.ts +80 -160
- package/extensions/lsp/tools.ts +175 -510
- package/extensions/lsp/types.ts +42 -0
- package/extensions/mcp/builtin.ts +126 -0
- package/extensions/mcp/client.ts +106 -0
- package/extensions/mcp/index.ts +123 -0
- package/extensions/{extend-model.ts → model-integration.ts} +127 -4
- package/extensions/patch.ts +842 -0
- package/extensions/providers/ark-coding.ts +2 -0
- package/extensions/safety/detect.ts +78 -707
- package/extensions/safety/entropy.ts +226 -0
- package/extensions/safety/index.ts +44 -97
- package/extensions/safety/patterns.ts +155 -0
- package/extensions/safety/types.ts +50 -0
- package/extensions/settings.ts +10 -0
- package/extensions/slash.ts +165 -9
- package/extensions/smart-at.ts +339 -111
- package/extensions/subdir-agents.ts +43 -13
- package/package.json +3 -4
- package/tsconfig.json +16 -0
- package/extensions/lsp/server-manager.ts +0 -309
- package/extensions/lsp/trust.ts +0 -45
package/README.md
CHANGED
|
@@ -1,65 +1,64 @@
|
|
|
1
1
|
# decorated-pi
|
|
2
2
|
|
|
3
|
-
`decorated-pi` is a
|
|
3
|
+
`decorated-pi` is a practical enhancement pack for [Pi](https://github.com/earendil-works/pi).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:decorated-pi
|
|
9
|
+
pi install git:github.com/lcwecker/decorated-pi
|
|
10
|
+
pi install /path/to/decorated-pi
|
|
11
|
+
```
|
|
4
12
|
|
|
5
13
|
## Features
|
|
6
14
|
|
|
7
|
-
### 1.
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- c/cpp
|
|
55
|
-
- go
|
|
56
|
-
- java
|
|
57
|
-
- lua
|
|
58
|
-
- python
|
|
59
|
-
- ruby
|
|
60
|
-
- rust
|
|
61
|
-
- svelte
|
|
62
|
-
- typescript
|
|
15
|
+
### 1. Patch Tool
|
|
16
|
+
|
|
17
|
+
Replaces Pi's built-in `edit` / `write` with a stronger `patch` tool:
|
|
18
|
+
|
|
19
|
+
- **anchor mechanism** — narrows the search range by specifying a unique string that appears before `old_str`, preventing mismatches in files with repeated patterns
|
|
20
|
+
- **mtime tracking** — records file modification time on `read`, rejects `patch` if the file changed since last read, preventing blind or stale edits
|
|
21
|
+
- **explicit overwrite** — offer atomic `overwrite: true` mode for overwrite files or full-file creation to prevent unintened overwrite
|
|
22
|
+
|
|
23
|
+
### 2. Secret redaction
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
### 3. Built-in MCP Client
|
|
28
|
+
|
|
29
|
+
Zero-config MCP client with two built-in servers:
|
|
30
|
+
|
|
31
|
+
| Server | Tool Prefix | Source |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Context7 | `context7_*` | `https://mcp.context7.com/mcp` |
|
|
34
|
+
| Exa | `exa_*` | `https://mcp.exa.ai/mcp` |
|
|
35
|
+
|
|
36
|
+
**Project-level config** — add custom MCP servers by placing an `mcp.json` or `.mcp.json` (also `.pi/mcp.json`, `.agents/mcp.json`, `.claude/mcp.json` and their `.`-prefixed variants) anywhere in your project tree. Servers found closer to `cwd` take precedence over ancestor directories.
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"my-server": {
|
|
42
|
+
"url": "https://my-mcp.example.com/mcp",
|
|
43
|
+
"enabled": true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Global config** — persist custom servers in `~/.pi/agent/decorated-pi.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"my-server": { "url": "https://my-mcp.example.com/mcp" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Priority**: project > global > builtin. A server with the same name from a higher-priority source overrides lower ones.
|
|
60
|
+
|
|
61
|
+
Use `/mcp` to view connection status and registered tools.
|
|
63
62
|
|
|
64
63
|
### 4. Auxiliary Models (Image + Compact)
|
|
65
64
|
|
|
@@ -68,15 +67,32 @@ Uses cheaper models for auxiliary tasks, configured via `/dp-model`:
|
|
|
68
67
|
- **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)
|
|
69
68
|
- **Compact model** — uses a configured model for context compaction (instead of the main model), auto-resumes after compaction.
|
|
70
69
|
|
|
71
|
-
### 5.
|
|
70
|
+
### 5. Smart `@` File Search
|
|
71
|
+
|
|
72
|
+
Replaces Pi's default file search with a faster, project-aware search strategy:
|
|
73
|
+
|
|
74
|
+
- Uses project-aware file discovery
|
|
75
|
+
- Prioritizes filename-based matches for more intuitive results
|
|
76
|
+
- Reduces clutter from hidden, cache, and build directories
|
|
77
|
+
- Keeps default suggestions focused on visible project files
|
|
78
|
+
|
|
79
|
+
### 6. LSP Tool Suite
|
|
80
|
+
|
|
81
|
+
A cleaned-up, minimal LSP toolset. The extension keeps only the two LSP tools that cover the most practical coding workflows: checking diagnostics after edits and inspecting file structure before focused changes.
|
|
82
|
+
|
|
83
|
+
- **`lsp_diagnostics`** — file diagnostics with severity filtering
|
|
84
|
+
- **`lsp_document_symbols`** — file symbol outline
|
|
85
|
+
|
|
86
|
+
Supported languages: c/cpp, go, java, lua, json, python, ruby, rust, svelte, typescript
|
|
87
|
+
|
|
88
|
+
### 7. Dynamic Subdirectory `AGENTS.md` / `CLAUDE.md`
|
|
72
89
|
|
|
73
90
|
When the agent reads or edits a file:
|
|
74
91
|
|
|
75
92
|
- discovers `AGENTS.md` / `CLAUDE.md` in the file's directory and ancestor directories
|
|
76
93
|
- injects newly discovered guidance into tool results
|
|
77
|
-
- persists discovered files into the session so they are restored on resume
|
|
78
94
|
|
|
79
|
-
###
|
|
95
|
+
### 8. Extend Providers
|
|
80
96
|
|
|
81
97
|
Extend providers are registered via `/login` → "Use a subscription":
|
|
82
98
|
|
|
@@ -86,16 +102,6 @@ Extend providers are registered via `/login` → "Use a subscription":
|
|
|
86
102
|
| Baidu Qianfan | `qianfan.baidubce.com/v2/coding` |
|
|
87
103
|
| ARK Coding | `ark.cn-beijing.volces.com/api/coding/v3` |
|
|
88
104
|
|
|
89
|
-
## Install
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
pi install /path/to/decorated-pi #local
|
|
93
|
-
pi install npm:decorated-pi #npm
|
|
94
|
-
pi install git:github.com/lcwecker/decorated-pi #github
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Then reload Pi
|
|
98
|
-
|
|
99
105
|
## Configuration
|
|
100
106
|
|
|
101
107
|
Runtime settings are stored in:
|
|
@@ -110,24 +116,26 @@ Modules can be toggled on/off. Changes take effect after `/reload`.
|
|
|
110
116
|
|
|
111
117
|
| Module | Default | Effect when disabled |
|
|
112
118
|
| -------- | --------- | --------------------- |
|
|
113
|
-
| `
|
|
119
|
+
| `patch` | `true` | Reverts to Pi's built-in `edit` / `write` tools |
|
|
120
|
+
| `safety` | `true` | No secret redaction on `read` / `bash` output |
|
|
114
121
|
| `lsp` | `true` | All `lsp_*` tools unregistered — no diagnostics, hover, etc. |
|
|
115
122
|
| `smart-at` | `true` | Fallback to Pi's built-in `@` file completion |
|
|
123
|
+
| `mcp` | `true` | All `{server}_*` MCP tools unregistered |
|
|
116
124
|
|
|
117
125
|
Use `/dp-settings` to toggle, or edit the config file directly:
|
|
118
126
|
|
|
119
127
|
```json
|
|
120
128
|
{
|
|
121
129
|
"modules": {
|
|
130
|
+
"patch": true,
|
|
122
131
|
"safety": true,
|
|
123
132
|
"lsp": false,
|
|
124
|
-
"smart-at": true
|
|
133
|
+
"smart-at": true,
|
|
134
|
+
"mcp": true
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
```
|
|
128
138
|
|
|
129
|
-
Omitted keys default to `true` (enabled).
|
|
130
|
-
|
|
131
139
|
## License
|
|
132
140
|
|
|
133
141
|
MIT
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File mtime tracking for stale-read protection.
|
|
3
|
+
*
|
|
4
|
+
* - `read` tool records mtime when the LLM reads a file
|
|
5
|
+
* - `patch` tool checks mtime before editing — rejects if file changed since last read
|
|
6
|
+
* - `patch` tool updates mtime after a successful write
|
|
7
|
+
* - Markers are persisted to session custom entries and restored from the
|
|
8
|
+
* current branch after the last compaction boundary.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
|
|
14
|
+
export const FILE_TIMES_CUSTOM_TYPE = "decorated-pi.file-times";
|
|
15
|
+
|
|
16
|
+
export interface FileTimeMarkerData {
|
|
17
|
+
path: string;
|
|
18
|
+
mtimeMs: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SessionLikeEntry {
|
|
22
|
+
type: string;
|
|
23
|
+
customType?: string;
|
|
24
|
+
data?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Last-known mtime for each absolute file path (ms since epoch). */
|
|
28
|
+
const readMarkers = new Map<string, number>();
|
|
29
|
+
|
|
30
|
+
/** Get current file mtime in ms. Throws if file doesn't exist. */
|
|
31
|
+
function getFileMtime(absPath: string): number {
|
|
32
|
+
const stat = fs.statSync(absPath);
|
|
33
|
+
return stat.mtimeMs;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Record that the LLM has seen the current version of a file.
|
|
37
|
+
* Called after `read` tool completes and after `patch` writes. */
|
|
38
|
+
export function recordReadTime(absPath: string): void {
|
|
39
|
+
if (!fs.existsSync(absPath)) return;
|
|
40
|
+
readMarkers.set(absPath, getFileMtime(absPath));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Check if file has been modified since last read.
|
|
44
|
+
* Returns an error message if stale, or undefined if ok to edit. */
|
|
45
|
+
export function checkStaleFile(absPath: string, displayPath: string): string | undefined {
|
|
46
|
+
// If file doesn't exist on disk, always allow — creating a new file
|
|
47
|
+
// doesn't require reading it first, and recreating a deleted file is safe.
|
|
48
|
+
if (!fs.existsSync(absPath)) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const lastRead = readMarkers.get(absPath);
|
|
53
|
+
if (lastRead === undefined) {
|
|
54
|
+
// File exists but never read — must read first to avoid blind edits
|
|
55
|
+
return (
|
|
56
|
+
`File not read yet: ${displayPath}. ` +
|
|
57
|
+
`Please read the file with the read tool before editing.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const currentMtime = getFileMtime(absPath);
|
|
62
|
+
if (currentMtime > lastRead) {
|
|
63
|
+
return (
|
|
64
|
+
`File modified since last read: ${displayPath}. ` +
|
|
65
|
+
`Please re-read the file with the read tool before editing.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function toStoredPath(cwd: string, absPath: string): string {
|
|
73
|
+
const normalizedCwd = path.normalize(cwd);
|
|
74
|
+
const normalizedAbs = path.normalize(absPath);
|
|
75
|
+
const rel = path.relative(normalizedCwd, normalizedAbs);
|
|
76
|
+
if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) return rel;
|
|
77
|
+
return normalizedAbs;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function lastCompactionIndex(entries: SessionLikeEntry[]): number {
|
|
81
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
82
|
+
if (entries[i]?.type === "compaction") return i;
|
|
83
|
+
}
|
|
84
|
+
return -1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isFileTimeMarkerData(value: unknown): value is FileTimeMarkerData {
|
|
88
|
+
return !!value
|
|
89
|
+
&& typeof value === "object"
|
|
90
|
+
&& typeof (value as any).path === "string"
|
|
91
|
+
&& typeof (value as any).mtimeMs === "number";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Build a session-persisted marker payload for the current file version. */
|
|
95
|
+
export function createFileTimeMarkerData(cwd: string, absPath: string): FileTimeMarkerData | undefined {
|
|
96
|
+
if (!fs.existsSync(absPath)) return undefined;
|
|
97
|
+
return {
|
|
98
|
+
path: toStoredPath(cwd, absPath),
|
|
99
|
+
mtimeMs: getFileMtime(absPath),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Restore markers from the current branch, ignoring anything before the last compaction. */
|
|
104
|
+
export function restoreReadMarkersFromBranch(entries: SessionLikeEntry[], cwd: string): void {
|
|
105
|
+
clearReadMarkers();
|
|
106
|
+
const start = lastCompactionIndex(entries) + 1;
|
|
107
|
+
for (const entry of entries.slice(start)) {
|
|
108
|
+
if (entry.type !== "custom" || entry.customType !== FILE_TIMES_CUSTOM_TYPE) continue;
|
|
109
|
+
if (!isFileTimeMarkerData(entry.data)) continue;
|
|
110
|
+
const absPath = resolveAbsolutePath(cwd, entry.data.path);
|
|
111
|
+
readMarkers.set(absPath, entry.data.mtimeMs);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Clear all tracked file times (e.g., on session start or compaction). */
|
|
116
|
+
export function clearReadMarkers(): void {
|
|
117
|
+
readMarkers.clear();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Resolve a relative path to absolute (for consistent map keys). */
|
|
121
|
+
export function resolveAbsolutePath(cwd: string, filePath: string): string {
|
|
122
|
+
if (path.isAbsolute(filePath)) return path.normalize(filePath);
|
|
123
|
+
return path.normalize(path.resolve(cwd, filePath));
|
|
124
|
+
}
|
package/extensions/guidance.ts
CHANGED
|
@@ -9,9 +9,11 @@ export function setupGuidance(pi: ExtensionAPI) {
|
|
|
9
9
|
const guidance = [
|
|
10
10
|
DECORATED_PI_GUIDANCE_MARKER,
|
|
11
11
|
"",
|
|
12
|
-
"Before
|
|
13
|
-
"",
|
|
14
|
-
"
|
|
12
|
+
"- 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.",
|
|
13
|
+
"- Look before you leap! Ensure you have conducted thorough research before taking any action.",
|
|
14
|
+
"- Exercise caution when performing any **write** operations, especially when you are in a research or exploration phase.",
|
|
15
|
+
"- 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.",
|
|
16
|
+
"- CAUTION: Do not perform write operations in the following directories unless explicitly instructed: `node_modules`, `venv`, `env`, `__pycache__`, `.git` or any other hidden directories.",
|
|
15
17
|
].join("\n");
|
|
16
18
|
|
|
17
19
|
return {
|
package/extensions/index.ts
CHANGED
|
@@ -4,27 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { setupSafety } from "./safety/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { setupModelIntegration } from "./model-integration";
|
|
8
8
|
import { setupSlash } from "./slash";
|
|
9
9
|
import { setupSubdirAgents } from "./subdir-agents";
|
|
10
10
|
import { setupSessionTitle } from "./session-title";
|
|
11
|
+
import { setupIO } from "./io";
|
|
11
12
|
import { setupGuidance } from "./guidance";
|
|
12
13
|
import { setupLsp } from "./lsp/index";
|
|
13
14
|
import { setupProviders } from "./providers/index";
|
|
14
15
|
import { setupSmartAt } from "./smart-at";
|
|
16
|
+
import { setupMcp } from "./mcp/index.js";
|
|
15
17
|
import { isModuleEnabled } from "./settings";
|
|
16
18
|
|
|
17
19
|
export default function (pi: ExtensionAPI) {
|
|
18
20
|
// Always loaded — core commands and providers
|
|
19
21
|
setupSlash(pi);
|
|
20
22
|
setupProviders(pi);
|
|
21
|
-
|
|
23
|
+
setupModelIntegration(pi);
|
|
22
24
|
setupSubdirAgents(pi);
|
|
23
25
|
setupSessionTitle(pi);
|
|
24
26
|
setupGuidance(pi);
|
|
25
27
|
|
|
26
28
|
// Configurable modules
|
|
29
|
+
if (isModuleEnabled("patch")) setupIO(pi);
|
|
27
30
|
if (isModuleEnabled("safety")) setupSafety(pi);
|
|
28
31
|
if (isModuleEnabled("lsp")) setupLsp(pi);
|
|
29
32
|
if (isModuleEnabled("smart-at")) setupSmartAt(pi);
|
|
33
|
+
if (isModuleEnabled("mcp")) setupMcp(pi);
|
|
30
34
|
}
|