decorated-pi 0.7.1 → 0.7.2
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 +12 -7
- package/hooks/inject-agents-md.ts +20 -12
- package/hooks/mcp.ts +61 -64
- package/hooks/pi-tool-filter.ts +2 -1
- package/hooks/rtk.ts +3 -2
- package/hooks/skeleton.ts +46 -18
- package/hooks/smart-at.ts +8 -0
- package/hooks/wakatime.ts +3 -2
- package/index.ts +117 -3
- package/package.json +2 -4
- package/settings.ts +5 -3
- package/skills/pi-docs/SKILL.md +13 -0
- package/tools/lsp/tools.ts +0 -1
- package/tools/mcp/builtin/codegraph.ts +1 -1
- package/tools/mcp/client.ts +1 -1
- package/tools/patch/core.ts +10 -0
- package/tools/patch/index.ts +338 -226
package/index.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
19
20
|
|
|
20
21
|
import { createSkeleton } from "./hooks/skeleton.js";
|
|
21
22
|
|
|
@@ -36,8 +37,9 @@ import { setupRtk } from "./hooks/rtk.js";
|
|
|
36
37
|
import { registerPatchTool } from "./tools/patch/index.js";
|
|
37
38
|
import { registerLspTools } from "./tools/lsp/tools.js";
|
|
38
39
|
import { LspServerManager } from "./tools/lsp/manager.js";
|
|
40
|
+
import { collectLspDependencyStatuses } from "./tools/lsp/servers.js";
|
|
39
41
|
import { registerAskTool } from "./tools/ask/index.js";
|
|
40
|
-
import { resolveMcpConfigs, migrateLegacyGlobalMcpConfig } from "./tools/mcp/config.js";
|
|
42
|
+
import { resolveMcpConfigs, migrateLegacyGlobalMcpConfig, collectMcpDependencyStatuses } from "./tools/mcp/config.js";
|
|
41
43
|
import { ensureMcpServerReady } from "./hooks/mcp.js";
|
|
42
44
|
|
|
43
45
|
import { registerDpModelCommand } from "./commands/dp-model.js";
|
|
@@ -65,6 +67,71 @@ const BASE_GUIDANCE = [
|
|
|
65
67
|
"- CAUTION: Do not perform write operations in the following directories unless explicitly instructed: `node_modules`, `venv`, `env`, `__pycache__`, `.git` or any other hidden directories.",
|
|
66
68
|
].join("\n");
|
|
67
69
|
|
|
70
|
+
/** Remove the injected Pi documentation block from the base system prompt.
|
|
71
|
+
* Matches a line containing "Pi documentation" and deletes it plus all
|
|
72
|
+
* following non-empty lines, stopping at the first blank line. */
|
|
73
|
+
export function stripPiDocsBlock(prompt: string): string {
|
|
74
|
+
const lines = prompt.split("\n");
|
|
75
|
+
const out: string[] = [];
|
|
76
|
+
let i = 0;
|
|
77
|
+
while (i < lines.length) {
|
|
78
|
+
const line = lines[i];
|
|
79
|
+
if (line.includes("Pi documentation")) {
|
|
80
|
+
i++;
|
|
81
|
+
while (i < lines.length && lines[i].trim() !== "") i++;
|
|
82
|
+
// Drop the terminating blank line as well so we don't leave orphan whitespace.
|
|
83
|
+
if (i < lines.length && lines[i].trim() === "") i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
out.push(line);
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
return out.join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Sort the <available_skills> block in the system prompt by skill name.
|
|
93
|
+
* Pi core appends extension-provided skills after user/project skills and does
|
|
94
|
+
* not sort the XML; this makes the final prompt stable and cache-friendly. */
|
|
95
|
+
export function sortSkillsInSystemPrompt(prompt: string): string {
|
|
96
|
+
const startMarker = "\n<available_skills>";
|
|
97
|
+
const endMarker = "</available_skills>";
|
|
98
|
+
const startIdx = prompt.indexOf(startMarker);
|
|
99
|
+
if (startIdx === -1) return prompt;
|
|
100
|
+
const endIdx = prompt.indexOf(endMarker, startIdx);
|
|
101
|
+
if (endIdx === -1) return prompt;
|
|
102
|
+
|
|
103
|
+
const before = prompt.slice(0, startIdx + startMarker.length);
|
|
104
|
+
const after = prompt.slice(endIdx);
|
|
105
|
+
const inner = prompt.slice(startIdx + startMarker.length, endIdx);
|
|
106
|
+
|
|
107
|
+
const chunks: string[][] = [];
|
|
108
|
+
let current: string[] = [];
|
|
109
|
+
for (const line of inner.split("\n")) {
|
|
110
|
+
const trimmed = line.trim();
|
|
111
|
+
if (trimmed === "<skill>") {
|
|
112
|
+
current = [line];
|
|
113
|
+
} else if (trimmed === "</skill>") {
|
|
114
|
+
current.push(line);
|
|
115
|
+
chunks.push(current);
|
|
116
|
+
current = [];
|
|
117
|
+
} else if (current.length > 0) {
|
|
118
|
+
current.push(line);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const nameOf = (chunk: string[]) => {
|
|
123
|
+
const line = chunk.find((l) => l.trim().startsWith("<name>"));
|
|
124
|
+
if (!line) return "";
|
|
125
|
+
const t = line.trim();
|
|
126
|
+
return t.slice(6, t.indexOf("</name>"));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
chunks.sort((a, b) => nameOf(a).localeCompare(nameOf(b)));
|
|
130
|
+
|
|
131
|
+
const sortedInner = "\n" + chunks.map((chunk) => chunk.join("\n")).join("\n") + "\n";
|
|
132
|
+
return before + sortedInner + after;
|
|
133
|
+
}
|
|
134
|
+
|
|
68
135
|
/** Build the list of guideline strings to inject, in prompt order.
|
|
69
136
|
* Always-on rules first, then per-module guidelines. REDACT_GUIDANCE is
|
|
70
137
|
* gated by the secretRedaction module; CodeGraph guidance follows the MCP
|
|
@@ -80,6 +147,26 @@ function buildGuidelines(): string[] {
|
|
|
80
147
|
return out;
|
|
81
148
|
}
|
|
82
149
|
|
|
150
|
+
function canRegisterMcpServer(config: { name: string; command?: string }, deps: Array<{ module: string; state: string }>): boolean {
|
|
151
|
+
if (!config.command) return true;
|
|
152
|
+
const dep = deps.find((d) => d.module === `mcp:${config.name}`);
|
|
153
|
+
return dep ? dep.state === "ok" : true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Absolute path to the plugin's builtin skills directory.
|
|
157
|
+
* Used by `resources_discover` so the skill travels with the plugin
|
|
158
|
+
* regardless of which project pi is running in. */
|
|
159
|
+
export function getBuiltinSkillPaths(): string[] {
|
|
160
|
+
return [fileURLToPath(new URL("./skills", import.meta.url))];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Register the plugin's builtin skill paths with Pi core. */
|
|
164
|
+
function installBuiltinSkills(pi: ExtensionAPI): void {
|
|
165
|
+
pi.on("resources_discover", async (_event: any) => ({
|
|
166
|
+
skillPaths: getBuiltinSkillPaths(),
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
83
170
|
/** Install a single before_agent_start handler that appends every
|
|
84
171
|
* guideline in order, stripping the volatile "Current date: …" line
|
|
85
172
|
* for cache stability. Idempotent — re-injection is a no-op via marker. */
|
|
@@ -90,7 +177,9 @@ function installGuidelines(pi: ExtensionAPI): void {
|
|
|
90
177
|
|
|
91
178
|
pi.on("before_agent_start", async (event: any) => {
|
|
92
179
|
if (!event.systemPrompt) return undefined;
|
|
93
|
-
let prompt: string = event.systemPrompt
|
|
180
|
+
let prompt: string = stripPiDocsBlock(event.systemPrompt);
|
|
181
|
+
prompt = sortSkillsInSystemPrompt(prompt);
|
|
182
|
+
prompt = prompt.replace(/\nCurrent date: \d{4}-\d{2}-\d{2}/, "");
|
|
94
183
|
if (prompt.includes(marker)) return undefined; // already injected this turn
|
|
95
184
|
return { systemPrompt: `${prompt}\n\n${joined}` };
|
|
96
185
|
});
|
|
@@ -130,7 +219,19 @@ export default async function (pi: ExtensionAPI) {
|
|
|
130
219
|
|
|
131
220
|
// ── Tools (conditional on module switches) ────────────────────────────
|
|
132
221
|
if (isModuleEnabled("patchOverrideEdit")) registerPatchTool(pi);
|
|
133
|
-
if (isModuleEnabled("lsp"))
|
|
222
|
+
if (isModuleEnabled("lsp")) {
|
|
223
|
+
const lspDeps = collectLspDependencyStatuses(process.cwd());
|
|
224
|
+
if (lspDeps.some((d) => d.state === "ok")) {
|
|
225
|
+
registerLspTools(pi, new LspServerManager());
|
|
226
|
+
}
|
|
227
|
+
for (const dep of lspDeps) {
|
|
228
|
+
sk.declareDependency({
|
|
229
|
+
label: `lsp:${dep.label}`,
|
|
230
|
+
module: `lsp:${dep.label}`,
|
|
231
|
+
check: () => collectLspDependencyStatuses(process.cwd()).some((s) => s.label === dep.label && s.state === "ok"),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
134
235
|
if (isModuleEnabled("ask")) registerAskTool(pi);
|
|
135
236
|
|
|
136
237
|
// MCP: hook, tools, and /mcp command are gated together. Disabling the
|
|
@@ -142,16 +243,29 @@ export default async function (pi: ExtensionAPI) {
|
|
|
142
243
|
// explicitly here so `loadGlobalMcpConfigs` stays pure.
|
|
143
244
|
migrateLegacyGlobalMcpConfig();
|
|
144
245
|
sk.register(mcpModule);
|
|
246
|
+
const mcpDeps = collectMcpDependencyStatuses(process.cwd());
|
|
247
|
+
for (const dep of mcpDeps) {
|
|
248
|
+
sk.declareDependency({
|
|
249
|
+
label: dep.module,
|
|
250
|
+
module: dep.module,
|
|
251
|
+
check: () => collectMcpDependencyStatuses(process.cwd()).some((s) => s.module === dep.module && s.state === "ok"),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
145
254
|
const configs = resolveMcpConfigs(process.cwd()).filter(s => s.enabled);
|
|
146
255
|
// Per-server readiness: cache hit → register from cache (fast).
|
|
147
256
|
// Cache miss → connect synchronously, write cache, then register
|
|
148
257
|
// live tools. This blocks startup only for cache-miss servers.
|
|
258
|
+
// Skip servers whose binary is missing (dependency not met).
|
|
149
259
|
for (const config of configs) {
|
|
260
|
+
if (!canRegisterMcpServer(config, mcpDeps)) continue;
|
|
150
261
|
await ensureMcpServerReady(pi, config, process.cwd());
|
|
151
262
|
}
|
|
152
263
|
registerMcpStatusCommand(pi);
|
|
153
264
|
}
|
|
154
265
|
|
|
266
|
+
// ── Builtin skills (travel with the plugin in every project) ─────────────
|
|
267
|
+
installBuiltinSkills(pi);
|
|
268
|
+
|
|
155
269
|
// ── System-prompt guidelines (single handler, array order = prompt order) ──
|
|
156
270
|
installGuidelines(pi);
|
|
157
271
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "decorated-pi",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "decorated-pi is a practical enhancement pack for pi coding agent — token-efficient workflow, cache-friendly design, and smarter tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -14,9 +14,7 @@
|
|
|
14
14
|
"secret-redaction",
|
|
15
15
|
"codegraph",
|
|
16
16
|
"fff",
|
|
17
|
-
"
|
|
18
|
-
"fuzzy-search",
|
|
19
|
-
"file-finder"
|
|
17
|
+
"fuzzy-search"
|
|
20
18
|
],
|
|
21
19
|
"license": "MIT",
|
|
22
20
|
"repository": {
|
package/settings.ts
CHANGED
|
@@ -82,9 +82,11 @@ export function loadConfig(): DecoratedPiConfig {
|
|
|
82
82
|
try {
|
|
83
83
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
84
84
|
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8")) as DecoratedPiConfig;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
// Mutate in place. Do NOT call saveConfig here — saveConfig calls
|
|
86
|
+
// loadConfig first, which would re-trigger the migration (and the
|
|
87
|
+
// file on disk hasn't been written yet), causing deep recursion.
|
|
88
|
+
// The migrated shape is persisted on the next explicit saveConfig.
|
|
89
|
+
migrateModuleSettings(config);
|
|
88
90
|
return config;
|
|
89
91
|
}
|
|
90
92
|
} catch {}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-docs
|
|
3
|
+
description: pi docs resources
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Pi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):
|
|
7
|
+
- Main documentation: /home/liuchang/.local/share/fnm/node-versions/v24.14.0/installation/lib/node_modules/@earendil-works/pi-coding-agent/README.md
|
|
8
|
+
- Additional docs: /home/liuchang/.local/share/fnm/node-versions/v24.14.0/installation/lib/node_modules/@earendil-works/pi-coding-agent/docs
|
|
9
|
+
- Examples: /home/liuchang/.local/share/fnm/node-versions/v24.14.0/installation/lib/node_modules/@earendil-works/pi-coding-agent/examples (extensions, custom tools, SDK)
|
|
10
|
+
- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory
|
|
11
|
+
- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)
|
|
12
|
+
- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing
|
|
13
|
+
- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)
|
package/tools/lsp/tools.ts
CHANGED
|
@@ -69,7 +69,6 @@ function severityLabel(s: number): string {
|
|
|
69
69
|
// ─── Register tools ───────────────────────────────────────────────────────
|
|
70
70
|
|
|
71
71
|
export function registerLspTools(pi: ExtensionAPI, manager: LspServerManager) {
|
|
72
|
-
|
|
73
72
|
// ── lsp_diagnostics ────────────────────────────────────────────────────
|
|
74
73
|
pi.registerTool({
|
|
75
74
|
name: "lsp_diagnostics",
|
|
@@ -14,7 +14,7 @@ export const CODEGRAPH_BUILTIN: Omit<McpServerConfig, "source"> = {
|
|
|
14
14
|
name: "codegraph",
|
|
15
15
|
command: "codegraph",
|
|
16
16
|
args: ["serve", "--mcp"],
|
|
17
|
-
enabled:
|
|
17
|
+
enabled: true,
|
|
18
18
|
description:
|
|
19
19
|
"Local code knowledge graph (colbymchenry/codegraph). Enable via /mcp.",
|
|
20
20
|
canUseInProject: (cwd: string) => fs.existsSync(path.join(cwd, ".codegraph")),
|
package/tools/mcp/client.ts
CHANGED
|
@@ -16,7 +16,7 @@ export class McpConnection {
|
|
|
16
16
|
client: Client;
|
|
17
17
|
transport: StreamableHTTPClientTransport | SSEClientTransport | StdioClientTransport | undefined;
|
|
18
18
|
tools: McpToolSpec[] = [];
|
|
19
|
-
|
|
19
|
+
connected = false;
|
|
20
20
|
|
|
21
21
|
source: string = "unknown";
|
|
22
22
|
|
package/tools/patch/core.ts
CHANGED
|
@@ -1521,3 +1521,13 @@ function truncate(s: string, maxLen = 60): string {
|
|
|
1521
1521
|
if (firstLine.length <= maxLen) return firstLine;
|
|
1522
1522
|
return firstLine.slice(0, maxLen - 3) + "...";
|
|
1523
1523
|
}
|
|
1524
|
+
|
|
1525
|
+
// Test exports
|
|
1526
|
+
export const __patchCoreTest = {
|
|
1527
|
+
charOffsetToLine,
|
|
1528
|
+
detectTabWidth,
|
|
1529
|
+
normalizeIndentForFuzzy,
|
|
1530
|
+
truncate,
|
|
1531
|
+
collapseSequentialReplacements,
|
|
1532
|
+
generateReplacementDiff,
|
|
1533
|
+
};
|