pi-dev 0.1.6 → 0.1.8

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 CHANGED
@@ -42,21 +42,34 @@ You ask. It classifies. It executes. It reports.
42
42
  Requires Node ≥ 20 and the [pi runtime](https://github.com/badlogic/pi).
43
43
 
44
44
  ```bash
45
- # Install the skills + seed your global preferences
45
+ # Interactive: pick global vs project-local, then install + seed preferences
46
46
  npx pi-dev@latest install
47
47
 
48
- # Refresh skills only (keeps your preferences as-is)
48
+ # Or be explicit:
49
+ npx pi-dev@latest install --global # ~/.pi/agent/skills/ (every repo)
50
+ npx pi-dev@latest install --local # ./.pi/skills/ (this repo only)
51
+
52
+ # Refresh skills (auto-detects scope from disk; preferences are kept)
49
53
  npx pi-dev@latest update
50
54
 
51
- # See what's installed
55
+ # See what's installed under each scope
52
56
  npx pi-dev list
53
57
 
54
- # Verify your environment
58
+ # Verify the active scope's layout
55
59
  npx pi-dev doctor
56
60
  ```
57
61
 
58
- `install` copies the skill folders to `~/.pi/agent/skills/` (where the pi runtime looks) and seeds `~/.pi/agent/preferences.md` only if you don't already have one.
59
- `update` refreshes skill folders but leaves your preferences alone unless you pass `--include-prefs`.
62
+ **Scope choice cheat-sheet:**
63
+
64
+ | | global | local |
65
+ | --- | --- | --- |
66
+ | Skills | `~/.pi/agent/skills/` | `<repo>/.pi/skills/` |
67
+ | Preferences | `~/.pi/agent/preferences.md` | `<repo>/.pi/preferences.md` |
68
+ | Pi sessions see it from | every cwd | only this repo |
69
+ | Goes in the repo's git? | no | yes (commit `.pi/skills/`, gitignore `.pi/sessions/`) |
70
+ | Use when | the skills are part of *your* engineering taste | the skills are part of *the project's* contract |
71
+
72
+ Non-interactive runs (CI, piped input) default to `--global` silently. Pass `-y` to skip the prompt and accept the default.
60
73
 
61
74
  ## How `/do` actually flows
62
75
 
package/dist/cli.js CHANGED
@@ -9,20 +9,30 @@ function help() {
9
9
  console.log(`pi-dev — autonomous engineering skill framework for the pi runtime
10
10
 
11
11
  Usage:
12
- pi-dev install [--skip-prefs] Copy skills into ~/.pi/agent/skills and seed
13
- ~/.pi/agent/preferences.md if missing.
14
- pi-dev update [--include-prefs] Refresh skills. By default keeps your global
15
- preferences. Pass --include-prefs to overwrite.
16
- pi-dev list Show installed skills + global prefs path.
17
- pi-dev uninstall <skill> Soft-remove a skill (renamed to .removed-…).
18
- pi-dev doctor Check ~/.pi layout and external CLIs.
19
- pi-dev version Print version.
20
- pi-dev help This message.
12
+ pi-dev install [scope] [--skip-prefs] [-y]
13
+ Install skills + seed preferences. Scope is one of:
14
+ --global ~/.pi/agent/skills/ (default, every pi session sees it)
15
+ --local .pi/skills/ in cwd (only this repo)
16
+ Without a flag, an interactive TTY is prompted; non-TTY defaults to global.
17
+ Pass -y to skip the prompt and accept the default.
18
+
19
+ pi-dev update [--include-prefs] [--global|--local]
20
+ Refresh skills in place. Scope is auto-detected from disk
21
+ (local wins if .pi/skills/ exists in cwd). Preferences are kept by default;
22
+ pass --include-prefs to re-seed.
23
+
24
+ pi-dev list Show installed skills under both scopes (if present).
25
+ pi-dev uninstall <skill> [--global|--local]
26
+ Soft-remove a skill (renamed to .removed-…).
27
+ pi-dev doctor Check the active scope's layout and external CLIs.
28
+ pi-dev version Print version.
29
+ pi-dev help This message.
21
30
 
22
31
  After install, in any pi session, you primarily call:
23
- /do — the one-shot engineering entry point
24
- /taste — view or update preferences
25
- /where — recall prior pi sessions for this cwd
32
+ /do — the one-shot engineering entry point
33
+ /taste — view or update preferences
34
+ /where — recall prior pi sessions for this cwd
35
+ /improve-skill-flow — audit pi sessions, propose evidence-based skill edits
26
36
 
27
37
  All other skills are invoked automatically by /do.
28
38
  `);
@@ -30,44 +40,61 @@ All other skills are invoked automatically by /do.
30
40
  function getFlag(name) {
31
41
  return args.includes(`--${name}`);
32
42
  }
33
- switch (cmd) {
34
- case "install":
35
- install({ skipPrefs: getFlag("skip-prefs") });
36
- break;
37
- case "update":
38
- update({ skipPrefs: !getFlag("include-prefs") });
39
- break;
40
- case "list":
41
- listInstalled();
42
- break;
43
- case "uninstall": {
44
- const name = args[1];
45
- if (!name) {
46
- console.error("Usage: pi-dev uninstall <skill>");
47
- process.exit(1);
43
+ function getScope() {
44
+ if (getFlag("global"))
45
+ return "global";
46
+ if (getFlag("local"))
47
+ return "local";
48
+ return undefined;
49
+ }
50
+ async function main() {
51
+ switch (cmd) {
52
+ case "install":
53
+ await install({
54
+ skipPrefs: getFlag("skip-prefs"),
55
+ scope: getScope(),
56
+ yes: getFlag("yes") || args.includes("-y"),
57
+ });
58
+ break;
59
+ case "update":
60
+ await update({ skipPrefs: !getFlag("include-prefs"), scope: getScope() });
61
+ break;
62
+ case "list":
63
+ listInstalled();
64
+ break;
65
+ case "uninstall": {
66
+ const name = args[1];
67
+ if (!name || name.startsWith("--")) {
68
+ console.error("Usage: pi-dev uninstall <skill> [--global|--local]");
69
+ process.exit(1);
70
+ }
71
+ uninstallSkill(name, getScope());
72
+ break;
48
73
  }
49
- uninstallSkill(name);
50
- break;
51
- }
52
- case "doctor":
53
- doctor();
54
- break;
55
- case "version":
56
- case "--version":
57
- case "-v": {
58
- const here = dirname(fileURLToPath(import.meta.url));
59
- const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
60
- console.log(pkg.version);
61
- break;
74
+ case "doctor":
75
+ doctor();
76
+ break;
77
+ case "version":
78
+ case "--version":
79
+ case "-v": {
80
+ const here = dirname(fileURLToPath(import.meta.url));
81
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
82
+ console.log(pkg.version);
83
+ break;
84
+ }
85
+ case "help":
86
+ case "--help":
87
+ case "-h":
88
+ case undefined:
89
+ help();
90
+ break;
91
+ default:
92
+ console.error(`Unknown command: ${cmd}\n`);
93
+ help();
94
+ process.exit(1);
62
95
  }
63
- case "help":
64
- case "--help":
65
- case "-h":
66
- case undefined:
67
- help();
68
- break;
69
- default:
70
- console.error(`Unknown command: ${cmd}\n`);
71
- help();
72
- process.exit(1);
73
96
  }
97
+ main().catch((err) => {
98
+ console.error(err);
99
+ process.exit(1);
100
+ });
package/dist/install.js CHANGED
@@ -1,21 +1,55 @@
1
1
  import { existsSync, mkdirSync, cpSync, copyFileSync, renameSync } from "node:fs";
2
2
  import { execSync } from "node:child_process";
3
3
  import { join } from "node:path";
4
+ import { createInterface } from "node:readline";
4
5
  import { SKILLS } from "./manifest.js";
5
- import { PI_AGENT_DIR, PI_SKILLS_DIR, PI_GLOBAL_PREFS, PKG_SKILLS_DIR, PKG_GLOBAL_PREFS_PRESET, } from "./paths.js";
6
- export function install(opts = {}) {
7
- mkdirSync(PI_SKILLS_DIR, { recursive: true });
6
+ import { PKG_SKILLS_DIR, PKG_GLOBAL_PREFS_PRESET, destFor, } from "./paths.js";
7
+ function ask(question) {
8
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
9
+ return new Promise((res) => {
10
+ rl.question(question, (a) => {
11
+ rl.close();
12
+ res(a);
13
+ });
14
+ });
15
+ }
16
+ async function resolveScope(opts) {
17
+ if (opts.scope)
18
+ return opts.scope;
19
+ // Non-interactive (CI, piped) → default to global silently.
20
+ if (opts.yes || !process.stdin.isTTY)
21
+ return "global";
22
+ const cwd = process.cwd();
23
+ const { skillsDir: localSkills } = destFor("local", cwd);
24
+ const { skillsDir: globalSkills } = destFor("global");
25
+ console.log("");
26
+ console.log("pi-dev: where should the skills live?");
27
+ console.log("");
28
+ console.log(` [g] global → ${globalSkills}`);
29
+ console.log(` every pi session on this machine sees them.`);
30
+ console.log("");
31
+ console.log(` [l] local → ${localSkills}`);
32
+ console.log(` only pi sessions started from this repo see them.`);
33
+ console.log(` pi reads project-local config from .pi/ in the cwd.`);
34
+ console.log("");
35
+ const ans = (await ask("Choose [G/l] (default global): ")).trim().toLowerCase();
36
+ if (ans === "l" || ans === "local")
37
+ return "local";
38
+ return "global";
39
+ }
40
+ export async function install(opts = {}) {
41
+ const scope = await resolveScope(opts);
42
+ const { agentDir, skillsDir, prefsFile } = destFor(scope);
43
+ mkdirSync(skillsDir, { recursive: true });
8
44
  let copied = 0;
9
45
  for (const skill of SKILLS) {
10
46
  const src = join(PKG_SKILLS_DIR, skill.name);
11
- const dst = join(PI_SKILLS_DIR, skill.name);
47
+ const dst = join(skillsDir, skill.name);
12
48
  if (!existsSync(src)) {
13
49
  console.warn(` skip ${skill.name} (source not found in package)`);
14
50
  continue;
15
51
  }
16
52
  if (existsSync(dst) && !opts.force) {
17
- // Replace contents but keep the directory; safer than rmSync for skills
18
- // the user may have customised lightly.
19
53
  cpSync(src, dst, { recursive: true, force: true });
20
54
  }
21
55
  else {
@@ -23,63 +57,96 @@ export function install(opts = {}) {
23
57
  }
24
58
  copied++;
25
59
  }
26
- console.log(`Installed ${copied} skill(s) into ${PI_SKILLS_DIR}`);
60
+ console.log(`Installed ${copied} skill(s) into ${skillsDir} [${scope}]`);
27
61
  if (opts.skipPrefs) {
28
- console.log("Skipped global preferences (use --include-prefs on update to merge in new keys).");
62
+ console.log("Skipped preferences (pass --include-prefs on update to merge in new keys).");
29
63
  return;
30
64
  }
31
- if (!existsSync(PI_GLOBAL_PREFS)) {
65
+ if (!existsSync(prefsFile)) {
32
66
  if (existsSync(PKG_GLOBAL_PREFS_PRESET)) {
33
- copyFileSync(PKG_GLOBAL_PREFS_PRESET, PI_GLOBAL_PREFS);
34
- console.log(`Seeded global preferences at ${PI_GLOBAL_PREFS}`);
67
+ copyFileSync(PKG_GLOBAL_PREFS_PRESET, prefsFile);
68
+ console.log(`Seeded preferences at ${prefsFile} [${scope}]`);
35
69
  }
36
70
  }
37
71
  else {
38
- console.log(`Existing global preferences kept at ${PI_GLOBAL_PREFS} (use --include-prefs to merge new keys).`);
72
+ console.log(`Existing preferences kept at ${prefsFile} (pass --include-prefs to merge new keys).`);
73
+ }
74
+ if (scope === "local") {
75
+ console.log("");
76
+ console.log("Tip: add `.pi/sessions/` to .gitignore (sessions are local-only).");
77
+ console.log(" Skills under `.pi/skills/` are safe to commit.");
39
78
  }
40
79
  }
41
- export function update(opts = {}) {
42
- // Update is install with force, but defaults to NOT overwriting global prefs.
43
- install({ ...opts, force: true, skipPrefs: !opts.skipPrefs ? true : opts.skipPrefs });
80
+ export async function update(opts = {}) {
81
+ // Update is install with force. If scope wasn't passed, infer it from what's
82
+ // already on disk: a local `.pi/skills/` in cwd wins over global.
83
+ let scope = opts.scope;
84
+ if (!scope) {
85
+ const local = destFor("local");
86
+ if (existsSync(local.skillsDir))
87
+ scope = "local";
88
+ else
89
+ scope = "global";
90
+ }
91
+ await install({
92
+ ...opts,
93
+ scope,
94
+ force: true,
95
+ skipPrefs: !opts.skipPrefs ? true : opts.skipPrefs,
96
+ yes: true,
97
+ });
44
98
  }
45
- export function uninstallSkill(name) {
46
- const dst = join(PI_SKILLS_DIR, name);
99
+ export function uninstallSkill(name, scope) {
100
+ const resolved = scope ?? (existsSync(destFor("local").skillsDir) ? "local" : "global");
101
+ const { skillsDir } = destFor(resolved);
102
+ const dst = join(skillsDir, name);
47
103
  if (!existsSync(dst)) {
48
- console.error(`Skill not installed: ${name}`);
104
+ console.error(`Skill not installed [${resolved}]: ${name}`);
49
105
  process.exit(1);
50
106
  }
51
- // Soft delete: rename to .removed-<ts>
52
107
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
53
- const moved = join(PI_SKILLS_DIR, `.removed-${stamp}-${name}`);
108
+ const moved = join(skillsDir, `.removed-${stamp}-${name}`);
54
109
  renameSync(dst, moved);
55
- console.log(`Uninstalled ${name} (moved to ${moved}).`);
110
+ console.log(`Uninstalled ${name} from ${resolved} (moved to ${moved}).`);
56
111
  }
57
112
  export function listInstalled() {
58
- console.log(`Skills directory: ${PI_SKILLS_DIR}\n`);
59
- for (const skill of SKILLS) {
60
- const path = join(PI_SKILLS_DIR, skill.name, "SKILL.md");
61
- const installed = existsSync(path);
62
- const tag = skill.kind === "human" ? "[user] " : "[support]";
63
- const status = installed ? "ok" : "missing";
64
- console.log(` ${tag} /${skill.name.padEnd(34)} ${status} — ${skill.summary}`);
113
+ const local = destFor("local");
114
+ const global = destFor("global");
115
+ const targets = [];
116
+ if (existsSync(local.skillsDir))
117
+ targets.push({ label: "local", ...local });
118
+ targets.push({ label: "global", ...global });
119
+ for (const t of targets) {
120
+ console.log(`Skills directory [${t.label}]: ${t.skillsDir}`);
121
+ for (const skill of SKILLS) {
122
+ const path = join(t.skillsDir, skill.name, "SKILL.md");
123
+ const installed = existsSync(path);
124
+ const tag = skill.kind === "human" ? "[user] " : "[support]";
125
+ const status = installed ? "ok " : "missing";
126
+ console.log(` ${tag} /${skill.name.padEnd(34)} ${status} — ${skill.summary}`);
127
+ }
128
+ console.log(`Preferences: ${existsSync(t.prefsFile) ? t.prefsFile : "(missing)"}`);
129
+ console.log("");
65
130
  }
66
- console.log(`\nGlobal preferences: ${existsSync(PI_GLOBAL_PREFS) ? PI_GLOBAL_PREFS : "(missing)"}`);
67
131
  }
68
132
  export function doctor() {
69
133
  const issues = [];
70
- if (!existsSync(PI_AGENT_DIR))
71
- issues.push(`~/.pi/agent missing run: pi-dev install`);
72
- if (!existsSync(PI_SKILLS_DIR))
73
- issues.push(`~/.pi/agent/skills missing run: pi-dev install`);
134
+ const local = destFor("local");
135
+ const hasLocal = existsSync(local.skillsDir);
136
+ const activeScope = hasLocal ? "local" : "global";
137
+ const { agentDir, skillsDir, prefsFile } = destFor(activeScope);
138
+ if (!existsSync(agentDir))
139
+ issues.push(`${agentDir} missing — run: pi-dev install`);
140
+ if (!existsSync(skillsDir))
141
+ issues.push(`${skillsDir} missing — run: pi-dev install`);
74
142
  for (const skill of SKILLS.filter((s) => s.kind === "human")) {
75
- if (!existsSync(join(PI_SKILLS_DIR, skill.name, "SKILL.md"))) {
76
- issues.push(`Human skill /${skill.name} not installed — run: pi-dev update`);
143
+ if (!existsSync(join(skillsDir, skill.name, "SKILL.md"))) {
144
+ issues.push(`Human skill /${skill.name} not installed [${activeScope}] — run: pi-dev update`);
77
145
  }
78
146
  }
79
- if (!existsSync(PI_GLOBAL_PREFS)) {
80
- issues.push(`Global preferences missing — run: pi-dev install (or copy presets/preferences.md manually)`);
147
+ if (!existsSync(prefsFile)) {
148
+ issues.push(`Preferences missing at ${prefsFile} — run: pi-dev install (or copy presets/preferences.md manually)`);
81
149
  }
82
- // Quick external check (best-effort)
83
150
  const checkCmd = (cmd, label) => {
84
151
  try {
85
152
  execSync(`${cmd} --version`, { stdio: "ignore" });
@@ -90,6 +157,7 @@ export function doctor() {
90
157
  };
91
158
  checkCmd("git", "git");
92
159
  checkCmd("gh", "gh CLI");
160
+ console.log(`Active scope: ${activeScope}`);
93
161
  if (issues.length === 0) {
94
162
  console.log("All checks passed.");
95
163
  return;
package/dist/paths.js CHANGED
@@ -12,3 +12,15 @@ export const PKG_ROOT = resolve(__dirname, "..");
12
12
  export const PKG_SKILLS_DIR = join(PKG_ROOT, "skills");
13
13
  export const PKG_PRESETS_DIR = join(PKG_ROOT, "presets");
14
14
  export const PKG_GLOBAL_PREFS_PRESET = join(PKG_PRESETS_DIR, "preferences.md");
15
+ /** Compute install destinations for a given scope. `local` resolves against `cwd`. */
16
+ export function destFor(scope, cwd = process.cwd()) {
17
+ if (scope === "global") {
18
+ return { agentDir: PI_AGENT_DIR, skillsDir: PI_SKILLS_DIR, prefsFile: PI_GLOBAL_PREFS };
19
+ }
20
+ const agentDir = join(cwd, ".pi");
21
+ return {
22
+ agentDir,
23
+ skillsDir: join(agentDir, "skills"),
24
+ prefsFile: join(agentDir, "preferences.md"),
25
+ };
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-dev",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "An autonomous engineering skill framework for the pi runtime — built on Matt Pocock's skills.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,22 @@ The point: **never edit a SKILL.md from gut feeling.** Edit because session N sh
27
27
  - **Target repo path** (or its sessions directory). Defaults: the user names a repo; you resolve it.
28
28
  - Optional: a specific skill name to focus the audit on (`do`, `migrate`, `triage`, …).
29
29
  - Optional: a date range.
30
+ - **Install scope** for any fixes that come out of the audit — `global` or `project`. Auto-detected (see Step 5.5); user can override per finding.
31
+
32
+ ## Install scopes
33
+
34
+ pi-runtime today loads skill bodies from a single location: `~/.pi/agent/skills/<name>/SKILL.md`. The framework's 3-layer override is on **preferences**, not on SKILL bodies. So a finding lands in one of two places:
35
+
36
+ | scope | lands in | reaches | propagation | when to pick |
37
+ | --- | --- | --- | --- | --- |
38
+ | **global** | `pi-dev`'s `skills/<name>/SKILL.md` | every consumer after the next `npx pi-dev update` | release-please → npm publish | the SKILL.md wording itself is wrong; gap shows up generically |
39
+ | **project** | consumer repo's `docs/agents/preferences.md` (Project taboos / Diagnosis posture / Local-live playbook / Free notes — whichever section fits) | only this repo, on every `/do` bootstrap | regular consumer-repo commit | gap is the repo's domain / paths / conventions, not the SKILL.md |
40
+
41
+ Notes:
42
+
43
+ - A `global` apply is **always** mirrored into `~/.pi/agent/skills/<name>/` on the operator's machine so the next session picks it up immediately, without waiting for npm.
44
+ - A `project` apply touches no pi-dev files. It is committed to the consumer repo only.
45
+ - A single audit may produce a mix of global and project findings. Decide scope per finding, not per audit.
30
46
 
31
47
  ## Session-data location & format
32
48
 
@@ -141,33 +157,131 @@ For every 🔴 / 🟡 row, quote the smallest piece of evidence that makes the g
141
157
 
142
158
  If a finding cannot be backed by an excerpt, it is not actionable yet — demote to a TODO and keep digging.
143
159
 
144
- ### 6Propose SKILL.md edits
160
+ ### 5.5Decide install scope per finding (auto + user-overridable)
161
+
162
+ For each 🔴 / 🟡 finding, pick a default scope using this two-step heuristic, then show the table to the user once and let them flip individual rows before applying.
163
+
164
+ **Step A — detect operator context.** Run once at the start of this step:
165
+
166
+ ```bash
167
+ origin=$(git -C "$PWD" remote get-url origin 2>/dev/null || echo "")
168
+ pkg_name=$(jq -r '.name // empty' package.json 2>/dev/null)
169
+ is_maintainer=false
170
+ case "$origin" in *pi-dev*|*pi-dev.git) is_maintainer=true ;; esac
171
+ [ "$pkg_name" = "pi-dev" ] && is_maintainer=true
172
+ echo "operator_context=$([ \"$is_maintainer\" = true ] && echo maintainer || echo consumer)"
173
+ ```
174
+
175
+ - `operator_context=maintainer` → cwd is the pi-dev repo itself; the release path is available.
176
+ - `operator_context=consumer` → cwd is a downstream repo; no release path. `global` findings here become "draft a patch + open an upstream PR / issue" rather than "push and release".
177
+
178
+ **Step B — score each finding.** Default to `global` if the finding matches **any** of:
179
+
180
+ - Cites SKILL.md wording / phase / predicate / rule numbers.
181
+ - The proposed fix is a generic anti-pattern string, a terminator literal, a runway line, or a lockout that any repo would benefit from.
182
+ - The same gap would plausibly show up in two or more consumer repos.
183
+
184
+ Default to `project` if the finding matches **any** of:
185
+
186
+ - Cites a repo-specific path (`src/core/...`, `bin/...-smoke.ts`), brand, schema, table, or domain term.
187
+ - The fix is a taboo, a smoke convention, an env / boot detail, or a glossary entry.
188
+ - The fix would not apply (or would be wrong) in another consumer repo.
189
+
190
+ Present the scope-decision table:
191
+
192
+ ```
193
+ | # | finding (short) | default scope | target file | flip? |
194
+ | - | ---------------------------------------- | ------------- | ------------------------------------ | ----- |
195
+ | 1 | /do hands flow back between phases | global | pi-dev:skills/do/SKILL.md | |
196
+ | 2 | docs/handoff/ resurrected after marker | global | pi-dev:skills/migrate/SKILL.md | |
197
+ | 3 | retro-action-item label still alive | project | hugn:docs/agents/preferences.md | |
198
+ | 4 | smoke command name changed in S058 | project | hugn:docs/agents/preferences.md | |
199
+ ```
200
+
201
+ Ask the user once: "OK to proceed with these scopes? Reply with row numbers to flip, or `go`." Apply their flips and move on. If `operator_context=consumer`, any rows still marked `global` get the suffix `(via upstream PR — cannot release locally)` and the apply step adjusts accordingly.
202
+
203
+ ### 6 — Propose edits (per-finding, scoped)
204
+
205
+ For each 🔴 / 🟡 finding, draft the smallest possible edit that, **if it had been in place at session time, would have prevented the gap.** The shape of the draft depends on the scope from Step 5.5:
145
206
 
146
- For each 🔴 finding, draft the smallest possible edit that, **if it had been in the SKILL.md at session time, would have prevented the gap.** Constraints:
207
+ **Global findings (target: pi-dev SKILL.md):**
147
208
 
148
209
  - Edit a **rule** or a **step**, not a flavour sentence. The model must be able to detect the constraint in its own draft output.
149
210
  - Prefer **explicit anti-pattern strings** ("Do not say 'shall I continue?'") over abstract injunctions ("be decisive"). The hugn-2026-05 audit showed that named anti-patterns work.
150
211
  - Prefer **terminal markers** ("the summary's last line must be one of these two literals: …") over qualitative descriptions of "good wrap-up".
151
212
  - Update **at most three skills per run.** More than that means findings aren't anchored well enough.
152
213
 
153
- Show the edits as a unified diff before applying.
214
+ **Project findings (target: consumer's `docs/agents/preferences.md`):**
154
215
 
155
- ### 7 Apply, release, verify
216
+ - Pick the *narrowest* existing section that fits before adding a new one. Mapping:
156
217
 
157
- Standard pi-dev release loop:
218
+ | finding flavour | preferences section |
219
+ | --- | --- |
220
+ | forbidden path / file / module / command | `## Project taboos` |
221
+ | label / state / triage rule | `## Side-effect gates` or `## Project taboos` |
222
+ | smoke / test / endpoint convention | `## Smoke / test conventions` |
223
+ | boot / env / probe change | `## Local-live playbook` |
224
+ | error taxonomy / 5-axes / domain rule | `## Diagnosis posture` |
225
+ | glossary / context term clarification | `## Glossary alignment` |
226
+ | rationale that doesn't fit elsewhere | `## Free notes` (one paragraph max, dated) |
158
227
 
159
- 1. `git add skills/<name>/SKILL.md && git commit -m "<conventional commit anchoring the evidence>"` the commit body should cite the signal that motivated each change.
160
- 2. `cp` the edited SKILL.md into `~/.pi/agent/skills/<name>/` so the **next** session in any repo picks up the change immediately (release-please takes a minute and a half).
228
+ - One bullet per finding. Reference the evidence ticket ("S058 smoke name", "#103 missing disclaimer") so the line stays auditable.
229
+ - Do **not** invent new top-level sections unless three findings legitimately share one.
230
+
231
+ Show all drafts as one unified diff per target file before applying. Group by target file: pi-dev's `skills/<name>/SKILL.md` first (global), then consumer's `docs/agents/preferences.md` (project).
232
+
233
+ ### 7 — Apply, release, verify (branches on scope)
234
+
235
+ Run both branches if the audit produced mixed-scope findings. Each branch has its own terminal state.
236
+
237
+ **7a. Global branch** — only if any finding was approved as `global` **and** `operator_context=maintainer`:
238
+
239
+ 1. From the pi-dev checkout: `git add skills/<name>/SKILL.md && git commit -m "<conventional commit anchoring the evidence>"`. Commit body must cite the signal that motivated each change.
240
+ 2. `cp` each edited SKILL.md into `~/.pi/agent/skills/<name>/` so the **next** session anywhere picks up the change immediately (release-please takes a minute and a half).
161
241
  3. `git push origin main`; release-please opens the version-bump PR; merge it; npm publish runs automatically.
162
- 4. After the next consumer session lands, re-run **this skill** and confirm the targeted signals moved.
242
+ 4. Confirm `npm view pi-dev@latest version` matches the bumped tag.
243
+
244
+ **7a' — Global findings when `operator_context=consumer`:** you cannot release. Instead:
245
+
246
+ 1. Stash the proposed diffs to `/tmp/pi-dev-upstream-<date>.patch` with one file per skill.
247
+ 2. Open an issue on `pi-dev` (or a PR if the operator has clone+push rights) with the evidence excerpts and the patch attached.
248
+ 3. As a hotfix for this machine only, optionally `cp` the edited bodies into `~/.pi/agent/skills/<name>/` and note in the issue that the next `pi-dev update` will overwrite them — which is the desired end state once the upstream change lands.
249
+
250
+ **7b. Project branch** — only if any finding was approved as `project`:
251
+
252
+ 1. In the consumer repo: edit `docs/agents/preferences.md` per the drafts from Step 6. Keep the migration marker at the very end of the file undisturbed.
253
+ 2. Bump the `last-updated` line at the top of the file to today's UTC date.
254
+ 3. `git add docs/agents/preferences.md && git commit -m "docs(agents): <one-liner per finding>"`. Conventional Commits apply.
255
+ 4. Push per the repo's normal workflow. No release-please involvement — preferences are not packaged.
256
+
257
+ **Verification (both branches).** After the next pi session in the affected repo:
258
+
259
+ 1. Re-run **this skill** scoped to the last 24 h.
260
+ 2. Confirm each previously-🔴 signal has moved (chain depth up, intervention rate down, taboo writes gone, disclaimer coverage at 100%, etc.).
261
+ 3. If a signal did not move, the fix wording was too weak — file a follow-up audit, do not re-write from scratch.
163
262
 
164
263
  ## Terminal predicate
165
264
 
166
- This skill is done when **all three** are true:
265
+ This skill is done when **all four** are true:
167
266
 
168
267
  1. A signal table with severities and evidence excerpts has been presented.
169
- 2. Either (a) zero 🔴 findings flow is healthy, recorded as "no change this cycle", OR (b) a unified diff for each 🔴 finding has been applied to `skills/<name>/SKILL.md` AND mirrored into `~/.pi/agent/skills/<name>/`.
170
- 3. If diffs landed, the release loop (commit push merge release PR npm publish) finished and the new version is `npm view pi-dev@latest version`.
268
+ 2. Each finding has an approved scope (`global` / `project` / `defer`) on record, defaulted by Step 5.5 and confirmed by the user.
269
+ 3. Either (a) zero 🔴 findings — flow is healthy, recorded as "no change this cycle", OR (b) each 🔴 finding has landed in its scope's target file (or been stashed + filed upstream when an operator-context mismatch prevents release).
270
+ 4. For any landed change: if `global` and `maintainer`, the npm version has bumped (`npm view pi-dev@latest version`); if `project`, the consumer repo has the commit on its push-stream. Either way, the next-session re-audit plan is stated.
271
+
272
+ The summary's **last line** must be one of:
273
+
274
+ ```
275
+ audit complete — no changes this cycle.
276
+ ```
277
+
278
+ ```
279
+ audit complete — global v<X.Y.Z> released, project commit <sha>, next re-audit after the next session.
280
+ ```
281
+
282
+ ```
283
+ audit complete — upstream issue <#N> filed, project commit <sha>, hotfix mirrored to ~/.pi.
284
+ ```
171
285
 
172
286
  ## What this skill does not do
173
287