pi-gsd 2.0.23 → 2.0.24

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 (211) hide show
  1. package/dist/pi-gsd-hooks.js +5 -5
  2. package/dist/pi-gsd-tools.js +38 -38
  3. package/gsd/hooks/gsd-context-monitor.js +164 -0
  4. package/gsd/hooks/gsd-prompt-guard.js +99 -0
  5. package/gsd/hooks/gsd-workflow-guard.js +98 -0
  6. package/package.json +4 -5
  7. package/scripts/postinstall.js +119 -334
  8. package/.gsd/harnesses/pi/get-shit-done/workflows/discuss-phase.md.bak +0 -1049
  9. package/.gsd/harnesses/pi/get-shit-done/workflows/execute-phase.md.bak +0 -846
  10. package/.gsd/harnesses/pi/get-shit-done/workflows/new-milestone.md.bak +0 -486
  11. package/.gsd/harnesses/pi/get-shit-done/workflows/new-project.md.bak +0 -1250
  12. package/.gsd/harnesses/pi/get-shit-done/workflows/plan-phase.md.bak +0 -859
  13. package/.gsd/harnesses/pi/gsd-file-manifest.json +0 -219
  14. package/.gsd/harnesses/pi/hooks/gsd-context-monitor.js +0 -159
  15. package/.gsd/harnesses/pi/hooks/gsd-prompt-guard.js +0 -99
  16. package/.gsd/harnesses/pi/hooks/gsd-workflow-guard.js +0 -97
  17. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/VERSION +0 -0
  18. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-advisor-researcher.md +0 -0
  19. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-assumptions-analyzer.md +0 -0
  20. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-codebase-mapper.md +0 -0
  21. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-debugger.md +0 -0
  22. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-executor.md +0 -0
  23. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-integration-checker.md +0 -0
  24. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-nyquist-auditor.md +0 -0
  25. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-phase-researcher.md +0 -0
  26. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-plan-checker.md +0 -0
  27. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-planner.md +0 -0
  28. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-project-researcher.md +0 -0
  29. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-research-synthesizer.md +0 -0
  30. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-roadmapper.md +0 -0
  31. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-ui-auditor.md +0 -0
  32. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-ui-checker.md +0 -0
  33. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-ui-researcher.md +0 -0
  34. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-user-profiler.md +0 -0
  35. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/agents/gsd-verifier.md +0 -0
  36. /package/{.gsd/harnesses/pi → gsd}/hooks/gsd-check-update.js +0 -0
  37. /package/{.gsd/harnesses/pi → gsd}/hooks/gsd-statusline.js +0 -0
  38. /package/{prompts → gsd/prompts}/gsd-add-backlog.md +0 -0
  39. /package/{prompts → gsd/prompts}/gsd-add-phase.md +0 -0
  40. /package/{prompts → gsd/prompts}/gsd-add-tests.md +0 -0
  41. /package/{prompts → gsd/prompts}/gsd-add-todo.md +0 -0
  42. /package/{prompts → gsd/prompts}/gsd-audit-milestone.md +0 -0
  43. /package/{prompts → gsd/prompts}/gsd-audit-uat.md +0 -0
  44. /package/{prompts → gsd/prompts}/gsd-autonomous.md +0 -0
  45. /package/{prompts → gsd/prompts}/gsd-check-todos.md +0 -0
  46. /package/{prompts → gsd/prompts}/gsd-cleanup.md +0 -0
  47. /package/{prompts → gsd/prompts}/gsd-complete-milestone.md +0 -0
  48. /package/{prompts → gsd/prompts}/gsd-debug.md +0 -0
  49. /package/{prompts → gsd/prompts}/gsd-discuss-phase.md +0 -0
  50. /package/{prompts → gsd/prompts}/gsd-do.md +0 -0
  51. /package/{prompts → gsd/prompts}/gsd-execute-milestone.md +0 -0
  52. /package/{prompts → gsd/prompts}/gsd-execute-phase.md +0 -0
  53. /package/{prompts → gsd/prompts}/gsd-fast.md +0 -0
  54. /package/{prompts → gsd/prompts}/gsd-forensics.md +0 -0
  55. /package/{prompts → gsd/prompts}/gsd-insert-phase.md +0 -0
  56. /package/{prompts → gsd/prompts}/gsd-join-discord.md +0 -0
  57. /package/{prompts → gsd/prompts}/gsd-list-phase-assumptions.md +0 -0
  58. /package/{prompts → gsd/prompts}/gsd-list-workspaces.md +0 -0
  59. /package/{prompts → gsd/prompts}/gsd-manager.md +0 -0
  60. /package/{prompts → gsd/prompts}/gsd-map-codebase.md +0 -0
  61. /package/{prompts → gsd/prompts}/gsd-milestone-summary.md +0 -0
  62. /package/{prompts → gsd/prompts}/gsd-new-milestone.md +0 -0
  63. /package/{prompts → gsd/prompts}/gsd-new-project.md +0 -0
  64. /package/{prompts → gsd/prompts}/gsd-new-workspace.md +0 -0
  65. /package/{prompts → gsd/prompts}/gsd-note.md +0 -0
  66. /package/{prompts → gsd/prompts}/gsd-pause-work.md +0 -0
  67. /package/{prompts → gsd/prompts}/gsd-plan-milestone-gaps.md +0 -0
  68. /package/{prompts → gsd/prompts}/gsd-plan-milestone.md +0 -0
  69. /package/{prompts → gsd/prompts}/gsd-plan-phase.md +0 -0
  70. /package/{prompts → gsd/prompts}/gsd-plant-seed.md +0 -0
  71. /package/{prompts → gsd/prompts}/gsd-pr-branch.md +0 -0
  72. /package/{prompts → gsd/prompts}/gsd-profile-user.md +0 -0
  73. /package/{prompts → gsd/prompts}/gsd-quick.md +0 -0
  74. /package/{prompts → gsd/prompts}/gsd-reapply-patches.md +0 -0
  75. /package/{prompts → gsd/prompts}/gsd-remove-phase.md +0 -0
  76. /package/{prompts → gsd/prompts}/gsd-remove-workspace.md +0 -0
  77. /package/{prompts → gsd/prompts}/gsd-research-phase.md +0 -0
  78. /package/{prompts → gsd/prompts}/gsd-resume-work.md +0 -0
  79. /package/{prompts → gsd/prompts}/gsd-review-backlog.md +0 -0
  80. /package/{prompts → gsd/prompts}/gsd-review.md +0 -0
  81. /package/{prompts → gsd/prompts}/gsd-session-report.md +0 -0
  82. /package/{prompts → gsd/prompts}/gsd-set-profile.md +0 -0
  83. /package/{prompts → gsd/prompts}/gsd-settings.md +0 -0
  84. /package/{prompts → gsd/prompts}/gsd-ship.md +0 -0
  85. /package/{prompts → gsd/prompts}/gsd-thread.md +0 -0
  86. /package/{prompts → gsd/prompts}/gsd-ui-phase.md +0 -0
  87. /package/{prompts → gsd/prompts}/gsd-ui-review.md +0 -0
  88. /package/{prompts → gsd/prompts}/gsd-validate-phase.md +0 -0
  89. /package/{prompts → gsd/prompts}/gsd-verify-work.md +0 -0
  90. /package/{prompts → gsd/prompts}/gsd-workstreams.md +0 -0
  91. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/checkpoints.md +0 -0
  92. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/continuation-format.md +0 -0
  93. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/decimal-phase-calculation.md +0 -0
  94. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/git-integration.md +0 -0
  95. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/git-planning-commit.md +0 -0
  96. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/model-profile-resolution.md +0 -0
  97. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/model-profiles.md +0 -0
  98. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/phase-argument-parsing.md +0 -0
  99. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/planning-config.md +0 -0
  100. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/questioning.md +0 -0
  101. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/tdd.md +0 -0
  102. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/ui-brand.md +0 -0
  103. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/user-profiling.md +0 -0
  104. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/verification-patterns.md +0 -0
  105. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/references/workstream-flag.md +0 -0
  106. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/DEBUG.md +0 -0
  107. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/UAT.md +0 -0
  108. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/UI-SPEC.md +0 -0
  109. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/VALIDATION.md +0 -0
  110. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/claude-md.md +0 -0
  111. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/codebase/architecture.md +0 -0
  112. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/codebase/concerns.md +0 -0
  113. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/codebase/conventions.md +0 -0
  114. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/codebase/integrations.md +0 -0
  115. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/codebase/stack.md +0 -0
  116. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/codebase/structure.md +0 -0
  117. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/codebase/testing.md +0 -0
  118. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/config.json +0 -0
  119. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/context.md +0 -0
  120. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/continue-here.md +0 -0
  121. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/copilot-instructions.md +0 -0
  122. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/debug-subagent-prompt.md +0 -0
  123. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/dev-preferences.md +0 -0
  124. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/discovery.md +0 -0
  125. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/discussion-log.md +0 -0
  126. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/milestone-archive.md +0 -0
  127. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/milestone.md +0 -0
  128. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/phase-prompt.md +0 -0
  129. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/planner-subagent-prompt.md +0 -0
  130. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/project.md +0 -0
  131. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/requirements.md +0 -0
  132. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/research-project/ARCHITECTURE.md +0 -0
  133. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/research-project/FEATURES.md +0 -0
  134. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/research-project/PITFALLS.md +0 -0
  135. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/research-project/STACK.md +0 -0
  136. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/research-project/SUMMARY.md +0 -0
  137. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/research.md +0 -0
  138. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/retrospective.md +0 -0
  139. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/roadmap.md +0 -0
  140. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/state.md +0 -0
  141. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/summary-complex.md +0 -0
  142. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/summary-minimal.md +0 -0
  143. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/summary-standard.md +0 -0
  144. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/summary.md +0 -0
  145. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/user-profile.md +0 -0
  146. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/user-setup.md +0 -0
  147. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/templates/verification-report.md +0 -0
  148. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/add-backlog.md +0 -0
  149. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/add-phase.md +0 -0
  150. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/add-tests.md +0 -0
  151. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/add-todo.md +0 -0
  152. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/audit-milestone.md +0 -0
  153. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/audit-uat.md +0 -0
  154. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/autonomous.md +0 -0
  155. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/check-todos.md +0 -0
  156. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/cleanup.md +0 -0
  157. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/complete-milestone.md +0 -0
  158. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/debug.md +0 -0
  159. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/diagnose-issues.md +0 -0
  160. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/discovery-phase.md +0 -0
  161. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/discuss-phase-assumptions.md +0 -0
  162. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/discuss-phase.md +0 -0
  163. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/do.md +0 -0
  164. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/execute-milestone.md +0 -0
  165. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/execute-phase.md +0 -0
  166. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/execute-plan.md +0 -0
  167. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/fast.md +0 -0
  168. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/forensics.md +0 -0
  169. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/health.md +0 -0
  170. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/help.md +0 -0
  171. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/insert-phase.md +0 -0
  172. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/list-phase-assumptions.md +0 -0
  173. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/list-workspaces.md +0 -0
  174. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/manager.md +0 -0
  175. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/map-codebase.md +0 -0
  176. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/milestone-summary.md +0 -0
  177. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/new-milestone.md +0 -0
  178. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/new-project.md +0 -0
  179. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/new-workspace.md +0 -0
  180. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/next.md +0 -0
  181. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/node-repair.md +0 -0
  182. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/note.md +0 -0
  183. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/pause-work.md +0 -0
  184. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/plan-milestone-gaps.md +0 -0
  185. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/plan-milestone.md +0 -0
  186. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/plan-phase.md +0 -0
  187. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/plant-seed.md +0 -0
  188. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/pr-branch.md +0 -0
  189. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/profile-user.md +0 -0
  190. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/progress.md +0 -0
  191. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/quick.md +0 -0
  192. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/remove-phase.md +0 -0
  193. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/remove-workspace.md +0 -0
  194. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/research-phase.md +0 -0
  195. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/resume-project.md +0 -0
  196. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/review-backlog.md +0 -0
  197. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/review.md +0 -0
  198. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/session-report.md +0 -0
  199. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/set-profile.md +0 -0
  200. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/settings.md +0 -0
  201. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/ship.md +0 -0
  202. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/stats.md +0 -0
  203. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/thread.md +0 -0
  204. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/transition.md +0 -0
  205. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/ui-phase.md +0 -0
  206. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/ui-review.md +0 -0
  207. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/update.md +0 -0
  208. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/validate-phase.md +0 -0
  209. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/verify-phase.md +0 -0
  210. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/verify-work.md +0 -0
  211. /package/{.gsd/harnesses/pi/get-shit-done → gsd}/workflows/workstreams.md +0 -0
