@opengsd/gsd-core 1.13.0 → 1.15.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +459 -0
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +360 -0
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +848 -163
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +8 -3
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/review.md +2 -3
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +672 -146
- package/gsd-core/bin/lib/adr-parser.cjs +4 -2
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +119 -34
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +96 -189
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -2
- package/gsd-core/bin/lib/check-command-router.cjs +213 -49
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +823 -112
- package/gsd-core/bin/lib/config-loader.cjs +66 -4
- package/gsd-core/bin/lib/config.cjs +186 -45
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +164 -45
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +614 -86
- package/gsd-core/bin/lib/install-engine.cjs +29 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +37 -13
- package/gsd-core/bin/lib/model-resolver.cjs +253 -53
- package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +268 -27
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +393 -88
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +356 -42
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +181 -30
- package/gsd-core/bin/lib/state.cjs +265 -27
- package/gsd-core/bin/lib/surface.cjs +77 -3
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +9 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +315 -30
- package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
- package/gsd-core/bin/lib/verify.cjs +320 -48
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +13 -4
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +14 -2
- package/gsd-core/references/tdd.md +30 -4
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +433 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +29 -16
- package/gsd-core/workflows/check-todos.md +6 -4
- package/gsd-core/workflows/cleanup.md +5 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +216 -73
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +41 -264
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +17 -158
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +83 -172
- package/gsd-core/workflows/execute-plan.md +24 -10
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +52 -5
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +56 -14
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +39 -209
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +45 -187
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +132 -20
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +29 -10
- package/gsd-core/workflows/reapply-patches.md +86 -6
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +22 -9
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +14 -12
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +101 -196
- package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +71 -19
- package/hooks/dist/gsd-statusline.js +81 -20
- package/hooks/dist/gsd-validate-commit.sh +97 -8
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +66 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +71 -19
- package/hooks/gsd-statusline.js +81 -20
- package/hooks/gsd-validate-commit.sh +97 -8
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +11 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +131 -12
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +35 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +623 -13
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +29 -9
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
- package/scripts/prompt-injection-scan.sh +18 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +8 -3
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review/SKILL.md +2 -3
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -38,7 +38,7 @@ const commonjs_marker_cjs_1 = require("./commonjs-marker.cjs");
|
|
|
38
38
|
const imperative_hook_bus_cjs_1 = require("./host-integration-adapters/imperative-hook-bus.cjs");
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
40
|
const shellCmdProjection = require("./shell-command-projection.cjs");
|
|
41
|
-
const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, escapeTomlDoubleQuotedString, escapePosixDoubleQuoted, } = shellCmdProjection;
|
|
41
|
+
const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, escapeTomlDoubleQuotedString, escapePosixDoubleQuoted, resolveExecutableBinary, } = shellCmdProjection;
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
// Terminal color constants (mirrors install.js for console output parity)
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
@@ -459,9 +459,18 @@ function normalizeNodePath(execPath, opts) {
|
|
|
459
459
|
// survives the upgrade. Derive <prefix> from the path itself (more reliable
|
|
460
460
|
// than HOMEBREW_PREFIX env — the path IS the install location) so every layout
|
|
461
461
|
// is covered by one branch instead of one per known prefix (#2185).
|
|
462
|
+
//
|
|
463
|
+
// #4137: rewrite only when the symlink exists; otherwise fall through to the
|
|
464
|
+
// raw execPath, exactly like the mise and volta branches. A keg-only/versioned
|
|
465
|
+
// formula (node@24 installed but never `brew link`ed) has no <prefix>/bin/node
|
|
466
|
+
// at all, so the unconditional rewrite handed every managed hook a path that
|
|
467
|
+
// fails at invocation — a rewrite must never turn a working keg path into an
|
|
468
|
+
// immediately broken one.
|
|
462
469
|
const homebrewMatch = normalizedForMatch.match(/^(.+)\/Cellar\/node(@\d+)?\/[^/]+\/bin\/node(\.exe)?$/i);
|
|
463
470
|
if (homebrewMatch) {
|
|
464
|
-
|
|
471
|
+
const homebrewStable = `${homebrewMatch[1]}/bin/node${homebrewMatch[3] || ''}`;
|
|
472
|
+
if (existsSync(homebrewStable))
|
|
473
|
+
return homebrewStable;
|
|
465
474
|
}
|
|
466
475
|
// mise pins a concrete node version at <data>/installs/node/<ver>/bin/node
|
|
467
476
|
// (Windows: <data>/installs/node/<ver>/node.exe). Node realpaths
|
|
@@ -567,7 +576,7 @@ function buildBakedNodeToken(opts) {
|
|
|
567
576
|
* guarantee), ahead of `command -v node` and the well-known fallback list.
|
|
568
577
|
*/
|
|
569
578
|
const NODE_RUNNER_RESOLVER_HOOK = 'gsd-node-runner.sh';
|
|
570
|
-
function
|
|
579
|
+
function resolveBashExecutable(opts) {
|
|
571
580
|
const platform = (opts && opts.platform) || process.platform;
|
|
572
581
|
if (platform !== 'win32')
|
|
573
582
|
return 'bash';
|
|
@@ -585,12 +594,19 @@ function resolveBashRunner(opts) {
|
|
|
585
594
|
candidates.push(node_path_1.default.win32.join(env.SystemDrive, 'Program Files (x86)', 'Git', 'bin', 'bash.exe'));
|
|
586
595
|
}
|
|
587
596
|
for (const candidate of candidates) {
|
|
588
|
-
if (candidate && exists(candidate))
|
|
589
|
-
return
|
|
590
|
-
}
|
|
597
|
+
if (candidate && exists(candidate))
|
|
598
|
+
return shellCmdProjection.posixNormalize(candidate);
|
|
591
599
|
}
|
|
592
600
|
return null;
|
|
593
601
|
}
|
|
602
|
+
function resolveBashRunner(opts) {
|
|
603
|
+
const executable = resolveBashExecutable(opts);
|
|
604
|
+
if (executable === null)
|
|
605
|
+
return null;
|
|
606
|
+
return ((opts && opts.platform) || process.platform) === 'win32'
|
|
607
|
+
? JSON.stringify(executable)
|
|
608
|
+
: executable;
|
|
609
|
+
}
|
|
594
610
|
// #3662 — recognize the two runtime-resolving command shapes the installer
|
|
595
611
|
// emits, so the rewriter never churns (or un-does) an entry that already
|
|
596
612
|
// works in every environment sharing the config root.
|
|
@@ -802,16 +818,52 @@ function rewriteLegacyCodexHookBlock(content, absoluteRunner, opts) {
|
|
|
802
818
|
});
|
|
803
819
|
return { content: updated, changed };
|
|
804
820
|
}
|
|
821
|
+
function _installEngineSymlinkGuard() {
|
|
822
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
823
|
+
const mod = require('./install-engine.cjs');
|
|
824
|
+
return mod;
|
|
825
|
+
}
|
|
805
826
|
function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
806
827
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
807
828
|
const managedCommand = typeof opts.managedCommand === 'string' ? opts.managedCommand : null;
|
|
808
829
|
const commandWindows = typeof opts.commandWindows === 'string' ? opts.commandWindows : null;
|
|
809
830
|
const matcher = typeof opts.matcher === 'string' ? opts.matcher : undefined;
|
|
810
831
|
const timeout = typeof opts.timeout === 'number' ? opts.timeout : undefined;
|
|
832
|
+
// #2586 Major 2: every Codex hooks.json writer funnels through this one
|
|
833
|
+
// function, and atomicWriteFileSync's final step is a rename(2) onto
|
|
834
|
+
// `hooksJsonPath` — which, when that path is a symlink, REPLACES the
|
|
835
|
+
// symlink with a plain file rather than writing through it. Refuse (with
|
|
836
|
+
// the same GSD_ALLOW_SYMLINKED_DEST opt-in every other install call site
|
|
837
|
+
// honors) before reading or writing, so a symlinked hooks.json is neither
|
|
838
|
+
// silently destroyed nor left the caller no escape hatch.
|
|
839
|
+
const symlinkGuard = _installEngineSymlinkGuard();
|
|
840
|
+
// The path this function actually reads/writes. Defaults to the nominal
|
|
841
|
+
// hooks.json path; reassigned below to the symlink's real target when the
|
|
842
|
+
// opt-in is active, so the write lands on the file the user's symlink
|
|
843
|
+
// points at instead of clobbering the symlink itself (see note below).
|
|
844
|
+
let effectiveHooksJsonPath = hooksJsonPath;
|
|
845
|
+
if (node_fs_1.default.existsSync(hooksJsonPath) && node_fs_1.default.lstatSync(hooksJsonPath).isSymbolicLink()) {
|
|
846
|
+
if (symlinkGuard.hasExistingSymlinkBetween(targetDir, hooksJsonPath, {
|
|
847
|
+
allowOptInFollow: symlinkGuard.isSymlinkedDestOptIn(),
|
|
848
|
+
})) {
|
|
849
|
+
throw new Error(`hooks.json at "${hooksJsonPath}" contains a symlink the install root "${targetDir}" does not trust — ` +
|
|
850
|
+
'refusing to read or write it. If this is an intentional user-owned symlink layout, re-run with ' +
|
|
851
|
+
'GSD_ALLOW_SYMLINKED_DEST=1.');
|
|
852
|
+
}
|
|
853
|
+
// hasExistingSymlinkBetween returned false only because the opt-in is
|
|
854
|
+
// active (a symlinked leaf always trips it otherwise) — so this IS a
|
|
855
|
+
// symlink and we are cleared to follow it. atomicWriteFileSync's final
|
|
856
|
+
// step is a rename(2) onto its target, which REPLACES an existing
|
|
857
|
+
// symlink at that path rather than writing through it; resolving to the
|
|
858
|
+
// real path here makes the read AND the write operate on the symlink's
|
|
859
|
+
// target, leaving the symlink itself untouched, matching what "follow"
|
|
860
|
+
// is supposed to mean.
|
|
861
|
+
effectiveHooksJsonPath = node_fs_1.default.realpathSync(hooksJsonPath);
|
|
862
|
+
}
|
|
811
863
|
let parsed = {};
|
|
812
864
|
let currentContent = null;
|
|
813
|
-
if (node_fs_1.default.existsSync(
|
|
814
|
-
const raw = node_fs_1.default.readFileSync(
|
|
865
|
+
if (node_fs_1.default.existsSync(effectiveHooksJsonPath)) {
|
|
866
|
+
const raw = node_fs_1.default.readFileSync(effectiveHooksJsonPath, 'utf8');
|
|
815
867
|
currentContent = raw;
|
|
816
868
|
if (raw.trim()) {
|
|
817
869
|
try {
|
|
@@ -845,6 +897,12 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
845
897
|
}
|
|
846
898
|
parsed['hooks'] = hookTable;
|
|
847
899
|
const eventEntries = Array.isArray(hookTable[eventName]) ? hookTable[eventName] : [];
|
|
900
|
+
// Minor 5 (#2586 review): an event key the user already had, already
|
|
901
|
+
// holding an empty array, must survive removal as an empty array — not be
|
|
902
|
+
// deleted outright. Deleting is only correct when OUR removal is what
|
|
903
|
+
// emptied a previously non-empty array. Tracked before the loop below can
|
|
904
|
+
// mutate anything.
|
|
905
|
+
const wasArrayEmpty = Array.isArray(hookTable[eventName]) && eventEntries.length === 0;
|
|
848
906
|
let removedLegacy = false;
|
|
849
907
|
const sanitizedEntries = [];
|
|
850
908
|
for (const entry of eventEntries) {
|
|
@@ -886,6 +944,11 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
886
944
|
if (sanitizedEntries.length > 0) {
|
|
887
945
|
hookTable[eventName] = sanitizedEntries;
|
|
888
946
|
}
|
|
947
|
+
else if (wasArrayEmpty) {
|
|
948
|
+
// Nothing of ours was ever here to remove — preserve the user's own
|
|
949
|
+
// empty array exactly as found (Minor 5).
|
|
950
|
+
hookTable[eventName] = [];
|
|
951
|
+
}
|
|
889
952
|
else {
|
|
890
953
|
delete hookTable[eventName];
|
|
891
954
|
}
|
|
@@ -898,23 +961,25 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
898
961
|
const changed = currentContent !== nextContent;
|
|
899
962
|
const shouldWrite = changed && (currentContent !== null || Object.keys(parsed).length > 0);
|
|
900
963
|
if (shouldWrite) {
|
|
901
|
-
atomicWriteFileSync(
|
|
964
|
+
atomicWriteFileSync(effectiveHooksJsonPath, nextContent, 'utf8');
|
|
902
965
|
}
|
|
903
966
|
return { changed: changed || removedLegacy, wrote: shouldWrite, path: hooksJsonPath };
|
|
904
967
|
}
|
|
905
968
|
function reconcileCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
906
969
|
return reconcileCodexHooksJsonEvent(targetDir, 'SessionStart', opts);
|
|
907
970
|
}
|
|
908
|
-
function
|
|
909
|
-
if (!absoluteRunnerToken)
|
|
910
|
-
return null;
|
|
911
|
-
let interpreter;
|
|
971
|
+
function parseAbsoluteRunnerToken(absoluteRunnerToken) {
|
|
912
972
|
try {
|
|
913
|
-
|
|
973
|
+
return JSON.parse(absoluteRunnerToken);
|
|
914
974
|
}
|
|
915
975
|
catch {
|
|
916
|
-
|
|
976
|
+
return absoluteRunnerToken;
|
|
917
977
|
}
|
|
978
|
+
}
|
|
979
|
+
function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
|
|
980
|
+
if (!absoluteRunnerToken)
|
|
981
|
+
return null;
|
|
982
|
+
const interpreter = parseAbsoluteRunnerToken(absoluteRunnerToken);
|
|
918
983
|
const targetAbs = shellCmdProjection.posixNormalize(scriptAbsPath);
|
|
919
984
|
const scriptQuoted = JSON.stringify(targetAbs);
|
|
920
985
|
const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
|
|
@@ -939,6 +1004,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
939
1004
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
940
1005
|
const scriptPath = shellCmdProjection.posixNormalize(node_path_1.default.resolve(targetDir, 'hooks', 'gsd-check-update.js'));
|
|
941
1006
|
const cmdShimPath = scriptPath.replace(/\.js$/, '.cmd');
|
|
1007
|
+
const configuredEntrypoints = [];
|
|
942
1008
|
let managedCommand;
|
|
943
1009
|
if (platform === 'win32') {
|
|
944
1010
|
const shimIR = buildCodexHookWindowsShimIR(scriptPath, absoluteRunner);
|
|
@@ -955,6 +1021,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
955
1021
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
956
1022
|
}
|
|
957
1023
|
managedCommand = shimIR.hookCommand;
|
|
1024
|
+
configuredEntrypoints.push({ runtime: 'codex', configPath: hooksJsonPath, scriptPath: shimIR.cmdPath, platform, selfExecutable: true }, { runtime: 'codex', configPath: hooksJsonPath, scriptPath, interpreterCandidates: [parseAbsoluteRunnerToken(absoluteRunner)], platform });
|
|
958
1025
|
}
|
|
959
1026
|
else {
|
|
960
1027
|
managedCommand = projectManagedHookCommand({
|
|
@@ -963,13 +1030,23 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
963
1030
|
runtime: 'codex',
|
|
964
1031
|
platform,
|
|
965
1032
|
}) ?? undefined;
|
|
1033
|
+
if (managedCommand) {
|
|
1034
|
+
configuredEntrypoints.push({
|
|
1035
|
+
runtime: 'codex',
|
|
1036
|
+
configPath: hooksJsonPath,
|
|
1037
|
+
scriptPath,
|
|
1038
|
+
interpreterCandidates: [parseAbsoluteRunnerToken(absoluteRunner)],
|
|
1039
|
+
platform,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
966
1042
|
}
|
|
967
1043
|
if (!managedCommand)
|
|
968
1044
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
969
1045
|
const commandWindows = platform === 'win32'
|
|
970
1046
|
? JSON.stringify(shellCmdProjection.posixNormalize(cmdShimPath))
|
|
971
1047
|
: undefined;
|
|
972
|
-
|
|
1048
|
+
const result = reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand, commandWindows });
|
|
1049
|
+
return { ...result, configuredEntrypoints };
|
|
973
1050
|
}
|
|
974
1051
|
function ensureCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
975
1052
|
const platform = opts.platform || process.platform;
|
|
@@ -1015,6 +1092,186 @@ function removeCodexHooksJsonEvent(targetDir, eventName) {
|
|
|
1015
1092
|
function removeCodexHooksJsonSessionStart(targetDir) {
|
|
1016
1093
|
return reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand: null });
|
|
1017
1094
|
}
|
|
1095
|
+
// Literal, version-stable markers every shipped gsd-context-monitor.js
|
|
1096
|
+
// carries. Stable across the {{GSD_VERSION}} and runtime-path substitutions
|
|
1097
|
+
// the Codex copy step applies (#2586 design doc "Ownership check" — a raw
|
|
1098
|
+
// content hash would differ per runtime/version by construction, so a marker
|
|
1099
|
+
// check is used instead of manifest-membership, which has a bootstrap gap on
|
|
1100
|
+
// the exact case that matters most: a pre-#2586 install's manifest never
|
|
1101
|
+
// recorded this file at all).
|
|
1102
|
+
const CODEX_CONTEXT_MONITOR_OWNERSHIP_MARKERS = [
|
|
1103
|
+
'#!/usr/bin/env node',
|
|
1104
|
+
'// gsd-hook-version:',
|
|
1105
|
+
'// Context Monitor - PostToolUse/AfterTool hook',
|
|
1106
|
+
];
|
|
1107
|
+
function isGsdOwnedCodexContextMonitorScript(filePath) {
|
|
1108
|
+
let content;
|
|
1109
|
+
try {
|
|
1110
|
+
content = node_fs_1.default.readFileSync(filePath, 'utf8');
|
|
1111
|
+
}
|
|
1112
|
+
catch {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
// The .cmd shim (buildCodexHookWindowsShimIR) is a tiny generated batch
|
|
1116
|
+
// wrapper, not the JS file itself — it never carries the JS markers above,
|
|
1117
|
+
// so it gets its own narrower, still-specific signature: the exact
|
|
1118
|
+
// "@ECHO OFF" / "@SETLOCAL" preamble the shim generator emits, invoking a
|
|
1119
|
+
// script path that ends in gsd-context-monitor.js.
|
|
1120
|
+
if (filePath.endsWith('.cmd')) {
|
|
1121
|
+
return content.startsWith('@ECHO OFF') && content.includes('@SETLOCAL')
|
|
1122
|
+
&& /gsd-context-monitor\.js/.test(content);
|
|
1123
|
+
}
|
|
1124
|
+
return CODEX_CONTEXT_MONITOR_OWNERSHIP_MARKERS.every((marker) => content.includes(marker));
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Scan every event in hooks.json for a surviving reference to the
|
|
1128
|
+
* context-monitor script or its Windows .cmd shim, by basename — not scoped
|
|
1129
|
+
* to CODEX_EXTENDED_HOOK_EVENTS, so a user who hand-registered it under an
|
|
1130
|
+
* unrelated event key is still detected as "referenced" and the script is
|
|
1131
|
+
* preserved.
|
|
1132
|
+
*/
|
|
1133
|
+
function hooksJsonReferencesCodexContextMonitor(targetDir) {
|
|
1134
|
+
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
1135
|
+
if (!node_fs_1.default.existsSync(hooksJsonPath))
|
|
1136
|
+
return false;
|
|
1137
|
+
let raw;
|
|
1138
|
+
try {
|
|
1139
|
+
raw = node_fs_1.default.readFileSync(hooksJsonPath, 'utf8');
|
|
1140
|
+
}
|
|
1141
|
+
catch {
|
|
1142
|
+
return true; // unreadable — conservatively assume referenced, never delete
|
|
1143
|
+
}
|
|
1144
|
+
if (!raw.trim())
|
|
1145
|
+
return false;
|
|
1146
|
+
let parsed;
|
|
1147
|
+
try {
|
|
1148
|
+
parsed = JSON.parse(raw);
|
|
1149
|
+
}
|
|
1150
|
+
catch {
|
|
1151
|
+
return true; // unparseable — conservatively assume referenced
|
|
1152
|
+
}
|
|
1153
|
+
if (!parsed || typeof parsed !== 'object')
|
|
1154
|
+
return false;
|
|
1155
|
+
const hooks = parsed['hooks'];
|
|
1156
|
+
const table = hooks && typeof hooks === 'object' && !Array.isArray(hooks)
|
|
1157
|
+
? hooks
|
|
1158
|
+
: parsed;
|
|
1159
|
+
for (const key of Object.keys(table)) {
|
|
1160
|
+
const entries = table[key];
|
|
1161
|
+
if (!Array.isArray(entries))
|
|
1162
|
+
continue;
|
|
1163
|
+
for (const entry of entries) {
|
|
1164
|
+
if (!entry || typeof entry !== 'object')
|
|
1165
|
+
continue;
|
|
1166
|
+
const entryHooks = entry['hooks'];
|
|
1167
|
+
const hookList = Array.isArray(entryHooks) ? entryHooks : [entry];
|
|
1168
|
+
for (const hook of hookList) {
|
|
1169
|
+
if (!hook || typeof hook !== 'object')
|
|
1170
|
+
continue;
|
|
1171
|
+
const values = [
|
|
1172
|
+
hook['command'],
|
|
1173
|
+
hook['commandWindows'],
|
|
1174
|
+
];
|
|
1175
|
+
for (const value of values) {
|
|
1176
|
+
if (typeof value === 'string' && /gsd-context-monitor(\.js|\.cmd)?/.test(value)) {
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return false;
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* #2586 must-have #4/#8: after hooks.json registrations for
|
|
1187
|
+
* CODEX_EXTENDED_HOOK_EVENTS have been reconciled away (by the caller, via
|
|
1188
|
+
* removeCodexHooksJsonEvent), delete `hooks/gsd-context-monitor.js` and its
|
|
1189
|
+
* `.cmd` shim ONLY when (a) no surviving hooks.json registration under ANY
|
|
1190
|
+
* event still references either basename, and (b) the on-disk file carries
|
|
1191
|
+
* GSD's own ownership markers (a user's hand-edited or unrelated file at that
|
|
1192
|
+
* path is left alone). Each file is deleted independently — a failure
|
|
1193
|
+
* deleting one is reported as a warning and never rolls back the (already
|
|
1194
|
+
* safe, already-written) hooks.json deregistration the caller performed
|
|
1195
|
+
* first.
|
|
1196
|
+
*/
|
|
1197
|
+
function cleanupOrphanedCodexContextMonitorScript(targetDir) {
|
|
1198
|
+
const result = { deleted: [], warnings: [], stillReferenced: false };
|
|
1199
|
+
if (hooksJsonReferencesCodexContextMonitor(targetDir)) {
|
|
1200
|
+
result.stillReferenced = true;
|
|
1201
|
+
return result;
|
|
1202
|
+
}
|
|
1203
|
+
const candidates = [
|
|
1204
|
+
node_path_1.default.join(targetDir, 'hooks', 'gsd-context-monitor.js'),
|
|
1205
|
+
node_path_1.default.join(targetDir, 'hooks', 'gsd-context-monitor.cmd'),
|
|
1206
|
+
];
|
|
1207
|
+
for (const candidate of candidates) {
|
|
1208
|
+
if (!node_fs_1.default.existsSync(candidate))
|
|
1209
|
+
continue;
|
|
1210
|
+
if (!isGsdOwnedCodexContextMonitorScript(candidate))
|
|
1211
|
+
continue;
|
|
1212
|
+
try {
|
|
1213
|
+
node_fs_1.default.unlinkSync(candidate);
|
|
1214
|
+
result.deleted.push(candidate);
|
|
1215
|
+
}
|
|
1216
|
+
catch (err) {
|
|
1217
|
+
result.warnings.push({
|
|
1218
|
+
path: candidate,
|
|
1219
|
+
reason: err && err.message ? err.message : String(err),
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
function configuredEntrypointsForHook(configDir, hookName, opts) {
|
|
1226
|
+
const platform = opts.platform || process.platform;
|
|
1227
|
+
const runtime = opts.runtime || 'generic';
|
|
1228
|
+
const configPath = opts.configPath || configDir;
|
|
1229
|
+
const target = {
|
|
1230
|
+
runtime,
|
|
1231
|
+
configPath,
|
|
1232
|
+
scriptPath: node_path_1.default.join(configDir, 'hooks', hookName),
|
|
1233
|
+
platform,
|
|
1234
|
+
};
|
|
1235
|
+
const isShellHook = hookName.endsWith('.sh');
|
|
1236
|
+
if (shellHookOmitsBashRunner({ platform, runtime, isShellHook }))
|
|
1237
|
+
return [{ ...target, selfExecutable: true }];
|
|
1238
|
+
const bash = resolveBashExecutable(opts);
|
|
1239
|
+
if (isShellHook) {
|
|
1240
|
+
// An unresolved bash must still surface as an interpreterCandidates entry
|
|
1241
|
+
// (the literal token, same as the portableHooks runner below) so
|
|
1242
|
+
// validateConfiguredEntrypoints reports 'unresolved-interpreter' instead
|
|
1243
|
+
// of silently skipping the check because the field is absent.
|
|
1244
|
+
return [{ ...target, interpreterCandidates: [bash === null ? 'bash' : bash] }];
|
|
1245
|
+
}
|
|
1246
|
+
// #4249: check the SAME stable alias buildNodeRunnerChainToken bakes as its
|
|
1247
|
+
// first candidate (normalizeNodePath rewrites a version-manager shim like
|
|
1248
|
+
// fnm/nvm/mise/volta into its persistent path), not the raw, currently-
|
|
1249
|
+
// running process.execPath — which always trivially resolves regardless of
|
|
1250
|
+
// whether the alias actually baked into the persisted command still does.
|
|
1251
|
+
const nodeCandidates = [
|
|
1252
|
+
normalizeNodePath(opts.execPath || process.execPath, opts),
|
|
1253
|
+
'node',
|
|
1254
|
+
'/usr/local/bin/node',
|
|
1255
|
+
'/usr/bin/node',
|
|
1256
|
+
].filter((candidate) => Boolean(candidate));
|
|
1257
|
+
if (!opts.portableHooks) {
|
|
1258
|
+
return [{ ...target, interpreterCandidates: nodeCandidates }];
|
|
1259
|
+
}
|
|
1260
|
+
const runner = {
|
|
1261
|
+
runtime,
|
|
1262
|
+
configPath,
|
|
1263
|
+
scriptPath: node_path_1.default.join(configDir, 'hooks', NODE_RUNNER_RESOLVER_HOOK),
|
|
1264
|
+
interpreterCandidates: bash === null ? ['bash'] : [bash],
|
|
1265
|
+
platform,
|
|
1266
|
+
};
|
|
1267
|
+
return [runner, { ...target, interpreterCandidates: nodeCandidates }];
|
|
1268
|
+
}
|
|
1269
|
+
function recordConfiguredHookCommand(command, configDir, hookName, opts) {
|
|
1270
|
+
if (command && opts.configuredEntrypoints) {
|
|
1271
|
+
opts.configuredEntrypoints.push(...configuredEntrypointsForHook(configDir, hookName, opts).map(entry => ({ ...entry, command })));
|
|
1272
|
+
}
|
|
1273
|
+
return command;
|
|
1274
|
+
}
|
|
1018
1275
|
function buildHookCommand(configDir, hookName, opts) {
|
|
1019
1276
|
if (!opts)
|
|
1020
1277
|
opts = {};
|
|
@@ -1022,44 +1279,56 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
1022
1279
|
const runtime = opts.runtime || 'generic';
|
|
1023
1280
|
const hookShell = opts.hookShell;
|
|
1024
1281
|
const isShellHook = hookName.endsWith('.sh');
|
|
1282
|
+
const track = (command) => recordConfiguredHookCommand(command, configDir, hookName, opts);
|
|
1025
1283
|
if (shellHookOmitsBashRunner({ platform, runtime, isShellHook })) {
|
|
1026
1284
|
if (opts.portableHooks) {
|
|
1027
1285
|
const portableBaseDir = projectPortableHookBaseDir({
|
|
1028
1286
|
configDir,
|
|
1029
1287
|
homeDir: node_os_1.default.homedir(),
|
|
1030
1288
|
});
|
|
1031
|
-
return JSON.stringify(`${portableBaseDir}/hooks/${hookName}`);
|
|
1289
|
+
return track(JSON.stringify(`${portableBaseDir}/hooks/${hookName}`));
|
|
1032
1290
|
}
|
|
1033
|
-
return JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName);
|
|
1291
|
+
return track(JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName));
|
|
1034
1292
|
}
|
|
1035
1293
|
// .sh hooks keep the pre-#3662 shape everywhere: the bash runner resolves
|
|
1036
1294
|
// at install time like today, and `bash` itself is a PATH-stable binary
|
|
1037
1295
|
// (the absolute Git-Bash discovery covers win32 — #580/#3393).
|
|
1038
1296
|
if (isShellHook) {
|
|
1039
1297
|
const runner = resolveBashRunner(opts);
|
|
1040
|
-
if (runner === null)
|
|
1298
|
+
if (runner === null) {
|
|
1299
|
+
// #4249 (antigravity review): this early return skips `track()` below,
|
|
1300
|
+
// so an unresolved bash on win32 (no Git Bash found) previously left
|
|
1301
|
+
// this hook silently unregistered with nothing for
|
|
1302
|
+
// validateConfiguredEntrypoints to reject — configuredEntrypointsForHook's
|
|
1303
|
+
// own 'unresolved bash must still surface' comment describes intent this
|
|
1304
|
+
// return never reached. Push the entry directly (no `command`, since
|
|
1305
|
+
// none was ever built) so the gate actually sees it.
|
|
1306
|
+
if (opts.configuredEntrypoints) {
|
|
1307
|
+
opts.configuredEntrypoints.push(...configuredEntrypointsForHook(configDir, hookName, opts));
|
|
1308
|
+
}
|
|
1041
1309
|
return null;
|
|
1310
|
+
}
|
|
1042
1311
|
if (opts.portableHooks) {
|
|
1043
1312
|
const portableBaseDir = projectPortableHookBaseDir({
|
|
1044
1313
|
configDir,
|
|
1045
1314
|
homeDir: node_os_1.default.homedir(),
|
|
1046
1315
|
});
|
|
1047
|
-
return projectManagedHookCommand({
|
|
1316
|
+
return track(projectManagedHookCommand({
|
|
1048
1317
|
absoluteRunner: runner,
|
|
1049
1318
|
scriptPath: `${portableBaseDir}/hooks/${hookName}`,
|
|
1050
1319
|
runtime: opts.runtime || 'generic',
|
|
1051
1320
|
platform,
|
|
1052
1321
|
hookShell,
|
|
1053
|
-
});
|
|
1322
|
+
}));
|
|
1054
1323
|
}
|
|
1055
1324
|
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
|
1056
|
-
return projectManagedHookCommand({
|
|
1325
|
+
return track(projectManagedHookCommand({
|
|
1057
1326
|
absoluteRunner: runner,
|
|
1058
1327
|
scriptPath: hooksPath,
|
|
1059
1328
|
runtime,
|
|
1060
1329
|
platform,
|
|
1061
1330
|
hookShell,
|
|
1062
|
-
});
|
|
1331
|
+
}));
|
|
1063
1332
|
}
|
|
1064
1333
|
// JS hooks (#3662): the node runner is resolved at hook-fire time, never
|
|
1065
1334
|
// baked as a bare absolute path — an install-environment absolute path is
|
|
@@ -1082,7 +1351,7 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
1082
1351
|
// Absolute Git-Bash discovery on win32 when available (#580); `bash` on
|
|
1083
1352
|
// PATH otherwise — the same assumption .sh hooks already make.
|
|
1084
1353
|
const resolverRunner = resolveBashRunner(opts) || 'bash';
|
|
1085
|
-
return shellCmdProjection.projectShellCommandText({
|
|
1354
|
+
return track(shellCmdProjection.projectShellCommandText({
|
|
1086
1355
|
runnerToken: resolverRunner,
|
|
1087
1356
|
argTokens: [
|
|
1088
1357
|
JSON.stringify(`${portableBaseDir}/hooks/${NODE_RUNNER_RESOLVER_HOOK}`),
|
|
@@ -1092,19 +1361,19 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
1092
1361
|
runtime,
|
|
1093
1362
|
platform,
|
|
1094
1363
|
hookShell,
|
|
1095
|
-
});
|
|
1364
|
+
}));
|
|
1096
1365
|
}
|
|
1097
1366
|
const chainRunner = buildNodeRunnerChainToken(opts);
|
|
1098
1367
|
if (chainRunner === null)
|
|
1099
1368
|
return null;
|
|
1100
1369
|
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
|
1101
|
-
return shellCmdProjection.projectShellCommandText({
|
|
1370
|
+
return track(shellCmdProjection.projectShellCommandText({
|
|
1102
1371
|
runnerToken: chainRunner,
|
|
1103
1372
|
argTokens: [JSON.stringify(hooksPath)],
|
|
1104
1373
|
runtime,
|
|
1105
1374
|
platform,
|
|
1106
1375
|
hookShell,
|
|
1107
|
-
});
|
|
1376
|
+
}));
|
|
1108
1377
|
}
|
|
1109
1378
|
// ---------------------------------------------------------------------------
|
|
1110
1379
|
// Cline helpers
|
|
@@ -1205,6 +1474,7 @@ function mergeGsdAgentsMd(filePath, gsdContent) {
|
|
|
1205
1474
|
// ---------------------------------------------------------------------------
|
|
1206
1475
|
function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
1207
1476
|
const written = [];
|
|
1477
|
+
const configuredEntrypoints = [];
|
|
1208
1478
|
const clinerulesDir = node_path_1.default.join(targetDir, '.clinerules');
|
|
1209
1479
|
try {
|
|
1210
1480
|
if (node_fs_1.default.existsSync(clinerulesDir)) {
|
|
@@ -1230,6 +1500,20 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
|
1230
1500
|
catch { /* Windows: hooks unsupported anyway */ }
|
|
1231
1501
|
written.push('.clinerules/hooks/PreToolUse');
|
|
1232
1502
|
console.log(` ${green}✓${reset} Wrote .clinerules/hooks/PreToolUse`);
|
|
1503
|
+
// #4249 (CodeRabbit): Cline invokes this file directly via its own
|
|
1504
|
+
// `#!/usr/bin/env node` shebang — a hybrid case. The script itself still
|
|
1505
|
+
// needs the execute bit (selfExecutable), but unlike GSD's other JS hooks
|
|
1506
|
+
// (which bake an absolute, install-time-resolved node path specifically to
|
|
1507
|
+
// avoid this) its interpreter is looked up on PATH by `env` at hook-fire
|
|
1508
|
+
// time, so `node` must also resolve or the hook can never run.
|
|
1509
|
+
configuredEntrypoints.push({
|
|
1510
|
+
runtime: 'cline',
|
|
1511
|
+
configPath: hookPath,
|
|
1512
|
+
scriptPath: hookPath,
|
|
1513
|
+
interpreterCandidates: ['node'],
|
|
1514
|
+
selfExecutable: true,
|
|
1515
|
+
platform: process.platform,
|
|
1516
|
+
});
|
|
1233
1517
|
if (isGlobalInstall) {
|
|
1234
1518
|
try {
|
|
1235
1519
|
const agentsPath = node_path_1.default.join(node_os_1.default.homedir(), '.agents', 'AGENTS.md');
|
|
@@ -1240,7 +1524,7 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
|
1240
1524
|
console.warn(` ${yellow}⚠${reset} Could not write ~/.agents/AGENTS.md: ${err.message}`);
|
|
1241
1525
|
}
|
|
1242
1526
|
}
|
|
1243
|
-
return written;
|
|
1527
|
+
return { written, configuredEntrypoints };
|
|
1244
1528
|
}
|
|
1245
1529
|
// ---------------------------------------------------------------------------
|
|
1246
1530
|
// Cursor hook functions
|
|
@@ -1491,7 +1775,13 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
1491
1775
|
if (installedScripts.size > 0) {
|
|
1492
1776
|
ensureCommonJsMarker(hooksDir);
|
|
1493
1777
|
}
|
|
1494
|
-
const
|
|
1778
|
+
const configuredEntrypoints = [];
|
|
1779
|
+
const hookOpts = {
|
|
1780
|
+
runtime: 'cursor',
|
|
1781
|
+
platform: opts.platform || process.platform,
|
|
1782
|
+
configPath: node_path_1.default.join(targetDir, 'hooks.json'),
|
|
1783
|
+
configuredEntrypoints,
|
|
1784
|
+
};
|
|
1495
1785
|
const commands = {};
|
|
1496
1786
|
for (const ev of events) {
|
|
1497
1787
|
const script = imperative_hook_bus_cjs_1.CURSOR_EVENT_SCRIPT_MAP[ev];
|
|
@@ -1505,7 +1795,7 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
1505
1795
|
const managedEntries = (0, imperative_hook_bus_cjs_1.buildHookBusEntries)(events, commands);
|
|
1506
1796
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
1507
1797
|
const result = reconcileCursorHooksJson(hooksJsonPath, managedEntries);
|
|
1508
|
-
return { hooksJsonPath, changed: result.changed };
|
|
1798
|
+
return { hooksJsonPath, changed: result.changed, configuredEntrypoints };
|
|
1509
1799
|
}
|
|
1510
1800
|
function removeCursorHooksJson(targetDir) {
|
|
1511
1801
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
@@ -1710,7 +2000,13 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
|
|
|
1710
2000
|
if (installedScripts.size > 0) {
|
|
1711
2001
|
ensureCommonJsMarker(hooksDir);
|
|
1712
2002
|
}
|
|
1713
|
-
const
|
|
2003
|
+
const configuredEntrypoints = [];
|
|
2004
|
+
const hookOpts = {
|
|
2005
|
+
runtime: 'windsurf',
|
|
2006
|
+
platform: opts.platform || process.platform,
|
|
2007
|
+
configPath: node_path_1.default.join(targetDir, 'hooks.json'),
|
|
2008
|
+
configuredEntrypoints,
|
|
2009
|
+
};
|
|
1714
2010
|
const commands = {};
|
|
1715
2011
|
for (const ev of WINDSURF_HOOK_EVENTS) {
|
|
1716
2012
|
const script = WINDSURF_EVENT_SCRIPT_MAP[ev];
|
|
@@ -1724,7 +2020,7 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
|
|
|
1724
2020
|
}
|
|
1725
2021
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
1726
2022
|
const result = reconcileWindsurfHooksJson(hooksJsonPath, managedEntries);
|
|
1727
|
-
return { hooksJsonPath, changed: result.changed };
|
|
2023
|
+
return { hooksJsonPath, changed: result.changed, configuredEntrypoints };
|
|
1728
2024
|
}
|
|
1729
2025
|
/**
|
|
1730
2026
|
* Remove all GSD-managed Cascade hook entries from hooks.json. User-owned
|
|
@@ -2575,13 +2871,21 @@ function stripKimiHooksTomlBlock(content) {
|
|
|
2575
2871
|
* touch the file's mtime for no reason.
|
|
2576
2872
|
*/
|
|
2577
2873
|
function writeKimiHooksToml(configPath, targetDir, opts) {
|
|
2874
|
+
const configuredEntrypoints = [];
|
|
2875
|
+
const trackedOpts = {
|
|
2876
|
+
hookOpts: {
|
|
2877
|
+
...opts.hookOpts,
|
|
2878
|
+
configPath,
|
|
2879
|
+
configuredEntrypoints,
|
|
2880
|
+
},
|
|
2881
|
+
};
|
|
2578
2882
|
const existing = node_fs_1.default.existsSync(configPath) ? node_fs_1.default.readFileSync(configPath, 'utf8') : '';
|
|
2579
2883
|
const stripped = stripKimiHooksTomlBlock(existing) ?? '';
|
|
2580
|
-
const block = buildKimiHooksTomlBlock(targetDir,
|
|
2884
|
+
const block = buildKimiHooksTomlBlock(targetDir, trackedOpts);
|
|
2581
2885
|
const entryCount = block ? (block.match(/\[\[hooks\]\]/g) || []).length : 0;
|
|
2582
2886
|
if (!block) {
|
|
2583
2887
|
if (stripped === existing)
|
|
2584
|
-
return { changed: false, path: configPath, entryCount: 0 };
|
|
2888
|
+
return { changed: false, path: configPath, entryCount: 0, configuredEntrypoints };
|
|
2585
2889
|
if (stripped.trim() === '') {
|
|
2586
2890
|
if (node_fs_1.default.existsSync(configPath))
|
|
2587
2891
|
node_fs_1.default.unlinkSync(configPath);
|
|
@@ -2590,15 +2894,15 @@ function writeKimiHooksToml(configPath, targetDir, opts) {
|
|
|
2590
2894
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
|
|
2591
2895
|
atomicWriteFileSync(configPath, stripped, 'utf8');
|
|
2592
2896
|
}
|
|
2593
|
-
return { changed: true, path: configPath, entryCount: 0 };
|
|
2897
|
+
return { changed: true, path: configPath, entryCount: 0, configuredEntrypoints };
|
|
2594
2898
|
}
|
|
2595
2899
|
const separator = stripped.trim() === '' ? '' : (stripped.endsWith('\n') ? '\n' : '\n\n');
|
|
2596
2900
|
const next = stripped.trim() === '' ? `${block}\n` : `${stripped}${separator}${block}\n`;
|
|
2597
2901
|
if (next === existing)
|
|
2598
|
-
return { changed: false, path: configPath, entryCount };
|
|
2902
|
+
return { changed: false, path: configPath, entryCount, configuredEntrypoints };
|
|
2599
2903
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
|
|
2600
2904
|
atomicWriteFileSync(configPath, next, 'utf8');
|
|
2601
|
-
return { changed: true, path: configPath, entryCount };
|
|
2905
|
+
return { changed: true, path: configPath, entryCount, configuredEntrypoints };
|
|
2602
2906
|
}
|
|
2603
2907
|
/**
|
|
2604
2908
|
* Uninstall-time counterpart to writeKimiHooksToml: strips the GSD block and
|
|
@@ -2644,6 +2948,68 @@ function referencesHook(h, hookName) {
|
|
|
2644
2948
|
(Array.isArray(args) && args.some(a => typeof a === 'string' && a.includes(hookName))) ||
|
|
2645
2949
|
(typeof url === 'string' && url.includes(hookName));
|
|
2646
2950
|
}
|
|
2951
|
+
function validateConfiguredEntrypoints(entries, deps = {}) {
|
|
2952
|
+
const statSync = deps.statSync ?? node_fs_1.default.statSync;
|
|
2953
|
+
const accessSync = deps.accessSync ?? node_fs_1.default.accessSync;
|
|
2954
|
+
const resolve = deps.resolveExecutableBinary ?? resolveExecutableBinary;
|
|
2955
|
+
const invalid = [];
|
|
2956
|
+
for (const entry of entries) {
|
|
2957
|
+
let scriptOk = false;
|
|
2958
|
+
try {
|
|
2959
|
+
scriptOk = statSync(entry.scriptPath).isFile();
|
|
2960
|
+
if (!scriptOk) {
|
|
2961
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'wrong-file-type' });
|
|
2962
|
+
}
|
|
2963
|
+
else {
|
|
2964
|
+
// #4249: statSync only needs search permission on the parent dirs, so
|
|
2965
|
+
// it succeeds even for a chmod-000 file — the EACCES catch below
|
|
2966
|
+
// never fires for that case. Read permission on the file itself must
|
|
2967
|
+
// be checked explicitly: an interpreter opens the script directly,
|
|
2968
|
+
// and even a self-executable shebang script is opened and read by
|
|
2969
|
+
// its kernel-invoked interpreter, not just exec'd — X_OK alone does
|
|
2970
|
+
// not prove it's readable.
|
|
2971
|
+
try {
|
|
2972
|
+
accessSync(entry.scriptPath, node_fs_1.default.constants.R_OK);
|
|
2973
|
+
}
|
|
2974
|
+
catch {
|
|
2975
|
+
scriptOk = false;
|
|
2976
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'unreadable' });
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
catch (statErr) {
|
|
2981
|
+
// #4249 Nit: EACCES means a parent directory couldn't be searched —
|
|
2982
|
+
// a real (if rare) permission problem, distinct from ENOENT's "missing".
|
|
2983
|
+
// EPERM: Windows' equivalent permission-denied code for a directory a
|
|
2984
|
+
// parent path couldn't be traversed into.
|
|
2985
|
+
const code = statErr?.code;
|
|
2986
|
+
const reason = code === 'EACCES' || code === 'EPERM' ? 'unreadable' : 'missing';
|
|
2987
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason });
|
|
2988
|
+
}
|
|
2989
|
+
// #4249: selfExecutable is the sole source of truth for whether the OS
|
|
2990
|
+
// execs scriptPath directly via its own shebang (set explicitly by every
|
|
2991
|
+
// producer that needs it — a Windows-Claude .sh hook, Codex's Windows
|
|
2992
|
+
// .cmd shim, Cline's hybrid `env node` hook — rather than inferred from
|
|
2993
|
+
// the absence of interpreterCandidates, which Cline's hybrid case also
|
|
2994
|
+
// carries). Skip on win32 like resolveExecutableBinary's own X_OK
|
|
2995
|
+
// carve-out does: POSIX mode bits don't mean executable on Windows, and a
|
|
2996
|
+
// real accessSync(X_OK) there would fail a .cmd shim under a test that
|
|
2997
|
+
// simulates win32 on a POSIX runner (Node's own no-op only protects an
|
|
2998
|
+
// actual Windows machine). Cline is the only producer where this runs.
|
|
2999
|
+
if (scriptOk && entry.selfExecutable && (entry.platform ?? process.platform) !== 'win32') {
|
|
3000
|
+
try {
|
|
3001
|
+
accessSync(entry.scriptPath, node_fs_1.default.constants.X_OK);
|
|
3002
|
+
}
|
|
3003
|
+
catch {
|
|
3004
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'not-executable' });
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
if (entry.interpreterCandidates && !entry.interpreterCandidates.some(candidate => resolve(candidate, { platform: entry.platform, requireExecutable: true }) !== null)) {
|
|
3008
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'interpreter', path: entry.interpreterCandidates.join(' | '), reason: 'unresolved-interpreter' });
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
return invalid.length === 0 ? { ok: true } : { ok: false, invalid };
|
|
3012
|
+
}
|
|
2647
3013
|
module.exports = {
|
|
2648
3014
|
// Cline
|
|
2649
3015
|
buildClineRulesBody,
|
|
@@ -2692,6 +3058,9 @@ module.exports = {
|
|
|
2692
3058
|
removeCodexHooksJsonEvent,
|
|
2693
3059
|
removeCodexHooksJsonSessionStart,
|
|
2694
3060
|
buildCodexHookWindowsShimIR,
|
|
3061
|
+
cleanupOrphanedCodexContextMonitorScript,
|
|
3062
|
+
isGsdOwnedCodexContextMonitorScript,
|
|
3063
|
+
hooksJsonReferencesCodexContextMonitor,
|
|
2695
3064
|
// Codex TOML
|
|
2696
3065
|
buildCodexHookBlock,
|
|
2697
3066
|
rewriteLegacyCodexHookBlock,
|
|
@@ -2705,7 +3074,9 @@ module.exports = {
|
|
|
2705
3074
|
// Shared
|
|
2706
3075
|
stageTransitiveHookLibs,
|
|
2707
3076
|
buildHookCommand,
|
|
3077
|
+
recordConfiguredHookCommand,
|
|
2708
3078
|
applySettingsJsonHooks,
|
|
3079
|
+
validateConfiguredEntrypoints,
|
|
2709
3080
|
referencesHook,
|
|
2710
3081
|
rewriteLegacyManagedNodeHookCommands,
|
|
2711
3082
|
reconcileManagedShellHookCommands,
|