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.
Files changed (62) hide show
  1. package/.gsd/extensions/gsd-hooks.ts +237 -0
  2. package/README.md +108 -310
  3. package/dist/pi-gsd-tools.js +383 -0
  4. package/package.json +11 -10
  5. package/scripts/postinstall.js +270 -220
  6. package/skills/gsd-add-backlog/SKILL.md +3 -3
  7. package/skills/gsd-add-phase/SKILL.md +2 -2
  8. package/skills/gsd-add-tests/SKILL.md +2 -2
  9. package/skills/gsd-add-todo/SKILL.md +2 -2
  10. package/skills/gsd-audit-milestone/SKILL.md +2 -2
  11. package/skills/gsd-audit-uat/SKILL.md +1 -1
  12. package/skills/gsd-autonomous/SKILL.md +4 -4
  13. package/skills/gsd-check-todos/SKILL.md +2 -2
  14. package/skills/gsd-cleanup/SKILL.md +2 -2
  15. package/skills/gsd-complete-milestone/SKILL.md +2 -2
  16. package/skills/gsd-debug/SKILL.md +2 -2
  17. package/skills/gsd-discuss-phase/SKILL.md +6 -6
  18. package/skills/gsd-do/SKILL.md +3 -3
  19. package/skills/gsd-execute-phase/SKILL.md +4 -4
  20. package/skills/gsd-fast/SKILL.md +2 -2
  21. package/skills/gsd-forensics/SKILL.md +2 -2
  22. package/skills/gsd-health/SKILL.md +7 -3
  23. package/skills/gsd-help/SKILL.md +2 -2
  24. package/skills/gsd-insert-phase/SKILL.md +2 -2
  25. package/skills/gsd-list-phase-assumptions/SKILL.md +1 -1
  26. package/skills/gsd-list-workspaces/SKILL.md +3 -3
  27. package/skills/gsd-manager/SKILL.md +4 -4
  28. package/skills/gsd-map-codebase/SKILL.md +1 -1
  29. package/skills/gsd-milestone-summary/SKILL.md +2 -2
  30. package/skills/gsd-new-milestone/SKILL.md +6 -6
  31. package/skills/gsd-new-project/SKILL.md +6 -6
  32. package/skills/gsd-new-workspace/SKILL.md +3 -3
  33. package/skills/gsd-next/SKILL.md +2 -2
  34. package/skills/gsd-note/SKILL.md +3 -3
  35. package/skills/gsd-pause-work/SKILL.md +2 -2
  36. package/skills/gsd-plan-milestone-gaps/SKILL.md +2 -2
  37. package/skills/gsd-plan-phase/SKILL.md +3 -3
  38. package/skills/gsd-plant-seed/SKILL.md +2 -2
  39. package/skills/gsd-pr-branch/SKILL.md +2 -2
  40. package/skills/gsd-profile-user/SKILL.md +2 -2
  41. package/skills/gsd-progress/SKILL.md +7 -3
  42. package/skills/gsd-quick/SKILL.md +2 -2
  43. package/skills/gsd-remove-phase/SKILL.md +2 -2
  44. package/skills/gsd-remove-workspace/SKILL.md +3 -3
  45. package/skills/gsd-research-phase/SKILL.md +3 -3
  46. package/skills/gsd-resume-work/SKILL.md +2 -2
  47. package/skills/gsd-review/SKILL.md +2 -2
  48. package/skills/gsd-review-backlog/SKILL.md +2 -2
  49. package/skills/gsd-session-report/SKILL.md +2 -2
  50. package/skills/gsd-set-profile/SKILL.md +1 -1
  51. package/skills/gsd-settings/SKILL.md +2 -2
  52. package/skills/gsd-setup-pi/SKILL.md +105 -0
  53. package/skills/gsd-ship/SKILL.md +2 -2
  54. package/skills/gsd-stats/SKILL.md +6 -2
  55. package/skills/gsd-thread/SKILL.md +2 -2
  56. package/skills/gsd-ui-phase/SKILL.md +3 -3
  57. package/skills/gsd-ui-review/SKILL.md +3 -3
  58. package/skills/gsd-update/SKILL.md +2 -2
  59. package/skills/gsd-validate-phase/SKILL.md +2 -2
  60. package/skills/gsd-verify-work/SKILL.md +3 -3
  61. package/skills/gsd-workstreams/SKILL.md +1 -1
  62. 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
+ }