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 +19 -6
- package/dist/cli.js +77 -50
- package/dist/install.js +106 -38
- package/dist/paths.js +12 -0
- package/package.json +1 -1
- package/skills/improve-skill-flow/SKILL.md +125 -11
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
|
-
#
|
|
45
|
+
# Interactive: pick global vs project-local, then install + seed preferences
|
|
46
46
|
npx pi-dev@latest install
|
|
47
47
|
|
|
48
|
-
#
|
|
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
|
|
58
|
+
# Verify the active scope's layout
|
|
55
59
|
npx pi-dev doctor
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
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]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
pi-dev
|
|
20
|
-
|
|
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
|
|
24
|
-
/taste
|
|
25
|
-
/where
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 {
|
|
6
|
-
|
|
7
|
-
|
|
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(
|
|
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 ${
|
|
60
|
+
console.log(`Installed ${copied} skill(s) into ${skillsDir} [${scope}]`);
|
|
27
61
|
if (opts.skipPrefs) {
|
|
28
|
-
console.log("Skipped
|
|
62
|
+
console.log("Skipped preferences (pass --include-prefs on update to merge in new keys).");
|
|
29
63
|
return;
|
|
30
64
|
}
|
|
31
|
-
if (!existsSync(
|
|
65
|
+
if (!existsSync(prefsFile)) {
|
|
32
66
|
if (existsSync(PKG_GLOBAL_PREFS_PRESET)) {
|
|
33
|
-
copyFileSync(PKG_GLOBAL_PREFS_PRESET,
|
|
34
|
-
console.log(`Seeded
|
|
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
|
|
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
|
|
43
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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(
|
|
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(
|
|
80
|
-
issues.push(`
|
|
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
|
@@ -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
|
-
###
|
|
160
|
+
### 5.5 — Decide 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
|
-
|
|
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
|
-
|
|
214
|
+
**Project findings (target: consumer's `docs/agents/preferences.md`):**
|
|
154
215
|
|
|
155
|
-
|
|
216
|
+
- Pick the *narrowest* existing section that fits before adding a new one. Mapping:
|
|
156
217
|
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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.
|
|
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
|
|
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.
|
|
170
|
-
3.
|
|
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
|
|