@maxiaochao/pi-toolkit 0.10.24 → 0.11.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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 2026-10-12 - v0.11.1
2
+
3
+ - bundle herdr-subagents v0.4.11: add architecture documentation and clear-output guidance evaluation
4
+
5
+ ## 2026-10-12 - v0.11.0
6
+
7
+ - bundle worktree v0.4.0: Replace CLI start and prepare with the create-only command
8
+ - Replace `pi-worktree start` and `pi-worktree prepare` with the create-only `pi-worktree create` CLI; `/worktree start` and `enter_worktree` continue to switch the current Pi session.
9
+
1
10
  ## 2026-10-12 - v0.10.24
2
11
 
3
12
  - bundle herdr-subagents v0.4.10: Stabilize concurrency checks and cancellation synchronization on slower CI runners.
@@ -5,6 +14,7 @@
5
14
  ## 2026-10-12 - v0.10.23
6
15
 
7
16
  - bundle herdr-subagents v0.4.9: Stabilize concurrency checks on slower CI runners.
17
+
8
18
  ## 2026-10-12 - v0.10.22
9
19
 
10
20
  - bundle herdr-subagents v0.4.8: make blocking behave identically in serial and parallel runs and let respond continue a named failed task from its saved session
@@ -0,0 +1,19 @@
1
+ # AGENTS.md Clear Output Evaluation
2
+
3
+ This A/B evaluation measures the effect of the `Clear Output` guidance. The `before` variant uses a minimal coding-assistant prompt. The `after` variant adds only the two `Clear Output` rules.
4
+
5
+ Run the deterministic scorer tests:
6
+
7
+ ```bash
8
+ npm run eval:agents:test
9
+ ```
10
+
11
+ Run the model evaluation:
12
+
13
+ ```bash
14
+ npm run eval:agents -- --runs 2
15
+ ```
16
+
17
+ Set `PI_EVAL_MODEL` or pass `--model <provider/model>` to pin a model. Results are written to `results/latest.json` and `results/latest.md`.
18
+
19
+ The scorer measures sentence length, conjunction density, response language, terminology changes, and use of a visual for the branching-flow case. These are proxies. Review the saved outputs for correctness and completeness before accepting a prompt change.
@@ -0,0 +1,217 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const CLEAR_OUTPUT = `## Clear Output
6
+
7
+ - Make outputs easy to understand and verify. Use ASD-STE100-inspired principles: short sentences, one idea per sentence, and consistent terms. Apply its formal language rules only to English.
8
+ - When it improves understanding, use a diagram, HTML artifact, or video instead of long prose.`;
9
+
10
+ const BASE_SYSTEM = "You are an expert coding assistant. Give a direct and accurate answer. Reply in the same language as the user.";
11
+
12
+ export const variants = {
13
+ before: BASE_SYSTEM,
14
+ after: `${BASE_SYSTEM}\n\n${CLEAR_OUTPUT}`,
15
+ };
16
+
17
+ export const cases = [
18
+ {
19
+ id: "english-explanation",
20
+ language: "en",
21
+ prompt: "Explain how optimistic concurrency control prevents lost updates. Include the read, write, conflict, and retry stages. Write for an application developer.",
22
+ },
23
+ {
24
+ id: "chinese-explanation",
25
+ language: "zh",
26
+ prompt: "向应用开发者解释乐观并发控制如何防止更新丢失。需要涵盖读取、写入、冲突和重试阶段。",
27
+ },
28
+ {
29
+ id: "term-consistency",
30
+ language: "en",
31
+ aliases: ["work item", "job", "task"],
32
+ prompt: "A queue API uses job, task, and work item for the same object. Explain its lifecycle from creation through processing to completion. Resolve the naming ambiguity in your answer.",
33
+ },
34
+ {
35
+ id: "visual-flow",
36
+ language: "en",
37
+ expectVisual: true,
38
+ prompt: "Explain this request flow: gateway to authentication; failed authentication to rejection; successful authentication to cache; cache miss to database; transient database failure retries twice, then returns an error. Make the branching and retry path easy to verify.",
39
+ },
40
+ ];
41
+
42
+ function splitSentences(text) {
43
+ return [...new Intl.Segmenter(undefined, { granularity: "sentence" }).segment(text)]
44
+ .map(({ segment }) => segment.trim())
45
+ .filter(Boolean);
46
+ }
47
+
48
+ function tokenCount(sentence, language) {
49
+ if (language === "zh") {
50
+ return (sentence.match(/[\p{Script=Han}]|[A-Za-z0-9]+/gu) ?? []).length;
51
+ }
52
+ return (sentence.match(/[\p{L}\p{N}]+(?:[-'][\p{L}\p{N}]+)*/gu) ?? []).length;
53
+ }
54
+
55
+ export function scoreOutput(text, testCase) {
56
+ const sentences = splitSentences(text);
57
+ const lengths = sentences.map((sentence) => tokenCount(sentence, testCase.language));
58
+ const limit = testCase.language === "zh" ? 35 : 25;
59
+ const conjunctionPattern = testCase.language === "zh"
60
+ ? /(?:而且|但是|同时|以及|并且|然而|其中)/g
61
+ : /\b(?:and|but|while|whereas|which|although|however)\b/gi;
62
+ const conjunctions = sentences.reduce(
63
+ (sum, sentence) => sum + (sentence.match(conjunctionPattern) ?? []).length,
64
+ 0,
65
+ );
66
+ const firstParagraphEnd = text.indexOf("\n\n");
67
+ const bodyAfterNaming = firstParagraphEnd >= 0
68
+ ? text.slice(firstParagraphEnd + 2)
69
+ : sentences.slice(1).join(" ");
70
+ const usedAliasesAfterNaming = (testCase.aliases ?? []).filter((alias) =>
71
+ new RegExp(`\\b${alias.replaceAll(" ", "\\s+")}s?\\b`, "i").test(bodyAfterNaming),
72
+ );
73
+ const hasVisual = /```(?:mermaid)?|(?:-->|->|→|↓|├|└)|<svg\b|<html\b|\|[^\n]+\|[^\n]+\|/i.test(text);
74
+ const hanCharacters = (text.match(/[\p{Script=Han}]/gu) ?? []).length;
75
+ const latinWords = (text.match(/[A-Za-z]+/g) ?? []).length;
76
+ const languageMatches = testCase.language === "zh"
77
+ ? hanCharacters >= 20
78
+ : hanCharacters <= Math.max(5, latinWords * 0.05);
79
+
80
+ return {
81
+ characters: text.length,
82
+ sentences: sentences.length,
83
+ averageSentenceTokens: lengths.length
84
+ ? Number((lengths.reduce((sum, value) => sum + value, 0) / lengths.length).toFixed(1))
85
+ : 0,
86
+ longSentenceRate: lengths.length
87
+ ? Number((lengths.filter((value) => value > limit).length / lengths.length).toFixed(2))
88
+ : 0,
89
+ conjunctionsPerSentence: lengths.length
90
+ ? Number((conjunctions / lengths.length).toFixed(2))
91
+ : 0,
92
+ extraAliasesAfterNaming: Math.max(0, usedAliasesAfterNaming.length - 1),
93
+ languageMatches,
94
+ hasVisual,
95
+ };
96
+ }
97
+
98
+ function parseArguments(argv) {
99
+ const options = { runs: 1, model: process.env.PI_EVAL_MODEL };
100
+ for (let index = 0; index < argv.length; index += 1) {
101
+ if (argv[index] === "--runs") options.runs = Number(argv[++index]);
102
+ else if (argv[index] === "--model") options.model = argv[++index];
103
+ else throw new Error(`Unknown argument: ${argv[index]}`);
104
+ }
105
+ if (!Number.isInteger(options.runs) || options.runs < 1) {
106
+ throw new Error("--runs must be a positive integer");
107
+ }
108
+ return options;
109
+ }
110
+
111
+ function runPi(systemPrompt, prompt, model) {
112
+ const args = [
113
+ "--print",
114
+ "--no-session",
115
+ "--no-tools",
116
+ "--no-extensions",
117
+ "--no-skills",
118
+ "--no-prompt-templates",
119
+ "--no-themes",
120
+ "--no-context-files",
121
+ "--system-prompt",
122
+ systemPrompt,
123
+ ];
124
+ if (model) args.push("--model", model);
125
+ args.push("--", prompt);
126
+ return execFileSync("pi", args, { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] }).trim();
127
+ }
128
+
129
+ function average(rows, key) {
130
+ if (rows.length === 0) return 0;
131
+ return Number((rows.reduce((sum, row) => sum + Number(row.score[key]), 0) / rows.length).toFixed(2));
132
+ }
133
+
134
+ export function summarize(results) {
135
+ return Object.fromEntries(Object.keys(variants).map((variant) => {
136
+ const rows = results.filter((result) => result.variant === variant);
137
+ const visualRows = rows.filter((result) => cases.find(({ id }) => id === result.caseId).expectVisual);
138
+ const terminologyRows = rows.filter((result) => cases.find(({ id }) => id === result.caseId).aliases);
139
+ return [variant, {
140
+ averageCharacters: average(rows, "characters"),
141
+ averageSentenceTokens: average(rows, "averageSentenceTokens"),
142
+ longSentenceRate: average(rows, "longSentenceRate"),
143
+ conjunctionsPerSentence: average(rows, "conjunctionsPerSentence"),
144
+ languageMatchRate: average(rows, "languageMatches"),
145
+ visualFlowRate: average(visualRows, "hasVisual"),
146
+ extraAliasesAfterNaming: average(terminologyRows, "extraAliasesAfterNaming"),
147
+ }];
148
+ }));
149
+ }
150
+
151
+ export function markdownReport(payload) {
152
+ const { summary } = payload;
153
+ const lines = [
154
+ "# AGENTS.md Clear Output A/B Evaluation",
155
+ "",
156
+ `Runs per case: ${payload.options.runs}`,
157
+ `Model override: ${payload.options.model ?? "Pi default"}`,
158
+ "",
159
+ "| Variant | Mean characters | Mean sentence tokens | Long sentence rate | Conjunctions / sentence | Language match | Visual-flow rate | Extra aliases |",
160
+ "|---|---:|---:|---:|---:|---:|---:|---:|",
161
+ ...Object.entries(summary).map(([name, score]) =>
162
+ `| ${name} | ${score.averageCharacters} | ${score.averageSentenceTokens} | ${score.longSentenceRate} | ${score.conjunctionsPerSentence} | ${score.languageMatchRate} | ${score.visualFlowRate} | ${score.extraAliasesAfterNaming} |`,
163
+ ),
164
+ "",
165
+ "Lower is better for long sentence rate, conjunctions, and extra aliases after naming. Higher is better for language match and visual-flow rate. Character count is descriptive, not a quality score.",
166
+ "",
167
+ "## Per-case output",
168
+ "",
169
+ ...payload.results.flatMap((result) => [
170
+ `### ${result.caseId} · ${result.variant} · run ${result.run}`,
171
+ "",
172
+ `Score: \`${JSON.stringify(result.score)}\``,
173
+ "",
174
+ result.output,
175
+ "",
176
+ ]),
177
+ ];
178
+ return `${lines.join("\n")}\n`;
179
+ }
180
+
181
+ async function main() {
182
+ const options = parseArguments(process.argv.slice(2));
183
+ const results = [];
184
+ for (let run = 1; run <= options.runs; run += 1) {
185
+ for (const testCase of cases) {
186
+ const order = run % 2 === 1 ? ["before", "after"] : ["after", "before"];
187
+ for (const variant of order) {
188
+ process.stderr.write(`Running ${testCase.id} / ${variant} / ${run}\n`);
189
+ const output = runPi(variants[variant], testCase.prompt, options.model);
190
+ results.push({
191
+ caseId: testCase.id,
192
+ variant,
193
+ run,
194
+ output,
195
+ score: scoreOutput(output, testCase),
196
+ });
197
+ }
198
+ }
199
+ }
200
+
201
+ const payload = {
202
+ generatedAt: new Date().toISOString(),
203
+ options,
204
+ summary: summarize(results),
205
+ results,
206
+ };
207
+ const resultDirectory = fileURLToPath(new URL("./results", import.meta.url));
208
+ mkdirSync(resultDirectory, { recursive: true });
209
+ writeFileSync(`${resultDirectory}/latest.json`, `${JSON.stringify(payload, null, 2)}\n`);
210
+ writeFileSync(`${resultDirectory}/latest.md`, markdownReport(payload));
211
+ console.log(JSON.stringify(payload.summary, null, 2));
212
+ console.log(`\nFull report: ${resultDirectory}/latest.md`);
213
+ }
214
+
215
+ if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
216
+ await main();
217
+ }
@@ -0,0 +1,52 @@
1
+ import assert from "node:assert/strict";
2
+ import { scoreOutput } from "../run.mjs";
3
+
4
+ const concise = scoreOutput(
5
+ "Read the record. Compare its version. Reject a conflict. Retry the operation.",
6
+ { language: "en" },
7
+ );
8
+ assert.equal(concise.sentences, 4);
9
+ assert.equal(concise.longSentenceRate, 0);
10
+ assert.equal(concise.conjunctionsPerSentence, 0);
11
+ assert.equal(concise.languageMatches, true);
12
+
13
+ const dense = scoreOutput(
14
+ "Read the record and compare its version, but reject the write when another client changed it, while preserving the new value and returning a conflict which the caller can retry after loading the latest record.",
15
+ { language: "en" },
16
+ );
17
+ assert.equal(dense.sentences, 1);
18
+ assert.equal(dense.longSentenceRate, 1);
19
+ assert.ok(dense.conjunctionsPerSentence >= 4);
20
+
21
+ const chinese = scoreOutput(
22
+ "客户端读取记录。客户端比较版本。版本冲突时,服务拒绝写入。客户端重新读取后重试。",
23
+ { language: "zh" },
24
+ );
25
+ assert.equal(chinese.sentences, 4);
26
+ assert.equal(chinese.longSentenceRate, 0);
27
+ assert.equal(chinese.languageMatches, true);
28
+
29
+ const terms = scoreOutput(
30
+ "Create one work item. The worker processes the job and completes the task.",
31
+ { language: "en", aliases: ["work item", "job", "task"] },
32
+ );
33
+ assert.equal(terms.extraAliasesAfterNaming, 1);
34
+
35
+ const canonicalTerm = scoreOutput(
36
+ "Use work item as the canonical term for job, task, and work item. Create the work item. Process the work item. Complete the work item.",
37
+ { language: "en", aliases: ["work item", "job", "task"] },
38
+ );
39
+ assert.equal(canonicalTerm.extraAliasesAfterNaming, 0);
40
+
41
+ const wrongLanguage = scoreOutput("这是一个中文回答,包含足够多的中文字符,因此不应匹配英文问题。", {
42
+ language: "en",
43
+ });
44
+ assert.equal(wrongLanguage.languageMatches, false);
45
+
46
+ const visual = scoreOutput("gateway --> auth --> cache\n ↓\n reject", {
47
+ language: "en",
48
+ expectVisual: true,
49
+ });
50
+ assert.equal(visual.hasVisual, true);
51
+
52
+ console.log("AGENTS.md evaluation scorer tests passed");
package/global/AGENTS.md CHANGED
@@ -2,13 +2,18 @@
2
2
 