@@ -2,15 +2,24 @@
2
2
  /**
3
3
  * postinstall.js - GSD harness installer
4
4
  *
5
- * Runs automatically after `npm install pi-gsd`.
6
- * Copies the pi harness from this package's
7
- * \`.gsd/harnesses/pi/\` into the consumer project's \`.pi/gsd/\`
8
- * and installs the \`pi-gsd-hooks.ts\` extension into \`.pi/extensions/\`.
5
+ * Runs automatically after `npm install pi-gsd` (or `pi install npm:pi-gsd`).
6
+ * Copies runtime harness content from this package's `gsd/` into the consumer
7
+ * project's `.pi/gsd/`, and hook scripts into `.pi/hooks/`.
9
8
  *
10
- * Safe to re-run - files are skipped if already present (unless GSD_FORCE=1).
9
+ * Version detection: each installed .md file carries a `<gsd-version v="X.Y.Z" />`
10
+ * tag stamped with the npm package version that wrote it. On reinstall we read
11
+ * that tag from a sample file; if it doesn't match the current package version we
12
+ * overwrite everything so workflow/template fixes always reach installed projects.
13
+ * Files tagged `do-not-update` are never overwritten (user customisations).
14
+ *
15
+ * Excluded from the `.pi/gsd/` copy:
16
+ * gsd/prompts/ → served from npm package via pi.prompts (not installed locally)
17
+ * gsd/hooks/ → copied to .pi/hooks/ instead
11
18
  */
