pi-gsd 1.0.7 → 1.2.2
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/.gsd/extensions/gsd-hooks.ts +237 -0
- package/README.md +108 -310
- package/dist/pi-gsd-tools.js +383 -0
- package/package.json +11 -10
- package/scripts/postinstall.js +270 -220
- package/skills/gsd-add-backlog/SKILL.md +3 -3
- package/skills/gsd-add-phase/SKILL.md +2 -2
- package/skills/gsd-add-tests/SKILL.md +2 -2
- package/skills/gsd-add-todo/SKILL.md +2 -2
- package/skills/gsd-audit-milestone/SKILL.md +2 -2
- package/skills/gsd-audit-uat/SKILL.md +1 -1
- package/skills/gsd-autonomous/SKILL.md +4 -4
- package/skills/gsd-check-todos/SKILL.md +2 -2
- package/skills/gsd-cleanup/SKILL.md +2 -2
- package/skills/gsd-complete-milestone/SKILL.md +2 -2
- package/skills/gsd-debug/SKILL.md +2 -2
- package/skills/gsd-discuss-phase/SKILL.md +6 -6
- package/skills/gsd-do/SKILL.md +3 -3
- package/skills/gsd-execute-phase/SKILL.md +4 -4
- package/skills/gsd-fast/SKILL.md +2 -2
- package/skills/gsd-forensics/SKILL.md +2 -2
- package/skills/gsd-health/SKILL.md +7 -3
- package/skills/gsd-help/SKILL.md +2 -2
- package/skills/gsd-insert-phase/SKILL.md +2 -2
- package/skills/gsd-list-phase-assumptions/SKILL.md +1 -1
- package/skills/gsd-list-workspaces/SKILL.md +3 -3
- package/skills/gsd-manager/SKILL.md +4 -4
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-milestone-summary/SKILL.md +2 -2
- package/skills/gsd-new-milestone/SKILL.md +6 -6
- package/skills/gsd-new-project/SKILL.md +6 -6
- package/skills/gsd-new-workspace/SKILL.md +3 -3
- package/skills/gsd-next/SKILL.md +2 -2
- package/skills/gsd-note/SKILL.md +3 -3
- package/skills/gsd-pause-work/SKILL.md +2 -2
- package/skills/gsd-plan-milestone-gaps/SKILL.md +2 -2
- package/skills/gsd-plan-phase/SKILL.md +3 -3
- package/skills/gsd-plant-seed/SKILL.md +2 -2
- package/skills/gsd-pr-branch/SKILL.md +2 -2
- package/skills/gsd-profile-user/SKILL.md +2 -2
- package/skills/gsd-progress/SKILL.md +7 -3
- package/skills/gsd-quick/SKILL.md +2 -2
- package/skills/gsd-remove-phase/SKILL.md +2 -2
- package/skills/gsd-remove-workspace/SKILL.md +3 -3
- package/skills/gsd-research-phase/SKILL.md +3 -3
- package/skills/gsd-resume-work/SKILL.md +2 -2
- package/skills/gsd-review/SKILL.md +2 -2
- package/skills/gsd-review-backlog/SKILL.md +2 -2
- package/skills/gsd-session-report/SKILL.md +2 -2
- package/skills/gsd-set-profile/SKILL.md +1 -1
- package/skills/gsd-settings/SKILL.md +2 -2
- package/skills/gsd-setup-pi/SKILL.md +105 -0
- package/skills/gsd-ship/SKILL.md +2 -2
- package/skills/gsd-stats/SKILL.md +6 -2
- package/skills/gsd-thread/SKILL.md +2 -2
- package/skills/gsd-ui-phase/SKILL.md +3 -3
- package/skills/gsd-ui-review/SKILL.md +3 -3
- package/skills/gsd-update/SKILL.md +2 -2
- package/skills/gsd-validate-phase/SKILL.md +2 -2
- package/skills/gsd-verify-work/SKILL.md +3 -3
- package/skills/gsd-workstreams/SKILL.md +1 -1
- package/dist/gsd-tools.js +0 -380
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gsd-hooks.ts - GSD pi Extension
|
|
3
|
+
* gsd-extension-version: 1.30.0
|
|
4
|
+
*
|
|
5
|
+
* Pi lifecycle extension for the Get Shit Done (GSD) workflow framework.
|
|
6
|
+
* Provides three non-blocking hooks:
|
|
7
|
+
*
|
|
8
|
+
* session_start → background GSD update check (24 h cache)
|
|
9
|
+
* tool_call → workflow guard advisory (write/edit outside GSD context)
|
|
10
|
+
* tool_result → context usage monitor with debounced warnings
|
|
11
|
+
*
|
|
12
|
+
* Non-blocking guarantee: all failures are silent; hook errors never prevent
|
|
13
|
+
* tool execution or session startup.
|
|
14
|
+
*
|
|
15
|
+
* Auto-discovered by pi from .pi/extensions/ (no settings.json entry required).
|
|
16
|
+
* Source: https://github.com/fulgidus/pi-gsd
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import type { ContextUsage, ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
24
|
+
|
|
25
|
+
export default function (pi: ExtensionAPI) {
|
|
26
|
+
// ── session_start: GSD update check ──────────────────────────────────────
|
|
27
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
28
|
+
try {
|
|
29
|
+
const cacheDir = join(homedir(), ".pi", "cache");
|
|
30
|
+
const cacheFile = join(cacheDir, "gsd-update-check.json");
|
|
31
|
+
const CACHE_TTL_SECONDS = 86_400; // 24 hours
|
|
32
|
+
|
|
33
|
+
// Show cached update notification if available
|
|
34
|
+
if (existsSync(cacheFile)) {
|
|
35
|
+
try {
|
|
36
|
+
const cache = JSON.parse(readFileSync(cacheFile, "utf8")) as {
|
|
37
|
+
update_available?: boolean;
|
|
38
|
+
installed?: string;
|
|
39
|
+
latest?: string;
|
|
40
|
+
checked?: number;
|
|
41
|
+
};
|
|
42
|
+
const ageSeconds =
|
|
43
|
+
Math.floor(Date.now() / 1000) - (cache.checked ?? 0);
|
|
44
|
+
|
|
45
|
+
if (cache.update_available && cache.latest) {
|
|
46
|
+
ctx.ui.notify(
|
|
47
|
+
`GSD update available: ${cache.installed ?? "?"} → ${cache.latest}. Run: npm i -g pi-gsd`,
|
|
48
|
+
"info",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Cache is fresh - skip network check
|
|
53
|
+
if (ageSeconds < CACHE_TTL_SECONDS) return;
|
|
54
|
+
} catch {
|
|
55
|
+
// Corrupt cache - fall through to fresh check
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Run network check asynchronously after 3 s to avoid blocking startup
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
try {
|
|
62
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
63
|
+
|
|
64
|
+
// Resolve installed version from project or global GSD install
|
|
65
|
+
let installed = "0.0.0";
|
|
66
|
+
const versionPaths = [
|
|
67
|
+
join(ctx.cwd, ".pi", "gsd", "VERSION"),
|
|
68
|
+
join(homedir(), ".pi", "gsd", "VERSION"),
|
|
69
|
+
];
|
|
70
|
+
for (const vp of versionPaths) {
|
|
71
|
+
if (existsSync(vp)) {
|
|
72
|
+
try {
|
|
73
|
+
installed = readFileSync(vp, "utf8").trim();
|
|
74
|
+
break;
|
|
75
|
+
} catch {
|
|
76
|
+
/* skip unreadable */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let latest: string | null = null;
|
|
82
|
+
try {
|
|
83
|
+
latest = execSync("npm view pi-gsd version", {
|
|
84
|
+
encoding: "utf8",
|
|
85
|
+
timeout: 10_000,
|
|
86
|
+
windowsHide: true,
|
|
87
|
+
}).trim();
|
|
88
|
+
} catch {
|
|
89
|
+
/* offline or npm unavailable */
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
writeFileSync(
|
|
93
|
+
cacheFile,
|
|
94
|
+
JSON.stringify({
|
|
95
|
+
update_available:
|
|
96
|
+
latest !== null &&
|
|
97
|
+
installed !== "0.0.0" &&
|
|
98
|
+
installed !== latest,
|
|
99
|
+
installed,
|
|
100
|
+
latest: latest ?? "unknown",
|
|
101
|
+
checked: Math.floor(Date.now() / 1000),
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
} catch {
|
|
105
|
+
/* silent fail */
|
|
106
|
+
}
|
|
107
|
+
}, 3_000);
|
|
108
|
+
} catch {
|
|
109
|
+
/* silent fail - never throw from session_start */
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── tool_call: workflow guard (advisory only, never blocking) ────────────
|
|
114
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
115
|
+
try {
|
|
116
|
+
// Only guard write and edit tool calls
|
|
117
|
+
if (event.toolName !== "write" && event.toolName !== "edit")
|
|
118
|
+
return undefined;
|
|
119
|
+
|
|
120
|
+
const filePath = (event.input as { path?: string }).path ?? "";
|
|
121
|
+
|
|
122
|
+
// Allow .planning/ edits (GSD state management)
|
|
123
|
+
if (filePath.includes(".planning/")) return undefined;
|
|
124
|
+
|
|
125
|
+
// Allow common config/docs files that don't need GSD tracking
|
|
126
|
+
const allowed = [
|
|
127
|
+
/\.gitignore$/,
|
|
128
|
+
/\.env/,
|
|
129
|
+
/AGENTS\.md$/,
|
|
130
|
+
/settings\.json$/,
|
|
131
|
+
/gsd-hooks\.ts$/,
|
|
132
|
+
];
|
|
133
|
+
if (allowed.some((p) => p.test(filePath))) return undefined;
|
|
134
|
+
|
|
135
|
+
// Only activate when GSD project has workflow_guard enabled
|
|
136
|
+
const configPath = join(ctx.cwd, ".planning", "config.json");
|
|
137
|
+
if (!existsSync(configPath)) return undefined; // No GSD project
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const config = JSON.parse(readFileSync(configPath, "utf8")) as {
|
|
141
|
+
hooks?: { workflow_guard?: boolean };
|
|
142
|
+
};
|
|
143
|
+
if (!config.hooks?.workflow_guard) return undefined; // Guard disabled (default)
|
|
144
|
+
} catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Advisory only - never block tool execution
|
|
149
|
+
const fileName = filePath.split("/").pop() ?? filePath;
|
|
150
|
+
ctx.ui.notify(
|
|
151
|
+
`⚠️ GSD: Editing ${fileName} outside a GSD workflow. Consider /gsd-fast or /gsd-quick to maintain state tracking.`,
|
|
152
|
+
"info",
|
|
153
|
+
);
|
|
154
|
+
} catch {
|
|
155
|
+
/* silent fail - never block tool execution */
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return undefined;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── tool_result: context usage monitor ───────────────────────────────────
|
|
162
|
+
const WARNING_THRESHOLD = 35; // warn when remaining % ≤ 35
|
|
163
|
+
const CRITICAL_THRESHOLD = 25; // critical when remaining % ≤ 25
|
|
164
|
+
const DEBOUNCE_CALLS = 5; // minimum tool uses between repeated warnings
|
|
165
|
+
|
|
166
|
+
let callsSinceWarn = 0;
|
|
167
|
+
let lastLevel: "warning" | "critical" | null = null;
|
|
168
|
+
|
|
169
|
+
pi.on("tool_result", async (_event, ctx) => {
|
|
170
|
+
try {
|
|
171
|
+
const usage: ContextUsage | undefined = ctx.getContextUsage();
|
|
172
|
+
if (!usage || usage.percent === null) return undefined;
|
|
173
|
+
|
|
174
|
+
const usedPct = Math.round(usage.percent);
|
|
175
|
+
const remaining = 100 - usedPct;
|
|
176
|
+
|
|
177
|
+
// Below warning threshold - just increment debounce counter
|
|
178
|
+
if (remaining > WARNING_THRESHOLD) {
|
|
179
|
+
callsSinceWarn++;
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Respect opt-out via project config
|
|
184
|
+
const configPath = join(ctx.cwd, ".planning", "config.json");
|
|
185
|
+
if (existsSync(configPath)) {
|
|
186
|
+
try {
|
|
187
|
+
const config = JSON.parse(readFileSync(configPath, "utf8")) as {
|
|
188
|
+
hooks?: { context_warnings?: boolean };
|
|
189
|
+
};
|
|
190
|
+
if (config.hooks?.context_warnings === false) return undefined;
|
|
191
|
+
} catch {
|
|
192
|
+
/* ignore config errors */
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const isCritical = remaining <= CRITICAL_THRESHOLD;
|
|
197
|
+
const currentLevel: "warning" | "critical" = isCritical
|
|
198
|
+
? "critical"
|
|
199
|
+
: "warning";
|
|
200
|
+
|
|
201
|
+
callsSinceWarn++;
|
|
202
|
+
|
|
203
|
+
// Debounce - allow severity escalation (warning → critical bypasses debounce)
|
|
204
|
+
const severityEscalated =
|
|
205
|
+
currentLevel === "critical" && lastLevel === "warning";
|
|
206
|
+
if (
|
|
207
|
+
lastLevel !== null &&
|
|
208
|
+
callsSinceWarn < DEBOUNCE_CALLS &&
|
|
209
|
+
!severityEscalated
|
|
210
|
+
) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
callsSinceWarn = 0;
|
|
215
|
+
lastLevel = currentLevel;
|
|
216
|
+
|
|
217
|
+
const isGsdActive = existsSync(join(ctx.cwd, ".planning", "STATE.md"));
|
|
218
|
+
|
|
219
|
+
let msg: string;
|
|
220
|
+
if (isCritical) {
|
|
221
|
+
msg = isGsdActive
|
|
222
|
+
? `🔴 CONTEXT CRITICAL: ${usedPct}% used (${remaining}% left). GSD state is in STATE.md. Inform user to run /gsd-pause-work.`
|
|
223
|
+
: `🔴 CONTEXT CRITICAL: ${usedPct}% used (${remaining}% left). Inform user context is nearly exhausted.`;
|
|
224
|
+
} else {
|
|
225
|
+
msg = isGsdActive
|
|
226
|
+
? `⚠️ CONTEXT WARNING: ${usedPct}% used (${remaining}% left). Avoid starting new complex work.`
|
|
227
|
+
: `⚠️ CONTEXT WARNING: ${usedPct}% used (${remaining}% left). Context is getting limited.`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
ctx.ui.notify(msg, isCritical ? "error" : "info");
|
|
231
|
+
} catch {
|
|
232
|
+
/* silent fail - never throw from tool_result */
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return undefined;
|
|
236
|
+
});
|
|
237
|
+
}
|