pi-gsd 2.0.4 → 2.0.6
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/harnesses/pi/get-shit-done/workflows/add-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/add-tests.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/add-todo.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/audit-milestone.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/autonomous.md +6 -6
- package/.gsd/harnesses/pi/get-shit-done/workflows/check-todos.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/complete-milestone.md +2 -2
- package/.gsd/harnesses/pi/get-shit-done/workflows/discuss-phase-assumptions.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/discuss-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/do.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/execute-milestone.md +38 -38
- package/.gsd/harnesses/pi/get-shit-done/workflows/execute-plan.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/insert-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/list-workspaces.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/manager.md +2 -2
- package/.gsd/harnesses/pi/get-shit-done/workflows/map-codebase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/milestone-summary.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/new-milestone.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/new-project.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/new-workspace.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/pause-work.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/plan-milestone.md +8 -8
- package/.gsd/harnesses/pi/get-shit-done/workflows/progress.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/quick.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/remove-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/remove-workspace.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/research-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/resume-project.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/review.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/settings.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/ship.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/ui-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/ui-review.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/validate-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/verify-phase.md +1 -1
- package/.gsd/harnesses/pi/get-shit-done/workflows/verify-work.md +1 -1
- package/README.md +60 -60
- package/dist/pi-gsd-hooks.js +4 -4
- package/dist/pi-gsd-tools.js +2 -2
- package/package.json +1 -1
- package/prompts/gsd-discuss-phase.md +1 -1
- package/prompts/gsd-execute-phase.md +1 -1
- package/prompts/gsd-plan-phase.md +1 -1
- package/prompts/gsd-quick.md +1 -1
- package/prompts/gsd-verify-work.md +1 -1
- package/scripts/postinstall.js +323 -323
package/scripts/postinstall.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* \`.gsd/harnesses/pi/\` into the consumer project's \`.pi/gsd/\`
|
|
8
8
|
* and installs the \`pi-gsd-hooks.ts\` extension into \`.pi/extensions/\`.
|
|
9
9
|
*
|
|
10
|
-
* Safe to re-run
|
|
10
|
+
* Safe to re-run - files are skipped if already present (unless GSD_FORCE=1).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require("fs");
|
|
@@ -16,8 +16,8 @@ const path = require("path");
|
|
|
16
16
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
18
|
const FORCE =
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
process.env.GSD_FORCE_REINSTALL === "1" ||
|
|
20
|
+
process.argv.includes("--force-reinstall");
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Directory that contains this package's files.
|
|
@@ -60,32 +60,32 @@ const HARNESSES = [{ src: "pi", dest: ".pi", hooks: true, subdir: "gsd" }];
|
|
|
60
60
|
* @returns {{ copied: number, skipped: number }}
|
|
61
61
|
*/
|
|
62
62
|
function copyDir(src, dest, overwrite) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
63
|
+
let copied = 0;
|
|
64
|
+
let skipped = 0;
|
|
65
|
+
|
|
66
|
+
if (!fs.existsSync(src)) return { copied, skipped };
|
|
67
|
+
|
|
68
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
69
|
+
|
|
70
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
71
|
+
const srcEntry = path.join(src, entry.name);
|
|
72
|
+
const destEntry = path.join(dest, entry.name);
|
|
73
|
+
|
|
74
|
+
if (entry.isDirectory()) {
|
|
75
|
+
const sub = copyDir(srcEntry, destEntry, overwrite);
|
|
76
|
+
copied += sub.copied;
|
|
77
|
+
skipped += sub.skipped;
|
|
78
|
+
} else if (entry.isFile()) {
|
|
79
|
+
if (!overwrite && fs.existsSync(destEntry)) {
|
|
80
|
+
skipped++;
|
|
81
|
+
} else {
|
|
82
|
+
fs.copyFileSync(srcEntry, destEntry);
|
|
83
|
+
copied++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { copied, skipped };
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
@@ -96,203 +96,203 @@ function copyDir(src, dest, overwrite) {
|
|
|
96
96
|
* @param {string} msg
|
|
97
97
|
*/
|
|
98
98
|
function log(level, msg) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
const isTTY = process.stdout.isTTY;
|
|
100
|
+
const colours = {
|
|
101
|
+
ok: isTTY ? "\x1b[32m✓\x1b[0m" : "✓",
|
|
102
|
+
skip: isTTY ? "\x1b[33m–\x1b[0m" : "–",
|
|
103
|
+
warn: isTTY ? "\x1b[33m⚠\x1b[0m" : "⚠",
|
|
104
|
+
err: isTTY ? "\x1b[31m✗\x1b[0m" : "✗",
|
|
105
|
+
};
|
|
106
|
+
console.log(` ${colours[level] || " "} ${msg}`);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
110
110
|
|
|
111
111
|
function main() {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
112
|
+
// Skip when running inside the package's own development tree
|
|
113
|
+
// (i.e. when INIT_CWD === the package directory itself).
|
|
114
|
+
if (path.resolve(PROJECT_ROOT) === path.resolve(PKG_DIR)) {
|
|
115
|
+
log(
|
|
116
|
+
"skip",
|
|
117
|
+
"Running inside package source tree - skipping harness install.",
|
|
118
|
+
);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Skip when explicitly opted out
|
|
123
|
+
if (process.env.GSD_SKIP_INSTALL === "1") {
|
|
124
|
+
log("skip", "GSD_SKIP_INSTALL=1 - skipping harness install.");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const harnessesRoot = path.join(PKG_DIR, ".gsd", "harnesses");
|
|
129
|
+
const hooksRoot = path.join(PKG_DIR, ".gsd", "hooks");
|
|
130
|
+
|
|
131
|
+
console.log("");
|
|
132
|
+
console.log(" GSD - installing harness files into your project…");
|
|
133
|
+
if (FORCE)
|
|
134
|
+
console.log(" (force-reinstall mode: existing files will be overwritten)");
|
|
135
|
+
console.log("");
|
|
136
|
+
|
|
137
|
+
let totalCopied = 0;
|
|
138
|
+
let totalSkipped = 0;
|
|
139
|
+
let installed = 0;
|
|
140
|
+
|
|
141
|
+
for (const harness of HARNESSES) {
|
|
142
|
+
const srcHarness = path.join(harnessesRoot, harness.src);
|
|
143
|
+
const destHarness = path.join(PROJECT_ROOT, harness.dest);
|
|
144
|
+
|
|
145
|
+
// ── get-shit-done/ content ──────────────────────────────────────────────
|
|
146
|
+
const srcGsd = path.join(srcHarness, harness.subdir);
|
|
147
|
+
const destGsd = path.join(destHarness, harness.subdir);
|
|
148
|
+
|
|
149
|
+
if (!fs.existsSync(srcHarness)) {
|
|
150
|
+
log(
|
|
151
|
+
"skip",
|
|
152
|
+
`${harness.dest}/${harness.subdir} (source absent - skipped)`,
|
|
153
|
+
);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const { copied, skipped } = copyDir(srcGsd, destGsd, FORCE);
|
|
158
|
+
totalCopied += copied;
|
|
159
|
+
totalSkipped += skipped;
|
|
160
|
+
|
|
161
|
+
if (copied > 0 || skipped === 0) {
|
|
162
|
+
log(
|
|
163
|
+
"ok",
|
|
164
|
+
`${harness.dest}/${harness.subdir} (${copied} file${copied === 1 ? "" : "s"} installed)`,
|
|
165
|
+
);
|
|
166
|
+
} else {
|
|
167
|
+
log(
|
|
168
|
+
"skip",
|
|
169
|
+
`${harness.dest}/${harness.subdir} (already up-to-date, ${skipped} file${skipped === 1 ? "" : "s"} skipped)`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── gsd-file-manifest.json ──────────────────────────────────────────────
|
|
174
|
+
const manifestSrc = path.join(srcHarness, "gsd-file-manifest.json");
|
|
175
|
+
const manifestDest = path.join(destHarness, "gsd-file-manifest.json");
|
|
176
|
+
|
|
177
|
+
if (fs.existsSync(manifestSrc)) {
|
|
178
|
+
if (!FORCE && fs.existsSync(manifestDest)) {
|
|
179
|
+
totalSkipped++;
|
|
180
|
+
} else {
|
|
181
|
+
fs.mkdirSync(destHarness, { recursive: true });
|
|
182
|
+
fs.copyFileSync(manifestSrc, manifestDest);
|
|
183
|
+
totalCopied++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── hooks/ (platform-selective) ─────────────────────────────────────────
|
|
188
|
+
if (harness.hooks && fs.existsSync(hooksRoot)) {
|
|
189
|
+
const destHooks = path.join(destHarness, "hooks");
|
|
190
|
+
const h = copyDir(hooksRoot, destHooks, FORCE);
|
|
191
|
+
totalCopied += h.copied;
|
|
192
|
+
totalSkipped += h.skipped;
|
|
193
|
+
|
|
194
|
+
if (h.copied > 0) {
|
|
195
|
+
log(
|
|
196
|
+
"ok",
|
|
197
|
+
`${harness.dest}/hooks (${h.copied} hook${h.copied === 1 ? "" : "s"} installed)`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── skills/ (opencode only - present in .gsd/harnesses/opencode/skills) ─
|
|
203
|
+
const srcSkills = path.join(srcHarness, "skills");
|
|
204
|
+
const destSkills = path.join(destHarness, "skills");
|
|
205
|
+
|
|
206
|
+
if (fs.existsSync(srcSkills)) {
|
|
207
|
+
const s = copyDir(srcSkills, destSkills, FORCE);
|
|
208
|
+
totalCopied += s.copied;
|
|
209
|
+
totalSkipped += s.skipped;
|
|
210
|
+
|
|
211
|
+
if (s.copied > 0) {
|
|
212
|
+
log(
|
|
213
|
+
"ok",
|
|
214
|
+
`${harness.dest}/skills (${s.copied} skill file${s.copied === 1 ? "" : "s"} installed)`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
installed++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Pi extension - served from npm package via pi.extensions in package.json ─────
|
|
223
|
+
// No local copying needed. Cleanup stale files from old install approach.
|
|
224
|
+
const extDir = path.join(PROJECT_ROOT, ".pi", "extensions");
|
|
225
|
+
const staleExts = ["gsd-hooks.ts", "pi-gsd-hooks.ts"];
|
|
226
|
+
for (const name of staleExts) {
|
|
227
|
+
const stale = path.join(extDir, name);
|
|
228
|
+
if (fs.existsSync(stale)) {
|
|
229
|
+
fs.rmSync(stale);
|
|
230
|
+
log("ok", `.pi/extensions/${name} (removed - extension now served from package)`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const settingsFile2 = path.join(PROJECT_ROOT, ".pi", "settings.json");
|
|
234
|
+
if (fs.existsSync(settingsFile2)) {
|
|
235
|
+
try {
|
|
236
|
+
const settings2 = JSON.parse(fs.readFileSync(settingsFile2, "utf8"));
|
|
237
|
+
if (Array.isArray(settings2.extensions)) {
|
|
238
|
+
const cleaned2 = settings2.extensions.filter((e) => !staleExts.some((n) => e.endsWith(n)));
|
|
239
|
+
if (cleaned2.length !== settings2.extensions.length) {
|
|
240
|
+
settings2.extensions = cleaned2;
|
|
241
|
+
fs.writeFileSync(settingsFile2, JSON.stringify(settings2, null, "\t"), "utf8");
|
|
242
|
+
log("ok", ".pi/settings.json (removed stale extension entries)");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch { /* ignore */ }
|
|
246
|
+
}
|
|
247
|
+
// ── Pi prompt templates - cleanup stale local copies ───────────────────────
|
|
248
|
+
// Prompts are served directly from the npm package (user scope).
|
|
249
|
+
// Local copies in .pi/prompts/ cause collision warnings on every pi update.
|
|
250
|
+
// Remove any gsd-*.md files previously installed there.
|
|
251
|
+
// Also remove the old gsd-hooks.ts if present (renamed to pi-gsd-hooks.ts).
|
|
252
|
+
const promptsDest = path.join(PROJECT_ROOT, ".pi", "prompts");
|
|
253
|
+
if (fs.existsSync(promptsDest)) {
|
|
254
|
+
const stale = fs
|
|
255
|
+
.readdirSync(promptsDest)
|
|
256
|
+
.filter((f) => f.startsWith("gsd-") && f.endsWith(".md"));
|
|
257
|
+
if (stale.length > 0) {
|
|
258
|
+
for (const f of stale) fs.rmSync(path.join(promptsDest, f));
|
|
259
|
+
log(
|
|
260
|
+
"ok",
|
|
261
|
+
`.pi/prompts (removed ${stale.length} stale local gsd-*.md - served from package instead)`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const oldExt = path.join(PROJECT_ROOT, ".pi", "extensions", "gsd-hooks.ts");
|
|
266
|
+
if (fs.existsSync(oldExt)) {
|
|
267
|
+
fs.rmSync(oldExt);
|
|
268
|
+
log(
|
|
269
|
+
"ok",
|
|
270
|
+
".pi/extensions/gsd-hooks.ts (removed - renamed to pi-gsd-hooks.ts)",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log("");
|
|
275
|
+
|
|
276
|
+
if (installed === 0) {
|
|
277
|
+
log("warn", "No harness source directories found inside the package.");
|
|
278
|
+
log(
|
|
279
|
+
"warn",
|
|
280
|
+
"The package may be incomplete. Try: npm install --force get-shit-done-cc",
|
|
281
|
+
);
|
|
282
|
+
console.log("");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log(` GSD v${getPackageVersion()} installed successfully.`);
|
|
287
|
+
console.log(
|
|
288
|
+
` ${totalCopied} file${totalCopied === 1 ? "" : "s"} copied, ${totalSkipped} skipped.`,
|
|
289
|
+
);
|
|
290
|
+
console.log("");
|
|
291
|
+
console.log(" Next steps:");
|
|
292
|
+
console.log(" Run /gsd-new-project to initialise a project.");
|
|
293
|
+
console.log("");
|
|
294
|
+
console.log(" Docs: https://github.com/fulgidus/pi-gsd#readme");
|
|
295
|
+
console.log("");
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/**
|
|
@@ -302,14 +302,14 @@ function main() {
|
|
|
302
302
|
* @returns {string}
|
|
303
303
|
*/
|
|
304
304
|
function getPackageVersion() {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
305
|
+
try {
|
|
306
|
+
const pkg = JSON.parse(
|
|
307
|
+
fs.readFileSync(path.join(PKG_DIR, "package.json"), "utf8"),
|
|
308
|
+
);
|
|
309
|
+
return pkg.version || "unknown";
|
|
310
|
+
} catch {
|
|
311
|
+
return "unknown";
|
|
312
|
+
}
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
/**
|
|
@@ -327,100 +327,100 @@ function getPackageVersion() {
|
|
|
327
327
|
* @param {function} callback Called with (copied: boolean)
|
|
328
328
|
*/
|
|
329
329
|
function installPiExtension(projectRoot, pkgDir, force, callback) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
330
|
+
const piDir = path.join(projectRoot, ".pi");
|
|
331
|
+
const extDir = path.join(piDir, "extensions");
|
|
332
|
+
const extDest = path.join(extDir, "pi-gsd-hooks.ts");
|
|
333
|
+
const extSrc = path.join(pkgDir, ".gsd", "extensions", "pi-gsd-hooks.ts");
|
|
334
|
+
|
|
335
|
+
if (!fs.existsSync(extSrc)) {
|
|
336
|
+
log("warn", ".pi/extensions/pi-gsd-hooks.ts (source absent - skipped)");
|
|
337
|
+
callback(false);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Always update the extension - it is owned by pi-gsd, not the user.
|
|
342
|
+
// Compare pi-gsd-extension-version comments to detect staleness.
|
|
343
|
+
const extractVersion = (file) => {
|
|
344
|
+
try {
|
|
345
|
+
const match = fs
|
|
346
|
+
.readFileSync(file, "utf8")
|
|
347
|
+
.match(/pi-gsd-extension-version:\s*([\d.]+)/);
|
|
348
|
+
return match ? match[1] : null;
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const srcVersion = extractVersion(extSrc);
|
|
354
|
+
const destVersion = fs.existsSync(extDest) ? extractVersion(extDest) : null;
|
|
355
|
+
const needsUpdate = force || !destVersion || destVersion !== srcVersion;
|
|
356
|
+
|
|
357
|
+
if (!needsUpdate) {
|
|
358
|
+
log("skip", `.pi/extensions/pi-gsd-hooks.ts (up-to-date v${destVersion})`);
|
|
359
|
+
callback(false);
|
|
360
|
+
} else {
|
|
361
|
+
try {
|
|
362
|
+
fs.mkdirSync(extDir, { recursive: true });
|
|
363
|
+
fs.copyFileSync(extSrc, extDest);
|
|
364
|
+
log(
|
|
365
|
+
"ok",
|
|
366
|
+
".pi/extensions/pi-gsd-hooks.ts (GSD lifecycle extension installed)",
|
|
367
|
+
);
|
|
368
|
+
callback(true);
|
|
369
|
+
} catch (e) {
|
|
370
|
+
log(
|
|
371
|
+
"warn",
|
|
372
|
+
".pi/extensions/pi-gsd-hooks.ts (install failed: " + e.message + ")",
|
|
373
|
+
);
|
|
374
|
+
callback(false);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Update .pi/settings.json to include the extension path in the extensions array.
|
|
380
|
+
// The file is already auto-discovered from .pi/extensions/, but explicit registration
|
|
381
|
+
// is added as a belt-and-suspenders measure.
|
|
382
|
+
const settingsFile = path.join(piDir, "settings.json");
|
|
383
|
+
try {
|
|
384
|
+
let settings = {};
|
|
385
|
+
if (fs.existsSync(settingsFile)) {
|
|
386
|
+
try {
|
|
387
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
|
|
388
|
+
} catch {
|
|
389
|
+
// Unreadable settings - start fresh object
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const extensions = Array.isArray(settings.extensions)
|
|
394
|
+
? settings.extensions
|
|
395
|
+
: [];
|
|
396
|
+
|
|
397
|
+
// Remove stale gsd-hooks.ts entry if present (renamed to pi-gsd-hooks.ts)
|
|
398
|
+
const oldExtPath = path.join(extDir, "gsd-hooks.ts");
|
|
399
|
+
const cleaned = extensions.filter((e) => e !== oldExtPath);
|
|
400
|
+
|
|
401
|
+
// Avoid duplicate entries
|
|
402
|
+
if (!cleaned.includes(extDest)) {
|
|
403
|
+
settings.extensions = [...cleaned, extDest];
|
|
404
|
+
fs.mkdirSync(piDir, { recursive: true });
|
|
405
|
+
fs.writeFileSync(
|
|
406
|
+
settingsFile,
|
|
407
|
+
JSON.stringify(settings, null, "\t"),
|
|
408
|
+
"utf8",
|
|
409
|
+
);
|
|
410
|
+
log("ok", ".pi/settings.json (extensions array updated)");
|
|
411
|
+
} else if (cleaned.length !== extensions.length) {
|
|
412
|
+
// Removed stale entry but extDest already present
|
|
413
|
+
settings.extensions = cleaned;
|
|
414
|
+
fs.writeFileSync(
|
|
415
|
+
settingsFile,
|
|
416
|
+
JSON.stringify(settings, null, "\t"),
|
|
417
|
+
"utf8",
|
|
418
|
+
);
|
|
419
|
+
log("ok", ".pi/settings.json (removed stale gsd-hooks.ts entry)");
|
|
420
|
+
}
|
|
421
|
+
} catch (e) {
|
|
422
|
+
log("warn", ".pi/settings.json (could not update: " + e.message + ")");
|
|
423
|
+
}
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
main();
|