12
19
 
13
- const fs = require("fs");
20
+ "use strict";
21
+
22
+ const fs = require("fs");
14
23
  const path = require("path");
15
24
 
16
25
  // ─── Constants ────────────────────────────────────────────────────────────────
@@ -19,274 +28,178 @@ const FORCE =
19
28
  process.env.GSD_FORCE_REINSTALL === "1" ||
20
29
  process.argv.includes("--force-reinstall");
21
30
 
22
- /**
23
- * Directory that contains this package's files.
24
- * When executed via npm postinstall, __dirname is the package root.
25
- */
31
+ /** Directory that contains this package's files. */
26
32
  const PKG_DIR = path.resolve(__dirname, "..");
27
33
 
28
34
  /**
29
35
  * The consuming project's root.
30
36
  * npm sets INIT_CWD to the directory where `npm install` was run.
31
- * Fall back to process.cwd() for programmatic / npx usage.
32
37
  */
33
38
  const PROJECT_ROOT = process.env.INIT_CWD || process.cwd();
34
39
 
35
- /**
36
- * Harness definitions.
37
- *
38
- * Each entry maps:
39
- * src - subdirectory under <package>/.gsd/harnesses/
40
- * dest - directory in the consumer project root
41
- * hooks - whether this platform supports GSD hooks (copied from .gsd/hooks/)
42
- */
43
- const HARNESSES = [{ src: "pi", dest: ".pi", hooks: true, subdir: "gsd" }];
40
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
41
+
42
+ function getPackageVersion() {
43
+ try {
44
+ return JSON.parse(fs.readFileSync(path.join(PKG_DIR, "package.json"), "utf8")).version || "0.0.0";
45
+ } catch { return "0.0.0"; }
46
+ }
44
47
 
