decorated-pi 0.7.3 → 0.8.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/index.ts CHANGED
@@ -23,8 +23,12 @@ import { createSkeleton } from "./hooks/skeleton.js";
23
23
  import { setupRedact, REDACT_GUIDANCE } from "./hooks/redact.js";
24
24
  import { externalizeModule } from "./hooks/externalize.js";
25
25
  import { normalizeCodeblocksModule } from "./hooks/normalize-codeblocks.js";
26
+ import { thinkingLabelStripModule } from "./hooks/thinking-label-strip.js";
26
27
  import { trackMtimeModule } from "./hooks/track-mtime.js";
27
- import { injectAgentsMdModule, INJECT_AGENTS_MD_GUIDANCE } from "./hooks/inject-agents-md.js";
28
+ import {
29
+ injectAgentsMdModule,
30
+ INJECT_AGENTS_MD_GUIDANCE,
31
+ } from "./hooks/inject-agents-md.js";
28
32
  import { imageVisionModule } from "./hooks/image-vision.js";
29
33
  import { smartAtModule } from "./hooks/smart-at.js";
30
34
  import { sessionTitleModule } from "./hooks/session-title.js";
@@ -39,7 +43,11 @@ import { registerLspTools } from "./tools/lsp/tools.js";
39
43
  import { LspServerManager } from "./tools/lsp/manager.js";
40
44
  import { collectLspDependencyStatuses } from "./tools/lsp/servers.js";
41
45
  import { registerAskTool } from "./tools/ask/index.js";
42
- import { resolveMcpConfigs, migrateLegacyGlobalMcpConfig, collectMcpDependencyStatuses } from "./tools/mcp/config.js";
46
+ import {
47
+ resolveMcpConfigs,
48
+ migrateLegacyGlobalMcpConfig,
49
+ collectMcpDependencyStatuses,
50
+ } from "./tools/mcp/config.js";
43
51
  import { ensureMcpServerReady } from "./hooks/mcp.js";
44
52
 
45
53
  import { registerDpModelCommand } from "./commands/dp-model.js";
@@ -56,80 +64,98 @@ import { captureModuleSnapshot, isModuleEnabled } from "./settings.js";
56
64
  // its constant and pushing it here. No priority / sort logic — just push.
57
65
 
58
66
  const BASE_GUIDANCE = [
59
- "## Decorated Pi Guidance",
60
- "",
61
- "### Workflow, how to approach tasks",
62
- "- Before acting on a prompt, do sufficient research on the existing state — read files, search, investigate — and only proceed once you have a clear picture.",
63
- "- Exercise caution when performing any **write** operations, especially when you are in a research or exploration phase.",
64
- "- Before modifying code, match the user's existing code style (naming, formatting, patterns). Do not re-modify lines the user has manually edited since your last change.",
65
- "",
66
- "### Filesystem Safety, where NOT to write",
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.",
67
+ "## Decorated Pi Guidance",
68
+ "",
69
+ "### Workflow, how to approach tasks",
70
+ "- Before acting on a prompt, do sufficient research on the existing state — read files, search, investigate — and only proceed once you have a clear picture.",
71
+ "- Exercise caution when performing any **write** operations, especially when you are in a research or exploration phase.",
72
+ "- Before modifying code, match the user's existing code style (naming, formatting, patterns). Do not re-modify lines the user has manually edited since your last change.",
73
+ "",
74
+ "### Filesystem Safety, where NOT to write",
75
+ "- CAUTION: Do not perform write operations in the following directories unless explicitly instructed: `node_modules`, `venv`, `env`, `__pycache__`, `.git` or any other hidden directories.",
76
+ ].join("\n");
77
+
78
+ // Adapted from hexiecs/talk-normal (MIT), prompt-chatgpt.md v0.6.2.
79
+ const TALK_NORMAL_GUIDANCE = [
80
+ "## Your talking style",
81
+ "",
82
+ "- Be direct and informative. No filler, no fluff, but give enough to be useful.",
83
+ "- Lead with the answer; add context only if it helps.",
84
+ "- Prefer direct positive claims. Avoid negation-based contrastive phrasing like `不是X,而是Y` / `it's not X, it's Y`; state the positive claim directly.",
85
+ "- Kill filler: `I'd be happy to`, `Great question`, `It's worth noting`, `Certainly`, `Of course`, `首先`, `值得注意的是`, `综上所述`.",
86
+ "- Never restate the question.",
87
+ "- Yes/no questions: answer first, then give one sentence of reasoning.",
88
+ "- Comparisons: give a recommendation with brief reasoning, not a balanced essay.",
89
+ "- Code: give the code plus a usage example when non-trivial. Skip preambles like `Certainly! Here is...`.",
90
+ "- Use bullets or numbered lists only when the content has real parallel or sequential structure.",
91
+ "- Do not end with conditional follow-up offers like `If you want, I can...` / `如果你愿意,我还可以...`; take the real next step or name it directly.",
92
+ "- Do not use summary-stamp closings like `In summary`, `Hope this helps`, `一句话总结`, `总结一下`, `简而言之`; state the final claim directly.",
68
93
  ].join("\n");