3
3
  Behavioral guidelines to reduce common LLM coding mistakes. Project-specific instructions override this file when they conflict.
4
4
 
5
- ## 1. Think Before Coding
5
+ ## 1. Clear Output
6
+
7
+ - Make outputs easy to understand and verify. Use ASD-STE100-inspired principles: short sentences, one idea per sentence, and consistent terms. Apply its formal language rules only to English.
8
+ - When it improves understanding, use a diagram, HTML artifact, or video instead of long prose.
9
+
10
+ ## 2. Think Before Coding
6
11
 
7
12
  - Surface material assumptions, interpretations, and tradeoffs.
8
13
  - If unresolved ambiguity would materially change the result, ask. When no human is reachable, choose a defensible interpretation, state it, and proceed with verifiable criteria.
9
14
  - Prefer the simpler approach. Name conflicting requirements rather than silently compromising between them.
10
15
 
11
- ## 2. Minimal Solution
16
+ ## 3. Minimal Solution
12
17
 
13
18
  Understand the problem and relevant code flow before choosing the first rung that works:
14
19
 
@@ -24,11 +29,11 @@ Understand the problem and relevant code flow before choosing the first rung tha
24
29
  - For states excluded by enforced invariants, fail visibly instead of inventing fallback behavior.
25
30
  - State the ceiling and upgrade trigger of any deliberate simplification that cuts a real corner.