45
48
  /**
46
- * Subdirectory name used inside each harness's dest folder for
47
- * GSD-specific content (workflows, bin, references, templates …).
49
+ * Read the `<gsd-version v="..." />` tag from a file.
50
+ * Returns { version, doNotUpdate } or null.
48
51
  */
49
- // subdir is now per-harness (see harness config above)
50
-
51
- // ─── Helpers ──────────────────────────────────────────────────────────────────
52
+ function readGsdVersion(filePath) {
53
+ try {
54
+ const head = fs.readFileSync(filePath, "utf8").slice(0, 256);
55
+ const m = /<gsd-version\s+v="([^"]+)"(\s+do-not-update)?\s*\/>/.exec(head);
56
+ return m ? { version: m[1], doNotUpdate: Boolean(m[2]) } : null;
57
+ } catch { return null; }
58
+ }
52
59
 
53
60
  /**
54
- * Recursively copy a directory tree from `src` to `dest`.
55
- * If `overwrite` is false (default), existing files are left untouched.
56
- *
57
- * @param {string} src Absolute source path
58
- * @param {string} dest Absolute destination path
59
- * @param {boolean} overwrite Replace existing files when true
60
- * @returns {{ copied: number, skipped: number }}
61
+ * Recursively copy src dest, stamping each .md file with `pkgVersion`
62
+ * in its `<gsd-version>` tag (unless `do-not-update` is set).
63
+ * Skips top-level subdirs in `exclude`. Skips existing files when !overwrite
64
+ * (except: always updates if installed version tag differs from pkgVersion).
61
65
  */