69
94
 
70
95
  /** Remove the injected Pi documentation block from the base system prompt.
71
96
  * Matches a line containing "Pi documentation" and deletes it plus all
72
97
  * following non-empty lines, stopping at the first blank line. */
73
98
  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;
99
+ const lines = prompt.split("\n");
100
+ const out: string[] = [];
101
+ let i = 0;
102
+ while (i < lines.length) {
103
+ const line = lines[i];
104
+ if (line.includes("Pi documentation")) {
105
+ i++;
106
+ while (i < lines.length && lines[i].trim() !== "") i++;
107
+ // Drop the terminating blank line as well so we don't leave orphan whitespace.
108
+ if (i < lines.length && lines[i].trim() === "") i++;
109
+ continue;
110
+ }
111
+ out.push(line);
112
+ i++;
85
113
  }
86
- out.push(line);
87
- i++;
88
- }
89
- return out.join("\n");
114
+ return out.join("\n");
90
115
  }
91
116
 
92
117
  /** Sort the <available_skills> block in the system prompt by skill name.
93
118
  * Pi core appends extension-provided skills after user/project skills and does
94
119
  * not sort the XML; this makes the final prompt stable and cache-friendly. */
95
120
  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);
121
+ const startMarker = "\n<available_skills>";
122
+ const endMarker = "</available_skills>";
123
+ const startIdx = prompt.indexOf(startMarker);
124
+ if (startIdx === -1) return prompt;
125
+ const endIdx = prompt.indexOf(endMarker, startIdx);
126
+ if (endIdx === -1) return prompt;
127
+
128
+ const before = prompt.slice(0, startIdx + startMarker.length);
129
+ const after = prompt.slice(endIdx);
130
+ const inner = prompt.slice(startIdx + startMarker.length, endIdx);
131
+
132
+ const chunks: string[][] = [];
133
+ let current: string[] = [];
134
+ for (const line of inner.split("\n")) {
135
+ const trimmed = line.trim();
136
+ if (trimmed === "<skill>") {
137
+ current = [line];
138
+ } else if (trimmed === "</skill>") {
139
+ current.push(line);
140
+ chunks.push(current);
141
+ current = [];
142
+ } else if (current.length > 0) {
143
+ current.push(line);
144
+ }
119
145
  }
120
- }
121
146
 
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
- };
147
+ const nameOf = (chunk: string[]) => {
148
+ const line = chunk.find((l) => l.trim().startsWith("<name>"));
149
+ if (!line) return "";
150
+ const t = line.trim();
151
+ return t.slice(6, t.indexOf("</name>"));
152
+ };
128
153
 
