@pareto4/pp-core 1.5.0

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 (618) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/GEMINI.md +53 -0
  3. package/LICENSE +22 -0
  4. package/README.md +100 -0
  5. package/agents/pp-advisor-researcher.md +108 -0
  6. package/agents/pp-ai-researcher.md +114 -0
  7. package/agents/pp-assumptions-analyzer.md +105 -0
  8. package/agents/pp-code-fixer.md +669 -0
  9. package/agents/pp-code-reviewer.md +387 -0
  10. package/agents/pp-codebase-mapper.md +853 -0
  11. package/agents/pp-debug-session-manager.md +315 -0
  12. package/agents/pp-debugger.md +1453 -0
  13. package/agents/pp-doc-classifier.md +168 -0
  14. package/agents/pp-doc-synthesizer.md +204 -0
  15. package/agents/pp-doc-verifier.md +217 -0
  16. package/agents/pp-doc-writer.md +616 -0
  17. package/agents/pp-domain-researcher.md +147 -0
  18. package/agents/pp-eval-auditor.md +191 -0
  19. package/agents/pp-eval-planner.md +154 -0
  20. package/agents/pp-executor.md +808 -0
  21. package/agents/pp-framework-selector.md +160 -0
  22. package/agents/pp-integration-checker.md +470 -0
  23. package/agents/pp-intel-updater.md +339 -0
  24. package/agents/pp-mempalace-curator.md +47 -0
  25. package/agents/pp-nyquist-auditor.md +204 -0
  26. package/agents/pp-pattern-mapper.md +335 -0
  27. package/agents/pp-phase-researcher.md +867 -0
  28. package/agents/pp-plan-checker.md +988 -0
  29. package/agents/pp-planner.md +1052 -0
  30. package/agents/pp-project-researcher.md +612 -0
  31. package/agents/pp-research-synthesizer.md +260 -0
  32. package/agents/pp-roadmapper.md +741 -0
  33. package/agents/pp-security-auditor.md +156 -0
  34. package/agents/pp-ui-auditor.md +495 -0
  35. package/agents/pp-ui-checker.md +309 -0
  36. package/agents/pp-ui-researcher.md +375 -0
  37. package/agents/pp-user-profiler.md +171 -0
  38. package/agents/pp-verifier.md +967 -0
  39. package/assets/gsd-logo-2000-transparent.png +0 -0
  40. package/assets/gsd-logo-2000-transparent.svg +17 -0
  41. package/assets/gsd-logo-2000.png +0 -0
  42. package/assets/gsd-logo-2000.svg +21 -0
  43. package/assets/terminal.svg +68 -0
  44. package/bin/install.js +12370 -0
  45. package/bin/lib/ui-safety-gate.cjs +107 -0
  46. package/commands/pp-add-tests.md +42 -0
  47. package/commands/pp-ai-integration-phase.md +37 -0
  48. package/commands/pp-audit-fix.md +34 -0
  49. package/commands/pp-audit-milestone.md +37 -0
  50. package/commands/pp-audit-uat.md +24 -0
  51. package/commands/pp-autonomous.md +51 -0
  52. package/commands/pp-capture.md +62 -0
  53. package/commands/pp-cleanup.md +24 -0
  54. package/commands/pp-code-review.md +59 -0
  55. package/commands/pp-complete-milestone.md +143 -0
  56. package/commands/pp-config.md +56 -0
  57. package/commands/pp-debug.md +52 -0
  58. package/commands/pp-discovery.md +34 -0
  59. package/commands/pp-discuss-phase.md +77 -0
  60. package/commands/pp-docs-update.md +49 -0
  61. package/commands/pp-eval-review.md +33 -0
  62. package/commands/pp-execute-phase.md +65 -0
  63. package/commands/pp-explore.md +27 -0
  64. package/commands/pp-extract-learnings.md +23 -0
  65. package/commands/pp-fast.md +31 -0
  66. package/commands/pp-forensics.md +57 -0
  67. package/commands/pp-graphify.md +204 -0
  68. package/commands/pp-health.md +31 -0
  69. package/commands/pp-help.md +28 -0
  70. package/commands/pp-import.md +45 -0
  71. package/commands/pp-inbox.md +39 -0
  72. package/commands/pp-ingest-docs.md +42 -0
  73. package/commands/pp-manager.md +45 -0
  74. package/commands/pp-map-codebase.md +83 -0
  75. package/commands/pp-mempalace-capture.md +71 -0
  76. package/commands/pp-mempalace-recall.md +102 -0
  77. package/commands/pp-milestone-summary.md +51 -0
  78. package/commands/pp-mvp-phase.md +45 -0
  79. package/commands/pp-new-milestone.md +45 -0
  80. package/commands/pp-new-project.md +47 -0
  81. package/commands/pp-ns-context.md +25 -0
  82. package/commands/pp-ns-ideate.md +24 -0
  83. package/commands/pp-ns-manage.md +36 -0
  84. package/commands/pp-ns-project.md +27 -0
  85. package/commands/pp-ns-review.md +29 -0
  86. package/commands/pp-ns-workflow.md +35 -0
  87. package/commands/pp-pause-work.md +43 -0
  88. package/commands/pp-phase.md +56 -0
  89. package/commands/pp-plan-phase.md +63 -0
  90. package/commands/pp-plan-review-convergence.md +60 -0
  91. package/commands/pp-pr-branch.md +26 -0
  92. package/commands/pp-profile-user.md +46 -0
  93. package/commands/pp-progress.md +49 -0
  94. package/commands/pp-quick.md +174 -0
  95. package/commands/pp-resume-work.md +30 -0
  96. package/commands/pp-review-backlog.md +63 -0
  97. package/commands/pp-review.md +42 -0
  98. package/commands/pp-secure-phase.md +36 -0
  99. package/commands/pp-settings.md +29 -0
  100. package/commands/pp-ship.md +24 -0
  101. package/commands/pp-sketch.md +60 -0
  102. package/commands/pp-spec-phase.md +63 -0
  103. package/commands/pp-spike.md +57 -0
  104. package/commands/pp-stats.md +20 -0
  105. package/commands/pp-surface.md +162 -0
  106. package/commands/pp-thread.md +24 -0
  107. package/commands/pp-ui-phase.md +35 -0
  108. package/commands/pp-ui-review.md +33 -0
  109. package/commands/pp-ultraplan-phase.md +34 -0
  110. package/commands/pp-undo.md +35 -0
  111. package/commands/pp-update.md +49 -0
  112. package/commands/pp-validate-phase.md +36 -0
  113. package/commands/pp-verify-work.md +39 -0
  114. package/commands/pp-workspace.md +52 -0
  115. package/commands/pp-workstreams.md +70 -0
  116. package/gemini-extension.json +6 -0
  117. package/hooks/dist/lib/git-cmd.js +150 -0
  118. package/hooks/dist/lib/pp-graphify-rebuild.sh +65 -0
  119. package/hooks/dist/managed-hooks-registry.cjs +39 -0
  120. package/hooks/dist/pp-check-update-worker.js +108 -0
  121. package/hooks/dist/pp-check-update.js +66 -0
  122. package/hooks/dist/pp-config-reload.js +133 -0
  123. package/hooks/dist/pp-context-monitor.js +196 -0
  124. package/hooks/dist/pp-cursor-post-tool.js +75 -0
  125. package/hooks/dist/pp-cursor-session-start.js +52 -0
  126. package/hooks/dist/pp-ensure-canonical-path.js +305 -0
  127. package/hooks/dist/pp-graphify-update.sh +158 -0
  128. package/hooks/dist/pp-phase-boundary.sh +47 -0
  129. package/hooks/dist/pp-prompt-guard.js +97 -0
  130. package/hooks/dist/pp-read-guard.js +101 -0
  131. package/hooks/dist/pp-read-injection-scanner.js +203 -0
  132. package/hooks/dist/pp-session-state.sh +59 -0
  133. package/hooks/dist/pp-statusline.js +566 -0
  134. package/hooks/dist/pp-update-banner.js +138 -0
  135. package/hooks/dist/pp-validate-commit.sh +57 -0
  136. package/hooks/dist/pp-workflow-guard.js +167 -0
  137. package/hooks/dist/pp-worktree-path-guard.js +185 -0
  138. package/hooks/hooks.json +70 -0
  139. package/hooks/lib/git-cmd.js +150 -0
  140. package/hooks/lib/pp-graphify-rebuild.sh +65 -0
  141. package/hooks/managed-hooks-registry.cjs +39 -0
  142. package/hooks/pp-check-update-worker.js +108 -0
  143. package/hooks/pp-check-update.js +66 -0
  144. package/hooks/pp-config-reload.js +133 -0
  145. package/hooks/pp-context-monitor.js +196 -0
  146. package/hooks/pp-cursor-post-tool.js +75 -0
  147. package/hooks/pp-cursor-session-start.js +52 -0
  148. package/hooks/pp-ensure-canonical-path.js +305 -0
  149. package/hooks/pp-graphify-update.sh +158 -0
  150. package/hooks/pp-phase-boundary.sh +47 -0
  151. package/hooks/pp-prompt-guard.js +97 -0
  152. package/hooks/pp-read-guard.js +101 -0
  153. package/hooks/pp-read-injection-scanner.js +203 -0
  154. package/hooks/pp-session-state.sh +59 -0
  155. package/hooks/pp-statusline.js +566 -0
  156. package/hooks/pp-update-banner.js +138 -0
  157. package/hooks/pp-validate-commit.sh +57 -0
  158. package/hooks/pp-workflow-guard.js +167 -0
  159. package/hooks/pp-worktree-path-guard.js +185 -0
  160. package/package.json +125 -0
  161. package/pp-core/bin/check-latest-version.cjs +161 -0
  162. package/pp-core/bin/lib/active-workstream-store.cjs +297 -0
  163. package/pp-core/bin/lib/adr-parser.cjs +399 -0
  164. package/pp-core/bin/lib/agent-command-router.cjs +68 -0
  165. package/pp-core/bin/lib/agent-install-check.cjs +143 -0
  166. package/pp-core/bin/lib/artifacts.cjs +51 -0
  167. package/pp-core/bin/lib/audit-command-router.cjs +61 -0
  168. package/pp-core/bin/lib/audit.cjs +743 -0
  169. package/pp-core/bin/lib/capability-activation.cjs +113 -0
  170. package/pp-core/bin/lib/capability-registry.cjs +3727 -0
  171. package/pp-core/bin/lib/capability-state.cjs +497 -0
  172. package/pp-core/bin/lib/capability-writer.cjs +355 -0
  173. package/pp-core/bin/lib/check-command-router.cjs +800 -0
  174. package/pp-core/bin/lib/cjs-command-router-adapter.cjs +81 -0
  175. package/pp-core/bin/lib/cli-exit.cjs +61 -0
  176. package/pp-core/bin/lib/clock.cjs +95 -0
  177. package/pp-core/bin/lib/clusters.cjs +131 -0
  178. package/pp-core/bin/lib/code-review-flags.cjs +59 -0
  179. package/pp-core/bin/lib/command-aliases.cjs +809 -0
  180. package/pp-core/bin/lib/command-arg-projection.cjs +55 -0
  181. package/pp-core/bin/lib/command-roster.cjs +19 -0
  182. package/pp-core/bin/lib/command-routing-hub.cjs +300 -0
  183. package/pp-core/bin/lib/commands.cjs +1244 -0
  184. package/pp-core/bin/lib/config-loader.cjs +714 -0
  185. package/pp-core/bin/lib/config-schema.cjs +58 -0
  186. package/pp-core/bin/lib/config-types.cjs +20 -0
  187. package/pp-core/bin/lib/config.cjs +796 -0
  188. package/pp-core/bin/lib/configuration.cjs +209 -0
  189. package/pp-core/bin/lib/context-utilization.cjs +48 -0
  190. package/pp-core/bin/lib/core-utils.cjs +198 -0
  191. package/pp-core/bin/lib/decisions.cjs +136 -0
  192. package/pp-core/bin/lib/docs.cjs +264 -0
  193. package/pp-core/bin/lib/drift.cjs +364 -0
  194. package/pp-core/bin/lib/edge-probe.cjs +196 -0
  195. package/pp-core/bin/lib/fallow-runner.cjs +153 -0
  196. package/pp-core/bin/lib/federated-config.cjs +182 -0
  197. package/pp-core/bin/lib/frontmatter.cjs +494 -0
  198. package/pp-core/bin/lib/gap-checker.cjs +260 -0
  199. package/pp-core/bin/lib/git-base-branch.cjs +220 -0
  200. package/pp-core/bin/lib/graphify-command-router.cjs +72 -0
  201. package/pp-core/bin/lib/graphify.cjs +472 -0
  202. package/pp-core/bin/lib/gsd2-import.cjs +456 -0
  203. package/pp-core/bin/lib/init-command-router.cjs +62 -0
  204. package/pp-core/bin/lib/init.cjs +1912 -0
  205. package/pp-core/bin/lib/install-profiles.cjs +814 -0
  206. package/pp-core/bin/lib/installer-migration-authoring.cjs +122 -0
  207. package/pp-core/bin/lib/installer-migration-report.cjs +351 -0
  208. package/pp-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +225 -0
  209. package/pp-core/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +48 -0
  210. package/pp-core/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +94 -0
  211. package/pp-core/bin/lib/installer-migrations/003-rename-get-shit-done-to-gsd-core.cjs +108 -0
  212. package/pp-core/bin/lib/installer-migrations/004-prune-stale-pristine-snapshots.cjs +123 -0
  213. package/pp-core/bin/lib/installer-migrations/005-rename-gsd-core-to-pp-core.cjs +109 -0
  214. package/pp-core/bin/lib/installer-migrations/006-rename-gsd-command-namespace.cjs +135 -0
  215. package/pp-core/bin/lib/installer-migrations.cjs +823 -0
  216. package/pp-core/bin/lib/intel-command-router.cjs +119 -0
  217. package/pp-core/bin/lib/intel.cjs +584 -0
  218. package/pp-core/bin/lib/io.cjs +220 -0
  219. package/pp-core/bin/lib/learnings.cjs +270 -0
  220. package/pp-core/bin/lib/legacy-cleanup.cjs +253 -0
  221. package/pp-core/bin/lib/loop-host-contract.cjs +105 -0
  222. package/pp-core/bin/lib/loop-resolver.cjs +479 -0
  223. package/pp-core/bin/lib/milestone.cjs +381 -0
  224. package/pp-core/bin/lib/model-catalog.cjs +154 -0
  225. package/pp-core/bin/lib/model-profiles.cjs +24 -0
  226. package/pp-core/bin/lib/model-resolver.cjs +467 -0
  227. package/pp-core/bin/lib/observability/event.cjs +51 -0
  228. package/pp-core/bin/lib/observability/logger.cjs +146 -0
  229. package/pp-core/bin/lib/observability/redaction.cjs +48 -0
  230. package/pp-core/bin/lib/package-identity.cjs +35 -0
  231. package/pp-core/bin/lib/package-legitimacy.cjs +368 -0
  232. package/pp-core/bin/lib/phase-command-router.cjs +209 -0
  233. package/pp-core/bin/lib/phase-id.cjs +213 -0
  234. package/pp-core/bin/lib/phase-lifecycle.cjs +74 -0
  235. package/pp-core/bin/lib/phase-locator.cjs +145 -0
  236. package/pp-core/bin/lib/phase.cjs +1446 -0
  237. package/pp-core/bin/lib/phases-command-router.cjs +43 -0
  238. package/pp-core/bin/lib/plan-drift-guard.cjs +117 -0
  239. package/pp-core/bin/lib/plan-scan.cjs +91 -0
  240. package/pp-core/bin/lib/planning-workspace.cjs +245 -0
  241. package/pp-core/bin/lib/probe-core.cjs +402 -0
  242. package/pp-core/bin/lib/profile-output.cjs +1164 -0
  243. package/pp-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
  244. package/pp-core/bin/lib/profile-pipeline.cjs +519 -0
  245. package/pp-core/bin/lib/prohibition-enforcement.cjs +660 -0
  246. package/pp-core/bin/lib/project-root.cjs +119 -0
  247. package/pp-core/bin/lib/prompt-budget.cjs +305 -0
  248. package/pp-core/bin/lib/research-provider.cjs +137 -0
  249. package/pp-core/bin/lib/research-store.cjs +167 -0
  250. package/pp-core/bin/lib/review-reviewer-selection.cjs +121 -0
  251. package/pp-core/bin/lib/roadmap-command-router.cjs +166 -0
  252. package/pp-core/bin/lib/roadmap-parser.cjs +456 -0
  253. package/pp-core/bin/lib/roadmap-upgrade.cjs +476 -0
  254. package/pp-core/bin/lib/roadmap.cjs +738 -0
  255. package/pp-core/bin/lib/runtime-artifact-conversion.cjs +1966 -0
  256. package/pp-core/bin/lib/runtime-artifact-layout.cjs +342 -0
  257. package/pp-core/bin/lib/runtime-config-adapter-registry.cjs +103 -0
  258. package/pp-core/bin/lib/runtime-homes.cjs +272 -0
  259. package/pp-core/bin/lib/runtime-hooks-surface.cjs +1460 -0
  260. package/pp-core/bin/lib/runtime-name-policy.cjs +97 -0
  261. package/pp-core/bin/lib/runtime-slash.cjs +137 -0
  262. package/pp-core/bin/lib/schema-detect.cjs +159 -0
  263. package/pp-core/bin/lib/secrets.cjs +34 -0
  264. package/pp-core/bin/lib/security.cjs +480 -0
  265. package/pp-core/bin/lib/semver-compare.cjs +42 -0
  266. package/pp-core/bin/lib/shell-command-projection.cjs +550 -0
  267. package/pp-core/bin/lib/state-command-router.cjs +172 -0
  268. package/pp-core/bin/lib/state-document.cjs +312 -0
  269. package/pp-core/bin/lib/state.cjs +2587 -0
  270. package/pp-core/bin/lib/surface.cjs +505 -0
  271. package/pp-core/bin/lib/task-command-router.cjs +85 -0
  272. package/pp-core/bin/lib/teams-status.cjs +74 -0
  273. package/pp-core/bin/lib/template.cjs +246 -0
  274. package/pp-core/bin/lib/uat-predicate.cjs +329 -0
  275. package/pp-core/bin/lib/uat.cjs +359 -0
  276. package/pp-core/bin/lib/ui-safety-gate.cjs +98 -0
  277. package/pp-core/bin/lib/update-context.cjs +221 -0
  278. package/pp-core/bin/lib/validate-command-router.cjs +91 -0
  279. package/pp-core/bin/lib/validate.cjs +122 -0
  280. package/pp-core/bin/lib/verification-command-router.cjs +31 -0
  281. package/pp-core/bin/lib/verification.cjs +196 -0
  282. package/pp-core/bin/lib/verify-command-router.cjs +44 -0
  283. package/pp-core/bin/lib/verify.cjs +1932 -0
  284. package/pp-core/bin/lib/workstream-inventory-builder.cjs +81 -0
  285. package/pp-core/bin/lib/workstream-inventory.cjs +147 -0
  286. package/pp-core/bin/lib/workstream-name-policy.cjs +91 -0
  287. package/pp-core/bin/lib/workstream.cjs +386 -0
  288. package/pp-core/bin/lib/worktree-base-ref.cjs +350 -0
  289. package/pp-core/bin/lib/worktree-safety.cjs +985 -0
  290. package/pp-core/bin/pp-tools.cjs +2239 -0
  291. package/pp-core/bin/pp_run +20 -0
  292. package/pp-core/bin/shared/config-defaults.manifest.json +97 -0
  293. package/pp-core/bin/shared/config-schema.manifest.json +176 -0
  294. package/pp-core/bin/shared/model-catalog.json +160 -0
  295. package/pp-core/bin/shared/runtime-aliases.manifest.json +79 -0
  296. package/pp-core/bin/verify-reapply-patches.cjs +400 -0
  297. package/pp-core/contexts/dev.md +21 -0
  298. package/pp-core/contexts/research.md +22 -0
  299. package/pp-core/contexts/review.md +23 -0
  300. package/pp-core/references/agent-contracts.md +79 -0
  301. package/pp-core/references/ai-evals.md +156 -0
  302. package/pp-core/references/ai-frameworks.md +186 -0
  303. package/pp-core/references/artifact-types.md +131 -0
  304. package/pp-core/references/autonomous-smart-discuss.md +277 -0
  305. package/pp-core/references/checkpoints.md +814 -0
  306. package/pp-core/references/common-bug-patterns.md +114 -0
  307. package/pp-core/references/context-budget.md +85 -0
  308. package/pp-core/references/continuation-format.md +253 -0
  309. package/pp-core/references/debugger-philosophy.md +76 -0
  310. package/pp-core/references/decimal-phase-calculation.md +64 -0
  311. package/pp-core/references/doc-conflict-engine.md +91 -0
  312. package/pp-core/references/domain-probes.md +125 -0
  313. package/pp-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
  314. package/pp-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
  315. package/pp-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
  316. package/pp-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
  317. package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
  318. package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
  319. package/pp-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
  320. package/pp-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
  321. package/pp-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
  322. package/pp-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
  323. package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
  324. package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
  325. package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
  326. package/pp-core/references/edge-probe.md +272 -0
  327. package/pp-core/references/execute-mvp-tdd.md +81 -0
  328. package/pp-core/references/executor-examples.md +110 -0
  329. package/pp-core/references/few-shot-examples/plan-checker.md +73 -0
  330. package/pp-core/references/few-shot-examples/verifier.md +109 -0
  331. package/pp-core/references/gate-prompts.md +100 -0
  332. package/pp-core/references/gates.md +70 -0
  333. package/pp-core/references/git-integration.md +298 -0
  334. package/pp-core/references/git-planning-commit.md +40 -0
  335. package/pp-core/references/ios-scaffold.md +123 -0
  336. package/pp-core/references/loop-hook-dispatch.md +61 -0
  337. package/pp-core/references/mandatory-initial-read.md +2 -0
  338. package/pp-core/references/model-profile-resolution.md +38 -0
  339. package/pp-core/references/model-profiles.md +245 -0
  340. package/pp-core/references/mvp-concepts.md +49 -0
  341. package/pp-core/references/phase-argument-parsing.md +61 -0
  342. package/pp-core/references/planner-antipatterns.md +176 -0
  343. package/pp-core/references/planner-chunked.md +49 -0
  344. package/pp-core/references/planner-gap-closure.md +62 -0
  345. package/pp-core/references/planner-graphify-auto-update.md +67 -0
  346. package/pp-core/references/planner-guidance.md +186 -0
  347. package/pp-core/references/planner-human-verify-mode.md +57 -0
  348. package/pp-core/references/planner-interface-context.md +62 -0
  349. package/pp-core/references/planner-load-graph-context.md +36 -0
  350. package/pp-core/references/planner-mvp-mode.md +53 -0
  351. package/pp-core/references/planner-reviews.md +42 -0
  352. package/pp-core/references/planner-revision.md +87 -0
  353. package/pp-core/references/planner-source-audit.md +73 -0
  354. package/pp-core/references/planning-config.md +477 -0
  355. package/pp-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
  356. package/pp-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
  357. package/pp-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
  358. package/pp-core/references/prohibition-probe.md +326 -0
  359. package/pp-core/references/project-skills-discovery.md +19 -0
  360. package/pp-core/references/questioning.md +162 -0
  361. package/pp-core/references/research-documentation-lookup.md +29 -0
  362. package/pp-core/references/research-philosophy.md +29 -0
  363. package/pp-core/references/research-verification-protocol.md +27 -0
  364. package/pp-core/references/revision-loop.md +97 -0
  365. package/pp-core/references/scout-codebase.md +51 -0
  366. package/pp-core/references/skeleton-template.md +48 -0
  367. package/pp-core/references/sketch-interactivity.md +41 -0
  368. package/pp-core/references/sketch-theme-system.md +94 -0
  369. package/pp-core/references/sketch-tooling.md +45 -0
  370. package/pp-core/references/sketch-variant-patterns.md +81 -0
  371. package/pp-core/references/spidr-splitting.md +69 -0
  372. package/pp-core/references/tdd.md +330 -0
  373. package/pp-core/references/thinking-models-debug.md +44 -0
  374. package/pp-core/references/thinking-models-execution.md +50 -0
  375. package/pp-core/references/thinking-models-planning.md +62 -0
  376. package/pp-core/references/thinking-models-research.md +50 -0
  377. package/pp-core/references/thinking-models-verification.md +55 -0
  378. package/pp-core/references/thinking-partner.md +96 -0
  379. package/pp-core/references/ui-brand.md +162 -0
  380. package/pp-core/references/universal-anti-patterns.md +63 -0
  381. package/pp-core/references/user-profiling.md +681 -0
  382. package/pp-core/references/user-story-template.md +58 -0
  383. package/pp-core/references/verification-overrides.md +227 -0
  384. package/pp-core/references/verification-patterns.md +612 -0
  385. package/pp-core/references/verify-mvp-mode.md +85 -0
  386. package/pp-core/references/workstream-flag.md +111 -0
  387. package/pp-core/references/worktree-branch-check.md +44 -0
  388. package/pp-core/references/worktree-path-safety.md +67 -0
  389. package/pp-core/templates/AI-SPEC.md +246 -0
  390. package/pp-core/templates/DEBUG.md +169 -0
  391. package/pp-core/templates/README.md +77 -0
  392. package/pp-core/templates/SECURITY.md +61 -0
  393. package/pp-core/templates/UAT.md +265 -0
  394. package/pp-core/templates/UI-SPEC.md +100 -0
  395. package/pp-core/templates/VALIDATION.md +76 -0
  396. package/pp-core/templates/claude-md.md +145 -0
  397. package/pp-core/templates/codebase/architecture.md +255 -0
  398. package/pp-core/templates/codebase/concerns.md +310 -0
  399. package/pp-core/templates/codebase/conventions.md +307 -0
  400. package/pp-core/templates/codebase/integrations.md +280 -0
  401. package/pp-core/templates/codebase/stack.md +186 -0
  402. package/pp-core/templates/codebase/structure.md +285 -0
  403. package/pp-core/templates/codebase/testing.md +480 -0
  404. package/pp-core/templates/config.json +62 -0
  405. package/pp-core/templates/context.md +352 -0
  406. package/pp-core/templates/continue-here.md +78 -0
  407. package/pp-core/templates/copilot-instructions.md +7 -0
  408. package/pp-core/templates/debug-subagent-prompt.md +91 -0
  409. package/pp-core/templates/dev-preferences.md +21 -0
  410. package/pp-core/templates/discovery.md +146 -0
  411. package/pp-core/templates/discussion-log.md +63 -0
  412. package/pp-core/templates/milestone-archive.md +123 -0
  413. package/pp-core/templates/milestone.md +115 -0
  414. package/pp-core/templates/phase-prompt.md +610 -0
  415. package/pp-core/templates/planner-subagent-prompt.md +117 -0
  416. package/pp-core/templates/project.md +203 -0
  417. package/pp-core/templates/requirements.md +231 -0
  418. package/pp-core/templates/research-project/ARCHITECTURE.md +204 -0
  419. package/pp-core/templates/research-project/FEATURES.md +147 -0
  420. package/pp-core/templates/research-project/PITFALLS.md +200 -0
  421. package/pp-core/templates/research-project/STACK.md +120 -0
  422. package/pp-core/templates/research-project/SUMMARY.md +170 -0
  423. package/pp-core/templates/research.md +592 -0
  424. package/pp-core/templates/retrospective.md +54 -0
  425. package/pp-core/templates/roadmap.md +202 -0
  426. package/pp-core/templates/spec.md +333 -0
  427. package/pp-core/templates/state.md +195 -0
  428. package/pp-core/templates/summary-complex.md +60 -0
  429. package/pp-core/templates/summary-minimal.md +42 -0
  430. package/pp-core/templates/summary-standard.md +49 -0
  431. package/pp-core/templates/summary.md +249 -0
  432. package/pp-core/templates/user-profile.md +146 -0
  433. package/pp-core/templates/user-setup.md +311 -0
  434. package/pp-core/templates/verification-report.md +335 -0
  435. package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -0
  436. package/pp-core/workflows/add-backlog.md +91 -0
  437. package/pp-core/workflows/add-phase.md +113 -0
  438. package/pp-core/workflows/add-tests.md +355 -0
  439. package/pp-core/workflows/add-todo.md +161 -0
  440. package/pp-core/workflows/ai-integration-phase.md +295 -0
  441. package/pp-core/workflows/analyze-dependencies.md +96 -0
  442. package/pp-core/workflows/audit-fix.md +178 -0
  443. package/pp-core/workflows/audit-milestone.md +362 -0
  444. package/pp-core/workflows/audit-uat.md +110 -0
  445. package/pp-core/workflows/autonomous.md +886 -0
  446. package/pp-core/workflows/check-todos.md +180 -0
  447. package/pp-core/workflows/cleanup.md +195 -0
  448. package/pp-core/workflows/code-review-fix.md +506 -0
  449. package/pp-core/workflows/code-review.md +696 -0
  450. package/pp-core/workflows/complete-milestone.md +857 -0
  451. package/pp-core/workflows/debug.md +237 -0
  452. package/pp-core/workflows/diagnose-issues.md +245 -0
  453. package/pp-core/workflows/discovery-phase.md +291 -0
  454. package/pp-core/workflows/discovery-sprint.md +1546 -0
  455. package/pp-core/workflows/discuss-phase/modes/advisor.md +176 -0
  456. package/pp-core/workflows/discuss-phase/modes/all.md +28 -0
  457. package/pp-core/workflows/discuss-phase/modes/analyze.md +44 -0
  458. package/pp-core/workflows/discuss-phase/modes/auto.md +57 -0
  459. package/pp-core/workflows/discuss-phase/modes/batch.md +52 -0
  460. package/pp-core/workflows/discuss-phase/modes/chain.md +98 -0
  461. package/pp-core/workflows/discuss-phase/modes/default.md +141 -0
  462. package/pp-core/workflows/discuss-phase/modes/power.md +44 -0
  463. package/pp-core/workflows/discuss-phase/modes/text.md +55 -0
  464. package/pp-core/workflows/discuss-phase/templates/checkpoint.json +18 -0
  465. package/pp-core/workflows/discuss-phase/templates/context.md +136 -0
  466. package/pp-core/workflows/discuss-phase/templates/discussion-log.md +50 -0
  467. package/pp-core/workflows/discuss-phase-assumptions.md +675 -0
  468. package/pp-core/workflows/discuss-phase-power.md +291 -0
  469. package/pp-core/workflows/discuss-phase.md +520 -0
  470. package/pp-core/workflows/do.md +111 -0
  471. package/pp-core/workflows/docs-update.md +1168 -0
  472. package/pp-core/workflows/edit-phase.md +295 -0
  473. package/pp-core/workflows/eval-review.md +159 -0
  474. package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +95 -0
  475. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  476. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +117 -0
  477. package/pp-core/workflows/execute-phase/steps/worktree-recovery-policy.md +9 -0
  478. package/pp-core/workflows/execute-phase.md +1707 -0
  479. package/pp-core/workflows/execute-plan.md +541 -0
  480. package/pp-core/workflows/explore.md +146 -0
  481. package/pp-core/workflows/extract-learnings.md +243 -0
  482. package/pp-core/workflows/fast.md +124 -0
  483. package/pp-core/workflows/forensics.md +279 -0
  484. package/pp-core/workflows/graduation.md +196 -0
  485. package/pp-core/workflows/health.md +224 -0
  486. package/pp-core/workflows/help/modes/brief.md +22 -0
  487. package/pp-core/workflows/help/modes/default.md +50 -0
  488. package/pp-core/workflows/help/modes/full.md +791 -0
  489. package/pp-core/workflows/help/modes/topic.md +74 -0
  490. package/pp-core/workflows/help.md +24 -0
  491. package/pp-core/workflows/import.md +256 -0
  492. package/pp-core/workflows/inbox.md +387 -0
  493. package/pp-core/workflows/ingest-docs.md +340 -0
  494. package/pp-core/workflows/insert-phase.md +152 -0
  495. package/pp-core/workflows/list-phase-assumptions.md +178 -0
  496. package/pp-core/workflows/list-workspaces.md +57 -0
  497. package/pp-core/workflows/manager.md +435 -0
  498. package/pp-core/workflows/map-codebase.md +444 -0
  499. package/pp-core/workflows/milestone-summary.md +224 -0
  500. package/pp-core/workflows/mvp-phase.md +222 -0
  501. package/pp-core/workflows/new-milestone.md +643 -0
  502. package/pp-core/workflows/new-project.md +1563 -0
  503. package/pp-core/workflows/new-workspace.md +240 -0
  504. package/pp-core/workflows/next.md +347 -0
  505. package/pp-core/workflows/node-repair.md +92 -0
  506. package/pp-core/workflows/note.md +158 -0
  507. package/pp-core/workflows/pause-work.md +250 -0
  508. package/pp-core/workflows/plan-milestone-gaps.md +281 -0
  509. package/pp-core/workflows/plan-phase.md +1755 -0
  510. package/pp-core/workflows/plan-review-convergence.md +369 -0
  511. package/pp-core/workflows/plant-seed.md +230 -0
  512. package/pp-core/workflows/pr-branch.md +159 -0
  513. package/pp-core/workflows/profile-user.md +455 -0
  514. package/pp-core/workflows/progress.md +756 -0
  515. package/pp-core/workflows/quick.md +1045 -0
  516. package/pp-core/workflows/reapply-patches.md +443 -0
  517. package/pp-core/workflows/remove-phase.md +156 -0
  518. package/pp-core/workflows/remove-workspace.md +108 -0
  519. package/pp-core/workflows/resume-project.md +348 -0
  520. package/pp-core/workflows/review.md +765 -0
  521. package/pp-core/workflows/scan.md +107 -0
  522. package/pp-core/workflows/secure-phase.md +184 -0
  523. package/pp-core/workflows/session-report.md +146 -0
  524. package/pp-core/workflows/settings-advanced.md +816 -0
  525. package/pp-core/workflows/settings-integrations.md +312 -0
  526. package/pp-core/workflows/settings.md +592 -0
  527. package/pp-core/workflows/ship.md +465 -0
  528. package/pp-core/workflows/sketch-wrap-up.md +286 -0
  529. package/pp-core/workflows/sketch.md +361 -0
  530. package/pp-core/workflows/spec-phase.md +495 -0
  531. package/pp-core/workflows/spike-wrap-up.md +307 -0
  532. package/pp-core/workflows/spike.md +453 -0
  533. package/pp-core/workflows/stats.md +80 -0
  534. package/pp-core/workflows/sync-skills.md +182 -0
  535. package/pp-core/workflows/thread.md +222 -0
  536. package/pp-core/workflows/transition.md +694 -0
  537. package/pp-core/workflows/ui-phase.md +328 -0
  538. package/pp-core/workflows/ui-review.md +193 -0
  539. package/pp-core/workflows/ultraplan-phase.md +199 -0
  540. package/pp-core/workflows/undo.md +314 -0
  541. package/pp-core/workflows/update.md +501 -0
  542. package/pp-core/workflows/validate-phase.md +183 -0
  543. package/pp-core/workflows/verify-phase.md +569 -0
  544. package/pp-core/workflows/verify-work.md +804 -0
  545. package/scripts/affected-tests-lib.cjs +554 -0
  546. package/scripts/audit-workflow-script-paths.cjs +73 -0
  547. package/scripts/base64-scan.sh +351 -0
  548. package/scripts/build-hooks.js +254 -0
  549. package/scripts/changeset/README.md +129 -0
  550. package/scripts/changeset/cli.cjs +597 -0
  551. package/scripts/changeset/github-release-notes.cjs +199 -0
  552. package/scripts/changeset/lint.cjs +147 -0
  553. package/scripts/changeset/new.cjs +151 -0
  554. package/scripts/changeset/parse.cjs +114 -0
  555. package/scripts/changeset/render.cjs +34 -0
  556. package/scripts/changeset/serialize.cjs +130 -0
  557. package/scripts/check-alias-drift.cjs +114 -0
  558. package/scripts/check-env.cjs +312 -0
  559. package/scripts/check-npm-integrity.cjs +215 -0
  560. package/scripts/ci-guard-runner.cjs +22 -0
  561. package/scripts/ci-prepare-test-scope.cjs +93 -0
  562. package/scripts/ci-rebase-check.cjs +86 -0
  563. package/scripts/ci-test-scope.cjs +441 -0
  564. package/scripts/command-contract-helpers.cjs +64 -0
  565. package/scripts/diff-touches-shipped-paths.cjs +149 -0
  566. package/scripts/fix-slash-commands.cjs +161 -0
  567. package/scripts/gen-capability-registry.cjs +2646 -0
  568. package/scripts/gen-commands-reference.cjs +139 -0
  569. package/scripts/gen-inventory-manifest.cjs +114 -0
  570. package/scripts/gen-loop-host-contract.cjs +526 -0
  571. package/scripts/gen-research-agents.cjs +276 -0
  572. package/scripts/generate-package-identity.cjs +134 -0
  573. package/scripts/issue-dedupe.cjs +278 -0
  574. package/scripts/issue-version-gate.cjs +140 -0
  575. package/scripts/lib/allowlist-ratchet.cjs +236 -0
  576. package/scripts/lib/cli-exit.cjs +56 -0
  577. package/scripts/lint-allow-test-rule-refs.allowlist.json +328 -0
  578. package/scripts/lint-allow-test-rule-refs.cjs +162 -0
  579. package/scripts/lint-command-contract.cjs +118 -0
  580. package/scripts/lint-descriptions.cjs +87 -0
  581. package/scripts/lint-docs-required.cjs +222 -0
  582. package/scripts/lint-legacy-dir-name.cjs +166 -0
  583. package/scripts/lint-package-identity-drift.cjs +141 -0
  584. package/scripts/lint-pr-check-project-dir.cjs +99 -0
  585. package/scripts/lint-regression-test-names.allowlist.json +269 -0
  586. package/scripts/lint-regression-test-names.cjs +117 -0
  587. package/scripts/lint-shell-command-projection-drift.cjs +62 -0
  588. package/scripts/lint-skill-deps.cjs +185 -0
  589. package/scripts/lint-test-file-count.allowlist.json +181 -0
  590. package/scripts/lint-test-file-count.cjs +246 -0
  591. package/scripts/lint-windows-test-portability.cjs +178 -0
  592. package/scripts/mutation-matrix.cjs +323 -0
  593. package/scripts/pr-target-policy.cjs +63 -0
  594. package/scripts/pr-template-policy.cjs +268 -0
  595. package/scripts/prompt-injection-scan.sh +207 -0
  596. package/scripts/registry-smoke.cjs +337 -0
  597. package/scripts/release-notes/discord-release-summary.cjs +373 -0
  598. package/scripts/release-notes/format-github-release-notes.cjs +261 -0
  599. package/scripts/release-tarball-smoke.cjs +763 -0
  600. package/scripts/research-profiles.cjs +149 -0
  601. package/scripts/run-affected-tests.cjs +7 -0
  602. package/scripts/run-tests.cjs +589 -0
  603. package/scripts/secret-scan-lint.sh +231 -0
  604. package/scripts/secret-scan.sh +358 -0
  605. package/scripts/setup-branch-protection.sh +234 -0
  606. package/scripts/strip-prose-atrefs.cjs +106 -0
  607. package/scripts/sync-manifest-versions.cjs +119 -0
  608. package/scripts/sync-next-version.cjs +133 -0
  609. package/scripts/sync-rulesets.sh +34 -0
  610. package/scripts/sync-runtime-launcher.cjs +425 -0
  611. package/scripts/test-failure-reasons.cjs +34 -0
  612. package/scripts/update-size-baseline.cjs +68 -0
  613. package/scripts/verify-npm-publish.cjs +240 -0
  614. package/scripts/verify-opengsd-sweep.cjs +288 -0
  615. package/scripts/verify-phase-02-rename.sh +258 -0
  616. package/scripts/verify-phase-03-rename.sh +376 -0
  617. package/scripts/workflow-policy.cjs +483 -0
  618. package/scripts/workflow-size.cjs +90 -0