62
- function copyDir(src, dest, overwrite) {
63
- let copied = 0;
64
- let skipped = 0;
65
-
66
+ function copyDir(src, dest, overwrite, exclude, pkgVersion) {
67
+ let copied = 0, skipped = 0;
66
68
  if (!fs.existsSync(src)) return { copied, skipped };
67
-
68
69
  fs.mkdirSync(dest, { recursive: true });
69
70
 
70
71
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
71
- const srcEntry = path.join(src, entry.name);
72
+ if (exclude && exclude.includes(entry.name)) continue;
73
+ const srcEntry = path.join(src, entry.name);
72
74
  const destEntry = path.join(dest, entry.name);
73
75
 
74
76
  if (entry.isDirectory()) {
75
- const sub = copyDir(srcEntry, destEntry, overwrite);
76
- copied += sub.copied;
77
+ const sub = copyDir(srcEntry, destEntry, overwrite, null, pkgVersion);
78
+ copied += sub.copied;
77
79
  skipped += sub.skipped;
78
80
  } else if (entry.isFile()) {
81
+ const isMd = entry.name.endsWith(".md");
82
+
79
83
  if (!overwrite && fs.existsSync(destEntry)) {
80
- skipped++;
84
+ // Even in no-overwrite mode, update .md files whose version tag
85
+ // doesn't match the current package (the whole point of this system).
86
+ if (isMd) {
87
+ const installed = readGsdVersion(destEntry);
88
+ if (!installed || installed.doNotUpdate || installed.version === pkgVersion) {
89
+ skipped++;
90
+ continue;
91
+ }
92
+ // Fall through — version mismatch, needs update
93
+ } else {
94
+ skipped++;
95
+ continue;
96
+ }
97
+ }
98
+
99
+ if (isMd) {
100
+ // Stamp with current package version
101
+ let content = fs.readFileSync(srcEntry, "utf8");
102
+ content = content.replace(
103
+ /<gsd-version\s+v="[^"]*"(\s+do-not-update)?\s*\/>/,
104
+ (_, dnu) => dnu ? `<gsd-version v="${pkgVersion}" do-not-update />` : `<gsd-version v="${pkgVersion}" />`,
105
+ );
106
+ fs.writeFileSync(destEntry, content, "utf8");
81
107
  } else {
82
108
  fs.copyFileSync(srcEntry, destEntry);
83
- copied++;
84
109
  }
110
+ copied++;
85
111
  }
86
112
  }