129
- chunks.sort((a, b) => nameOf(a).localeCompare(nameOf(b)));
154
+ chunks.sort((a, b) => nameOf(a).localeCompare(nameOf(b)));
130
155
 
131
- const sortedInner = "\n" + chunks.map((chunk) => chunk.join("\n")).join("\n") + "\n";
132
- return before + sortedInner + after;
156
+ const sortedInner =
157
+ "\n" + chunks.map((chunk) => chunk.join("\n")).join("\n") + "\n";
158
+ return before + sortedInner + after;
133
159
  }
134
160
 
135
161
  /** Build the list of guideline strings to inject, in prompt order.
@@ -139,146 +165,151 @@ export function sortSkillsInSystemPrompt(prompt: string): string {
139
165
  * injected). The mcp module check lives in `resolveMcpConfigs`, so this
140
166
  * code only needs to look at the server's own `enabled` flag. */
141
167
  function buildGuidelines(): string[] {
142
- const out: string[] = [
143
- BASE_GUIDANCE,
144
- INJECT_AGENTS_MD_GUIDANCE, // from hooks/inject-agents-md.ts — always on
145
- ];
146
- if (isModuleEnabled("secretRedaction")) out.push(REDACT_GUIDANCE);
147
- return out;
168
+ const out: string[] = [
169
+ BASE_GUIDANCE,
170
+ TALK_NORMAL_GUIDANCE,
171
+ INJECT_AGENTS_MD_GUIDANCE, // from hooks/inject-agents-md.ts — always on
172
+ ];
173
+ if (isModuleEnabled("secretRedaction")) out.push(REDACT_GUIDANCE);
174
+ return out;
148
175
  }
149
176
 
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;
177
+ function canRegisterMcpServer(
178
+ config: { name: string; command?: string },
179
+ deps: Array<{ module: string; state: string }>,
180
+ ): boolean {
181
+ if (!config.command) return true;
182
+ const dep = deps.find((d) => d.module === `mcp:${config.name}`);
183
+ return dep ? dep.state === "ok" : true;
154
184
  }
155
185
 
156
186
  /** Absolute path to the plugin's builtin skills directory.
157
187
  * Used by `resources_discover` so the skill travels with the plugin
158
188
  * regardless of which project pi is running in. */
159
189
  export function getBuiltinSkillPaths(): string[] {
160
- return [fileURLToPath(new URL("./skills", import.meta.url))];
190
+ return [fileURLToPath(new URL("./skills", import.meta.url))];
161
191
  }
162
192
 
163
193
  /** Register the plugin's builtin skill paths with Pi core. */
164
194
  function installBuiltinSkills(pi: ExtensionAPI): void {
165
- pi.on("resources_discover", async (_event: any) => ({
166
- skillPaths: getBuiltinSkillPaths(),
167
- }));
195
+ pi.on("resources_discover", async (_event: any) => ({
196
+ skillPaths: getBuiltinSkillPaths(),
197
+ }));
168
198
  }
169
199
 
170
200
  /** Install a single before_agent_start handler that appends every
171
- * guideline in order, stripping the volatile "Current date: …" line
172
- * for cache stability. Idempotent — re-injection is a no-op via marker. */
201
+ * guideline in order. Idempotent re-injection is a no-op via marker. */
173
202
  function installGuidelines(pi: ExtensionAPI): void {
174
- const blocks = buildGuidelines();
175
- const joined = blocks.join("\n\n");
176
- const marker = "## Decorated Pi Guidance";
177
-
178
- pi.on("before_agent_start", async (event: any) => {
179
- if (!event.systemPrompt) return undefined;
180
- let prompt: string = stripPiDocsBlock(event.systemPrompt);
181
- prompt = sortSkillsInSystemPrompt(prompt);
182
- prompt = prompt.replace(/\nCurrent date: \d{4}-\d{2}-\d{2}/, "");
183
- if (prompt.includes(marker)) return undefined; // already injected this turn
184
- return { systemPrompt: `${prompt}\n\n${joined}` };
185
- });
203
+ const blocks = buildGuidelines();
204
+ const joined = blocks.join("\n\n");
205
+ const marker = "## Decorated Pi Guidance";
206
+
207
+ pi.on("before_agent_start", async (event: any) => {
208
+ if (!event.systemPrompt) return undefined;
209
+ let prompt: string = stripPiDocsBlock(event.systemPrompt);
210
+ prompt = sortSkillsInSystemPrompt(prompt);
211
+ if (prompt.includes(marker)) return undefined; // already injected this turn
212
+ return { systemPrompt: `${prompt}\n\n${joined}` };
213
+ });
186
214
  }