26
31
 
27
- ## 3. Skill Boundaries
32
+ ## 4. Skill Boundaries
28
33
 
29
34
  After loading a skill, treat its documented interface as sufficient for normal use. Do not inspect the skill's implementation, installed executable, or package source unless the user asks to review, debug, or modify it; its documented behavior conflicts with observed behavior; or required usage details are missing from the skill.
30
35
 
31
- ## 4. Read, Then Change Surgically
36
+ ## 5. Read, Then Change Surgically
32
37
 
33
38
  Before editing, read the affected public API and its immediate callers. For a bug, find every statically discoverable caller and relevant implementation.
34
39
 
@@ -38,23 +43,23 @@ Every changed line traces to the user's request or to a defect that request reve
38
43
  - Remove imports, variables, and functions that your changes make unused. Leave pre-existing dead code in place and mention it.
39
44
  - Fix a faulty shared function once and name affected sibling callers; if only one caller is wrong, fix that caller instead of guarding a correct shared function.
40
45
 
41
- ## 5. Goal-Driven Execution
46
+ ## 6. Goal-Driven Execution
42
47
 
43
48
  - Turn work into checkable outcomes: reproduce bugs before fixing them, preserve behavior during refactors, and optimize against a tested reference implementation.
44
49
  - For multi-step or materially ambiguous work, state a brief plan and give every step its own verification.