87
-
88
113
  return { copied, skipped };
89
114
  }
90
115
 
91
- /**
92
- * Emit a coloured status line to stdout.
93
- * Colours are stripped when stdout is not a TTY (CI / pipe).
94
- *
95
- * @param {'ok'|'skip'|'warn'|'err'} level
96
- * @param {string} msg
97
- */
98
116
  function log(level, msg) {
99
117
  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}`);
118
+ const sym = { ok: isTTY ? "\x1b[32m✓\x1b[0m" : "✓", skip: isTTY ? "\x1b[33m–\x1b[0m" : "–", warn: isTTY ? "\x1b[33m⚠\x1b[0m" : "⚠" };
119
+ console.log(` ${sym[level] || " "} ${msg}`);
107
120
  }
108
121
 
109
122
  // ─── Main ─────────────────────────────────────────────────────────────────────
110
123
 
111
124
  function main() {
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
125
  if (process.env.GSD_SKIP_INSTALL === "1") {
124
126
  log("skip", "GSD_SKIP_INSTALL=1 - skipping harness install.");
125
127
  return;
126
128
  }
127
129
 
128
- const harnessesRoot = path.join(PKG_DIR, ".gsd", "harnesses");
129
- const hooksRoot = path.join(PKG_DIR, ".gsd", "hooks");
130
+ const pkgVersion = getPackageVersion();
131
+ const gsdSrc = path.join(PKG_DIR, "gsd");
132
+ const gsdDest = path.join(PROJECT_ROOT, ".pi", "gsd");
133
+ const hooksSrc = path.join(gsdSrc, "hooks");
134
+ const hooksDest = path.join(PROJECT_ROOT, ".pi", "hooks");
130
135
 
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
- }
136
+ // Detect installed version from a sample file's <gsd-version> tag.
137
+ const sample = path.join(gsdDest, "workflows", "plan-phase.md");
138
+ const installedTag = readGsdVersion(sample);
139
+ const installedVersion = installedTag?.version ?? null;
140
+ const versionChanged = installedVersion !== pkgVersion;
156
141
 
157
- const { copied, skipped } = copyDir(srcGsd, destGsd, FORCE);
158
- totalCopied += copied;
159
- totalSkipped += skipped;
142
+ // In no-force mode, copyDir still updates individual files whose version
143
+ // tag doesn't match — so we only need to force-overwrite non-.md files
144
+ // (config.json, VERSION, hooks) when the version changes.
145
+ const overwriteAll = FORCE || versionChanged;
160
146
 
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
- }
147
+ console.log("");
148
+ console.log(` GSD v${pkgVersion} - installing into project…`);
149
+ if (versionChanged && installedVersion) {
150
+ console.log(` (updating from v${installedVersion} → v${pkgVersion})`);
151
+ } else if (FORCE) {
152
+ console.log(" (force-reinstall: existing files will be overwritten)");
153
+ }
154
+ console.log("");
201
155
 
202
- // ── skills/ (opencode only - present in .gsd/harnesses/opencode/skills)
203
- const srcSkills = path.join(srcHarness, "skills");
204
- const destSkills = path.join(destHarness, "skills");
156
+ // ── Runtime harness .pi/gsd/ ────────────────────────────────────────────
157
+ const { copied: gsdCopied, skipped: gsdSkipped } = copyDir(
158
+ gsdSrc, gsdDest, overwriteAll, ["prompts", "hooks"], pkgVersion,
159
+ );
205
160
 
206
- if (fs.existsSync(srcSkills)) {
207
- const s = copyDir(srcSkills, destSkills, FORCE);
208
- totalCopied += s.copied;
209
- totalSkipped += s.skipped;
161
+ if (gsdCopied > 0) {
162
+ log("ok", `.pi/gsd (${gsdCopied} file${gsdCopied === 1 ? "" : "s"} updated to v${pkgVersion})`);
163
+ } else {
164
+ log("skip", `.pi/gsd (v${pkgVersion} already installed, ${gsdSkipped} files skipped)`);
165
+ }
210
166
 
211
- if (s.copied > 0) {
212
- log(
213
- "ok",
214
- `${harness.dest}/skills (${s.copied} skill file${s.copied === 1 ? "" : "s"} installed)`,
215
- );
216
- }
167
+ // ── Hook scripts → .pi/hooks/ ─────────────────────────────────────────────
168
+ if (fs.existsSync(hooksSrc)) {
169
+ const { copied: hCopied } = copyDir(hooksSrc, hooksDest, overwriteAll, null, pkgVersion);
170
+ if (hCopied > 0) {
171
+ log("ok", `.pi/hooks (${hCopied} hook${hCopied === 1 ? "" : "s"} installed)`);
217
172
  }
218
-
219
- installed++;
220
173
  }
221
174
 
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.
175
+ // ── Cleanup: stale files from old install layouts ──────────────────────────
224
176
  const extDir = path.join(PROJECT_ROOT, ".pi", "extensions");
225
- const staleExts = ["gsd-hooks.ts", "pi-gsd-hooks.ts"];
226
- for (const name of staleExts) {
177
+ for (const name of ["gsd-hooks.ts", "pi-gsd-hooks.ts"]) {
227
178
  const stale = path.join(extDir, name);
228
179
  if (fs.existsSync(stale)) {
229
180
  fs.rmSync(stale);
230
181
  log("ok", `.pi/extensions/${name} (removed - extension now served from package)`);
231
182
  }
232
183
  }
233
- const settingsFile2 = path.join(PROJECT_ROOT, ".pi", "settings.json");
234
- if (fs.existsSync(settingsFile2)) {
184
+ const settingsFile = path.join(PROJECT_ROOT, ".pi", "settings.json");
185
+ if (fs.existsSync(settingsFile)) {
235
186
  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");
187
+ const settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
188
+ if (Array.isArray(settings.extensions)) {
189
+ const staleNames = ["gsd-hooks.ts", "pi-gsd-hooks.ts"];
190
+ const cleaned = settings.extensions.filter((e) => !staleNames.some((n) => e.endsWith(n)));
191
+ if (cleaned.length !== settings.extensions.length) {
192
+ settings.extensions = cleaned;
193
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, "\t"), "utf8");
242
194
  log("ok", ".pi/settings.json (removed stale extension entries)");
243
195
  }
244
196
  }
245
197
  } catch { /* ignore */ }
246
198
  }
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
199
 
274
200
  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
- );
201
+ console.log(` GSD v${pkgVersion} installed successfully.`);
202
+ console.log(` ${gsdCopied} file${gsdCopied === 1 ? "" : "s"} written.`);
290
203
  console.log("");
291
204
  console.log(" Next steps:");
292
205
  console.log(" Run /gsd-new-project to initialise a project.");
@@ -295,132 +208,4 @@ function main() {
295
208
  console.log("");
296
209
  }
297
210
 
298
- /**
299
- * Read the version from this package's own package.json.
300
- * Gracefully returns 'unknown' if the file is unreadable.
301
- *
302
- * @returns {string}
303
- */
304
- function getPackageVersion() {
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
- }
314
-
315
- /**
316
- * Install the GSD pi extension into the consumer project's .pi/extensions/ directory.
317
- * Also updates .pi/settings.json to include the extension in the extensions array.
318
- *
319
- * The extension registers three non-blocking pi lifecycle hooks:
320
- * session_start → background GSD update check
321
- * tool_call → workflow guard advisory (write/edit outside GSD context)
322
- * tool_result → context usage monitor
323
- *
324
- * @param {string} projectRoot Consumer project root
325
- * @param {string} pkgDir This package's root directory
326
- * @param {boolean} force Overwrite existing files
327
- * @param {function} callback Called with (copied: boolean)
328
- */
329
- function installPiExtension(projectRoot, pkgDir, force, callback) {
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
- }
425
-
426
211
  main();