187
215
 
188
216
  export default async function (pi: ExtensionAPI) {
189
- // Snapshot the module settings that pi is about to load. /dp-settings
190
- // compares against this to avoid prompting for reload when the user
191
- // has only returned the settings to the currently-loaded state.
192
- captureModuleSnapshot();
193
-
194
- // ── Skeleton (hooks) ───────────────────────────────────────────────────
195
- const sk = createSkeleton();
196
-
197
- // Order matters for tool_result compose chain:
198
- // 1. redact → normalize-codeblocks → externalize → track-mtime → inject-agents-md → image-vision → wakatime
199
- // The first module registered for a given event runs first (compose chain).
200
- if (isModuleEnabled("secretRedaction")) setupRedact(sk);
201
- sk.register(normalizeCodeblocksModule);
202
- sk.register(externalizeModule);
203
- sk.register(trackMtimeModule);
204
- sk.register(injectAgentsMdModule);
205
- sk.register(imageVisionModule);
206
-
207
- // session_start handlers (parallel)
208
- // pi-tool-filter must register first so native tools are dropped before
209
- // anything else inspects the tool list.
210
- sk.register(piToolFilterModule);
211
- sk.register(sessionTitleModule);
212
- if (isModuleEnabled("atOverride")) sk.register(smartAtModule);
213
- if (isModuleEnabled("wakatime")) sk.register(wakatimeModule);
214
-
215
- // Compaction + RTK (these also install their own pi.on via setup<>()).
216
- setupCompaction(sk);
217
- if (isModuleEnabled("rtk")) setupRtk(sk);
218
- if (isModuleEnabled("wakatime")) setupWakatime(sk, pi);
219
-
220
- // ── Tools (conditional on module switches) ────────────────────────────
221
- if (isModuleEnabled("patchOverrideEdit")) registerPatchTool(pi);
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
- if (dep.state !== "ok") {
229
- sk.declareMissing({
230
- name: dep.label,
231
- module: "lsp",
232
- hint: dep.detail,
233
- });
234
- }
235
- }
236
- }
237
- if (isModuleEnabled("ask")) registerAskTool(pi);
238
-
239
- // MCP: hook, tools, and /mcp command are gated together. Disabling the
240
- // module means no session_start handler runs, no tools register, no
241
- // /mcp command is available, and no background connections are attempted.
242
- if (isModuleEnabled("mcp")) {
243
- // One-time migration: legacy global MCP configs in
244
- // ~/.pi/agent/decorated-pi.json move to ~/.pi/agent/mcp.json. Run
245
- // explicitly here so `loadGlobalMcpConfigs` stays pure.
246
- migrateLegacyGlobalMcpConfig();
247
- sk.register(mcpModule);
248
- const mcpDeps = collectMcpDependencyStatuses(process.cwd());
249
- for (const dep of mcpDeps) {
250
- if (dep.state !== "ok") {
251
- sk.declareMissing({
252
- name: dep.label, // binary name (e.g. "codegraph")
253
- module: "mcp",
254
- hint: dep.detail,
255
- });
256
- }
217
+ // Snapshot the module settings that pi is about to load. /dp-settings
218
+ // compares against this to avoid prompting for reload when the user
219
+ // has only returned the settings to the currently-loaded state.
220
+ captureModuleSnapshot();
221
+
222
+ // ── Skeleton (hooks) ───────────────────────────────────────────────────
223
+ const sk = createSkeleton();
224
+
225
+ // Order matters for tool_result compose chain:
226
+ // 1. redact → normalize-codeblocks → externalize → track-mtime → inject-agents-md → image-vision → wakatime
227
+ // The first module registered for a given event runs first (compose chain).
228
+ if (isModuleEnabled("secretRedaction")) setupRedact(sk);
229
+ sk.register(normalizeCodeblocksModule);
230
+ sk.register(thinkingLabelStripModule);
231
+ sk.register(externalizeModule);
232
+ sk.register(trackMtimeModule);
233
+ sk.register(injectAgentsMdModule);
234
+ sk.register(imageVisionModule);
235
+
236
+ // session_start handlers (parallel)
237
+ // pi-tool-filter must register first so native tools are dropped before
238
+ // anything else inspects the tool list.
239
+ sk.register(piToolFilterModule);
240
+ sk.register(sessionTitleModule);
241
+ if (isModuleEnabled("atOverride")) sk.register(smartAtModule);
242
+ if (isModuleEnabled("wakatime")) sk.register(wakatimeModule);
243
+
244
+ // Compaction + RTK (these also install their own pi.on via setup<>()).
245
+ setupCompaction(sk);
246
+ if (isModuleEnabled("rtk")) setupRtk(sk);
247
+ if (isModuleEnabled("wakatime")) setupWakatime(sk, pi);
248
+
249
+ // ── Tools (conditional on module switches) ────────────────────────────
250
+ if (isModuleEnabled("patchOverrideEdit")) registerPatchTool(pi);
251
+ if (isModuleEnabled("lsp")) {
252
+ const lspDeps = collectLspDependencyStatuses(process.cwd());
253
+ if (lspDeps.some((d) => d.state === "ok")) {
254
+ registerLspTools(pi, new LspServerManager());
255
+ }
256
+ for (const dep of lspDeps) {
257
+ if (dep.state !== "ok") {
258
+ sk.declareMissing({
259
+ name: dep.label,
260
+ module: "lsp",
261
+ hint: dep.detail,
262
+ });
263
+ }
264
+ }
257
265
  }
258
- const configs = resolveMcpConfigs(process.cwd()).filter(s => s.enabled);
259
- // Per-server readiness: cache hit → register from cache (fast).
260
- // Cache miss connect synchronously, write cache, then register
261
- // live tools. This blocks startup only for cache-miss servers.
262
- // Skip servers whose binary is missing (dependency not met).
263
- for (const config of configs) {
264
- if (!canRegisterMcpServer(config, mcpDeps)) continue;
265
- await ensureMcpServerReady(pi, config, process.cwd());
266
+ if (isModuleEnabled("ask")) registerAskTool(pi);
267
+
268
+ // MCP: hook, tools, and /mcp command are gated together. Disabling the
269
+ // module means no session_start handler runs, no tools register, no
270
+ // /mcp command is available, and no background connections are attempted.
271
+ if (isModuleEnabled("mcp")) {
272
+ // One-time migration: legacy global MCP configs in
273
+ // ~/.pi/agent/decorated-pi.json move to ~/.pi/agent/mcp.json. Run
274
+ // explicitly here so `loadGlobalMcpConfigs` stays pure.
275
+ migrateLegacyGlobalMcpConfig();
276
+ sk.register(mcpModule);
277
+ const mcpDeps = collectMcpDependencyStatuses(process.cwd());
278
+ for (const dep of mcpDeps) {
279
+ if (dep.state !== "ok") {
280
+ sk.declareMissing({
281
+ name: dep.label, // binary name (e.g. "codegraph")
282
+ module: "mcp",
283
+ hint: dep.detail,
284
+ });
285
+ }
286
+ }
287
+ const configs = resolveMcpConfigs(process.cwd()).filter(
288
+ (s) => s.enabled,
289
+ );
290
+ // Per-server readiness: cache hit → register from cache (fast).
291
+ // Cache miss → connect synchronously, write cache, then register
292
+ // live tools. This blocks startup only for cache-miss servers.
293
+ // Skip servers whose binary is missing (dependency not met).
294
+ for (const config of configs) {
295
+ if (!canRegisterMcpServer(config, mcpDeps)) continue;
296
+ await ensureMcpServerReady(pi, config, process.cwd());
297
+ }
298
+ registerMcpStatusCommand(pi);
266
299
  }
267
- registerMcpStatusCommand(pi);
268
- }
269
300
 
270
- // ── Builtin skills (travel with the plugin in every project) ─────────────
271
- installBuiltinSkills(pi);
301
+ // ── Builtin skills (travel with the plugin in every project) ─────────────
302
+ installBuiltinSkills(pi);
272
303
 
273
- // ── System-prompt guidelines (single handler, array order = prompt order) ──
274
- installGuidelines(pi);
304
+ // ── System-prompt guidelines (single handler, array order = prompt order) ──
305
+ installGuidelines(pi);
275
306
 
276
- // ── Commands ──────────────────────────────────────────────────────────
277
- registerDpModelCommand(pi);
278
- registerDpSettingsCommand(pi);
279
- if (isModuleEnabled("retry")) registerRetryCommand(pi);
280
- if (isModuleEnabled("usage")) registerUsageCommand(pi);
307
+ // ── Commands ──────────────────────────────────────────────────────────
308
+ registerDpModelCommand(pi);
309
+ registerDpSettingsCommand(pi);
310
+ if (isModuleEnabled("retry")) registerRetryCommand(pi);
311
+ if (isModuleEnabled("usage")) registerUsageCommand(pi);
281
312
 
282
- // ── Install skeleton (last) ────────────────────────────────────────────
283
- sk.install(pi);
313
+ // ── Install skeleton (last) ────────────────────────────────────────────
314
+ sk.install(pi);
284
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decorated-pi",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
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",
@@ -26,7 +26,9 @@
26
26
  "dependencies": {
27
27
  "@ff-labs/fff-node": "^0.9.4",
28
28
  "@modelcontextprotocol/sdk": "^1.29.0",
29
+ "chardet": "^2.2.0",
29
30
  "file-type": "^21.3.4",
31
+ "iconv-lite": "^0.7.2",
30
32
  "openai": "^6.37.0"
31
33
  },
32
34
  "peerDependencies": {
@@ -4,10 +4,9 @@ description: pi docs resources
4
4
  ---
5
5
 
6
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
7
+ - Main documentation: `$(npm root -g)/@earendil-works/pi-coding-agent/README.md`
8
+ - Additional docs: `$(npm root -g)/@earendil-works/pi-coding-agent/docs`
9
+ - Examples: `$(npm root -g)/@earendil-works/pi-coding-agent/examples` (extensions, custom tools, SDK)
11
10
  - 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
11
  - When working on pi topics, read the docs and examples, and follow .md cross-references before implementing
13
12
  - Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)
@@ -17,7 +17,7 @@ const askQuestionSchema = Type.Object({
17
17
  { description: "text = free input, single = one option, multi = many options" },
18
18
  ),
19
19
  question: Type.String({ description: "Question text shown to the user." }),
20
- options: Type.Optional(Type.Array(Type.String(), { description: "Options for single or multi choice." })),
20
+ options: Type.Optional(Type.Array(Type.String(), { description: "Options for single or multi choice. Each option MUST be a plain string (not an object). Example: [\"选项A\", \"选项B\", \"选项C\"]. The user picks by index; do NOT pass {id,text} objects." })),
21
21
  default: Type.Optional(Type.String({ description: "Default answer. For multi, comma-separated values." })),
22
22
  });
23
23