pi-tool-discipline 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jiangkoumo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # pi-tool-discipline
2
+
3
+ pi extension that enforces a **search-tools-first discipline** and removes the
4
+ conflicting "use bash for file operations" guidance pi injects by default.
5
+
6
+ ## Why
7
+
8
+ Pi's system-prompt builder only injects
9
+ `Use bash for file operations like ls, rg, find` when **no tool named
10
+ `grep` / `find` / `ls` is active** ([dist/core/system-prompt.js]). FFF-style
11
+ search tools are named `ffgrep` / `fffind`, so pi mistakes them for "no search
12
+ tool available" and tells the model to use bash for everything — directly
13
+ fighting project instructions (like an `AGENTS.md` that says "use the grep
14
+ tool, not bash").
15
+
16
+ ## How it works
17
+
18
+ Two mechanisms, applied automatically in every session:
19
+
20
+ 1. **Placeholder tools (root fix).** Registers placeholder tools named
21
+ `grep`, `find`, and `ls` (skipped if already registered). They carry no
22
+ prompt snippet, so they never appear in the model's tool list and are never
23
+ callable in practice. Their mere presence flips pi's `hasGrep`/`hasFind`/
24
+ `hasLs` check, so the conflicting bash guideline is **never generated**.
25
+ 2. **System-prompt injection (fallback + rules).** On every agent start,
26
+ appends an idempotent "Tool Discipline" section to the system prompt:
27
+ content search with `ffgrep`, path search with `fffind`, file reads with
28
+ `read` (offset/limit), no bash `grep`/`rg`/`find`/`ls`/`cat`/`sed`/`head`/
29
+ `tail`/`which` for searching, bash reserved for pipelines/git/npm/network.
30
+ Also strips the default bash guideline text in environments where the
31
+ placeholder registration is disabled.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pi install npm:pi-tool-discipline
37
+ # or try without installing:
38
+ pi -e npm:pi-tool-discipline
39
+ ```
40
+
41
+ Requires `@ff-labs/pi-fff` (or any other extension providing `ffgrep`/`fffind`)
42
+ for the discipline to point at real search tools.
43
+
44
+ ## Verify
45
+
46
+ Run `/tool-discipline` in a pi session — it reports whether the placeholder
47
+ tools are registered and whether the discipline section is injected.
48
+
49
+ You can also inspect the system prompt: the string
50
+ `Use bash for file operations like ls, rg, find` should no longer appear, and
51
+ `## Tool Discipline (pi-tool-discipline)` should be present.
52
+
53
+ ## Security
54
+
55
+ This extension runs with full system access like any pi extension. It only
56
+ registers inert placeholder tools and appends text to the system prompt; it
57
+ does not execute commands, read files, or touch network. Review the source in
58
+ `extensions/index.ts` before installing.
59
+
60
+ ## License
61
+
62
+ MIT
@@ -0,0 +1,118 @@
1
+ /**
2
+ * pi-tool-discipline
3
+ *
4
+ * Enforces a "search tools first" file-search discipline in pi sessions and
5
+ * neutralizes the built-in "Use bash for file operations like ls, rg, find"
6
+ * guideline that conflicts with project instructions (e.g. AGENTS.md).
7
+ *
8
+ * Why it exists:
9
+ * pi only injects the bash file-operation guideline when NO tool named
10
+ * `grep` / `find` / `ls` is active (dist/core/system-prompt.js). FFF-style
11
+ * search tools are named `ffgrep` / `fffind`, so pi mistakenly thinks no
12
+ * search tool exists and tells the model to use bash — fighting AGENTS.md
13
+ * rules that say to use the grep tool instead.
14
+ *
15
+ * How it works:
16
+ * A. Registers placeholder tools named `grep` / `find` / `ls` (skipped when
17
+ * already registered). They carry no promptSnippet, so they never appear
18
+ * in the model's tool list and are never actually callable in practice.
19
+ * Their presence flips pi's hasGrep/hasFind/hasLs check, so the
20
+ * conflicting bash guideline is never generated.
21
+ * B. On before_agent_start, appends the tool-discipline rules to the system
22
+ * prompt (idempotent) and strips the bash guideline text as a fallback
23
+ * for environments where the placeholder registration is disabled.
24
+ */
25
+
26
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
27
+ import { Type } from "typebox";
28
+
29
+ const MARK = "<!-- pi-tool-discipline:v1 -->";
30
+
31
+ /** Placeholder tool names that flip pi's hasGrep/hasFind/hasLs checks. */
32
+ const PLACEHOLDER_NAMES = ["grep", "find", "ls"] as const;
33
+
34
+ /**
35
+ * The exact guidelines pi's builder injects when no search tool is active.
36
+ * Stripped from the system prompt as a fallback (plan B).
37
+ */
38
+ const BASH_GUIDELINES = [
39
+ "Use bash for file operations like ls, rg, find",
40
+ "Use bash or PowerShell for file operations like listing, searching, and finding files",
41
+ "Use PowerShell for file operations like listing, searching, and finding files",
42
+ ];
43
+
44
+ const DISCIPLINE = `
45
+ ## Tool Discipline (pi-tool-discipline)
46
+
47
+ Search and read files with dedicated tools, not bash:
48
+
49
+ - Content search: use \`ffgrep\`. Path search: use \`fffind\`.
50
+ - Read files with \`read\` (use \`offset\`/\`limit\` for large files).
51
+ - Do NOT use the bash tool for \`grep\`/\`rg\`/\`find\`/\`ls\`/\`cat\`/\`sed\`/\`head\`/\`tail\`/\`which\` searches or file reads.
52
+ - Bash stays allowed only when dedicated tools cannot do the job: pipelines, git, npm, running programs, network requests, file mutations.
53
+ - If bash searching is truly unavoidable, prefer \`rg\` over \`grep\`.`;
54
+
55
+ function stripBashGuidelines(prompt: string): string {
56
+ let out = prompt;
57
+ for (const guideline of BASH_GUIDELINES) {
58
+ out = out.replaceAll(guideline, "Use ffgrep/fffind for file operations like ls, rg, find");
59
+ }
60
+ return out;
61
+ }
62
+
63
+ export default function toolDiscipline(pi: ExtensionAPI) {
64
+ // A. Register placeholder search tools so pi stops generating the bash guideline.
65
+ // Done in session_start: action methods (getAllTools/registerTool) are not
66
+ // available during extension loading, and tools registered here are
67
+ // refreshed into the session (and system prompt) immediately.
68
+ pi.on("session_start", () => {
69
+ const existing = new Set(pi.getAllTools().map((t) => t.name));
70
+ for (const name of PLACEHOLDER_NAMES) {
71
+ if (existing.has(name)) continue; // already present — the check already passes
72
+ pi.registerTool({
73
+ name,
74
+ label: `${name} (placeholder)`,
75
+ description:
76
+ `Placeholder tool registered by pi-tool-discipline so pi knows a ${name} tool exists ` +
77
+ `and does not inject its default "use bash for file operations" guideline. ` +
78
+ `Do not call this tool — use ffgrep for content search and fffind for path search instead.`,
79
+ parameters: Type.Object({}),
80
+ async execute() {
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: "This placeholder tool has no implementation. Use ffgrep for content search and fffind for path search instead.",
86
+ },
87
+ ],
88
+ details: { placeholder: true },
89
+ };
90
+ },
91
+ });
92
+ }
93
+ });
94
+
95
+ // B. Inject the discipline into the system prompt (idempotent per turn).
96
+ pi.on("before_agent_start", async (event) => {
97
+ const { systemPrompt } = event;
98
+ if (systemPrompt.includes(MARK)) return; // already injected
99
+ const prompt = stripBashGuidelines(systemPrompt);
100
+ return { systemPrompt: `${prompt}\n${MARK}\n${DISCIPLINE}` };
101
+ });
102
+
103
+ // Status command: /tool-discipline — verify placeholder tools and injection.
104
+ pi.registerCommand("tool-discipline", {
105
+ description: "Show pi-tool-discipline status (placeholder tools + injected guideline)",
106
+ handler: async (_args, ctx) => {
107
+ const tools = pi.getAllTools().map((t) => t.name);
108
+ const placeholders = PLACEHOLDER_NAMES.filter((n) => tools.includes(n));
109
+ const injected = ctx.getSystemPrompt().includes(MARK);
110
+ ctx.ui.notify(
111
+ `pi-tool-discipline\n` +
112
+ `placeholder tools: ${placeholders.length > 0 ? placeholders.join(", ") : "(none)"}\n` +
113
+ `discipline injected: ${injected ? "yes" : "no"}`,
114
+ "info",
115
+ );
116
+ },
117
+ });
118
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "pi-tool-discipline",
3
+ "version": "0.1.0",
4
+ "description": "pi extension: enforce ffgrep/fffind-first search discipline and neutralize the default bash file-operation guideline",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "pi",
9
+ "pi-package",
10
+ "pi-extension",
11
+ "ffgrep",
12
+ "fffind",
13
+ "grep",
14
+ "search",
15
+ "discipline"
16
+ ],
17
+ "pi": {
18
+ "extensions": ["./extensions/index.ts"]
19
+ },
20
+ "files": [
21
+ "extensions",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "typecheck": "tsc --noEmit"
30
+ },
31
+ "peerDependencies": {
32
+ "@earendil-works/pi-coding-agent": "*",
33
+ "@sinclair/typebox": "*"
34
+ },
35
+ "devDependencies": {
36
+ "@earendil-works/pi-coding-agent": "*",
37
+ "@sinclair/typebox": "*",
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "^5.0.0"
40
+ }
41
+ }