@@ -0,0 +1,1460 @@
1
+ 'use strict';
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ /**
6
+ * Runtime Hooks Surface Module — hook-surface writer functions extracted from
7
+ * bin/install.js (ADR-857 phase 5f-1).
8
+ *
9
+ * Owns the lifecycle writer functions for hook surfaces managed by GSD on four
10
+ * runtimes:
11
+ * Cline: writeClineArtifacts + supporting helpers/constants
12
+ * Cursor: buildCursorHookEntry, isManagedCursorHookEntry,
13
+ * reconcileCursorHooksJson, writeCursorHooksJson, removeCursorHooksJson
14
+ * Copilot: buildCopilotHookConfig, writeCopilotHookConfig
15
+ * Codex hooks.json: ensureCodexHooksJsonSessionStart, ensureCodexHooksJsonEvent,
16
+ * reconcileCodexHooksJsonEvent, reconcileCodexHooksJsonSessionStart,
17
+ * removeCodexHooksJsonEvent, removeCodexHooksJsonSessionStart,
18
+ * buildCodexHookWindowsShimIR, buildCodexHookBlock, rewriteLegacyCodexHookBlock
19
+ * Shared: buildHookCommand, rewriteLegacyManagedNodeHookCommands
20
+ *
21
+ * BEHAVIOR-PRESERVING RELOCATION: all logic is copied verbatim from
22
+ * bin/install.js. No behavior change, no descriptor reads, no new IO.
23
+ *
24
+ * bin/install.js re-exports every symbol from this module so existing
25
+ * consumers that do require('../bin/install.js').writeCursorHooksJson
26
+ * (etc.) continue to work unchanged.
27
+ */
28
+ const node_fs_1 = __importDefault(require("node:fs"));
29
+ const node_path_1 = __importDefault(require("node:path"));
30
+ const node_os_1 = __importDefault(require("node:os"));
31
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
32
+ const shellCmdProjection = require("./shell-command-projection.cjs");
33
+ const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, } = shellCmdProjection;
34
+ // ---------------------------------------------------------------------------
35
+ // Terminal color constants (mirrors install.js for console output parity)
36
+ // ---------------------------------------------------------------------------
37
+ const green = '\x1b[32m';
38
+ const yellow = '\x1b[33m';
39
+ const reset = '\x1b[0m';
40
+ // ---------------------------------------------------------------------------
41
+ // Codex config.toml constants (subset needed by this module)
42
+ // ---------------------------------------------------------------------------
43
+ // ---------------------------------------------------------------------------
44
+ // Copilot hook constants
45
+ // ---------------------------------------------------------------------------
46
+ const GSD_COPILOT_HOOK_FILE = 'pp-session.json';
47
+ const GSD_COPILOT_SESSION_MSG_PRESENT = 'pp-core: .planning/STATE.md present - review the current phase and any blockers before acting.';
48
+ const GSD_COPILOT_SESSION_MSG_ABSENT = 'pp-core: no .planning/ workflow found - run /pp-new-project to start a tracked workflow.';
49
+ const GSD_COPILOT_SESSION_HOOK_BASH = 'if [ -f .planning/STATE.md ]; then ' +
50
+ `printf '%s' '{"additionalContext":"${GSD_COPILOT_SESSION_MSG_PRESENT}"}'; else ` +
51
+ `printf '%s' '{"additionalContext":"${GSD_COPILOT_SESSION_MSG_ABSENT}"}'; fi`;
52
+ const GSD_COPILOT_SESSION_HOOK_PWSH = 'if (Test-Path .planning/STATE.md) ' +
53
+ `{ '{"additionalContext":"${GSD_COPILOT_SESSION_MSG_PRESENT}"}' } ` +
54
+ `else { '{"additionalContext":"${GSD_COPILOT_SESSION_MSG_ABSENT}"}' }`;
55
+ // ---------------------------------------------------------------------------
56
+ // Cursor hook constants
57
+ // ---------------------------------------------------------------------------
58
+ const GSD_CURSOR_SESSION_HOOK_SCRIPT = 'pp-cursor-session-start.js';
59
+ const GSD_CURSOR_POST_TOOL_HOOK_SCRIPT = 'pp-cursor-post-tool.js';
60
+ const GSD_CURSOR_HOOK_MARKER = 'gsd-managed';
61
+ // ---------------------------------------------------------------------------
62
+ // Cline / AGENTS.md constants
63
+ // ---------------------------------------------------------------------------
64
+ const GSD_AGENTS_MD_MARKER = '<!-- PP Configuration — managed by pp-core installer -->';
65
+ const GSD_AGENTS_MD_CLOSE_MARKER = '<!-- End GSD Configuration -->';
66
+ // ---------------------------------------------------------------------------
67
+ // atomicWriteFileSync — shared canonical implementation.
68
+ //
69
+ // __atomicWrittenTmps is exported so bin/install.js can merge it into its
70
+ // _cleanTmpFiles() scan, ensuring that atomic writes performed by this
71
+ // module (Cursor hooks.json, Codex hooks.json shims) participate in the
72
+ // same temp-file cleanup as writes performed directly by install.js.
73
+ //
74
+ // Every temp path written is recorded in the Set so _cleanTmpFiles() can
75
+ // scope cleanup to files this installer process actually created, avoiding
76
+ // accidental deletion of unrelated tools' temp files.
77
+ // ---------------------------------------------------------------------------
78
+ let __atomicWriteCounter = 0;
79
+ // Set<string> — absolute paths of .tmp-<pid>-<n> files this process created.
80
+ const __atomicWrittenTmps = new Set();
81
+ function atomicWriteFileSync(target, data, options) {
82
+ __atomicWriteCounter += 1;
83
+ const tmp = `${target}.tmp-${process.pid}-${__atomicWriteCounter}`;
84
+ __atomicWrittenTmps.add(tmp);
85
+ try {
86
+ node_fs_1.default.writeFileSync(tmp, data, options);
87
+ node_fs_1.default.renameSync(tmp, target);
88
+ // Successful rename: the tmp path no longer exists, but leave it in the
89
+ // Set so _cleanTmpFiles can recognise it as installer-owned if it somehow
90
+ // lingers (e.g. a rename succeeded but left a stale entry on some FS).
91
+ }
92
+ catch (e) {
93
+ try {
94
+ node_fs_1.default.rmSync(tmp, { force: true });
95
+ }
96
+ catch { /* ignore */ }
97
+ throw e;
98
+ }
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // parseTomlValue + findMultilineBasicStringClose
102
+ // (needed by rewriteLegacyCodexHookBlock — pure TOML helpers, no state)
103
+ // ---------------------------------------------------------------------------
104
+ function findMultilineBasicStringClose(line, startIndex) {
105
+ let i = startIndex;
106
+ while (i < line.length) {
107
+ if (line.startsWith('"""', i) && (i === 0 || line[i - 1] !== '\\')) {
108
+ return i;
109
+ }
110
+ i += 1;
111
+ }
112
+ return -1;
113
+ }
114
+ function parseTomlValue(text, i) {
115
+ // Skip leading whitespace.
116
+ while (i < text.length && (text[i] === ' ' || text[i] === '\t')) {
117
+ i += 1;
118
+ }
119
+ if (i >= text.length) {
120
+ throw new Error('expected value, got end of input');
121
+ }
122
+ const ch = text[i];
123
+ // Basic string
124
+ if (ch === '"') {
125
+ if (text.startsWith('"""', i)) {
126
+ const close = findMultilineBasicStringClose(text, i + 3);
127
+ if (close === -1) {
128
+ throw new Error('unterminated multi-line basic string');
129
+ }
130
+ const raw = text.slice(i + 3, close);
131
+ return { value: raw.replace(/^\r?\n/, ''), end: close + 3 };
132
+ }
133
+ let j = i + 1;
134
+ let out = '';
135
+ while (j < text.length) {
136
+ const c = text[j];
137
+ if (c === '\\') {
138
+ const next = text[j + 1];
139
+ if (next === 'n') {
140
+ out += '\n';
141
+ j += 2;
142
+ continue;
143
+ }
144
+ if (next === 't') {
145
+ out += '\t';
146
+ j += 2;
147
+ continue;
148
+ }
149
+ if (next === 'r') {
150
+ out += '\r';
151
+ j += 2;
152
+ continue;
153
+ }
154
+ if (next === '\\') {
155
+ out += '\\';
156
+ j += 2;
157
+ continue;
158
+ }
159
+ if (next === '"') {
160
+ out += '"';
161
+ j += 2;
162
+ continue;
163
+ }
164
+ if (next === '/') {
165
+ out += '/';
166
+ j += 2;
167
+ continue;
168
+ }
169
+ out += next === undefined ? '' : next;
170
+ j += 2;
171
+ continue;
172
+ }
173
+ if (c === '"') {
174
+ return { value: out, end: j + 1 };
175
+ }
176
+ out += c;
177
+ j += 1;
178
+ }
179
+ throw new Error('unterminated basic string');
180
+ }
181
+ // Literal string
182
+ if (ch === '\'') {
183
+ if (text.startsWith("'''", i)) {
184
+ const close = text.indexOf("'''", i + 3);
185
+ if (close === -1)
186
+ throw new Error('unterminated multi-line literal string');
187
+ return { value: text.slice(i + 3, close).replace(/^\r?\n/, ''), end: close + 3 };
188
+ }
189
+ const close = text.indexOf('\'', i + 1);
190
+ if (close === -1)
191
+ throw new Error('unterminated literal string');
192
+ return { value: text.slice(i + 1, close), end: close + 1 };
193
+ }
194
+ // Boolean
195
+ if (text.startsWith('true', i))
196
+ return { value: true, end: i + 4 };
197
+ if (text.startsWith('false', i))
198
+ return { value: false, end: i + 5 };
199
+ // Number (integer or float, simplified)
200
+ const numMatch = text.slice(i).match(/^[+-]?(?:0x[0-9a-fA-F_]+|0o[0-7_]+|0b[01_]+|[0-9][0-9_]*(?:\.[0-9_]+)?(?:[eE][+-]?[0-9_]+)?|inf|nan)/);
201
+ if (numMatch) {
202
+ const raw = numMatch[0];
203
+ const cleaned = raw.replace(/_/g, '');
204
+ const num = Number(cleaned);
205
+ return { value: isNaN(num) ? cleaned : num, end: i + raw.length };
206
+ }
207
+ // Datetime (simplified passthrough)
208
+ const dtMatch = text.slice(i).match(/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?/);
209
+ if (dtMatch) {
210
+ return { value: dtMatch[0], end: i + dtMatch[0].length };
211
+ }
212
+ throw new Error(`parseTomlValue: unexpected character '${ch}' at position ${i}`);
213
+ }
214
+ function normalizeNodePath(execPath, opts) {
215
+ if (!execPath)
216
+ return execPath;
217
+ const env = (opts && opts.env) || process.env;
218
+ const existsSync = (opts && opts.existsSync) || node_fs_1.default.existsSync;
219
+ const normalizedForMatch = execPath.replace(/\\/g, '/');
220
+ if (/\/fnm_multishells\/[0-9]+_[0-9]+\/node(\.exe)?$/i.test(normalizedForMatch)) {
221
+ const candidates = [];
222
+ if (env.FNM_DIR) {
223
+ candidates.push(`${env.FNM_DIR}/aliases/default/node.exe`);
224
+ candidates.push(`${env.FNM_DIR}/aliases/default/bin/node`);
225
+ }
226
+ if (env.APPDATA) {
227
+ candidates.push(`${env.APPDATA}/fnm/aliases/default/node.exe`);
228
+ }
229
+ for (const candidate of candidates) {
230
+ if (candidate && existsSync(candidate))
231
+ return candidate;
232
+ }
233
+ return execPath;
234
+ }
235
+ if (/^\/usr\/local\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/.test(execPath)) {
236
+ return '/usr/local/bin/node';
237
+ }
238
+ if (/^\/opt\/homebrew\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/.test(execPath)) {
239
+ return '/opt/homebrew/bin/node';
240
+ }
241
+ return execPath;
242
+ }
243
+ function resolveNodeRunner(opts) {
244
+ const execPath = typeof process.execPath === 'string' ? process.execPath : '';
245
+ if (!execPath)
246
+ return null;
247
+ const stablePath = normalizeNodePath(execPath, opts);
248
+ return JSON.stringify(stablePath.replace(/\\/g, '/'));
249
+ }
250
+ function resolveBashRunner(opts) {
251
+ const platform = (opts && opts.platform) || process.platform;
252
+ if (platform !== 'win32')
253
+ return 'bash';
254
+ const env = (opts && opts.env) || process.env;
255
+ const exists = (opts && opts.existsSync) || node_fs_1.default.existsSync;
256
+ const candidates = [];
257
+ if (env.GSD_BASH_PATH)
258
+ candidates.push(env.GSD_BASH_PATH);
259
+ if (env.ProgramFiles)
260
+ candidates.push(node_path_1.default.win32.join(env.ProgramFiles, 'Git', 'bin', 'bash.exe'));
261
+ if (env['ProgramFiles(x86)'])
262
+ candidates.push(node_path_1.default.win32.join(env['ProgramFiles(x86)'], 'Git', 'bin', 'bash.exe'));
263
+ if (env.SystemDrive) {
264
+ candidates.push(node_path_1.default.win32.join(env.SystemDrive, 'Program Files', 'Git', 'bin', 'bash.exe'));
265
+ candidates.push(node_path_1.default.win32.join(env.SystemDrive, 'Program Files (x86)', 'Git', 'bin', 'bash.exe'));
266
+ }
267
+ for (const candidate of candidates) {
268
+ if (candidate && exists(candidate)) {
269
+ return JSON.stringify(candidate.replace(/\\/g, '/'));
270
+ }
271
+ }
272
+ return null;
273
+ }
274
+ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
275
+ if (!settings || !settings.hooks || !absoluteRunner)
276
+ return false;
277
+ if (!opts)
278
+ opts = {};
279
+ const platform = opts.platform || process.platform;
280
+ let changed = false;
281
+ for (const entries of Object.values(settings.hooks)) {
282
+ if (!Array.isArray(entries))
283
+ continue;
284
+ for (const entry of entries) {
285
+ if (!entry || !Array.isArray(entry.hooks))
286
+ continue;
287
+ for (const h of entry.hooks) {
288
+ if (!h || typeof h.command !== 'string')
289
+ continue;
290
+ if (Array.isArray(h.args) && h.args.length > 0)
291
+ continue;
292
+ let trimmed = h.command.trim();
293
+ const hadPowerShellCallOperator = platform === 'win32' && /^&\s+/.test(trimmed);
294
+ if (hadPowerShellCallOperator) {
295
+ trimmed = trimmed.replace(/^&\s+/, '').trim();
296
+ }
297
+ const m = trimmed.match(/^node\s+("([^"]+)"|'([^']+)'|(\S+))\s*$/) ||
298
+ trimmed.match(/^("([^"]+)"|'([^']+)'|(\S+))\s+("([^"]+)"|'([^']+)'|(\S+))\s*$/);
299
+ if (!m)
300
+ continue;
301
+ let _runnerToken, scriptToken, scriptPath;
302
+ if (/^node\s+/.test(trimmed)) {
303
+ _runnerToken = 'node';
304
+ scriptToken = m[1];
305
+ scriptPath = m[2] || m[3] || m[4] || '';
306
+ }
307
+ else {
308
+ _runnerToken = m[1];
309
+ const runnerPath = (m[2] || m[3] || m[4] || '').replace(/\\/g, '/');
310
+ const stableRunner = normalizeNodePath(runnerPath);
311
+ if (stableRunner === runnerPath && platform !== 'win32')
312
+ continue;
313
+ scriptToken = m[5];
314
+ scriptPath = m[6] || m[7] || m[8] || '';
315
+ }
316
+ if (!isManagedHookBasename(scriptPath, { surface: 'settings-json' }))
317
+ continue;
318
+ const projectedCommand = projectLegacySettingsHookCommand({
319
+ absoluteRunner,
320
+ scriptPath,
321
+ scriptToken,
322
+ runtime: opts.runtime || 'generic',
323
+ platform,
324
+ });
325
+ if (!projectedCommand)
326
+ continue;
327
+ if (h.command === projectedCommand)
328
+ continue;
329
+ h.command = projectedCommand;
330
+ changed = true;
331
+ }
332
+ }
333
+ }
334
+ return changed;
335
+ }
336
+ function buildCodexHookBlock(targetDir, opts) {
337
+ const absoluteRunner = opts && opts.absoluteRunner;
338
+ if (!absoluteRunner)
339
+ return null;
340
+ const eol = (opts && opts.eol) || '\n';
341
+ const platform = (opts && opts.platform) || process.platform;
342
+ const updateCheckScript = node_path_1.default.resolve(targetDir, 'hooks', 'pp-check-update.js');
343
+ const commandValue = projectCodexHookTomlCommand({
344
+ absoluteRunner,
345
+ scriptPath: updateCheckScript,
346
+ platform,
347
+ });
348
+ return `${eol}# GSD Hooks${eol}` +
349
+ `[[hooks.SessionStart]]${eol}` +
350
+ `${eol}` +
351
+ `[[hooks.SessionStart.hooks]]${eol}` +
352
+ `type = "command"${eol}` +
353
+ `command = "${commandValue}"${eol}`;
354
+ }
355
+ function rewriteLegacyCodexHookBlock(content, absoluteRunner, opts) {
356
+ if (!content || !absoluteRunner)
357
+ return { content, changed: false };
358
+ const platform = (opts && opts.platform) || process.platform;
359
+ let changed = false;
360
+ const updated = content.replace(/^(command\s*=\s*")node\s+((?:\\"[^"]+\\"|\S+))("\s*)$/gm, (full, prefix, scriptToken, suffix) => {
361
+ const quoted = scriptToken.match(/^\\"([\s\S]+)\\"$/);
362
+ let scriptPath = scriptToken;
363
+ if (quoted) {
364
+ try {
365
+ scriptPath = String(parseTomlValue(`"${quoted[1]}"`, 0).value);
366
+ }
367
+ catch {
368
+ scriptPath = quoted[1];
369
+ }
370
+ }
371
+ if (!isManagedHookBasename(scriptPath, { surface: 'codex-toml' }))
372
+ return full;
373
+ const desiredCommand = projectCodexHookTomlCommand({
374
+ absoluteRunner,
375
+ scriptPath,
376
+ platform,
377
+ });
378
+ const currentCommand = `${prefix}${scriptToken}${suffix}`.replace(/^(command\s*=\s*")|("\s*)$/g, '');
379
+ if (currentCommand === desiredCommand)
380
+ return full;
381
+ changed = true;
382
+ return `${prefix}${desiredCommand}${suffix}`;
383
+ });
384
+ return { content: updated, changed };
385
+ }
386
+ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
387
+ const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
388
+ const managedCommand = typeof opts.managedCommand === 'string' ? opts.managedCommand : null;
389
+ const commandWindows = typeof opts.commandWindows === 'string' ? opts.commandWindows : null;
390
+ const matcher = typeof opts.matcher === 'string' ? opts.matcher : undefined;
391
+ const timeout = typeof opts.timeout === 'number' ? opts.timeout : undefined;
392
+ let parsed = {};
393
+ let currentContent = null;
394
+ if (node_fs_1.default.existsSync(hooksJsonPath)) {
395
+ const raw = node_fs_1.default.readFileSync(hooksJsonPath, 'utf8');
396
+ currentContent = raw;
397
+ if (raw.trim()) {
398
+ try {
399
+ parsed = JSON.parse(raw);
400
+ }
401
+ catch (err) {
402
+ throw new Error(`hooks.json parse failed: ${err && err.message ? err.message : String(err)}`);
403
+ }
404
+ }
405
+ }
406
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
407
+ parsed = {};
408
+ const usesNestedHooksObject = parsed['hooks'] && typeof parsed['hooks'] === 'object' && !Array.isArray(parsed['hooks']);
409
+ // #1348: canonicalize every write to the nested { hooks: { <Event>: [...] } }
410
+ // shape. Lift ANY top-level event array (legacy, empty, OR mixed nested+top-level)
411
+ // into the nested table — merging when the same event exists in both — so
412
+ // user/legacy entries are preserved under `hooks` and no stray top-level event
413
+ // key survives (Codex deny_unknown_fields rejects them). Mirrors reconcileCursorHooksJson.
414
+ const hookTable = usesNestedHooksObject
415
+ ? parsed['hooks']
416
+ : {};
417
+ for (const key of Object.keys(parsed)) {
418
+ if (key === 'hooks')
419
+ continue;
420
+ if (Array.isArray(parsed[key])) {
421
+ const lifted = parsed[key];
422
+ const existing = Array.isArray(hookTable[key]) ? hookTable[key] : [];
423
+ hookTable[key] = [...lifted, ...existing];
424
+ delete parsed[key];
425
+ }
426
+ }
427
+ parsed['hooks'] = hookTable;
428
+ const eventEntries = Array.isArray(hookTable[eventName]) ? hookTable[eventName] : [];
429
+ let removedLegacy = false;
430
+ const sanitizedEntries = [];
431
+ for (const entry of eventEntries) {
432
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
433
+ continue;
434
+ const entryObj = entry;
435
+ const originalHooks = Array.isArray(entryObj['hooks']) ? entryObj['hooks'] : [];
436
+ if (originalHooks.length === 0) {
437
+ sanitizedEntries.push(entry);
438
+ continue;
439
+ }
440
+ const keptHooks = originalHooks.filter((hook) => {
441
+ const cmd = hook && typeof hook === 'object' ? hook['command'] : null;
442
+ const managed = isManagedHookCommand(cmd, {
443
+ surface: 'codex-hooks-json',
444
+ includeLegacyAliases: true,
445
+ configDir: targetDir,
446
+ });
447
+ if (managed)
448
+ removedLegacy = true;
449
+ return !managed;
450
+ });
451
+ if (keptHooks.length === 0)
452
+ continue;
453
+ const nextEntry = { ...entryObj, hooks: keptHooks };
454
+ sanitizedEntries.push(nextEntry);
455
+ }
456
+ if (managedCommand) {
457
+ const hookEntry = { type: 'command', command: managedCommand };
458
+ if (commandWindows)
459
+ hookEntry['commandWindows'] = commandWindows;
460
+ if (timeout !== undefined)
461
+ hookEntry['timeout'] = timeout;
462
+ const newEntry = { hooks: [hookEntry] };
463
+ if (matcher !== undefined)
464
+ newEntry['matcher'] = matcher;
465
+ sanitizedEntries.push(newEntry);
466
+ }
467
+ if (sanitizedEntries.length > 0) {
468
+ hookTable[eventName] = sanitizedEntries;
469
+ }
470
+ else {
471
+ delete hookTable[eventName];
472
+ }
473
+ // Avoid writing an empty `{ "hooks": {} }` artifact (e.g. removal on an absent
474
+ // file): collapse an empty hook table back to `{}` so the existing
475
+ // shouldWrite/no-write-on-empty behavior is preserved.
476
+ if (Object.keys(hookTable).length === 0)
477
+ delete parsed['hooks'];
478
+ const nextContent = `${JSON.stringify(parsed, null, 2)}\n`;
479
+ const changed = currentContent !== nextContent;
480
+ const shouldWrite = changed && (currentContent !== null || Object.keys(parsed).length > 0);
481
+ if (shouldWrite) {
482
+ atomicWriteFileSync(hooksJsonPath, nextContent, 'utf8');
483
+ }
484
+ return { changed: changed || removedLegacy, wrote: shouldWrite, path: hooksJsonPath };
485
+ }
486
+ function reconcileCodexHooksJsonSessionStart(targetDir, opts = {}) {
487
+ return reconcileCodexHooksJsonEvent(targetDir, 'SessionStart', opts);
488
+ }
489
+ function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
490
+ if (!absoluteRunnerToken)
491
+ return null;
492
+ let interpreter;
493
+ try {
494
+ interpreter = JSON.parse(absoluteRunnerToken);
495
+ }
496
+ catch {
497
+ interpreter = absoluteRunnerToken;
498
+ }
499
+ const targetAbs = scriptAbsPath.replace(/\\/g, '/');
500
+ const scriptQuoted = JSON.stringify(targetAbs);
501
+ const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
502
+ const hookCommand = JSON.stringify(cmdPath.replace(/\\/g, '/'));
503
+ const runnerQuoted = JSON.stringify(interpreter);
504
+ return {
505
+ invocation: { interpreter, target: scriptAbsPath },
506
+ cmdPath,
507
+ hookCommand,
508
+ eol: { cmd: '\r\n' },
509
+ passthroughArgs: true,
510
+ render: {
511
+ cmd: () => `@ECHO OFF\r\n@SETLOCAL\r\n@${runnerQuoted} ${scriptQuoted} %*\r\n`,
512
+ },
513
+ };
514
+ }
515
+ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
516
+ const platform = opts.platform || process.platform;
517
+ const absoluteRunner = opts.absoluteRunner || null;
518
+ const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
519
+ if (!absoluteRunner)
520
+ return { changed: false, wrote: false, path: hooksJsonPath };
521
+ const scriptPath = node_path_1.default.resolve(targetDir, 'hooks', 'pp-check-update.js').replace(/\\/g, '/');
522
+ const cmdShimPath = scriptPath.replace(/\.js$/, '.cmd');
523
+ let managedCommand;
524
+ if (platform === 'win32') {
525
+ const shimIR = buildCodexHookWindowsShimIR(scriptPath, absoluteRunner);
526
+ if (!shimIR)
527
+ return { changed: false, wrote: false, path: hooksJsonPath };
528
+ try {
529
+ atomicWriteFileSync(shimIR.cmdPath, shimIR.render.cmd(), 'utf8');
530
+ }
531
+ catch (shimWriteErr) {
532
+ const reason = shimWriteErr && shimWriteErr.message ? shimWriteErr.message : String(shimWriteErr);
533
+ console.warn(` ${yellow}⚠${reset} Codex Windows hook NOT installed — .cmd shim write failed: ${reason}. ` +
534
+ `Fix the write error (permissions? disk full?) and re-run the installer. ` +
535
+ `Do NOT use the legacy node.exe command path — it triggers the #3426 bash.exe POSIX-exec failure.`);
536
+ return { changed: false, wrote: false, path: hooksJsonPath };
537
+ }
538
+ managedCommand = shimIR.hookCommand;
539
+ }
540
+ else {
541
+ managedCommand = projectManagedHookCommand({
542
+ absoluteRunner,
543
+ scriptPath,
544
+ runtime: 'codex',
545
+ platform,
546
+ }) ?? undefined;
547
+ }
548
+ if (!managedCommand)
549
+ return { changed: false, wrote: false, path: hooksJsonPath };
550
+ const commandWindows = platform === 'win32'
551
+ ? JSON.stringify(cmdShimPath.replace(/\\/g, '/'))
552
+ : undefined;
553
+ return reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand, commandWindows });
554
+ }
555
+ function ensureCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
556
+ const platform = opts.platform || process.platform;
557
+ const absoluteRunner = opts.absoluteRunner || null;
558
+ const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
559
+ if (!absoluteRunner)
560
+ return { changed: false, wrote: false, path: hooksJsonPath };
561
+ const scriptPath = node_path_1.default.resolve(targetDir, 'hooks', 'pp-context-monitor.js').replace(/\\/g, '/');
562
+ let managedCommand;
563
+ if (platform === 'win32') {
564
+ const shimIR = buildCodexHookWindowsShimIR(scriptPath, absoluteRunner);
565
+ if (!shimIR)
566
+ return { changed: false, wrote: false, path: hooksJsonPath };
567
+ try {
568
+ atomicWriteFileSync(shimIR.cmdPath, shimIR.render.cmd(), 'utf8');
569
+ }
570
+ catch (shimWriteErr) {
571
+ const reason = shimWriteErr && shimWriteErr.message ? shimWriteErr.message : String(shimWriteErr);
572
+ console.warn(` ${yellow}⚠${reset} Codex Windows hook NOT installed — .cmd shim write failed for ${eventName}: ${reason}. ` +
573
+ `Fix the write error (permissions? disk full?) and re-run the installer.`);
574
+ return { changed: false, wrote: false, path: hooksJsonPath };
575
+ }
576
+ managedCommand = shimIR.hookCommand;
577
+ }
578
+ else {
579
+ managedCommand = projectManagedHookCommand({
580
+ absoluteRunner,
581
+ scriptPath,
582
+ runtime: 'codex',
583
+ platform,
584
+ }) ?? undefined;
585
+ }
586
+ if (!managedCommand)
587
+ return { changed: false, wrote: false, path: hooksJsonPath };
588
+ return reconcileCodexHooksJsonEvent(targetDir, eventName, { managedCommand, timeout: 10 });
589
+ }
590
+ // ---------------------------------------------------------------------------
591
+ // removeCodexHooksJsonEvent / removeCodexHooksJsonSessionStart
592
+ // ---------------------------------------------------------------------------
593
+ function removeCodexHooksJsonEvent(targetDir, eventName) {
594
+ return reconcileCodexHooksJsonEvent(targetDir, eventName, { managedCommand: null });
595
+ }
596
+ function removeCodexHooksJsonSessionStart(targetDir) {
597
+ return reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand: null });
598
+ }
599
+ function buildHookCommand(configDir, hookName, opts) {
600
+ if (!opts)
601
+ opts = {};
602
+ const platform = opts.platform || process.platform;
603
+ const runtime = opts.runtime || 'generic';
604
+ const isShellHook = hookName.endsWith('.sh');
605
+ if (shellHookOmitsBashRunner({ platform, runtime, isShellHook })) {
606
+ if (opts.portableHooks) {
607
+ const portableBaseDir = projectPortableHookBaseDir({
608
+ configDir,
609
+ homeDir: node_os_1.default.homedir(),
610
+ });
611
+ return JSON.stringify(`${portableBaseDir}/hooks/${hookName}`);
612
+ }
613
+ return JSON.stringify(configDir.replace(/\\/g, '/') + '/hooks/' + hookName);
614
+ }
615
+ const nodeRunner = resolveNodeRunner();
616
+ const runner = isShellHook ? resolveBashRunner(opts) : nodeRunner;
617
+ if (runner === null)
618
+ return null;
619
+ if (opts.portableHooks) {
620
+ const portableBaseDir = projectPortableHookBaseDir({
621
+ configDir,
622
+ homeDir: node_os_1.default.homedir(),
623
+ });
624
+ return projectManagedHookCommand({
625
+ absoluteRunner: runner,
626
+ scriptPath: `${portableBaseDir}/hooks/${hookName}`,
627
+ runtime: opts.runtime || 'generic',
628
+ platform,
629
+ });
630
+ }
631
+ const hooksPath = configDir.replace(/\\/g, '/') + '/hooks/' + hookName;
632
+ return projectManagedHookCommand({
633
+ absoluteRunner: runner,
634
+ scriptPath: hooksPath,
635
+ runtime,
636
+ platform,
637
+ });
638
+ }
639
+ // ---------------------------------------------------------------------------
640
+ // Cline helpers
641
+ // ---------------------------------------------------------------------------
642
+ function buildClineRulesBody() {
643
+ return [
644
+ '# pp-core — The Pareto Project AI delivery framework.',
645
+ '',
646
+ '- pp-core workflows live in `pp-core/workflows/`. Load the relevant workflow when',
647
+ ' the user runs a `/pp-*` command.',
648
+ '- pp-core agents live in `agents/`. Use the matching agent when spawning subagents.',
649
+ '- pp-core tools are at `pp-core/bin/pp-tools.cjs`. Run with `node`.',
650
+ '- Planning artifacts live in `.planning/`. Never edit them outside a pp-core workflow.',
651
+ '- Do not apply pp-core workflows unless the user explicitly asks for them.',
652
+ '- When a pp-core command triggers a deliverable (feature, fix, docs), offer the next',
653
+ ' step to the user using Cline\'s ask_user tool after completing it.',
654
+ ].join('\n') + '\n';
655
+ }
656
+ function buildClineAgentsMdBody() {
657
+ return buildClineRulesBody();
658
+ }
659
+ function buildClinePreToolUseHook() {
660
+ return `#!/usr/bin/env node
661
+ 'use strict';
662
+ /* pp-core-managed Cline PreToolUse hook — gsd-core issue #787.
663
+ * Protocol: JSON on stdin -> JSON decision on stdout.
664
+ * Honored fields: { cancel, errorMessage, contextModification }.
665
+ * Fails open: any error allows the operation. */
666
+ let raw = '';
667
+ process.stdin.setEncoding('utf8');
668
+ process.stdin.on('data', (c) => { raw += c; });
669
+ process.stdin.on('end', () => {
670
+ const allow = () => process.stdout.write(JSON.stringify({ cancel: false }));
671
+ let input;
672
+ try { input = JSON.parse(raw || '{}'); } catch { return allow(); }
673
+ try {
674
+ const tool = String(
675
+ input.toolName || input.tool_name || input.tool ||
676
+ (input.toolInput && input.toolInput.name) || (input.tool_input && input.tool_input.name) || ''
677
+ ).toLowerCase();
678
+ const isWrite = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/.test(tool);
679
+ // Collect only PATH-bearing field values (not free-form content), so a doc
680
+ // that merely mentions ".planning/" in its body is never falsely blocked.
681
+ const paths = [];
682
+ const PATH_KEY = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
683
+ const walk = (v, depth) => {
684
+ if (depth > 5 || paths.length > 64) return;
685
+ if (Array.isArray(v)) { for (const x of v) walk(x, depth + 1); return; }
686
+ if (v && typeof v === 'object') {
687
+ for (const k of Object.keys(v)) {
688
+ const val = v[k];
689
+ if (typeof val === 'string' && PATH_KEY.test(k)) paths.push(val);
690
+ else walk(val, depth + 1);
691
+ }
692
+ }
693
+ };
694
+ walk(input, 0);
695
+ const isPlanningPath = (s) => /(^|[\\\\/])\\.planning([\\\\/]|$)/.test(s);
696
+ if (isWrite && paths.some(isPlanningPath)) {
697
+ return process.stdout.write(JSON.stringify({
698
+ cancel: true,
699
+ errorMessage:
700
+ 'pp-core: .planning/ artifacts are managed by pp-core workflows. Edit them only through a /pp-* command, not directly.',
701
+ }));
702
+ }
703
+ } catch { /* fall through to allow */ }
704
+ return allow();
705
+ });
706
+ `;
707
+ }
708
+ function mergeGsdAgentsMd(filePath, gsdContent) {
709
+ const gsdBlock = GSD_AGENTS_MD_MARKER + '\n' + gsdContent.trim() + '\n' + GSD_AGENTS_MD_CLOSE_MARKER;
710
+ if (!node_fs_1.default.existsSync(filePath)) {
711
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
712
+ node_fs_1.default.writeFileSync(filePath, gsdBlock + '\n');
713
+ return;
714
+ }
715
+ const existing = node_fs_1.default.readFileSync(filePath, 'utf8');
716
+ const openIndex = existing.indexOf(GSD_AGENTS_MD_MARKER);
717
+ const closeIndex = existing.indexOf(GSD_AGENTS_MD_CLOSE_MARKER);
718
+ if (openIndex !== -1 && closeIndex !== -1) {
719
+ const before = existing.substring(0, openIndex).trimEnd();
720
+ const after = existing.substring(closeIndex + GSD_AGENTS_MD_CLOSE_MARKER.length).trimStart();
721
+ let newContent = '';
722
+ if (before)
723
+ newContent += before + '\n\n';
724
+ newContent += gsdBlock;
725
+ if (after)
726
+ newContent += '\n\n' + after;
727
+ newContent += '\n';
728
+ node_fs_1.default.writeFileSync(filePath, newContent);
729
+ return;
730
+ }
731
+ node_fs_1.default.writeFileSync(filePath, existing.trimEnd() + '\n\n' + gsdBlock + '\n');
732
+ }
733
+ // ---------------------------------------------------------------------------
734
+ // writeClineArtifacts
735
+ // ---------------------------------------------------------------------------
736
+ function writeClineArtifacts(targetDir, isGlobalInstall) {
737
+ const written = [];
738
+ const clinerulesDir = node_path_1.default.join(targetDir, '.clinerules');
739
+ try {
740
+ if (node_fs_1.default.existsSync(clinerulesDir)) {
741
+ const st = node_fs_1.default.lstatSync(clinerulesDir);
742
+ if (st.isFile() || st.isSymbolicLink()) {
743
+ node_fs_1.default.unlinkSync(clinerulesDir);
744
+ console.log(` ${green}✓${reset} Migrated legacy .clinerules to directory form`);
745
+ }
746
+ }
747
+ }
748
+ catch { /* best-effort migration */ }
749
+ node_fs_1.default.mkdirSync(clinerulesDir, { recursive: true });
750
+ node_fs_1.default.writeFileSync(node_path_1.default.join(clinerulesDir, 'gsd.md'), buildClineRulesBody());
751
+ written.push('.clinerules/gsd.md');
752
+ console.log(` ${green}✓${reset} Wrote .clinerules/gsd.md`);
753
+ const hooksDir = node_path_1.default.join(clinerulesDir, 'hooks');
754
+ node_fs_1.default.mkdirSync(hooksDir, { recursive: true });
755
+ const hookPath = node_path_1.default.join(hooksDir, 'PreToolUse');
756
+ node_fs_1.default.writeFileSync(hookPath, buildClinePreToolUseHook());
757
+ try {
758
+ node_fs_1.default.chmodSync(hookPath, 0o755);
759
+ }
760
+ catch { /* Windows: hooks unsupported anyway */ }
761
+ written.push('.clinerules/hooks/PreToolUse');
762
+ console.log(` ${green}✓${reset} Wrote .clinerules/hooks/PreToolUse`);
763
+ if (isGlobalInstall) {
764
+ try {
765
+ const agentsPath = node_path_1.default.join(node_os_1.default.homedir(), '.agents', 'AGENTS.md');
766
+ mergeGsdAgentsMd(agentsPath, buildClineAgentsMdBody());
767
+ console.log(` ${green}✓${reset} Merged GSD instructions into ~/.agents/AGENTS.md`);
768
+ }
769
+ catch (err) {
770
+ console.warn(` ${yellow}⚠${reset} Could not write ~/.agents/AGENTS.md: ${err.message}`);
771
+ }
772
+ }
773
+ return written;
774
+ }
775
+ // ---------------------------------------------------------------------------
776
+ // Cursor hook functions
777
+ // ---------------------------------------------------------------------------
778
+ function buildCursorHookEntry(scriptPath) {
779
+ return {
780
+ type: 'command',
781
+ command: scriptPath.replace(/\\/g, '/'),
782
+ [GSD_CURSOR_HOOK_MARKER]: true,
783
+ };
784
+ }
785
+ function isManagedCursorHookEntry(entry) {
786
+ return Boolean(entry && typeof entry === 'object' && entry[GSD_CURSOR_HOOK_MARKER]);
787
+ }
788
+ function reconcileCursorHooksJson(hooksJsonPath, managedEntries) {
789
+ let parsed = {};
790
+ let currentContent = null;
791
+ if (node_fs_1.default.existsSync(hooksJsonPath)) {
792
+ const raw = node_fs_1.default.readFileSync(hooksJsonPath, 'utf8');
793
+ currentContent = raw;
794
+ if (raw.trim()) {
795
+ try {
796
+ parsed = JSON.parse(raw);
797
+ }
798
+ catch (err) {
799
+ throw new Error(`Cursor hooks.json parse failed: ${err && err.message ? err.message : String(err)}`);
800
+ }
801
+ }
802
+ }
803
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
804
+ parsed = {};
805
+ const hasNestedHooksObject = parsed['hooks'] && typeof parsed['hooks'] === 'object' && !Array.isArray(parsed['hooks']);
806
+ if (!hasNestedHooksObject) {
807
+ const eventKeys = ['sessionStart', 'postToolUse'];
808
+ const lifted = {};
809
+ for (const k of eventKeys) {
810
+ if (Array.isArray(parsed[k])) {
811
+ lifted[k] = parsed[k];
812
+ delete parsed[k];
813
+ }
814
+ }
815
+ parsed['hooks'] = lifted;
816
+ }
817
+ if (!parsed['version'])
818
+ parsed['version'] = 1;
819
+ const hookTable = parsed['hooks'];
820
+ const MANAGED_EVENTS = ['sessionStart', 'postToolUse'];
821
+ const entries = managedEntries || {};
822
+ for (const event of MANAGED_EVENTS) {
823
+ const existing = Array.isArray(hookTable[event]) ? hookTable[event] : [];
824
+ const userOwned = existing.filter((e) => !isManagedCursorHookEntry(e));
825
+ const newEntry = entries[event] || null;
826
+ if (newEntry) {
827
+ hookTable[event] = [...userOwned, newEntry];
828
+ }
829
+ else {
830
+ if (userOwned.length > 0) {
831
+ hookTable[event] = userOwned;
832
+ }
833
+ else {
834
+ delete hookTable[event];
835
+ }
836
+ }
837
+ }
838
+ const nextContent = `${JSON.stringify(parsed, null, 2)}\n`;
839
+ const changed = currentContent !== nextContent;
840
+ const shouldWrite = changed && (currentContent !== null || Object.keys(parsed).length > 0);
841
+ if (shouldWrite) {
842
+ atomicWriteFileSync(hooksJsonPath, nextContent, 'utf8');
843
+ }
844
+ return { changed: changed, wrote: shouldWrite, path: hooksJsonPath };
845
+ }
846
+ function writeCursorHooksJson(targetDir, src, opts) {
847
+ opts = opts || {};
848
+ const hooksDir = node_path_1.default.join(targetDir, 'hooks');
849
+ node_fs_1.default.mkdirSync(hooksDir, { recursive: true });
850
+ const hookScripts = [GSD_CURSOR_SESSION_HOOK_SCRIPT, GSD_CURSOR_POST_TOOL_HOOK_SCRIPT];
851
+ const srcHooksDir = node_path_1.default.join(src, 'hooks');
852
+ const installedScripts = new Set();
853
+ for (const script of hookScripts) {
854
+ const srcPath = node_path_1.default.join(srcHooksDir, script);
855
+ const destPath = node_path_1.default.join(hooksDir, script);
856
+ if (node_fs_1.default.existsSync(srcPath)) {
857
+ let content = node_fs_1.default.readFileSync(srcPath, 'utf8');
858
+ content = content.replace(/pp:/gi, 'pp-').replace(/gsd:/gi, 'pp-');
859
+ node_fs_1.default.writeFileSync(destPath, content);
860
+ try {
861
+ node_fs_1.default.chmodSync(destPath, 0o755);
862
+ }
863
+ catch { /* Windows: ignore chmod */ }
864
+ installedScripts.add(script);
865
+ }
866
+ }
867
+ const hookOpts = { runtime: 'cursor', platform: opts.platform || process.platform };
868
+ const sessionStartCmd = installedScripts.has('pp-cursor-session-start.js')
869
+ ? buildHookCommand(targetDir, 'pp-cursor-session-start.js', hookOpts)
870
+ : null;
871
+ const postToolCmd = installedScripts.has('pp-cursor-post-tool.js')
872
+ ? buildHookCommand(targetDir, 'pp-cursor-post-tool.js', hookOpts)
873
+ : null;
874
+ const managedEntries = {};
875
+ if (sessionStartCmd) {
876
+ managedEntries['sessionStart'] = {
877
+ type: 'command',
878
+ command: sessionStartCmd,
879
+ [GSD_CURSOR_HOOK_MARKER]: true,
880
+ };
881
+ }
882
+ if (postToolCmd) {
883
+ managedEntries['postToolUse'] = {
884
+ type: 'command',
885
+ command: postToolCmd,
886
+ [GSD_CURSOR_HOOK_MARKER]: true,
887
+ };
888
+ }
889
+ const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
890
+ const result = reconcileCursorHooksJson(hooksJsonPath, managedEntries);
891
+ return { hooksJsonPath, changed: result.changed };
892
+ }
893
+ function removeCursorHooksJson(targetDir) {
894
+ const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
895
+ if (!node_fs_1.default.existsSync(hooksJsonPath))
896
+ return { changed: false };
897
+ const result = reconcileCursorHooksJson(hooksJsonPath, null);
898
+ if (result.changed) {
899
+ try {
900
+ const contentRaw = node_fs_1.default.readFileSync(hooksJsonPath, 'utf8');
901
+ const parsed = JSON.parse(contentRaw);
902
+ const hookTable = (parsed['hooks'] && typeof parsed['hooks'] === 'object' && !Array.isArray(parsed['hooks']))
903
+ ? parsed['hooks']
904
+ : {};
905
+ const hasAnyEvents = Object.keys(hookTable).some((k) => Array.isArray(hookTable[k]) && hookTable[k].length > 0);
906
+ if (!hasAnyEvents) {
907
+ node_fs_1.default.unlinkSync(hooksJsonPath);
908
+ return { changed: true };
909
+ }
910
+ }
911
+ catch { /* best-effort: leave the file */ }
912
+ }
913
+ return { changed: result.changed };
914
+ }
915
+ // ---------------------------------------------------------------------------
916
+ // Copilot hook functions
917
+ // ---------------------------------------------------------------------------
918
+ function buildCopilotHookConfig() {
919
+ return {
920
+ version: 1,
921
+ hooks: {
922
+ sessionStart: [
923
+ {
924
+ type: 'command',
925
+ bash: GSD_COPILOT_SESSION_HOOK_BASH,
926
+ powershell: GSD_COPILOT_SESSION_HOOK_PWSH,
927
+ timeoutSec: 10,
928
+ },
929
+ ],
930
+ },
931
+ };
932
+ }
933
+ function writeCopilotHookConfig(targetDir) {
934
+ const hooksDir = node_path_1.default.join(targetDir, 'hooks');
935
+ node_fs_1.default.mkdirSync(hooksDir, { recursive: true });
936
+ const hookPath = node_path_1.default.join(hooksDir, GSD_COPILOT_HOOK_FILE);
937
+ node_fs_1.default.writeFileSync(hookPath, JSON.stringify(buildCopilotHookConfig(), null, 2) + '\n');
938
+ return hookPath;
939
+ }
940
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
941
+ function applySettingsJsonHooks(settings, opts) {
942
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access,
943
+ @typescript-eslint/no-unsafe-call,
944
+ @typescript-eslint/no-unsafe-assignment */
945
+ const { runtime, isGlobal, targetDir, postToolEvent, hookEvents, extendedHookEvents, hooksSurface, updateCheckCommand, contextMonitorCommand, promptGuardCommand, readGuardCommand, readInjectionScannerCommand, configReloadCommand, hookOpts, localCmd, localShellCmd, } = opts;
946
+ // ADR-857 phase 5f-3: extended hook events are now driven by the registry
947
+ // descriptor field rather than hardcoded runtime-name checks.
948
+ const extendedEvents = Array.isArray(extendedHookEvents) ? extendedHookEvents : [];
949
+ // ADR-857 phase 5g drive 3: hook-skip guard is driven by the hooksSurface
950
+ // descriptor field. Only runtimes with hooksSurface === 'settings-json'
951
+ // register settings.json hooks; runtimes with hooksSurface === 'none'
952
+ // (opencode, kilo) are skipped. Equivalence: hooksSurface !== 'none' iff
953
+ // the old !isOpencode && !isKilo check.
954
+ if (hooksSurface !== 'none') {
955
+ if (!settings.hooks) {
956
+ settings.hooks = {};
957
+ }
958
+ if (!settings.hooks.SessionStart) {
959
+ settings.hooks.SessionStart = [];
960
+ }
961
+ const hasGsdUpdateHook = settings.hooks.SessionStart.some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-check-update')));
962
+ // Guard: only register if the hook file was actually installed (#1754).
963
+ // When hooks/dist/ is missing from the npm package (as in v1.32.0), the
964
+ // copy step produces no files but the registration step ran unconditionally,
965
+ // causing "hook error" on every tool invocation.
966
+ const checkUpdateFile = node_path_1.default.join(targetDir, 'hooks', 'pp-check-update.js');
967
+ if (!hasGsdUpdateHook && node_fs_1.default.existsSync(checkUpdateFile) && updateCheckCommand) {
968
+ settings.hooks.SessionStart.push({
969
+ hooks: [
970
+ {
971
+ type: 'command',
972
+ command: updateCheckCommand
973
+ }
974
+ ]
975
+ });
976
+ console.log(` ${green}✓${reset} Configured update check hook`);
977
+ }
978
+ else if (!hasGsdUpdateHook && !node_fs_1.default.existsSync(checkUpdateFile)) {
979
+ console.warn(` ${yellow}⚠${reset} Skipped update check hook — pp-check-update.js not found at target`);
980
+ }
981
+ // Configure post-tool hook for context window monitoring
982
+ if (!settings.hooks[postToolEvent]) {
983
+ settings.hooks[postToolEvent] = [];
984
+ }
985
+ const hasContextMonitorHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-context-monitor')));
986
+ const contextMonitorFile = node_path_1.default.join(targetDir, 'hooks', 'pp-context-monitor.js');
987
+ if (!hasContextMonitorHook && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
988
+ settings.hooks[postToolEvent].push({
989
+ matcher: 'Bash|Edit|Write|MultiEdit|Agent|Task',
990
+ hooks: [
991
+ {
992
+ type: 'command',
993
+ command: contextMonitorCommand,
994
+ timeout: 10
995
+ }
996
+ ]
997
+ });
998
+ console.log(` ${green}✓${reset} Configured context window monitor hook`);
999
+ }
1000
+ else if (!hasContextMonitorHook && !node_fs_1.default.existsSync(contextMonitorFile)) {
1001
+ console.warn(` ${yellow}⚠${reset} Skipped context monitor hook — pp-context-monitor.js not found at target`);
1002
+ }
1003
+ else {
1004
+ // Migrate existing context monitor hooks: add matcher and timeout if missing
1005
+ for (const entry of settings.hooks[postToolEvent]) {
1006
+ if (entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-context-monitor'))) {
1007
+ let migrated = false;
1008
+ if (!entry.matcher) {
1009
+ entry.matcher = 'Bash|Edit|Write|MultiEdit|Agent|Task';
1010
+ migrated = true;
1011
+ }
1012
+ for (const h of entry.hooks) {
1013
+ if (referencesHook(h, 'pp-context-monitor') && !h.timeout) {
1014
+ h.timeout = 10;
1015
+ migrated = true;
1016
+ }
1017
+ }
1018
+ if (migrated) {
1019
+ console.log(` ${green}✓${reset} Updated context monitor hook (added matcher + timeout)`);
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+ // Configure PreToolUse hook for prompt injection detection
1025
+ // ADR-857 phase 5f-2: drive dialect from opts.hookEvents (registry descriptor).
1026
+ // hookEvents='gemini' → BeforeTool; all others → PreToolUse.
1027
+ // Equivalence: hookEvents='gemini' iff runtime∈{gemini,antigravity} (same as old check).
1028
+ const preToolEvent = hookEvents === 'gemini' ? 'BeforeTool' : 'PreToolUse';
1029
+ if (!settings.hooks[preToolEvent]) {
1030
+ settings.hooks[preToolEvent] = [];
1031
+ }
1032
+ const hasPromptGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-prompt-guard')));
1033
+ const promptGuardFile = node_path_1.default.join(targetDir, 'hooks', 'pp-prompt-guard.js');
1034
+ if (!hasPromptGuardHook && node_fs_1.default.existsSync(promptGuardFile) && promptGuardCommand) {
1035
+ settings.hooks[preToolEvent].push({
1036
+ matcher: 'Write|Edit',
1037
+ hooks: [
1038
+ {
1039
+ type: 'command',
1040
+ command: promptGuardCommand,
1041
+ timeout: 5
1042
+ }
1043
+ ]
1044
+ });
1045
+ console.log(` ${green}✓${reset} Configured prompt injection guard hook`);
1046
+ }
1047
+ else if (!hasPromptGuardHook && !node_fs_1.default.existsSync(promptGuardFile)) {
1048
+ console.warn(` ${yellow}⚠${reset} Skipped prompt guard hook — pp-prompt-guard.js not found at target`);
1049
+ }
1050
+ // Configure PreToolUse hook for read-before-edit guidance (#1628)
1051
+ // Prevents infinite retry loops when non-Claude models attempt to edit
1052
+ // files without reading them first. Advisory-only — does not block.
1053
+ const hasReadGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-read-guard')));
1054
+ const readGuardFile = node_path_1.default.join(targetDir, 'hooks', 'pp-read-guard.js');
1055
+ if (!hasReadGuardHook && node_fs_1.default.existsSync(readGuardFile) && readGuardCommand) {
1056
+ settings.hooks[preToolEvent].push({
1057
+ matcher: 'Write|Edit',
1058
+ hooks: [
1059
+ {
1060
+ type: 'command',
1061
+ command: readGuardCommand,
1062
+ timeout: 5
1063
+ }
1064
+ ]
1065
+ });
1066
+ console.log(` ${green}✓${reset} Configured read-before-edit guard hook`);
1067
+ }
1068
+ else if (!hasReadGuardHook && !node_fs_1.default.existsSync(readGuardFile)) {
1069
+ console.warn(` ${yellow}⚠${reset} Skipped read guard hook — pp-read-guard.js not found at target`);
1070
+ }
1071
+ // Configure PostToolUse hook for read-time prompt injection scanning (#2201)
1072
+ // Scans content returned by the Read tool for injection patterns, including
1073
+ // summarisation-specific patterns that survive context compression.
1074
+ const hasReadInjectionScannerHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-read-injection-scanner')));
1075
+ const readInjectionScannerFile = node_path_1.default.join(targetDir, 'hooks', 'pp-read-injection-scanner.js');
1076
+ if (!hasReadInjectionScannerHook && node_fs_1.default.existsSync(readInjectionScannerFile) && readInjectionScannerCommand) {
1077
+ settings.hooks[postToolEvent].push({
1078
+ matcher: 'Read',
1079
+ hooks: [
1080
+ {
1081
+ type: 'command',
1082
+ command: readInjectionScannerCommand,
1083
+ timeout: 5
1084
+ }
1085
+ ]
1086
+ });
1087
+ console.log(` ${green}✓${reset} Configured read injection scanner hook`);
1088
+ }
1089
+ else if (!hasReadInjectionScannerHook && !node_fs_1.default.existsSync(readInjectionScannerFile)) {
1090
+ console.warn(` ${yellow}⚠${reset} Skipped read injection scanner hook — pp-read-injection-scanner.js not found at target`);
1091
+ }
1092
+ // Community hooks — registered on install but opt-in at runtime.
1093
+ // Each hook checks .planning/config.json for hooks.community: true
1094
+ // and exits silently (no-op) if not enabled. This lets users enable
1095
+ // them per-project by adding: "hooks": { "community": true }
1096
+ // Configure workflow guard hook (opt-in via hooks.workflow_guard: true)
1097
+ // Detects file edits outside pp-core workflow context and advises using
1098
+ // /pp-quick or /pp-fast for state-tracked changes. Also hard-blocks
1099
+ // unsafe Bash commands that violate worktree-agent isolation.
1100
+ const workflowGuardCommand = isGlobal
1101
+ ? buildHookCommand(targetDir, 'pp-workflow-guard.js', hookOpts)
1102
+ : localCmd('pp-workflow-guard.js');
1103
+ const workflowGuardMatcher = 'Bash|Edit|Write|MultiEdit';
1104
+ const workflowGuardHookEntry = settings.hooks[preToolEvent].find((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-workflow-guard')));
1105
+ const hasWorkflowGuardHook = Boolean(workflowGuardHookEntry);
1106
+ const workflowGuardFile = node_path_1.default.join(targetDir, 'hooks', 'pp-workflow-guard.js');
1107
+ if (hasWorkflowGuardHook && workflowGuardHookEntry.matcher !== workflowGuardMatcher) {
1108
+ workflowGuardHookEntry.matcher = workflowGuardMatcher;
1109
+ console.log(` ${green}✓${reset} Updated workflow guard hook matcher`);
1110
+ }
1111
+ else if (!hasWorkflowGuardHook && node_fs_1.default.existsSync(workflowGuardFile) && workflowGuardCommand) {
1112
+ settings.hooks[preToolEvent].push({
1113
+ matcher: workflowGuardMatcher,
1114
+ hooks: [
1115
+ {
1116
+ type: 'command',
1117
+ command: workflowGuardCommand,
1118
+ timeout: 5
1119
+ }
1120
+ ]
1121
+ });
1122
+ console.log(` ${green}✓${reset} Configured workflow guard hook (opt-in via hooks.workflow_guard)`);
1123
+ }
1124
+ else if (!hasWorkflowGuardHook && !node_fs_1.default.existsSync(workflowGuardFile)) {
1125
+ console.warn(` ${yellow}⚠${reset} Skipped workflow guard hook — pp-workflow-guard.js not found at target`);
1126
+ }
1127
+ // Configure PreToolUse hook for worktree absolute-path safety (#260)
1128
+ // Hard-blocks Edit/Write/MultiEdit tool calls with absolute paths that resolve
1129
+ // outside the current worktree root. Prevents executor agents from
1130
+ // accidentally writing to the main checkout when running in isolation="worktree".
1131
+ const worktreePathGuardCommand = isGlobal
1132
+ ? buildHookCommand(targetDir, 'pp-worktree-path-guard.js', hookOpts)
1133
+ : localCmd('pp-worktree-path-guard.js');
1134
+ const hasWorktreePathGuardHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-worktree-path-guard')));
1135
+ const worktreePathGuardFile = node_path_1.default.join(targetDir, 'hooks', 'pp-worktree-path-guard.js');
1136
+ if (!hasWorktreePathGuardHook && node_fs_1.default.existsSync(worktreePathGuardFile) && worktreePathGuardCommand) {
1137
+ settings.hooks[preToolEvent].push({
1138
+ matcher: 'Write|Edit|MultiEdit',
1139
+ hooks: [
1140
+ {
1141
+ type: 'command',
1142
+ command: worktreePathGuardCommand,
1143
+ timeout: 5
1144
+ }
1145
+ ]
1146
+ });
1147
+ console.log(` ${green}✓${reset} Configured worktree path guard hook`);
1148
+ }
1149
+ else if (!hasWorktreePathGuardHook && !node_fs_1.default.existsSync(worktreePathGuardFile)) {
1150
+ console.warn(` ${yellow}⚠${reset} Skipped worktree path guard hook — pp-worktree-path-guard.js not found at target`);
1151
+ }
1152
+ // Configure commit validation hook (Conventional Commits enforcement, opt-in)
1153
+ const validateCommitCommand = isGlobal
1154
+ ? buildHookCommand(targetDir, 'pp-validate-commit.sh', hookOpts)
1155
+ : localShellCmd('pp-validate-commit.sh');
1156
+ const hasValidateCommitHook = settings.hooks[preToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-validate-commit')));
1157
+ // Guard: only register if the .sh file was actually installed. If the npm package
1158
+ // omitted the file (as happened in v1.32.0, bug #1817), registering a missing hook
1159
+ // causes a hook error on every Bash tool invocation.
1160
+ const validateCommitFile = node_path_1.default.join(targetDir, 'hooks', 'pp-validate-commit.sh');
1161
+ if (!hasValidateCommitHook && node_fs_1.default.existsSync(validateCommitFile) && validateCommitCommand) {
1162
+ settings.hooks[preToolEvent].push({
1163
+ matcher: 'Bash',
1164
+ hooks: [
1165
+ {
1166
+ type: 'command',
1167
+ command: validateCommitCommand,
1168
+ timeout: 5
1169
+ }
1170
+ ]
1171
+ });
1172
+ console.log(` ${green}✓${reset} Configured commit validation hook (opt-in via config)`);
1173
+ }
1174
+ else if (!hasValidateCommitHook && !node_fs_1.default.existsSync(validateCommitFile)) {
1175
+ console.warn(` ${yellow}⚠${reset} Skipped commit validation hook — pp-validate-commit.sh not found at target`);
1176
+ }
1177
+ else if (!hasValidateCommitHook && !validateCommitCommand) {
1178
+ console.warn(` ${yellow}⚠${reset} Skipped commit validation hook — Bash executable path unavailable (#3393)`);
1179
+ }
1180
+ // Configure graphify auto-update hook (opt-in via graphify.auto_update; default false, #3347).
1181
+ // PostToolUse Bash matcher — fires after git commit/merge/pull/rebase --continue/cherry-pick
1182
+ // on the default branch, dispatches `graphify update .` in a detached subprocess. No-op unless
1183
+ // .planning/config.json has BOTH graphify.enabled=true AND graphify.auto_update=true.
1184
+ const graphifyUpdateCommand = isGlobal
1185
+ ? buildHookCommand(targetDir, 'pp-graphify-update.sh', hookOpts)
1186
+ : localShellCmd('pp-graphify-update.sh');
1187
+ const hasGraphifyUpdateHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-graphify-update')));
1188
+ const graphifyUpdateFile = node_path_1.default.join(targetDir, 'hooks', 'pp-graphify-update.sh');
1189
+ if (!hasGraphifyUpdateHook && node_fs_1.default.existsSync(graphifyUpdateFile) && graphifyUpdateCommand) {
1190
+ settings.hooks[postToolEvent].push({
1191
+ matcher: 'Bash',
1192
+ hooks: [
1193
+ {
1194
+ type: 'command',
1195
+ command: graphifyUpdateCommand,
1196
+ timeout: 5
1197
+ }
1198
+ ]
1199
+ });
1200
+ console.log(` ${green}✓${reset} Configured graphify auto-update hook (opt-in via graphify.auto_update)`);
1201
+ }
1202
+ else if (!hasGraphifyUpdateHook && !node_fs_1.default.existsSync(graphifyUpdateFile)) {
1203
+ console.warn(` ${yellow}⚠${reset} Skipped graphify auto-update hook — pp-graphify-update.sh not found at target`);
1204
+ }
1205
+ else if (!hasGraphifyUpdateHook && !graphifyUpdateCommand) {
1206
+ console.warn(` ${yellow}⚠${reset} Skipped graphify auto-update hook — Bash executable path unavailable (#3393)`);
1207
+ }
1208
+ // Configure session state orientation hook (opt-in)
1209
+ const sessionStateCommand = isGlobal
1210
+ ? buildHookCommand(targetDir, 'pp-session-state.sh', hookOpts)
1211
+ : localShellCmd('pp-session-state.sh');
1212
+ const hasSessionStateHook = settings.hooks.SessionStart.some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-session-state')));
1213
+ const sessionStateFile = node_path_1.default.join(targetDir, 'hooks', 'pp-session-state.sh');
1214
+ if (!hasSessionStateHook && node_fs_1.default.existsSync(sessionStateFile) && sessionStateCommand) {
1215
+ settings.hooks.SessionStart.push({
1216
+ hooks: [
1217
+ {
1218
+ type: 'command',
1219
+ command: sessionStateCommand
1220
+ }
1221
+ ]
1222
+ });
1223
+ console.log(` ${green}✓${reset} Configured session state orientation hook (opt-in via config)`);
1224
+ }
1225
+ else if (!hasSessionStateHook && !node_fs_1.default.existsSync(sessionStateFile)) {
1226
+ console.warn(` ${yellow}⚠${reset} Skipped session state hook — pp-session-state.sh not found at target`);
1227
+ }
1228
+ else if (!hasSessionStateHook && !sessionStateCommand) {
1229
+ console.warn(` ${yellow}⚠${reset} Skipped session state hook — Bash executable path unavailable (#3393)`);
1230
+ }
1231
+ // Configure phase boundary detection hook (opt-in)
1232
+ const phaseBoundaryCommand = isGlobal
1233
+ ? buildHookCommand(targetDir, 'pp-phase-boundary.sh', hookOpts)
1234
+ : localShellCmd('pp-phase-boundary.sh');
1235
+ const hasPhaseBoundaryHook = settings.hooks[postToolEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-phase-boundary')));
1236
+ const phaseBoundaryFile = node_path_1.default.join(targetDir, 'hooks', 'pp-phase-boundary.sh');
1237
+ if (!hasPhaseBoundaryHook && node_fs_1.default.existsSync(phaseBoundaryFile) && phaseBoundaryCommand) {
1238
+ settings.hooks[postToolEvent].push({
1239
+ matcher: 'Write|Edit',
1240
+ hooks: [
1241
+ {
1242
+ type: 'command',
1243
+ command: phaseBoundaryCommand,
1244
+ timeout: 5
1245
+ }
1246
+ ]
1247
+ });
1248
+ console.log(` ${green}✓${reset} Configured phase boundary detection hook (opt-in via config)`);
1249
+ }
1250
+ else if (!hasPhaseBoundaryHook && !node_fs_1.default.existsSync(phaseBoundaryFile)) {
1251
+ console.warn(` ${yellow}⚠${reset} Skipped phase boundary hook — pp-phase-boundary.sh not found at target`);
1252
+ }
1253
+ else if (!hasPhaseBoundaryHook && !phaseBoundaryCommand) {
1254
+ console.warn(` ${yellow}⚠${reset} Skipped phase boundary hook — Bash executable path unavailable (#3393)`);
1255
+ }
1256
+ // ── Extended hook events: SubagentStop / Stop / PreCompact (#788 + #770) ──
1257
+ // Claude Code (since #770) and Qwen Code (since #788) both support these
1258
+ // three lifecycle events. Wire pp-context-monitor so agents get context-
1259
+ // headroom warnings at subagent completion, model stop, and pre-compaction
1260
+ // (the most critical moment to surface headroom info).
1261
+ //
1262
+ // SubagentStop — subagent lifecycle completion (context headroom tracking)
1263
+ // Stop — model stop / final-response moment (context headroom)
1264
+ // PreCompact — fires before conversation compaction (most critical
1265
+ // moment to surface context headroom warnings)
1266
+ //
1267
+ // Note: UserPromptSubmit is NOT wired here. That event carries the raw
1268
+ // user prompt text, not a tool invocation, so pp-prompt-guard (which
1269
+ // exits unless tool_name is Write/Edit) would be a silent no-op. A
1270
+ // dedicated handler for UserPromptSubmit is deferred to a follow-on issue.
1271
+ // SubagentStop, Stop, PreCompact — route through the context monitor.
1272
+ // Guard is now descriptor-driven: only events present in extendedEvents are wired.
1273
+ {
1274
+ const runtimeLabel = runtime === 'qwen' ? 'Qwen Code' : runtime === 'claude' ? 'Claude Code' : runtime;
1275
+ for (const event of ['SubagentStop', 'Stop', 'PreCompact']) {
1276
+ if (!extendedEvents.includes(event))
1277
+ continue;
1278
+ if (!settings.hooks[event]) {
1279
+ settings.hooks[event] = [];
1280
+ }
1281
+ const alreadyHasContextMonitor = settings.hooks[event].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-context-monitor')));
1282
+ if (!alreadyHasContextMonitor && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
1283
+ settings.hooks[event].push({
1284
+ hooks: [
1285
+ {
1286
+ type: 'command',
1287
+ command: contextMonitorCommand,
1288
+ timeout: 10
1289
+ }
1290
+ ]
1291
+ });
1292
+ console.log(` ${green}✓${reset} Configured ${event} context monitor hook (${runtimeLabel})`);
1293
+ }
1294
+ else if (!alreadyHasContextMonitor && !node_fs_1.default.existsSync(contextMonitorFile)) {
1295
+ console.warn(` ${yellow}⚠${reset} Skipped ${event} hook — pp-context-monitor.js not found at target`);
1296
+ }
1297
+ }
1298
+ }
1299
+ // ── end SubagentStop / Stop / PreCompact events ────────────────────────────
1300
+ // ── Gemini-only extended hook events (#776) ───────────────────────────────
1301
+ // Gemini CLI exposes several hook events beyond BeforeTool/AfterTool that
1302
+ // gsd previously did not register. Three high-value events are added here:
1303
+ //
1304
+ // BeforeAgent — fires after user submits a prompt, before the agent
1305
+ // plans. Wire pp-context-monitor for context headroom
1306
+ // awareness at prompt time.
1307
+ // AfterAgent — fires once per turn after the model generates its final
1308
+ // response. Wire pp-context-monitor to track headroom
1309
+ // after each agent turn completes.
1310
+ // BeforeModel — fires before each LLM call (per-turn, not per-session).
1311
+ // Wire pp-context-monitor for per-turn context injection
1312
+ // — more precise than session-start-only injection.
1313
+ //
1314
+ // All three reuse pp-context-monitor.js — no new hook files needed.
1315
+ // The `decision:"deny"` retry capability of AfterAgent is intentionally
1316
+ // left to the hook script to implement when triggered (pp-context-monitor
1317
+ // exits 0 / advisory-only today; an active quality gate is a follow-on).
1318
+ //
1319
+ // Note: BeforeToolSelection is NOT wired. That event does not map to a
1320
+ // pp-core hook use case at this time; deferred to a follow-on issue.
1321
+ //
1322
+ // Guard is now descriptor-driven: only events present in extendedEvents are wired.
1323
+ for (const geminiEvent of ['BeforeAgent', 'AfterAgent', 'BeforeModel']) {
1324
+ if (!extendedEvents.includes(geminiEvent))
1325
+ continue;
1326
+ if (!Array.isArray(settings.hooks[geminiEvent])) {
1327
+ settings.hooks[geminiEvent] = [];
1328
+ }
1329
+ const alreadyHasContextMonitor = settings.hooks[geminiEvent].some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-context-monitor')));
1330
+ if (!alreadyHasContextMonitor && node_fs_1.default.existsSync(contextMonitorFile) && contextMonitorCommand) {
1331
+ settings.hooks[geminiEvent].push({
1332
+ hooks: [
1333
+ {
1334
+ type: 'command',
1335
+ command: contextMonitorCommand,
1336
+ timeout: 10
1337
+ }
1338
+ ]
1339
+ });
1340
+ console.log(` ${green}✓${reset} Configured ${geminiEvent} context monitor hook (Gemini)`);
1341
+ }
1342
+ else if (!alreadyHasContextMonitor && !node_fs_1.default.existsSync(contextMonitorFile)) {
1343
+ console.warn(` ${yellow}⚠${reset} Skipped ${geminiEvent} hook — pp-context-monitor.js not found at target`);
1344
+ }
1345
+ }
1346
+ // ── end Gemini-only extended hook events ──────────────────────────────────
1347
+ // ── FileChanged hook: hot-reload gsd config on .planning/config.json edits ─
1348
+ // Claude Code fires FileChanged when a watched file changes on disk. Wire
1349
+ // pp-config-reload.js to reload the gsd config context whenever the user
1350
+ // edits .planning/config.json mid-session, eliminating the need to restart.
1351
+ //
1352
+ // The matcher "config.json" watches for changes to any file named config.json
1353
+ // (Claude Code matches by filename, not full path). The hook exits silently
1354
+ // when the changed file is not the gsd config.
1355
+ //
1356
+ // Scoped to Claude Code only: Qwen Code's FileChanged support is not yet
1357
+ // verified; extend in a follow-on if empirically confirmed.
1358
+ if (extendedEvents.includes('FileChanged')) {
1359
+ if (!settings.hooks.FileChanged) {
1360
+ settings.hooks.FileChanged = [];
1361
+ }
1362
+ const configReloadFile = node_path_1.default.join(targetDir, 'hooks', 'pp-config-reload.js');
1363
+ const alreadyHasConfigReload = settings.hooks.FileChanged.some((entry) => entry.hooks && entry.hooks.some((h) => referencesHook(h, 'pp-config-reload')));
1364
+ if (!alreadyHasConfigReload && node_fs_1.default.existsSync(configReloadFile) && configReloadCommand) {
1365
+ settings.hooks.FileChanged.push({
1366
+ matcher: 'config.json',
1367
+ hooks: [
1368
+ {
1369
+ type: 'command',
1370
+ command: configReloadCommand,
1371
+ timeout: 8
1372
+ }
1373
+ ]
1374
+ });
1375
+ console.log(` ${green}✓${reset} Configured FileChanged config-reload hook (Claude Code)`);
1376
+ }
1377
+ else if (!alreadyHasConfigReload && !node_fs_1.default.existsSync(configReloadFile)) {
1378
+ console.warn(` ${yellow}⚠${reset} Skipped FileChanged hook — pp-config-reload.js not found at target`);
1379
+ }
1380
+ else if (!alreadyHasConfigReload && !configReloadCommand) {
1381
+ console.warn(` ${yellow}⚠${reset} Skipped FileChanged hook — Node executable path unavailable`);
1382
+ }
1383
+ }
1384
+ // ── end FileChanged hook ────────────────────────────────────────────────────
1385
+ }
1386
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access,
1387
+ @typescript-eslint/no-unsafe-call,
1388
+ @typescript-eslint/no-unsafe-assignment */
1389
+ }
1390
+ // ---------------------------------------------------------------------------
1391
+ // referencesHook
1392
+ //
1393
+ // Pure predicate — checks whether a hook entry object references a managed
1394
+ // hook by name. Covers all three registration shapes used by GSD:
1395
+ // • plain command string (standard form)
1396
+ // • args array (command+args / wrapped-launcher form used by windowless
1397
+ // launchers on Windows and some custom PATH-less environments) (#976)
1398
+ // • url field (type:"http" local-server routing form) (#1004)
1399
+ // Without covering all three, an http-form or args-form entry is invisible
1400
+ // and a stock string-command entry is appended on every install/update,
1401
+ // running the hook twice.
1402
+ //
1403
+ // Originally declared inside install()/finishInstall() as a local function;
1404
+ // promoted here so applySettingsJsonHooks() and finishInstall() share one
1405
+ // copy (ADR-857 phase 5f-1b).
1406
+ // ---------------------------------------------------------------------------
1407
+ function referencesHook(h, hookName) {
1408
+ const cmd = h['command'];
1409
+ const args = h['args'];
1410
+ const url = h['url'];
1411
+ return (typeof cmd === 'string' && cmd.includes(hookName)) ||
1412
+ (Array.isArray(args) && args.some(a => typeof a === 'string' && a.includes(hookName))) ||
1413
+ (typeof url === 'string' && url.includes(hookName));
1414
+ }
1415
+ module.exports = {
1416
+ // Cline
1417
+ buildClineRulesBody,
1418
+ buildClineAgentsMdBody,
1419
+ buildClinePreToolUseHook,
1420
+ mergeGsdAgentsMd,
1421
+ writeClineArtifacts,
1422
+ GSD_AGENTS_MD_MARKER,
1423
+ GSD_AGENTS_MD_CLOSE_MARKER,
1424
+ // Cursor
1425
+ buildCursorHookEntry,
1426
+ isManagedCursorHookEntry,
1427
+ reconcileCursorHooksJson,
1428
+ writeCursorHooksJson,
1429
+ removeCursorHooksJson,
1430
+ GSD_CURSOR_SESSION_HOOK_SCRIPT,
1431
+ GSD_CURSOR_POST_TOOL_HOOK_SCRIPT,
1432
+ GSD_CURSOR_HOOK_MARKER,
1433
+ // Copilot
1434
+ buildCopilotHookConfig,
1435
+ writeCopilotHookConfig,
1436
+ GSD_COPILOT_HOOK_FILE,
1437
+ // Codex hooks.json
1438
+ reconcileCodexHooksJsonEvent,
1439
+ reconcileCodexHooksJsonSessionStart,
1440
+ ensureCodexHooksJsonSessionStart,
1441
+ ensureCodexHooksJsonEvent,
1442
+ removeCodexHooksJsonEvent,
1443
+ removeCodexHooksJsonSessionStart,
1444
+ buildCodexHookWindowsShimIR,
1445
+ // Codex TOML
1446
+ buildCodexHookBlock,
1447
+ rewriteLegacyCodexHookBlock,
1448
+ // Shared
1449
+ buildHookCommand,
1450
+ applySettingsJsonHooks,
1451
+ referencesHook,
1452
+ rewriteLegacyManagedNodeHookCommands,
1453
+ normalizeNodePath,
1454
+ resolveNodeRunner,
1455
+ resolveBashRunner,
1456
+ // Atomic write seam (shared with bin/install.js so all writes participate
1457
+ // in install.js's _cleanTmpFiles() scoped temp-cleanup).
1458
+ atomicWriteFileSync,
1459
+ __atomicWrittenTmps,
1460
+ };