45
50
  - Leave the smallest relevant runnable check for new or changed non-trivial logic. Do not introduce a test framework solely for that check.
46
51
 
47
- ## 6. Fail Visibly
52
+ ## 7. Fail Visibly
48
53
 
49
54
  - "Completed" is wrong if anything required was silently skipped.
50
55
  - "Tests pass" is wrong if any required test was skipped or not run.
51
56
  - If a required input, file, or tool is missing, or a step is impossible under the stated constraints, report what is missing and what you need. Never fabricate it to make a check pass.
52
57
 
53
- ## 7. Frontend Verification
58
+ ## 8. Frontend Verification
54
59
 
55
60
  For frontend feature development, load the `web-browser` skill and verify the result in a real Chrome browser through CDP. Exercise the relevant user flow with real clicks, capture screenshots of the resulting UI, and test the actual behavior before reporting completion. Static inspection or non-browser tests do not replace this verification.
56
61
 
57
- ## 8. Worktree Isolation
62
+ ## 9. Worktree Isolation
58
63
 
59
64
  For a Git-repository task that coordinates substantial changes across multiple files or modules, or implements a feature intended for release, ask before editing whether to isolate it in a worktree. Skip the question when already in a linked worktree.
60
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-toolkit",
3
- "version": "0.10.24",
3
+ "version": "0.11.1",
4
4
  "description": "Personal pi coding agent toolkit: scheduler, /btw, /cache_export, apply_patch, browser automation, and bundled theme/skills.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -12,6 +12,7 @@
12
12
  ],
13
13
  "files": [
14
14
  "extensions",
15
+ "evaluations/agents-md",
15
16
  "packages/cache-export",
16
17
  "packages/codex-edit",
17
18
  "packages/herdr-subagents",