@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.
- package/.claude-plugin/plugin.json +23 -0
- package/GEMINI.md +53 -0
- package/LICENSE +22 -0
- package/README.md +100 -0
- package/agents/pp-advisor-researcher.md +108 -0
- package/agents/pp-ai-researcher.md +114 -0
- package/agents/pp-assumptions-analyzer.md +105 -0
- package/agents/pp-code-fixer.md +669 -0
- package/agents/pp-code-reviewer.md +387 -0
- package/agents/pp-codebase-mapper.md +853 -0
- package/agents/pp-debug-session-manager.md +315 -0
- package/agents/pp-debugger.md +1453 -0
- package/agents/pp-doc-classifier.md +168 -0
- package/agents/pp-doc-synthesizer.md +204 -0
- package/agents/pp-doc-verifier.md +217 -0
- package/agents/pp-doc-writer.md +616 -0
- package/agents/pp-domain-researcher.md +147 -0
- package/agents/pp-eval-auditor.md +191 -0
- package/agents/pp-eval-planner.md +154 -0
- package/agents/pp-executor.md +808 -0
- package/agents/pp-framework-selector.md +160 -0
- package/agents/pp-integration-checker.md +470 -0
- package/agents/pp-intel-updater.md +339 -0
- package/agents/pp-mempalace-curator.md +47 -0
- package/agents/pp-nyquist-auditor.md +204 -0
- package/agents/pp-pattern-mapper.md +335 -0
- package/agents/pp-phase-researcher.md +867 -0
- package/agents/pp-plan-checker.md +988 -0
- package/agents/pp-planner.md +1052 -0
- package/agents/pp-project-researcher.md +612 -0
- package/agents/pp-research-synthesizer.md +260 -0
- package/agents/pp-roadmapper.md +741 -0
- package/agents/pp-security-auditor.md +156 -0
- package/agents/pp-ui-auditor.md +495 -0
- package/agents/pp-ui-checker.md +309 -0
- package/agents/pp-ui-researcher.md +375 -0
- package/agents/pp-user-profiler.md +171 -0
- package/agents/pp-verifier.md +967 -0
- package/assets/gsd-logo-2000-transparent.png +0 -0
- package/assets/gsd-logo-2000-transparent.svg +17 -0
- package/assets/gsd-logo-2000.png +0 -0
- package/assets/gsd-logo-2000.svg +21 -0
- package/assets/terminal.svg +68 -0
- package/bin/install.js +12370 -0
- package/bin/lib/ui-safety-gate.cjs +107 -0
- package/commands/pp-add-tests.md +42 -0
- package/commands/pp-ai-integration-phase.md +37 -0
- package/commands/pp-audit-fix.md +34 -0
- package/commands/pp-audit-milestone.md +37 -0
- package/commands/pp-audit-uat.md +24 -0
- package/commands/pp-autonomous.md +51 -0
- package/commands/pp-capture.md +62 -0
- package/commands/pp-cleanup.md +24 -0
- package/commands/pp-code-review.md +59 -0
- package/commands/pp-complete-milestone.md +143 -0
- package/commands/pp-config.md +56 -0
- package/commands/pp-debug.md +52 -0
- package/commands/pp-discovery.md +34 -0
- package/commands/pp-discuss-phase.md +77 -0
- package/commands/pp-docs-update.md +49 -0
- package/commands/pp-eval-review.md +33 -0
- package/commands/pp-execute-phase.md +65 -0
- package/commands/pp-explore.md +27 -0
- package/commands/pp-extract-learnings.md +23 -0
- package/commands/pp-fast.md +31 -0
- package/commands/pp-forensics.md +57 -0
- package/commands/pp-graphify.md +204 -0
- package/commands/pp-health.md +31 -0
- package/commands/pp-help.md +28 -0
- package/commands/pp-import.md +45 -0
- package/commands/pp-inbox.md +39 -0
- package/commands/pp-ingest-docs.md +42 -0
- package/commands/pp-manager.md +45 -0
- package/commands/pp-map-codebase.md +83 -0
- package/commands/pp-mempalace-capture.md +71 -0
- package/commands/pp-mempalace-recall.md +102 -0
- package/commands/pp-milestone-summary.md +51 -0
- package/commands/pp-mvp-phase.md +45 -0
- package/commands/pp-new-milestone.md +45 -0
- package/commands/pp-new-project.md +47 -0
- package/commands/pp-ns-context.md +25 -0
- package/commands/pp-ns-ideate.md +24 -0
- package/commands/pp-ns-manage.md +36 -0
- package/commands/pp-ns-project.md +27 -0
- package/commands/pp-ns-review.md +29 -0
- package/commands/pp-ns-workflow.md +35 -0
- package/commands/pp-pause-work.md +43 -0
- package/commands/pp-phase.md +56 -0
- package/commands/pp-plan-phase.md +63 -0
- package/commands/pp-plan-review-convergence.md +60 -0
- package/commands/pp-pr-branch.md +26 -0
- package/commands/pp-profile-user.md +46 -0
- package/commands/pp-progress.md +49 -0
- package/commands/pp-quick.md +174 -0
- package/commands/pp-resume-work.md +30 -0
- package/commands/pp-review-backlog.md +63 -0
- package/commands/pp-review.md +42 -0
- package/commands/pp-secure-phase.md +36 -0
- package/commands/pp-settings.md +29 -0
- package/commands/pp-ship.md +24 -0
- package/commands/pp-sketch.md +60 -0
- package/commands/pp-spec-phase.md +63 -0
- package/commands/pp-spike.md +57 -0
- package/commands/pp-stats.md +20 -0
- package/commands/pp-surface.md +162 -0
- package/commands/pp-thread.md +24 -0
- package/commands/pp-ui-phase.md +35 -0
- package/commands/pp-ui-review.md +33 -0
- package/commands/pp-ultraplan-phase.md +34 -0
- package/commands/pp-undo.md +35 -0
- package/commands/pp-update.md +49 -0
- package/commands/pp-validate-phase.md +36 -0
- package/commands/pp-verify-work.md +39 -0
- package/commands/pp-workspace.md +52 -0
- package/commands/pp-workstreams.md +70 -0
- package/gemini-extension.json +6 -0
- package/hooks/dist/lib/git-cmd.js +150 -0
- package/hooks/dist/lib/pp-graphify-rebuild.sh +65 -0
- package/hooks/dist/managed-hooks-registry.cjs +39 -0
- package/hooks/dist/pp-check-update-worker.js +108 -0
- package/hooks/dist/pp-check-update.js +66 -0
- package/hooks/dist/pp-config-reload.js +133 -0
- package/hooks/dist/pp-context-monitor.js +196 -0
- package/hooks/dist/pp-cursor-post-tool.js +75 -0
- package/hooks/dist/pp-cursor-session-start.js +52 -0
- package/hooks/dist/pp-ensure-canonical-path.js +305 -0
- package/hooks/dist/pp-graphify-update.sh +158 -0
- package/hooks/dist/pp-phase-boundary.sh +47 -0
- package/hooks/dist/pp-prompt-guard.js +97 -0
- package/hooks/dist/pp-read-guard.js +101 -0
- package/hooks/dist/pp-read-injection-scanner.js +203 -0
- package/hooks/dist/pp-session-state.sh +59 -0
- package/hooks/dist/pp-statusline.js +566 -0
- package/hooks/dist/pp-update-banner.js +138 -0
- package/hooks/dist/pp-validate-commit.sh +57 -0
- package/hooks/dist/pp-workflow-guard.js +167 -0
- package/hooks/dist/pp-worktree-path-guard.js +185 -0
- package/hooks/hooks.json +70 -0
- package/hooks/lib/git-cmd.js +150 -0
- package/hooks/lib/pp-graphify-rebuild.sh +65 -0
- package/hooks/managed-hooks-registry.cjs +39 -0
- package/hooks/pp-check-update-worker.js +108 -0
- package/hooks/pp-check-update.js +66 -0
- package/hooks/pp-config-reload.js +133 -0
- package/hooks/pp-context-monitor.js +196 -0
- package/hooks/pp-cursor-post-tool.js +75 -0
- package/hooks/pp-cursor-session-start.js +52 -0
- package/hooks/pp-ensure-canonical-path.js +305 -0
- package/hooks/pp-graphify-update.sh +158 -0
- package/hooks/pp-phase-boundary.sh +47 -0
- package/hooks/pp-prompt-guard.js +97 -0
- package/hooks/pp-read-guard.js +101 -0
- package/hooks/pp-read-injection-scanner.js +203 -0
- package/hooks/pp-session-state.sh +59 -0
- package/hooks/pp-statusline.js +566 -0
- package/hooks/pp-update-banner.js +138 -0
- package/hooks/pp-validate-commit.sh +57 -0
- package/hooks/pp-workflow-guard.js +167 -0
- package/hooks/pp-worktree-path-guard.js +185 -0
- package/package.json +125 -0
- package/pp-core/bin/check-latest-version.cjs +161 -0
- package/pp-core/bin/lib/active-workstream-store.cjs +297 -0
- package/pp-core/bin/lib/adr-parser.cjs +399 -0
- package/pp-core/bin/lib/agent-command-router.cjs +68 -0
- package/pp-core/bin/lib/agent-install-check.cjs +143 -0
- package/pp-core/bin/lib/artifacts.cjs +51 -0
- package/pp-core/bin/lib/audit-command-router.cjs +61 -0
- package/pp-core/bin/lib/audit.cjs +743 -0
- package/pp-core/bin/lib/capability-activation.cjs +113 -0
- package/pp-core/bin/lib/capability-registry.cjs +3727 -0
- package/pp-core/bin/lib/capability-state.cjs +497 -0
- package/pp-core/bin/lib/capability-writer.cjs +355 -0
- package/pp-core/bin/lib/check-command-router.cjs +800 -0
- package/pp-core/bin/lib/cjs-command-router-adapter.cjs +81 -0
- package/pp-core/bin/lib/cli-exit.cjs +61 -0
- package/pp-core/bin/lib/clock.cjs +95 -0
- package/pp-core/bin/lib/clusters.cjs +131 -0
- package/pp-core/bin/lib/code-review-flags.cjs +59 -0
- package/pp-core/bin/lib/command-aliases.cjs +809 -0
- package/pp-core/bin/lib/command-arg-projection.cjs +55 -0
- package/pp-core/bin/lib/command-roster.cjs +19 -0
- package/pp-core/bin/lib/command-routing-hub.cjs +300 -0
- package/pp-core/bin/lib/commands.cjs +1244 -0
- package/pp-core/bin/lib/config-loader.cjs +714 -0
- package/pp-core/bin/lib/config-schema.cjs +58 -0
- package/pp-core/bin/lib/config-types.cjs +20 -0
- package/pp-core/bin/lib/config.cjs +796 -0
- package/pp-core/bin/lib/configuration.cjs +209 -0
- package/pp-core/bin/lib/context-utilization.cjs +48 -0
- package/pp-core/bin/lib/core-utils.cjs +198 -0
- package/pp-core/bin/lib/decisions.cjs +136 -0
- package/pp-core/bin/lib/docs.cjs +264 -0
- package/pp-core/bin/lib/drift.cjs +364 -0
- package/pp-core/bin/lib/edge-probe.cjs +196 -0
- package/pp-core/bin/lib/fallow-runner.cjs +153 -0
- package/pp-core/bin/lib/federated-config.cjs +182 -0
- package/pp-core/bin/lib/frontmatter.cjs +494 -0
- package/pp-core/bin/lib/gap-checker.cjs +260 -0
- package/pp-core/bin/lib/git-base-branch.cjs +220 -0
- package/pp-core/bin/lib/graphify-command-router.cjs +72 -0
- package/pp-core/bin/lib/graphify.cjs +472 -0
- package/pp-core/bin/lib/gsd2-import.cjs +456 -0
- package/pp-core/bin/lib/init-command-router.cjs +62 -0
- package/pp-core/bin/lib/init.cjs +1912 -0
- package/pp-core/bin/lib/install-profiles.cjs +814 -0
- package/pp-core/bin/lib/installer-migration-authoring.cjs +122 -0
- package/pp-core/bin/lib/installer-migration-report.cjs +351 -0
- package/pp-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +225 -0
- package/pp-core/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +48 -0
- package/pp-core/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +94 -0
- package/pp-core/bin/lib/installer-migrations/003-rename-get-shit-done-to-gsd-core.cjs +108 -0
- package/pp-core/bin/lib/installer-migrations/004-prune-stale-pristine-snapshots.cjs +123 -0
- package/pp-core/bin/lib/installer-migrations/005-rename-gsd-core-to-pp-core.cjs +109 -0
- package/pp-core/bin/lib/installer-migrations/006-rename-gsd-command-namespace.cjs +135 -0
- package/pp-core/bin/lib/installer-migrations.cjs +823 -0
- package/pp-core/bin/lib/intel-command-router.cjs +119 -0
- package/pp-core/bin/lib/intel.cjs +584 -0
- package/pp-core/bin/lib/io.cjs +220 -0
- package/pp-core/bin/lib/learnings.cjs +270 -0
- package/pp-core/bin/lib/legacy-cleanup.cjs +253 -0
- package/pp-core/bin/lib/loop-host-contract.cjs +105 -0
- package/pp-core/bin/lib/loop-resolver.cjs +479 -0
- package/pp-core/bin/lib/milestone.cjs +381 -0
- package/pp-core/bin/lib/model-catalog.cjs +154 -0
- package/pp-core/bin/lib/model-profiles.cjs +24 -0
- package/pp-core/bin/lib/model-resolver.cjs +467 -0
- package/pp-core/bin/lib/observability/event.cjs +51 -0
- package/pp-core/bin/lib/observability/logger.cjs +146 -0
- package/pp-core/bin/lib/observability/redaction.cjs +48 -0
- package/pp-core/bin/lib/package-identity.cjs +35 -0
- package/pp-core/bin/lib/package-legitimacy.cjs +368 -0
- package/pp-core/bin/lib/phase-command-router.cjs +209 -0
- package/pp-core/bin/lib/phase-id.cjs +213 -0
- package/pp-core/bin/lib/phase-lifecycle.cjs +74 -0
- package/pp-core/bin/lib/phase-locator.cjs +145 -0
- package/pp-core/bin/lib/phase.cjs +1446 -0
- package/pp-core/bin/lib/phases-command-router.cjs +43 -0
- package/pp-core/bin/lib/plan-drift-guard.cjs +117 -0
- package/pp-core/bin/lib/plan-scan.cjs +91 -0
- package/pp-core/bin/lib/planning-workspace.cjs +245 -0
- package/pp-core/bin/lib/probe-core.cjs +402 -0
- package/pp-core/bin/lib/profile-output.cjs +1164 -0
- package/pp-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
- package/pp-core/bin/lib/profile-pipeline.cjs +519 -0
- package/pp-core/bin/lib/prohibition-enforcement.cjs +660 -0
- package/pp-core/bin/lib/project-root.cjs +119 -0
- package/pp-core/bin/lib/prompt-budget.cjs +305 -0
- package/pp-core/bin/lib/research-provider.cjs +137 -0
- package/pp-core/bin/lib/research-store.cjs +167 -0
- package/pp-core/bin/lib/review-reviewer-selection.cjs +121 -0
- package/pp-core/bin/lib/roadmap-command-router.cjs +166 -0
- package/pp-core/bin/lib/roadmap-parser.cjs +456 -0
- package/pp-core/bin/lib/roadmap-upgrade.cjs +476 -0
- package/pp-core/bin/lib/roadmap.cjs +738 -0
- package/pp-core/bin/lib/runtime-artifact-conversion.cjs +1966 -0
- package/pp-core/bin/lib/runtime-artifact-layout.cjs +342 -0
- package/pp-core/bin/lib/runtime-config-adapter-registry.cjs +103 -0
- package/pp-core/bin/lib/runtime-homes.cjs +272 -0
- package/pp-core/bin/lib/runtime-hooks-surface.cjs +1460 -0
- package/pp-core/bin/lib/runtime-name-policy.cjs +97 -0
- package/pp-core/bin/lib/runtime-slash.cjs +137 -0
- package/pp-core/bin/lib/schema-detect.cjs +159 -0
- package/pp-core/bin/lib/secrets.cjs +34 -0
- package/pp-core/bin/lib/security.cjs +480 -0
- package/pp-core/bin/lib/semver-compare.cjs +42 -0
- package/pp-core/bin/lib/shell-command-projection.cjs +550 -0
- package/pp-core/bin/lib/state-command-router.cjs +172 -0
- package/pp-core/bin/lib/state-document.cjs +312 -0
- package/pp-core/bin/lib/state.cjs +2587 -0
- package/pp-core/bin/lib/surface.cjs +505 -0
- package/pp-core/bin/lib/task-command-router.cjs +85 -0
- package/pp-core/bin/lib/teams-status.cjs +74 -0
- package/pp-core/bin/lib/template.cjs +246 -0
- package/pp-core/bin/lib/uat-predicate.cjs +329 -0
- package/pp-core/bin/lib/uat.cjs +359 -0
- package/pp-core/bin/lib/ui-safety-gate.cjs +98 -0
- package/pp-core/bin/lib/update-context.cjs +221 -0
- package/pp-core/bin/lib/validate-command-router.cjs +91 -0
- package/pp-core/bin/lib/validate.cjs +122 -0
- package/pp-core/bin/lib/verification-command-router.cjs +31 -0
- package/pp-core/bin/lib/verification.cjs +196 -0
- package/pp-core/bin/lib/verify-command-router.cjs +44 -0
- package/pp-core/bin/lib/verify.cjs +1932 -0
- package/pp-core/bin/lib/workstream-inventory-builder.cjs +81 -0
- package/pp-core/bin/lib/workstream-inventory.cjs +147 -0
- package/pp-core/bin/lib/workstream-name-policy.cjs +91 -0
- package/pp-core/bin/lib/workstream.cjs +386 -0
- package/pp-core/bin/lib/worktree-base-ref.cjs +350 -0
- package/pp-core/bin/lib/worktree-safety.cjs +985 -0
- package/pp-core/bin/pp-tools.cjs +2239 -0
- package/pp-core/bin/pp_run +20 -0
- package/pp-core/bin/shared/config-defaults.manifest.json +97 -0
- package/pp-core/bin/shared/config-schema.manifest.json +176 -0
- package/pp-core/bin/shared/model-catalog.json +160 -0
- package/pp-core/bin/shared/runtime-aliases.manifest.json +79 -0
- package/pp-core/bin/verify-reapply-patches.cjs +400 -0
- package/pp-core/contexts/dev.md +21 -0
- package/pp-core/contexts/research.md +22 -0
- package/pp-core/contexts/review.md +23 -0
- package/pp-core/references/agent-contracts.md +79 -0
- package/pp-core/references/ai-evals.md +156 -0
- package/pp-core/references/ai-frameworks.md +186 -0
- package/pp-core/references/artifact-types.md +131 -0
- package/pp-core/references/autonomous-smart-discuss.md +277 -0
- package/pp-core/references/checkpoints.md +814 -0
- package/pp-core/references/common-bug-patterns.md +114 -0
- package/pp-core/references/context-budget.md +85 -0
- package/pp-core/references/continuation-format.md +253 -0
- package/pp-core/references/debugger-philosophy.md +76 -0
- package/pp-core/references/decimal-phase-calculation.md +64 -0
- package/pp-core/references/doc-conflict-engine.md +91 -0
- package/pp-core/references/domain-probes.md +125 -0
- package/pp-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
- package/pp-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
- package/pp-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
- package/pp-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
- package/pp-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
- package/pp-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
- package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
- package/pp-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
- package/pp-core/references/edge-probe.md +272 -0
- package/pp-core/references/execute-mvp-tdd.md +81 -0
- package/pp-core/references/executor-examples.md +110 -0
- package/pp-core/references/few-shot-examples/plan-checker.md +73 -0
- package/pp-core/references/few-shot-examples/verifier.md +109 -0
- package/pp-core/references/gate-prompts.md +100 -0
- package/pp-core/references/gates.md +70 -0
- package/pp-core/references/git-integration.md +298 -0
- package/pp-core/references/git-planning-commit.md +40 -0
- package/pp-core/references/ios-scaffold.md +123 -0
- package/pp-core/references/loop-hook-dispatch.md +61 -0
- package/pp-core/references/mandatory-initial-read.md +2 -0
- package/pp-core/references/model-profile-resolution.md +38 -0
- package/pp-core/references/model-profiles.md +245 -0
- package/pp-core/references/mvp-concepts.md +49 -0
- package/pp-core/references/phase-argument-parsing.md +61 -0
- package/pp-core/references/planner-antipatterns.md +176 -0
- package/pp-core/references/planner-chunked.md +49 -0
- package/pp-core/references/planner-gap-closure.md +62 -0
- package/pp-core/references/planner-graphify-auto-update.md +67 -0
- package/pp-core/references/planner-guidance.md +186 -0
- package/pp-core/references/planner-human-verify-mode.md +57 -0
- package/pp-core/references/planner-interface-context.md +62 -0
- package/pp-core/references/planner-load-graph-context.md +36 -0
- package/pp-core/references/planner-mvp-mode.md +53 -0
- package/pp-core/references/planner-reviews.md +42 -0
- package/pp-core/references/planner-revision.md +87 -0
- package/pp-core/references/planner-source-audit.md +73 -0
- package/pp-core/references/planning-config.md +477 -0
- package/pp-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
- package/pp-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
- package/pp-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
- package/pp-core/references/prohibition-probe.md +326 -0
- package/pp-core/references/project-skills-discovery.md +19 -0
- package/pp-core/references/questioning.md +162 -0
- package/pp-core/references/research-documentation-lookup.md +29 -0
- package/pp-core/references/research-philosophy.md +29 -0
- package/pp-core/references/research-verification-protocol.md +27 -0
- package/pp-core/references/revision-loop.md +97 -0
- package/pp-core/references/scout-codebase.md +51 -0
- package/pp-core/references/skeleton-template.md +48 -0
- package/pp-core/references/sketch-interactivity.md +41 -0
- package/pp-core/references/sketch-theme-system.md +94 -0
- package/pp-core/references/sketch-tooling.md +45 -0
- package/pp-core/references/sketch-variant-patterns.md +81 -0
- package/pp-core/references/spidr-splitting.md +69 -0
- package/pp-core/references/tdd.md +330 -0
- package/pp-core/references/thinking-models-debug.md +44 -0
- package/pp-core/references/thinking-models-execution.md +50 -0
- package/pp-core/references/thinking-models-planning.md +62 -0
- package/pp-core/references/thinking-models-research.md +50 -0
- package/pp-core/references/thinking-models-verification.md +55 -0
- package/pp-core/references/thinking-partner.md +96 -0
- package/pp-core/references/ui-brand.md +162 -0
- package/pp-core/references/universal-anti-patterns.md +63 -0
- package/pp-core/references/user-profiling.md +681 -0
- package/pp-core/references/user-story-template.md +58 -0
- package/pp-core/references/verification-overrides.md +227 -0
- package/pp-core/references/verification-patterns.md +612 -0
- package/pp-core/references/verify-mvp-mode.md +85 -0
- package/pp-core/references/workstream-flag.md +111 -0
- package/pp-core/references/worktree-branch-check.md +44 -0
- package/pp-core/references/worktree-path-safety.md +67 -0
- package/pp-core/templates/AI-SPEC.md +246 -0
- package/pp-core/templates/DEBUG.md +169 -0
- package/pp-core/templates/README.md +77 -0
- package/pp-core/templates/SECURITY.md +61 -0
- package/pp-core/templates/UAT.md +265 -0
- package/pp-core/templates/UI-SPEC.md +100 -0
- package/pp-core/templates/VALIDATION.md +76 -0
- package/pp-core/templates/claude-md.md +145 -0
- package/pp-core/templates/codebase/architecture.md +255 -0
- package/pp-core/templates/codebase/concerns.md +310 -0
- package/pp-core/templates/codebase/conventions.md +307 -0
- package/pp-core/templates/codebase/integrations.md +280 -0
- package/pp-core/templates/codebase/stack.md +186 -0
- package/pp-core/templates/codebase/structure.md +285 -0
- package/pp-core/templates/codebase/testing.md +480 -0
- package/pp-core/templates/config.json +62 -0
- package/pp-core/templates/context.md +352 -0
- package/pp-core/templates/continue-here.md +78 -0
- package/pp-core/templates/copilot-instructions.md +7 -0
- package/pp-core/templates/debug-subagent-prompt.md +91 -0
- package/pp-core/templates/dev-preferences.md +21 -0
- package/pp-core/templates/discovery.md +146 -0
- package/pp-core/templates/discussion-log.md +63 -0
- package/pp-core/templates/milestone-archive.md +123 -0
- package/pp-core/templates/milestone.md +115 -0
- package/pp-core/templates/phase-prompt.md +610 -0
- package/pp-core/templates/planner-subagent-prompt.md +117 -0
- package/pp-core/templates/project.md +203 -0
- package/pp-core/templates/requirements.md +231 -0
- package/pp-core/templates/research-project/ARCHITECTURE.md +204 -0
- package/pp-core/templates/research-project/FEATURES.md +147 -0
- package/pp-core/templates/research-project/PITFALLS.md +200 -0
- package/pp-core/templates/research-project/STACK.md +120 -0
- package/pp-core/templates/research-project/SUMMARY.md +170 -0
- package/pp-core/templates/research.md +592 -0
- package/pp-core/templates/retrospective.md +54 -0
- package/pp-core/templates/roadmap.md +202 -0
- package/pp-core/templates/spec.md +333 -0
- package/pp-core/templates/state.md +195 -0
- package/pp-core/templates/summary-complex.md +60 -0
- package/pp-core/templates/summary-minimal.md +42 -0
- package/pp-core/templates/summary-standard.md +49 -0
- package/pp-core/templates/summary.md +249 -0
- package/pp-core/templates/user-profile.md +146 -0
- package/pp-core/templates/user-setup.md +311 -0
- package/pp-core/templates/verification-report.md +335 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -0
- package/pp-core/workflows/add-backlog.md +91 -0
- package/pp-core/workflows/add-phase.md +113 -0
- package/pp-core/workflows/add-tests.md +355 -0
- package/pp-core/workflows/add-todo.md +161 -0
- package/pp-core/workflows/ai-integration-phase.md +295 -0
- package/pp-core/workflows/analyze-dependencies.md +96 -0
- package/pp-core/workflows/audit-fix.md +178 -0
- package/pp-core/workflows/audit-milestone.md +362 -0
- package/pp-core/workflows/audit-uat.md +110 -0
- package/pp-core/workflows/autonomous.md +886 -0
- package/pp-core/workflows/check-todos.md +180 -0
- package/pp-core/workflows/cleanup.md +195 -0
- package/pp-core/workflows/code-review-fix.md +506 -0
- package/pp-core/workflows/code-review.md +696 -0
- package/pp-core/workflows/complete-milestone.md +857 -0
- package/pp-core/workflows/debug.md +237 -0
- package/pp-core/workflows/diagnose-issues.md +245 -0
- package/pp-core/workflows/discovery-phase.md +291 -0
- package/pp-core/workflows/discovery-sprint.md +1546 -0
- package/pp-core/workflows/discuss-phase/modes/advisor.md +176 -0
- package/pp-core/workflows/discuss-phase/modes/all.md +28 -0
- package/pp-core/workflows/discuss-phase/modes/analyze.md +44 -0
- package/pp-core/workflows/discuss-phase/modes/auto.md +57 -0
- package/pp-core/workflows/discuss-phase/modes/batch.md +52 -0
- package/pp-core/workflows/discuss-phase/modes/chain.md +98 -0
- package/pp-core/workflows/discuss-phase/modes/default.md +141 -0
- package/pp-core/workflows/discuss-phase/modes/power.md +44 -0
- package/pp-core/workflows/discuss-phase/modes/text.md +55 -0
- package/pp-core/workflows/discuss-phase/templates/checkpoint.json +18 -0
- package/pp-core/workflows/discuss-phase/templates/context.md +136 -0
- package/pp-core/workflows/discuss-phase/templates/discussion-log.md +50 -0
- package/pp-core/workflows/discuss-phase-assumptions.md +675 -0
- package/pp-core/workflows/discuss-phase-power.md +291 -0
- package/pp-core/workflows/discuss-phase.md +520 -0
- package/pp-core/workflows/do.md +111 -0
- package/pp-core/workflows/docs-update.md +1168 -0
- package/pp-core/workflows/edit-phase.md +295 -0
- package/pp-core/workflows/eval-review.md +159 -0
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +95 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +117 -0
- package/pp-core/workflows/execute-phase/steps/worktree-recovery-policy.md +9 -0
- package/pp-core/workflows/execute-phase.md +1707 -0
- package/pp-core/workflows/execute-plan.md +541 -0
- package/pp-core/workflows/explore.md +146 -0
- package/pp-core/workflows/extract-learnings.md +243 -0
- package/pp-core/workflows/fast.md +124 -0
- package/pp-core/workflows/forensics.md +279 -0
- package/pp-core/workflows/graduation.md +196 -0
- package/pp-core/workflows/health.md +224 -0
- package/pp-core/workflows/help/modes/brief.md +22 -0
- package/pp-core/workflows/help/modes/default.md +50 -0
- package/pp-core/workflows/help/modes/full.md +791 -0
- package/pp-core/workflows/help/modes/topic.md +74 -0
- package/pp-core/workflows/help.md +24 -0
- package/pp-core/workflows/import.md +256 -0
- package/pp-core/workflows/inbox.md +387 -0
- package/pp-core/workflows/ingest-docs.md +340 -0
- package/pp-core/workflows/insert-phase.md +152 -0
- package/pp-core/workflows/list-phase-assumptions.md +178 -0
- package/pp-core/workflows/list-workspaces.md +57 -0
- package/pp-core/workflows/manager.md +435 -0
- package/pp-core/workflows/map-codebase.md +444 -0
- package/pp-core/workflows/milestone-summary.md +224 -0
- package/pp-core/workflows/mvp-phase.md +222 -0
- package/pp-core/workflows/new-milestone.md +643 -0
- package/pp-core/workflows/new-project.md +1563 -0
- package/pp-core/workflows/new-workspace.md +240 -0
- package/pp-core/workflows/next.md +347 -0
- package/pp-core/workflows/node-repair.md +92 -0
- package/pp-core/workflows/note.md +158 -0
- package/pp-core/workflows/pause-work.md +250 -0
- package/pp-core/workflows/plan-milestone-gaps.md +281 -0
- package/pp-core/workflows/plan-phase.md +1755 -0
- package/pp-core/workflows/plan-review-convergence.md +369 -0
- package/pp-core/workflows/plant-seed.md +230 -0
- package/pp-core/workflows/pr-branch.md +159 -0
- package/pp-core/workflows/profile-user.md +455 -0
- package/pp-core/workflows/progress.md +756 -0
- package/pp-core/workflows/quick.md +1045 -0
- package/pp-core/workflows/reapply-patches.md +443 -0
- package/pp-core/workflows/remove-phase.md +156 -0
- package/pp-core/workflows/remove-workspace.md +108 -0
- package/pp-core/workflows/resume-project.md +348 -0
- package/pp-core/workflows/review.md +765 -0
- package/pp-core/workflows/scan.md +107 -0
- package/pp-core/workflows/secure-phase.md +184 -0
- package/pp-core/workflows/session-report.md +146 -0
- package/pp-core/workflows/settings-advanced.md +816 -0
- package/pp-core/workflows/settings-integrations.md +312 -0
- package/pp-core/workflows/settings.md +592 -0
- package/pp-core/workflows/ship.md +465 -0
- package/pp-core/workflows/sketch-wrap-up.md +286 -0
- package/pp-core/workflows/sketch.md +361 -0
- package/pp-core/workflows/spec-phase.md +495 -0
- package/pp-core/workflows/spike-wrap-up.md +307 -0
- package/pp-core/workflows/spike.md +453 -0
- package/pp-core/workflows/stats.md +80 -0
- package/pp-core/workflows/sync-skills.md +182 -0
- package/pp-core/workflows/thread.md +222 -0
- package/pp-core/workflows/transition.md +694 -0
- package/pp-core/workflows/ui-phase.md +328 -0
- package/pp-core/workflows/ui-review.md +193 -0
- package/pp-core/workflows/ultraplan-phase.md +199 -0
- package/pp-core/workflows/undo.md +314 -0
- package/pp-core/workflows/update.md +501 -0
- package/pp-core/workflows/validate-phase.md +183 -0
- package/pp-core/workflows/verify-phase.md +569 -0
- package/pp-core/workflows/verify-work.md +804 -0
- package/scripts/affected-tests-lib.cjs +554 -0
- package/scripts/audit-workflow-script-paths.cjs +73 -0
- package/scripts/base64-scan.sh +351 -0
- package/scripts/build-hooks.js +254 -0
- package/scripts/changeset/README.md +129 -0
- package/scripts/changeset/cli.cjs +597 -0
- package/scripts/changeset/github-release-notes.cjs +199 -0
- package/scripts/changeset/lint.cjs +147 -0
- package/scripts/changeset/new.cjs +151 -0
- package/scripts/changeset/parse.cjs +114 -0
- package/scripts/changeset/render.cjs +34 -0
- package/scripts/changeset/serialize.cjs +130 -0
- package/scripts/check-alias-drift.cjs +114 -0
- package/scripts/check-env.cjs +312 -0
- package/scripts/check-npm-integrity.cjs +215 -0
- package/scripts/ci-guard-runner.cjs +22 -0
- package/scripts/ci-prepare-test-scope.cjs +93 -0
- package/scripts/ci-rebase-check.cjs +86 -0
- package/scripts/ci-test-scope.cjs +441 -0
- package/scripts/command-contract-helpers.cjs +64 -0
- package/scripts/diff-touches-shipped-paths.cjs +149 -0
- package/scripts/fix-slash-commands.cjs +161 -0
- package/scripts/gen-capability-registry.cjs +2646 -0
- package/scripts/gen-commands-reference.cjs +139 -0
- package/scripts/gen-inventory-manifest.cjs +114 -0
- package/scripts/gen-loop-host-contract.cjs +526 -0
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/generate-package-identity.cjs +134 -0
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/issue-version-gate.cjs +140 -0
- package/scripts/lib/allowlist-ratchet.cjs +236 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +328 -0
- package/scripts/lint-allow-test-rule-refs.cjs +162 -0
- package/scripts/lint-command-contract.cjs +118 -0
- package/scripts/lint-descriptions.cjs +87 -0
- package/scripts/lint-docs-required.cjs +222 -0
- package/scripts/lint-legacy-dir-name.cjs +166 -0
- package/scripts/lint-package-identity-drift.cjs +141 -0
- package/scripts/lint-pr-check-project-dir.cjs +99 -0
- package/scripts/lint-regression-test-names.allowlist.json +269 -0
- package/scripts/lint-regression-test-names.cjs +117 -0
- package/scripts/lint-shell-command-projection-drift.cjs +62 -0
- package/scripts/lint-skill-deps.cjs +185 -0
- package/scripts/lint-test-file-count.allowlist.json +181 -0
- package/scripts/lint-test-file-count.cjs +246 -0
- package/scripts/lint-windows-test-portability.cjs +178 -0
- package/scripts/mutation-matrix.cjs +323 -0
- package/scripts/pr-target-policy.cjs +63 -0
- package/scripts/pr-template-policy.cjs +268 -0
- package/scripts/prompt-injection-scan.sh +207 -0
- package/scripts/registry-smoke.cjs +337 -0
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/release-notes/format-github-release-notes.cjs +261 -0
- package/scripts/release-tarball-smoke.cjs +763 -0
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +7 -0
- package/scripts/run-tests.cjs +589 -0
- package/scripts/secret-scan-lint.sh +231 -0
- package/scripts/secret-scan.sh +358 -0
- package/scripts/setup-branch-protection.sh +234 -0
- package/scripts/strip-prose-atrefs.cjs +106 -0
- package/scripts/sync-manifest-versions.cjs +119 -0
- package/scripts/sync-next-version.cjs +133 -0
- package/scripts/sync-rulesets.sh +34 -0
- package/scripts/sync-runtime-launcher.cjs +425 -0
- package/scripts/test-failure-reasons.cjs +34 -0
- package/scripts/update-size-baseline.cjs +68 -0
- package/scripts/verify-npm-publish.cjs +240 -0
- package/scripts/verify-opengsd-sweep.cjs +288 -0
- package/scripts/verify-phase-02-rename.sh +258 -0
- package/scripts/verify-phase-03-rename.sh +376 -0
- package/scripts/workflow-policy.cjs +483 -0
- package/scripts/workflow-size.cjs +90 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pp-hook-version: {{PP_VERSION}}
|
|
3
|
+
// Context Monitor - PostToolUse/AfterTool hook (Gemini uses AfterTool)
|
|
4
|
+
// Reads context metrics from the statusline bridge file and injects
|
|
5
|
+
// warnings when context usage is high. This makes the AGENT aware of
|
|
6
|
+
// context limits (the statusline only shows the user).
|
|
7
|
+
//
|
|
8
|
+
// How it works:
|
|
9
|
+
// 1. The statusline hook writes metrics to /tmp/claude-ctx-{session_id}.json
|
|
10
|
+
// 2. This hook reads those metrics after each tool use
|
|
11
|
+
// 3. When remaining context drops below thresholds, it injects a warning
|
|
12
|
+
// as additionalContext, which the agent sees in its conversation
|
|
13
|
+
//
|
|
14
|
+
// Thresholds:
|
|
15
|
+
// WARNING (remaining <= 35%): Agent should wrap up current task
|
|
16
|
+
// CRITICAL (remaining <= 25%): Agent should stop immediately and save state
|
|
17
|
+
//
|
|
18
|
+
// Debounce: 5 tool uses between warnings to avoid spam
|
|
19
|
+
// Severity escalation bypasses debounce (WARNING -> CRITICAL fires immediately)
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { spawn } = require('child_process');
|
|
25
|
+
|
|
26
|
+
const WARNING_THRESHOLD = 35; // remaining_percentage <= 35%
|
|
27
|
+
const CRITICAL_THRESHOLD = 25; // remaining_percentage <= 25%
|
|
28
|
+
const STALE_SECONDS = 60; // ignore metrics older than 60s
|
|
29
|
+
const DEBOUNCE_CALLS = 5; // min tool uses between warnings
|
|
30
|
+
|
|
31
|
+
let input = '';
|
|
32
|
+
// Timeout guard: if stdin doesn't close within 10s (e.g. pipe issues on
|
|
33
|
+
// Windows/Git Bash, or slow Claude Code piping during large outputs),
|
|
34
|
+
// exit silently instead of hanging until Claude Code kills the process
|
|
35
|
+
// and reports "hook error". See #775, #1162.
|
|
36
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 10000);
|
|
37
|
+
process.stdin.setEncoding('utf8');
|
|
38
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
39
|
+
process.stdin.on('end', () => {
|
|
40
|
+
clearTimeout(stdinTimeout);
|
|
41
|
+
try {
|
|
42
|
+
const data = JSON.parse(input);
|
|
43
|
+
const sessionId = data.session_id;
|
|
44
|
+
|
|
45
|
+
if (!sessionId) {
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Reject session IDs that contain path traversal sequences or path separators.
|
|
50
|
+
// session_id is used to construct file paths in /tmp — an unsanitized value
|
|
51
|
+
// could escape the temp directory and read or write arbitrary files.
|
|
52
|
+
if (/[/\\]|\.\./.test(sessionId)) {
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if context warnings are disabled via config.
|
|
57
|
+
// Collapsed existsSync+readFileSync into a single read guarded by try/catch
|
|
58
|
+
// (ENOENT or parse error → use defaults, same as old "planningDir absent" branch).
|
|
59
|
+
const cwd = data.cwd || process.cwd();
|
|
60
|
+
try {
|
|
61
|
+
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
62
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
63
|
+
if (config.hooks?.context_warnings === false) {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
// Missing or unparseable config → proceed with defaults (context warnings enabled)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tmpDir = os.tmpdir();
|
|
71
|
+
const metricsPath = path.join(tmpDir, `claude-ctx-${sessionId}.json`);
|
|
72
|
+
|
|
73
|
+
// If no metrics file, this is a subagent or fresh session -- exit silently.
|
|
74
|
+
// Collapsed existsSync+readFileSync: ENOENT → exit 0 (identical to old !existsSync branch),
|
|
75
|
+
// other errors rethrow to the outer catch (swallowed → exit 0, as before).
|
|
76
|
+
let metricsRaw;
|
|
77
|
+
try {
|
|
78
|
+
metricsRaw = fs.readFileSync(metricsPath, 'utf8');
|
|
79
|
+
} catch (e) {
|
|
80
|
+
if (e && e.code === 'ENOENT') process.exit(0);
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
const metrics = JSON.parse(metricsRaw);
|
|
84
|
+
const now = Math.floor(Date.now() / 1000);
|
|
85
|
+
|
|
86
|
+
// Ignore stale metrics
|
|
87
|
+
if (metrics.timestamp && (now - metrics.timestamp) > STALE_SECONDS) {
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const remaining = metrics.remaining_percentage;
|
|
92
|
+
const usedPct = metrics.used_pct;
|
|
93
|
+
|
|
94
|
+
// No warning needed
|
|
95
|
+
if (remaining > WARNING_THRESHOLD) {
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Debounce: check if we warned recently
|
|
100
|
+
const warnPath = path.join(tmpDir, `claude-ctx-${sessionId}-warned.json`);
|
|
101
|
+
let warnData = { callsSinceWarn: 0, lastLevel: null };
|
|
102
|
+
let firstWarn = true;
|
|
103
|
+
|
|
104
|
+
// Collapsed existsSync+readFileSync: ENOENT or parse error → keep default warnData
|
|
105
|
+
// (same as old "file absent" branch). firstWarn tracks whether we read a valid sentinel.
|
|
106
|
+
try {
|
|
107
|
+
warnData = JSON.parse(fs.readFileSync(warnPath, 'utf8'));
|
|
108
|
+
firstWarn = false;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
// Missing or corrupted sentinel → firstWarn stays true, warnData stays at defaults
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
warnData.callsSinceWarn = (warnData.callsSinceWarn || 0) + 1;
|
|
114
|
+
|
|
115
|
+
const isCritical = remaining <= CRITICAL_THRESHOLD;
|
|
116
|
+
const currentLevel = isCritical ? 'critical' : 'warning';
|
|
117
|
+
|
|
118
|
+
// Emit immediately on first warning, then debounce subsequent ones
|
|
119
|
+
// Severity escalation (WARNING -> CRITICAL) bypasses debounce
|
|
120
|
+
const severityEscalated = currentLevel === 'critical' && warnData.lastLevel === 'warning';
|
|
121
|
+
if (!firstWarn && warnData.callsSinceWarn < DEBOUNCE_CALLS && !severityEscalated) {
|
|
122
|
+
// Update counter and exit without warning
|
|
123
|
+
fs.writeFileSync(warnPath, JSON.stringify(warnData));
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Reset debounce counter
|
|
128
|
+
warnData.callsSinceWarn = 0;
|
|
129
|
+
warnData.lastLevel = currentLevel;
|
|
130
|
+
fs.writeFileSync(warnPath, JSON.stringify(warnData));
|
|
131
|
+
|
|
132
|
+
// Detect if GSD is active (has .planning/STATE.md in working directory)
|
|
133
|
+
const isGsdActive = fs.existsSync(path.join(cwd, '.planning', 'STATE.md'));
|
|
134
|
+
|
|
135
|
+
// On CRITICAL with active GSD project, auto-record session state as a
|
|
136
|
+
// breadcrumb for /gsd:resume-work (#1974). Fire-and-forget subprocess —
|
|
137
|
+
// doesn't block the hook or the agent. Fires ONCE per CRITICAL session,
|
|
138
|
+
// guarded by warnData.criticalRecorded to prevent repeated overwrites
|
|
139
|
+
// of the "crash moment" record on every debounce cycle.
|
|
140
|
+
if (isCritical && isGsdActive && !warnData.criticalRecorded) {
|
|
141
|
+
try {
|
|
142
|
+
// Runtime-agnostic path: this hook lives at <runtime-config>/hooks/
|
|
143
|
+
// and gsd-tools.cjs lives at <runtime-config>/gsd-core/bin/.
|
|
144
|
+
// Using __dirname makes this work on Claude Code, OpenCode, Gemini,
|
|
145
|
+
// Kilo, etc. without hardcoding ~/.claude/.
|
|
146
|
+
const gsdTools = path.join(__dirname, '..', 'pp-core', 'bin', 'pp-tools.cjs');
|
|
147
|
+
// Coerce usedPct to a safe number in case bridge file is malformed
|
|
148
|
+
const safeUsedPct = Number(usedPct) || 0;
|
|
149
|
+
const stoppedAt = `context exhaustion at ${safeUsedPct}% (${new Date().toISOString().split('T')[0]})`;
|
|
150
|
+
spawn(
|
|
151
|
+
process.execPath,
|
|
152
|
+
[gsdTools, 'state', 'record-session', '--stopped-at', stoppedAt],
|
|
153
|
+
{ cwd, detached: true, stdio: 'ignore', windowsHide: true }
|
|
154
|
+
).unref();
|
|
155
|
+
warnData.criticalRecorded = true;
|
|
156
|
+
// Persist the sentinel so subsequent debounce cycles don't re-fire
|
|
157
|
+
fs.writeFileSync(warnPath, JSON.stringify(warnData));
|
|
158
|
+
} catch { /* non-critical — don't let state recording break the hook */ }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Build advisory warning message (never use imperative commands that
|
|
162
|
+
// override user preferences — see #884)
|
|
163
|
+
let message;
|
|
164
|
+
if (isCritical) {
|
|
165
|
+
message = isGsdActive
|
|
166
|
+
? `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
|
|
167
|
+
'Context is nearly exhausted. Do NOT start new complex work or write handoff files — ' +
|
|
168
|
+
'GSD state is already tracked in STATE.md. Inform the user so they can run ' +
|
|
169
|
+
'/gsd:pause-work at the next natural stopping point.'
|
|
170
|
+
: `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
|
|
171
|
+
'Context is nearly exhausted. Inform the user that context is low and ask how they ' +
|
|
172
|
+
'want to proceed. Do NOT autonomously save state or write handoff files unless the user asks.';
|
|
173
|
+
} else {
|
|
174
|
+
message = isGsdActive
|
|
175
|
+
? `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
|
|
176
|
+
'Context is getting limited. Avoid starting new complex work. If not between ' +
|
|
177
|
+
'defined plan steps, inform the user so they can prepare to pause.'
|
|
178
|
+
: `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
|
|
179
|
+
'Be aware that context is getting limited. Avoid unnecessary exploration or ' +
|
|
180
|
+
'starting new complex work.';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const output = {
|
|
184
|
+
hookSpecificOutput: {
|
|
185
|
+
hookEventName: (data.hook_event_name && data.hook_event_name.trim())
|
|
186
|
+
|| (process.env.GEMINI_API_KEY ? "AfterTool" : "PostToolUse"),
|
|
187
|
+
additionalContext: message
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
process.stdout.write(JSON.stringify(output));
|
|
192
|
+
} catch (e) {
|
|
193
|
+
// Silent fail -- never block tool execution
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pp-hook-version: {{PP_VERSION}}
|
|
3
|
+
// gsd-cursor-post-tool.js — Cursor postToolUse hook (issue #777)
|
|
4
|
+
//
|
|
5
|
+
// Cursor invokes this script after each tool call completes.
|
|
6
|
+
// Protocol: JSON from Cursor on stdin; JSON response on stdout.
|
|
7
|
+
//
|
|
8
|
+
// Input schema (cursor postToolUse):
|
|
9
|
+
// { tool_name, tool_input, tool_output, duration,
|
|
10
|
+
// conversation_id, generation_id, model, hook_event_name,
|
|
11
|
+
// cursor_version, workspace_roots, user_email, transcript_path }
|
|
12
|
+
//
|
|
13
|
+
// Output schema (cursor postToolUse):
|
|
14
|
+
// { additional_context?: string } ← injected as context after the tool use
|
|
15
|
+
//
|
|
16
|
+
// Behaviour:
|
|
17
|
+
// - After a write-class tool that targets .planning/, reminds the agent
|
|
18
|
+
// to keep STATE.md current.
|
|
19
|
+
// - Fails open: any error silently exits 0.
|
|
20
|
+
//
|
|
21
|
+
// Cursor docs: https://cursor.com/docs/hooks
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const WRITE_TOOL_RE = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
|
|
26
|
+
const PATH_KEY_RE = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
|
|
27
|
+
const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
|
|
28
|
+
|
|
29
|
+
let raw = '';
|
|
30
|
+
const stdinTimeout = setTimeout(() => {
|
|
31
|
+
// Timeout guard: exit silently rather than hanging.
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}, 10000);
|
|
34
|
+
|
|
35
|
+
process.stdin.setEncoding('utf8');
|
|
36
|
+
process.stdin.on('data', (chunk) => { raw += chunk; });
|
|
37
|
+
process.stdin.on('end', () => {
|
|
38
|
+
clearTimeout(stdinTimeout);
|
|
39
|
+
try {
|
|
40
|
+
let input;
|
|
41
|
+
try { input = JSON.parse(raw || '{}'); } catch { process.stdout.write(JSON.stringify({})); return; }
|
|
42
|
+
|
|
43
|
+
const toolName = String(
|
|
44
|
+
input.tool_name || input.toolName || ''
|
|
45
|
+
).toLowerCase();
|
|
46
|
+
|
|
47
|
+
const isWrite = WRITE_TOOL_RE.test(toolName);
|
|
48
|
+
if (!isWrite) { process.stdout.write(JSON.stringify({})); return; }
|
|
49
|
+
|
|
50
|
+
// Collect only PATH-bearing field values (not free-form content).
|
|
51
|
+
const paths = [];
|
|
52
|
+
const walk = (v, depth) => {
|
|
53
|
+
if (depth > 5 || paths.length > 64) return;
|
|
54
|
+
if (Array.isArray(v)) { for (const x of v) walk(x, depth + 1); return; }
|
|
55
|
+
if (v && typeof v === 'object') {
|
|
56
|
+
for (const k of Object.keys(v)) {
|
|
57
|
+
const val = v[k];
|
|
58
|
+
if (typeof val === 'string' && PATH_KEY_RE.test(k)) paths.push(val);
|
|
59
|
+
else walk(val, depth + 1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(input.tool_input || input.toolInput || {}, 0);
|
|
64
|
+
|
|
65
|
+
if (paths.some((p) => PLANNING_PATH_RE.test(p))) {
|
|
66
|
+
process.stdout.write(JSON.stringify({
|
|
67
|
+
additional_context:
|
|
68
|
+
'GSD: .planning/ artifact updated — ensure STATE.md reflects the latest phase and progress.',
|
|
69
|
+
}));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
} catch { /* fall through to empty response */ }
|
|
73
|
+
|
|
74
|
+
process.stdout.write(JSON.stringify({}));
|
|
75
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pp-hook-version: {{PP_VERSION}}
|
|
3
|
+
// gsd-cursor-session-start.js — Cursor sessionStart hook (issue #777)
|
|
4
|
+
//
|
|
5
|
+
// Cursor invokes this script at the start of each agent session.
|
|
6
|
+
// Protocol: JSON from Cursor on stdin; JSON response on stdout.
|
|
7
|
+
//
|
|
8
|
+
// Input schema (cursor sessionStart):
|
|
9
|
+
// { session_id, is_background_agent, composer_mode, conversation_id,
|
|
10
|
+
// generation_id, model, hook_event_name, cursor_version,
|
|
11
|
+
// workspace_roots, user_email, transcript_path }
|
|
12
|
+
//
|
|
13
|
+
// Output schema (cursor sessionStart):
|
|
14
|
+
// { additional_context?: string } ← injected into the session as context
|
|
15
|
+
//
|
|
16
|
+
// Behaviour:
|
|
17
|
+
// - If .planning/STATE.md is present, injects a brief state reminder.
|
|
18
|
+
// - If absent, nudges the user toward /gsd:new-project.
|
|
19
|
+
// - Fails open: any error silently exits 0 so a hook bug never wedges Cursor.
|
|
20
|
+
//
|
|
21
|
+
// Cursor docs: https://cursor.com/docs/hooks
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
const MSG_PRESENT =
|
|
29
|
+
'GSD: .planning/STATE.md is present — review the current phase and any blockers before acting.';
|
|
30
|
+
const MSG_ABSENT =
|
|
31
|
+
'GSD: no .planning/ workflow found — run /gsd:new-project to start a tracked workflow.';
|
|
32
|
+
|
|
33
|
+
let raw = '';
|
|
34
|
+
const stdinTimeout = setTimeout(() => {
|
|
35
|
+
// Timeout guard: exit silently rather than hanging.
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}, 10000);
|
|
38
|
+
|
|
39
|
+
process.stdin.setEncoding('utf8');
|
|
40
|
+
process.stdin.on('data', (chunk) => { raw += chunk; });
|
|
41
|
+
process.stdin.on('end', () => {
|
|
42
|
+
clearTimeout(stdinTimeout);
|
|
43
|
+
try {
|
|
44
|
+
const statePath = path.join(process.cwd(), '.planning', 'STATE.md');
|
|
45
|
+
const statePresent = fs.existsSync(statePath);
|
|
46
|
+
const msg = statePresent ? MSG_PRESENT : MSG_ABSENT;
|
|
47
|
+
process.stdout.write(JSON.stringify({ additional_context: msg }));
|
|
48
|
+
} catch {
|
|
49
|
+
// Fail open — never block a Cursor session because of a GSD hook error.
|
|
50
|
+
process.stdout.write(JSON.stringify({}));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pp-hook-version: {{PP_VERSION}}
|
|
3
|
+
//
|
|
4
|
+
// gsd-ensure-canonical-path — SessionStart hook (#997)
|
|
5
|
+
//
|
|
6
|
+
// PROBLEM: GSD agents/commands/templates use markdown `@`-file-includes that
|
|
7
|
+
// hardcode the canonical path `@~/.claude/gsd-core/...` (references, workflows,
|
|
8
|
+
// templates, contexts, bin). Markdown @-includes expand `~` but do NOT expand
|
|
9
|
+
// environment variables, so `${CLAUDE_PLUGIN_ROOT}` cannot be used in them.
|
|
10
|
+
// In a classic `bin/install.js` install the canonical path is a real directory
|
|
11
|
+
// holding the bundled tree, so the includes resolve. In a Claude Code
|
|
12
|
+
// *marketplace plugin* install the plugin manager only unpacks the package
|
|
13
|
+
// into the version-pinned plugin cache and never runs `bin/install.js`, so
|
|
14
|
+
// `~/.claude/gsd-core/` is never created and every @-include resolves to
|
|
15
|
+
// nothing — every agent that depends on one fails (e.g. the executor).
|
|
16
|
+
//
|
|
17
|
+
// FIX: On SessionStart, when running under a plugin install (CLAUDE_PLUGIN_ROOT
|
|
18
|
+
// set and a bundled `gsd-core/` tree found beneath it), ensure
|
|
19
|
+
// `~/.claude/gsd-core/` exists and its immutable subdirs (bin, contexts,
|
|
20
|
+
// references, templates, workflows) are symlinked to the plugin's bundled tree.
|
|
21
|
+
// This changes ZERO @-references, is a no-op in classic installs (where each
|
|
22
|
+
// subdir is already a real directory), preserves user-generated files
|
|
23
|
+
// (USER-PROFILE.md, STATE.md, VERSION, …), prunes stale links so it self-heals
|
|
24
|
+
// after `claude plugin update` rotates the version dir, and uses Windows
|
|
25
|
+
// junctions for symlinks on win32.
|
|
26
|
+
//
|
|
27
|
+
// SECURITY: the resolved bundled-tree path and every per-subdir link target are
|
|
28
|
+
// kept strictly inside the resolved plugin root (realpath-normalised, prefix-
|
|
29
|
+
// checked). A real (non-symlink) file or directory already sitting at a managed
|
|
30
|
+
// link target is NEVER clobbered.
|
|
31
|
+
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
const os = require('os');
|
|
37
|
+
|
|
38
|
+
// Immutable, bundled subdirectories that the canonical path must expose. These
|
|
39
|
+
// are the directories `@~/.claude/gsd-core/<subdir>/...` includes point into.
|
|
40
|
+
// User-generated artifacts (USER-PROFILE.md, STATE.md, VERSION, config, …) are
|
|
41
|
+
// NOT in this list and are never created, moved, or deleted by this hook.
|
|
42
|
+
const MANAGED_SUBDIRS = ['bin', 'contexts', 'references', 'templates', 'workflows'];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the canonical runtime config dir for the active runtime.
|
|
46
|
+
*
|
|
47
|
+
* Honours CLAUDE_CONFIG_DIR for custom/multi-account setups (mirrors
|
|
48
|
+
* gsd-check-update.js detectConfigDir), else falls back to ~/.claude. The
|
|
49
|
+
* canonical GSD tree always lives at `<configDir>/gsd-core`.
|
|
50
|
+
*/
|
|
51
|
+
function resolveConfigDir(homeDir, env) {
|
|
52
|
+
const envDir = env.CLAUDE_CONFIG_DIR;
|
|
53
|
+
if (envDir && typeof envDir === 'string' && envDir.trim().length > 0) {
|
|
54
|
+
return envDir;
|
|
55
|
+
}
|
|
56
|
+
return path.join(homeDir, '.claude');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Locate the bundled `gsd-core/` tree beneath a plugin root.
|
|
61
|
+
*
|
|
62
|
+
* Claude Code unpacks the package so the bundled tree sits at
|
|
63
|
+
* `<pluginRoot>/gsd-core/`. Returns the absolute, realpath-normalised path to
|
|
64
|
+
* that directory, or null if it is absent / not a directory. Resolving with
|
|
65
|
+
* realpath collapses symlinks/.. so the subsequent containment check is sound.
|
|
66
|
+
*/
|
|
67
|
+
function resolveBundledTree(pluginRoot) {
|
|
68
|
+
if (!pluginRoot || typeof pluginRoot !== 'string' || pluginRoot.trim().length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
let root;
|
|
72
|
+
try {
|
|
73
|
+
root = fs.realpathSync(pluginRoot);
|
|
74
|
+
} catch (_) {
|
|
75
|
+
return null; // plugin root does not exist
|
|
76
|
+
}
|
|
77
|
+
const bundled = path.join(root, 'pp-core');
|
|
78
|
+
let bundledReal;
|
|
79
|
+
try {
|
|
80
|
+
// The bundled tree must be a real directory (or a symlink to one) that
|
|
81
|
+
// resolves to a path inside the plugin root. realpathSync throws ENOENT/
|
|
82
|
+
// ENOTDIR if <pluginRoot>/gsd-core is absent, so no separate existence
|
|
83
|
+
// check is needed. Reject anything that does not resolve to a directory.
|
|
84
|
+
bundledReal = fs.realpathSync(bundled);
|
|
85
|
+
if (!fs.statSync(bundledReal).isDirectory()) return null;
|
|
86
|
+
} catch (_) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
// SECURITY: the resolved bundled tree must stay inside the resolved plugin
|
|
90
|
+
// root. A crafted symlink at <pluginRoot>/gsd-core pointing outside the root
|
|
91
|
+
// is rejected — we never link the canonical path at content we do not own.
|
|
92
|
+
const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep;
|
|
93
|
+
if (bundledReal !== root && !bundledReal.startsWith(rootWithSep)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return bundledReal;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The fs.symlinkSync `type` to use for a directory link on a given platform.
|
|
101
|
+
*
|
|
102
|
+
* On Windows, unprivileged users cannot create symlinks but CAN create
|
|
103
|
+
* junctions; 'junction' requires an absolute target (we always pass one). On
|
|
104
|
+
* POSIX a 'dir' symlink is used. Exported so the win32 branch is unit-testable
|
|
105
|
+
* without a Windows host.
|
|
106
|
+
*/
|
|
107
|
+
function dirLinkType(platform) {
|
|
108
|
+
return platform === 'win32' ? 'junction' : 'dir';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create a directory symlink (junction on win32) from linkPath -> target.
|
|
113
|
+
* Throws on real failure so the caller records it.
|
|
114
|
+
*/
|
|
115
|
+
function createDirLink(target, linkPath, platform) {
|
|
116
|
+
fs.symlinkSync(target, linkPath, dirLinkType(platform));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Does `linkPath` already correctly point at `expectedTarget`?
|
|
121
|
+
* Used to make the hook idempotent — a correct link is left untouched.
|
|
122
|
+
*/
|
|
123
|
+
function linkPointsAt(linkPath, expectedTarget) {
|
|
124
|
+
try {
|
|
125
|
+
if (!fs.lstatSync(linkPath).isSymbolicLink()) return false;
|
|
126
|
+
const resolved = fs.realpathSync(linkPath);
|
|
127
|
+
return resolved === fs.realpathSync(expectedTarget);
|
|
128
|
+
} catch (_) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Ensure the canonical `~/.claude/gsd-core/` path exposes the bundled subdirs.
|
|
135
|
+
*
|
|
136
|
+
* Pure, dependency-injected core so tests drive it with a fake home, fake
|
|
137
|
+
* plugin root, and explicit platform. Returns a structured result describing
|
|
138
|
+
* exactly what happened (never throws for ordinary conditions — only truly
|
|
139
|
+
* unexpected I/O errors propagate, and the thin CLI wrapper swallows those so
|
|
140
|
+
* a hook failure never blocks a session).
|
|
141
|
+
*
|
|
142
|
+
* @param {object} opts
|
|
143
|
+
* @param {string} [opts.homeDir] home directory (default os.homedir())
|
|
144
|
+
* @param {string} [opts.pluginRoot] CLAUDE_PLUGIN_ROOT (default from env)
|
|
145
|
+
* @param {string} [opts.platform] process.platform override (tests)
|
|
146
|
+
* @param {object} [opts.env] environment (default process.env)
|
|
147
|
+
* @returns {{status:string, canonicalDir?:string, bundledTree?:string,
|
|
148
|
+
* linked?:string[], prunedStale?:string[], preserved?:string[],
|
|
149
|
+
* skipped?:string[], reason?:string}}
|
|
150
|
+
*/
|
|
151
|
+
function ensureCanonicalPath(opts = {}) {
|
|
152
|
+
const env = opts.env || process.env;
|
|
153
|
+
const homeDir = opts.homeDir || os.homedir();
|
|
154
|
+
const platform = opts.platform || process.platform;
|
|
155
|
+
const pluginRoot = opts.pluginRoot !== undefined ? opts.pluginRoot : env.CLAUDE_PLUGIN_ROOT;
|
|
156
|
+
|
|
157
|
+
// Uniform result contract: every return carries the four action arrays so
|
|
158
|
+
// callers can read result.linked/etc without first switching on status.
|
|
159
|
+
const empty = { linked: [], prunedStale: [], preserved: [], skipped: [] };
|
|
160
|
+
|
|
161
|
+
// No plugin context → classic/npm install or non-plugin runtime. No-op.
|
|
162
|
+
const bundledTree = resolveBundledTree(pluginRoot);
|
|
163
|
+
if (!bundledTree) {
|
|
164
|
+
return { status: 'noop', reason: 'no-plugin-bundle', ...empty };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const configDir = resolveConfigDir(homeDir, env);
|
|
168
|
+
const canonicalDir = path.join(configDir, 'pp-core');
|
|
169
|
+
|
|
170
|
+
// Inspect the canonical path itself exactly once.
|
|
171
|
+
// - If it is a SYMLINK, the user (or another tool) deliberately pointed the
|
|
172
|
+
// canonical path elsewhere. We must NOT write managed links *through* that
|
|
173
|
+
// symlink into a directory we do not own — bail as a no-op.
|
|
174
|
+
// - If it is a REAL directory with at least one REAL (non-link) managed
|
|
175
|
+
// subdir, this is a classic `bin/install.js` install — leave it alone.
|
|
176
|
+
let canonicalStat = null;
|
|
177
|
+
try { canonicalStat = fs.lstatSync(canonicalDir); } catch (_) { canonicalStat = null; }
|
|
178
|
+
|
|
179
|
+
if (canonicalStat && canonicalStat.isSymbolicLink()) {
|
|
180
|
+
return { status: 'noop', reason: 'canonical-is-symlink', canonicalDir, bundledTree, ...empty };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (canonicalStat && canonicalStat.isDirectory()) {
|
|
184
|
+
for (const sub of MANAGED_SUBDIRS) {
|
|
185
|
+
try {
|
|
186
|
+
const subSt = fs.lstatSync(path.join(canonicalDir, sub));
|
|
187
|
+
if (subSt.isDirectory() && !subSt.isSymbolicLink()) {
|
|
188
|
+
return { status: 'noop', reason: 'classic-install', canonicalDir, bundledTree, ...empty };
|
|
189
|
+
}
|
|
190
|
+
} catch (_) { /* subdir absent — keep checking */ }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Ensure the canonical directory exists (as a real directory). We never
|
|
195
|
+
// replace an existing real directory; recursive mkdir is a no-op if present.
|
|
196
|
+
try {
|
|
197
|
+
fs.mkdirSync(canonicalDir, { recursive: true });
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return { status: 'error', reason: `mkdir-canonical: ${e.code || e.message}`, canonicalDir, bundledTree, ...empty };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const linked = [];
|
|
203
|
+
const prunedStale = [];
|
|
204
|
+
const preserved = [];
|
|
205
|
+
const skipped = [];
|
|
206
|
+
|
|
207
|
+
// SECURITY: prefix used to confirm every per-subdir link target resolves
|
|
208
|
+
// strictly inside the bundled tree. Defence-in-depth against a tampered
|
|
209
|
+
// bundle that ships an internally-escaping symlink at <bundledTree>/<sub>.
|
|
210
|
+
const bundledWithSep = bundledTree.endsWith(path.sep) ? bundledTree : bundledTree + path.sep;
|
|
211
|
+
|
|
212
|
+
for (const sub of MANAGED_SUBDIRS) {
|
|
213
|
+
const target = path.join(bundledTree, sub);
|
|
214
|
+
// Only expose subdirs the bundle actually ships, AND only when the target
|
|
215
|
+
// resolves to a real directory that stays inside the bundled tree. A
|
|
216
|
+
// subdir whose realpath escapes the bundle (e.g. a planted symlink) is
|
|
217
|
+
// skipped — we never point the canonical path at content outside the
|
|
218
|
+
// validated plugin bundle.
|
|
219
|
+
let targetIsDir = false;
|
|
220
|
+
try {
|
|
221
|
+
const targetReal = fs.realpathSync(target);
|
|
222
|
+
// A NAMED subdir must resolve strictly BELOW the bundled tree root. We do
|
|
223
|
+
// NOT accept targetReal === bundledTree here: a subdir that self-links to
|
|
224
|
+
// the tree root would otherwise be exposed at the wrong level (e.g.
|
|
225
|
+
// `workflows` -> the whole tree), making `@.../workflows/foo` resolve to
|
|
226
|
+
// `<tree>/foo` instead of `<tree>/workflows/foo`.
|
|
227
|
+
targetIsDir = fs.statSync(targetReal).isDirectory()
|
|
228
|
+
&& targetReal.startsWith(bundledWithSep);
|
|
229
|
+
} catch (_) { targetIsDir = false; }
|
|
230
|
+
if (!targetIsDir) {
|
|
231
|
+
skipped.push(sub);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const linkPath = path.join(canonicalDir, sub);
|
|
236
|
+
|
|
237
|
+
// Already a correct link → idempotent no-op.
|
|
238
|
+
if (linkPointsAt(linkPath, target)) {
|
|
239
|
+
linked.push(sub);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let existing = null;
|
|
244
|
+
try { existing = fs.lstatSync(linkPath); } catch (_) { existing = null; }
|
|
245
|
+
|
|
246
|
+
if (existing) {
|
|
247
|
+
// lstat().isSymbolicLink() is true for BOTH POSIX symlinks and Windows
|
|
248
|
+
// junctions, so this single predicate identifies every GSD-managed link.
|
|
249
|
+
if (existing.isSymbolicLink()) {
|
|
250
|
+
// A GSD-managed link that is stale or points elsewhere (e.g. previous
|
|
251
|
+
// plugin version after `claude plugin update`). Prune and recreate.
|
|
252
|
+
try {
|
|
253
|
+
fs.unlinkSync(linkPath);
|
|
254
|
+
prunedStale.push(sub);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
skipped.push(sub);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
// A REAL file or directory the user (or a classic install) owns. NEVER
|
|
261
|
+
// clobber it — preserve it untouched. This is the USER-PROFILE.md /
|
|
262
|
+
// partially-real-canonical-dir safety case.
|
|
263
|
+
preserved.push(sub);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
createDirLink(target, linkPath, platform);
|
|
270
|
+
linked.push(sub);
|
|
271
|
+
} catch (e) {
|
|
272
|
+
skipped.push(sub);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
status: 'ensured',
|
|
278
|
+
canonicalDir,
|
|
279
|
+
bundledTree,
|
|
280
|
+
linked,
|
|
281
|
+
prunedStale,
|
|
282
|
+
preserved,
|
|
283
|
+
skipped,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
ensureCanonicalPath,
|
|
289
|
+
resolveBundledTree,
|
|
290
|
+
resolveConfigDir,
|
|
291
|
+
dirLinkType,
|
|
292
|
+
MANAGED_SUBDIRS,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// CLI entry: run on SessionStart. Never block the session — any unexpected
|
|
296
|
+
// failure is swallowed (best-effort self-heal). Emit nothing on stdout to keep
|
|
297
|
+
// the hook silent in normal operation.
|
|
298
|
+
if (require.main === module) {
|
|
299
|
+
try {
|
|
300
|
+
ensureCanonicalPath();
|
|
301
|
+
} catch (_) {
|
|
302
|
+
// Best-effort: a canonical-path failure must never abort a session.
|
|
303
|
+
}
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|