@opengsd/gsd-core 1.17.0 → 1.19.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 +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +3 -3
- package/agents/gsd-code-fixer.md +5 -4
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +460 -105
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +124 -18
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +41 -8
- package/gsd-core/bin/lib/capability-registry.cjs +162 -74
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +20 -11
- package/gsd-core/bin/lib/capability-validator.cjs +23 -4
- package/gsd-core/bin/lib/commands.cjs +5 -1
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +93 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +21 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +19 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +114 -18
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +76 -5
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -7
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +235 -37
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +89 -30
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +428 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +123 -16
- package/gsd-core/bin/lib/smart-entry.cjs +37 -16
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +47 -5
- package/gsd-core/bin/lib/state-transition.cjs +150 -34
- package/gsd-core/bin/lib/state.cjs +140 -88
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +7 -28
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +38 -1
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +14 -3
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +5 -4
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +66 -8
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +64 -157
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +34 -6
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +44 -5
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +11 -11
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +19 -8
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +21 -3
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +7 -3
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +46 -11
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +6 -2
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +7 -3
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +54 -86
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +23 -8
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +5 -1
- package/gsd-core/workflows/review.md +22 -15
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +4 -3
- package/gsd-core/workflows/ship.md +16 -14
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +15 -6
- package/hooks/dist/gsd-agent-isolation-guard.js +14 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -3
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +244 -53
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +14 -2
- package/hooks/gsd-cursor-subagent-start.js +8 -3
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +244 -53
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +19 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +41 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/release-tarball-smoke.cjs +1 -0
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -35,6 +35,7 @@ const {
|
|
|
35
35
|
getGlobalSkillsBase,
|
|
36
36
|
resolveKimiHooksTomlDir,
|
|
37
37
|
isRegisteredRuntimeId,
|
|
38
|
+
resolveGsdHome,
|
|
38
39
|
} = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
|
39
40
|
// #2870: the Install Scope Module — turns a bare 'global' | 'local' scope id
|
|
40
41
|
// plus a runtime into one resolved value (configHome, settingsFile,
|
|
@@ -62,6 +63,8 @@ const { composeWorkflow } = require('../gsd-core/bin/lib/workflow-fragments.cjs'
|
|
|
62
63
|
// #3072: THE shared composition-scope predicate (also consumed by the served
|
|
63
64
|
// MCP catalog, src/mcp-catalog.cts) — see the comment at its call site below.
|
|
64
65
|
const { shouldCompose } = require('../gsd-core/bin/lib/mcp-catalog.cjs');
|
|
66
|
+
// #5637: per-install terminator for the typed-text capture heredoc.
|
|
67
|
+
const { armArgsNonce, disarmArgsNonce, stampInstalledContent } =require('../gsd-core/bin/lib/args-terminator.cjs');
|
|
65
68
|
const runtimeArtifactConversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
|
66
69
|
const { escapeRegex: escapeRegExp } = require('../gsd-core/bin/lib/pattern.cjs');
|
|
67
70
|
// #2873: cross-scope shadow detection — reports (never fails) when a
|
|
@@ -843,6 +846,9 @@ const {
|
|
|
843
846
|
migrateLegacyDevPreferencesToSkill,
|
|
844
847
|
USER_OWNED_ARTIFACTS,
|
|
845
848
|
_snapshotDir,
|
|
849
|
+
createSkillDirOwnership,
|
|
850
|
+
_warnPreservedSkillDirs,
|
|
851
|
+
USER_OWNED_SKILL_DIRS,
|
|
846
852
|
} = installEngine;
|
|
847
853
|
|
|
848
854
|
// #2875 (epic #2866 Phase 6): durable on-disk staging for USER_OWNED_ARTIFACTS
|
|
@@ -1639,14 +1645,13 @@ const claudeToOpencodeTools = {
|
|
|
1639
1645
|
const claudeToAntigravityTools = {
|
|
1640
1646
|
// #4705: Antigravity-NATIVE tool names (see src/runtime-artifact-conversion.cts)
|
|
1641
1647
|
Read: 'view_file',
|
|
1642
|
-
Write: '
|
|
1648
|
+
Write: 'write_to_file',
|
|
1643
1649
|
Edit: 'replace_file_content',
|
|
1644
1650
|
Bash: 'run_command',
|
|
1645
|
-
Glob: '
|
|
1651
|
+
Glob: 'find_by_name',
|
|
1646
1652
|
Grep: 'grep_search',
|
|
1647
|
-
WebSearch: '
|
|
1648
|
-
WebFetch: '
|
|
1649
|
-
TodoWrite: 'write_todos',
|
|
1653
|
+
WebSearch: 'search_web',
|
|
1654
|
+
WebFetch: 'read_url_content',
|
|
1650
1655
|
}
|
|
1651
1656
|
|
|
1652
1657
|
// Tool name mapping from Claude/GSD agents to Kimi CLI module paths.
|
|
@@ -1715,13 +1720,16 @@ function convertAntigravityToolName(claudeTool) {
|
|
|
1715
1720
|
// built-in tool; the lowercase fallback would emit an invalid
|
|
1716
1721
|
// 'skill'/'slashcommand' name that fails frontmatter validation
|
|
1717
1722
|
// (tools.N: Invalid tool name) and aborts the entire agent load (#1394).
|
|
1723
|
+
// TodoWrite: exclude — agy 1.3.5 registers no todo tool, and an unknown name
|
|
1724
|
+
// stops the agent from starting (#5447).
|
|
1718
1725
|
if (
|
|
1719
1726
|
claudeTool === 'Task' ||
|
|
1720
1727
|
claudeTool === 'Agent' ||
|
|
1721
1728
|
claudeTool === 'AskUserQuestion' ||
|
|
1722
1729
|
claudeTool === 'ask_user' ||
|
|
1723
1730
|
claudeTool === 'Skill' ||
|
|
1724
|
-
claudeTool === 'SlashCommand'
|
|
1731
|
+
claudeTool === 'SlashCommand' ||
|
|
1732
|
+
claudeTool === 'TodoWrite'
|
|
1725
1733
|
) {
|
|
1726
1734
|
return null;
|
|
1727
1735
|
}
|
|
@@ -2155,43 +2163,11 @@ function parseKimiAgentSource(source) {
|
|
|
2155
2163
|
};
|
|
2156
2164
|
}
|
|
2157
2165
|
|
|
2166
|
+
// #5447: the tools list is read by the compiled module's parser (quoted entries,
|
|
2167
|
+
// `#` comments, inline and block lists), the one the runtime pipeline uses, so this
|
|
2168
|
+
// copy of the Antigravity and Kimi converters cannot diverge from it.
|
|
2158
2169
|
function parseFrontmatterTools(frontmatter) {
|
|
2159
|
-
|
|
2160
|
-
const lines = frontmatter.split(/\r?\n/);
|
|
2161
|
-
const tools = [];
|
|
2162
|
-
let collecting = false;
|
|
2163
|
-
|
|
2164
|
-
for (const line of lines) {
|
|
2165
|
-
const trimmed = line.trim();
|
|
2166
|
-
if (!trimmed) continue;
|
|
2167
|
-
|
|
2168
|
-
if (collecting) {
|
|
2169
|
-
if (trimmed.startsWith('- ')) {
|
|
2170
|
-
tools.push(trimmed.slice(2).trim());
|
|
2171
|
-
continue;
|
|
2172
|
-
}
|
|
2173
|
-
collecting = false;
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
if (trimmed === 'tools:' || trimmed === 'allowed-tools:') {
|
|
2177
|
-
collecting = true;
|
|
2178
|
-
continue;
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
if (trimmed.startsWith('tools:') || trimmed.startsWith('allowed-tools:')) {
|
|
2182
|
-
const value = trimmed.slice(trimmed.indexOf(':') + 1).trim();
|
|
2183
|
-
if (value) {
|
|
2184
|
-
for (const tool of value.split(',')) {
|
|
2185
|
-
const name = tool.trim();
|
|
2186
|
-
if (name) tools.push(name);
|
|
2187
|
-
}
|
|
2188
|
-
} else {
|
|
2189
|
-
collecting = true;
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
return tools;
|
|
2170
|
+
return runtimeArtifactConversion.parseFrontmatterTools(frontmatter);
|
|
2195
2171
|
}
|
|
2196
2172
|
|
|
2197
2173
|
function addKimiAgentDiagnostic(diagnostics, code, message, value, source = null) {
|
|
@@ -2499,10 +2475,11 @@ function convertClaudeAgentToAntigravityAgent(content, isGlobal = false) {
|
|
|
2499
2475
|
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
2500
2476
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
2501
2477
|
const color = extractFrontmatterField(frontmatter, 'color');
|
|
2502
|
-
const toolsRaw = extractFrontmatterField(frontmatter, 'tools') || '';
|
|
2503
2478
|
|
|
2504
|
-
// Map tools to Antigravity equivalents (reuse existing convertAntigravityToolName)
|
|
2505
|
-
|
|
2479
|
+
// Map tools to Antigravity equivalents (reuse existing convertAntigravityToolName).
|
|
2480
|
+
// parseFrontmatterTools reads both the inline comma list and the YAML block
|
|
2481
|
+
// list (e.g. agents/gsd-security-auditor.md) (#5447).
|
|
2482
|
+
const claudeTools = parseFrontmatterTools(frontmatter);
|
|
2506
2483
|
const mappedTools = claudeTools.map(t => convertAntigravityToolName(t)).filter(Boolean);
|
|
2507
2484
|
|
|
2508
2485
|
// #2876: quote description for the same reason as the skill variant.
|
|
@@ -3274,6 +3251,12 @@ function _stripCallArgument(spanText, matchStart, matchEnd, { consumeLeadingComm
|
|
|
3274
3251
|
*/
|
|
3275
3252
|
function _projectRoleArgument(spanText, matchStart, matchEnd, roleValueExpr, toolConfig, canOrchestrate) {
|
|
3276
3253
|
const { namedRoleParam, promptContentParam, structuralRoleParam, leafRoleValue } = toolConfig;
|
|
3254
|
+
// #5569: a primitive that resolves the role prompt itself (Pi `gsd_dispatch`)
|
|
3255
|
+
// takes the role name alone — no prompt-content argument, no structural role.
|
|
3256
|
+
// The argument's own separator is left in place.
|
|
3257
|
+
if (toolConfig.rolePromptResolution === 'tool') {
|
|
3258
|
+
return spanText.slice(0, matchStart) + `${namedRoleParam}=${roleValueExpr}` + spanText.slice(matchEnd);
|
|
3259
|
+
}
|
|
3277
3260
|
const lineStart = spanText.lastIndexOf('\n', matchStart - 1) + 1;
|
|
3278
3261
|
const startsOwnLine = /^[ \t]*$/.test(spanText.slice(lineStart, matchStart));
|
|
3279
3262
|
|
|
@@ -3405,6 +3388,20 @@ function _normalizeDispatchCallSpan(spanText, hasBraceWrapper, dispatch, toolCon
|
|
|
3405
3388
|
}
|
|
3406
3389
|
}
|
|
3407
3390
|
|
|
3391
|
+
// 1b. #5569: a tool-resolved primitive (`rolePromptResolution: 'tool'`) takes
|
|
3392
|
+
// `role` + `task` only. The task text arrives as `prompt`, so the key is
|
|
3393
|
+
// renamed; `description` has no counterpart and is dropped.
|
|
3394
|
+
if (toolConfig.rolePromptResolution === 'tool') {
|
|
3395
|
+
const pm = /\bprompt(\s*[=:])/.exec(_maskStringsAndComments(text));
|
|
3396
|
+
if (pm) {
|
|
3397
|
+
text = text.slice(0, pm.index) + toolConfig.taskParam + text.slice(pm.index + 'prompt'.length);
|
|
3398
|
+
}
|
|
3399
|
+
const dm = _callArgValueRe('description').exec(_maskStringsAndComments(text));
|
|
3400
|
+
if (dm) {
|
|
3401
|
+
text = _stripCallArgument(text, dm.index, dm.index + dm[0].length);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3408
3405
|
// 2. Per-call model argument (model= / model:) — stripped entirely when the
|
|
3409
3406
|
// target has no per-call model-selection parameter (there is no
|
|
3410
3407
|
// `dispatch` axis for this — it is inherent tool vocabulary, like the
|
|
@@ -3636,6 +3633,15 @@ function projectNamedDispatchToStructuralDelegate(content, dispatch, toolConfig)
|
|
|
3636
3633
|
if (namedDispatch) {
|
|
3637
3634
|
return `The \`${toolName}\` tool IS available in a top-level ${sessionName} session. Always dispatch ${rosterFlat} as separate \`${toolName}()\` calls.`;
|
|
3638
3635
|
}
|
|
3636
|
+
if (toolConfig.rolePromptResolution === 'tool') {
|
|
3637
|
+
return (
|
|
3638
|
+
`The \`${toolName}\` tool IS available in a top-level ${sessionName} session. It takes a \`${toolConfig.namedRoleParam}\` ` +
|
|
3639
|
+
`(the installed gsd-* role name) and a \`${toolConfig.taskParam}\`, resolves the role prompt from the install itself, and ` +
|
|
3640
|
+
'runs the role in an isolated child session; the child cannot dispatch further. FAIL CLOSED — surface the ' +
|
|
3641
|
+
`tool's error and stop — if it reports the role unresolved; never execute the role inline as a substitute. ` +
|
|
3642
|
+
`Always dispatch ${rosterFlat} as separate \`${toolName}\` calls.`
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3639
3645
|
return (
|
|
3640
3646
|
`${sessionName} has no \`Agent\` tool. It exposes \`${toolName}\`, which dispatches by structural role — ` +
|
|
3641
3647
|
'it has no concept of a named subagent identity. GSD projects each named gsd-* role onto this primitive ' +
|
|
@@ -3793,6 +3799,31 @@ function convertClaudeToHermesMarkdown(content, ctx) {
|
|
|
3793
3799
|
|
|
3794
3800
|
// ── End Hermes converters ────────────────────────────────────────────────────
|
|
3795
3801
|
|
|
3802
|
+
// #5569: Pi projects host-neutral `Agent(...)` calls onto its `gsd_dispatch`
|
|
3803
|
+
// tool (pi/gsd.cjs). The tool resolves and verifies the role prompt itself, so
|
|
3804
|
+
// the projection only renames the arguments (`rolePromptResolution: 'tool'`).
|
|
3805
|
+
const PI_DISPATCH_TOOL_CONFIG = Object.freeze({
|
|
3806
|
+
toolName: 'gsd_dispatch',
|
|
3807
|
+
namedRoleParam: 'role',
|
|
3808
|
+
taskParam: 'task',
|
|
3809
|
+
promptContentParam: 'task',
|
|
3810
|
+
structuralRoleParam: 'role',
|
|
3811
|
+
leafRoleValue: 'leaf',
|
|
3812
|
+
backgroundParam: 'background',
|
|
3813
|
+
supportsPerCallModel: false,
|
|
3814
|
+
rolePromptResolution: 'tool',
|
|
3815
|
+
});
|
|
3816
|
+
|
|
3817
|
+
function convertClaudeToPiMarkdown(content, ctx) {
|
|
3818
|
+
const runtime = (ctx && ctx.runtime) || 'pi';
|
|
3819
|
+
const dispatch = _hostIntegrationDispatch(runtime);
|
|
3820
|
+
const toolConfig = Object.assign({}, PI_DISPATCH_TOOL_CONFIG, {
|
|
3821
|
+
availableRoles: _resolveAvailableGsdRoles(),
|
|
3822
|
+
runtime,
|
|
3823
|
+
});
|
|
3824
|
+
return projectNamedDispatchToStructuralDelegate(content, dispatch, toolConfig);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3796
3827
|
function convertSlashCommandsToCodexSkillMentions(content) {
|
|
3797
3828
|
// Colon-style /gsd: never appears as a filesystem path segment, so no boundary guard is needed (unlike the hyphen-style below).
|
|
3798
3829
|
let converted = content.replace(/\/gsd:([a-z0-9-]+)/gi, (_, commandName) => {
|
|
@@ -4284,8 +4315,8 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
4284
4315
|
* - Returns immediately if skillsDir does not exist (fails open).
|
|
4285
4316
|
* - Only touches directories whose names start with "gsd-".
|
|
4286
4317
|
* - Skips user-owned dirs (gsd-dev-preferences) — their agents/ content is
|
|
4287
|
-
* never modified,
|
|
4288
|
-
* installOpencodeFamilySkills.
|
|
4318
|
+
* never modified, by the same USER_OWNED_SKILL_DIRS guard
|
|
4319
|
+
* installOpencodeFamilySkills uses.
|
|
4289
4320
|
* - For each managed gsd-* dir, if agents/openai.yaml exists, deletes it.
|
|
4290
4321
|
* - If agents/ is now empty, removes the directory; if it still contains
|
|
4291
4322
|
* other files (e.g. user-added content), leaves it in place.
|
|
@@ -4295,15 +4326,17 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
4295
4326
|
*
|
|
4296
4327
|
* @param {string} skillsDir - Path to the skills/ directory (e.g. ~/.codex/skills)
|
|
4297
4328
|
*/
|
|
4298
|
-
function cleanupCodexSkillMetadataSidecars(skillsDir) {
|
|
4329
|
+
function cleanupCodexSkillMetadataSidecars(skillsDir, owns) {
|
|
4299
4330
|
if (!fs.existsSync(skillsDir)) return;
|
|
4300
|
-
//
|
|
4301
|
-
//
|
|
4302
|
-
//
|
|
4303
|
-
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
4331
|
+
// USER_OWNED_SKILL_DIRS (#2973) is the engine's one list. We MUST skip these
|
|
4332
|
+
// dirs — their contents are user-generated and must never be modified by
|
|
4333
|
+
// GSD's install path.
|
|
4304
4334
|
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
4305
4335
|
if (!entry.isDirectory() || !entry.name.startsWith('gsd-')) continue;
|
|
4306
|
-
if (
|
|
4336
|
+
if (USER_OWNED_SKILL_DIRS.includes(entry.name)) continue; // preserve user content
|
|
4337
|
+
// #5161: install keeps gsd-* dirs GSD does not own; their files are not
|
|
4338
|
+
// GSD's sidecars to clean.
|
|
4339
|
+
if (owns && !owns(entry.name)) continue;
|
|
4307
4340
|
const agentsSubdir = path.join(skillsDir, entry.name, 'agents');
|
|
4308
4341
|
const sidecarPath = path.join(agentsSubdir, 'openai.yaml');
|
|
4309
4342
|
try {
|
|
@@ -4337,21 +4370,27 @@ function cleanupCodexSkillMetadataSidecars(skillsDir) {
|
|
|
4337
4370
|
* - non-gsd-* dirs under .devin/skills/ (user-authored skills)
|
|
4338
4371
|
* - gsd-dev-preferences/ (user-owned per #2973)
|
|
4339
4372
|
* - any files (not dirs) under .devin/skills/
|
|
4373
|
+
* - with `owns` (#5161), any gsd-* dir GSD does not own, which is named by path
|
|
4340
4374
|
*
|
|
4341
4375
|
* @param {string} workspaceDir - workspace root (process.cwd() for local installs)
|
|
4376
|
+
* @param {(name: string) => boolean} [owns] ownership predicate (createSkillDirOwnership)
|
|
4342
4377
|
* @returns {number} count of removed legacy gsd-* skill directories
|
|
4343
4378
|
*/
|
|
4344
|
-
function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
4379
|
+
function cleanupWindsurfLegacyDevinSkills(workspaceDir, owns) {
|
|
4345
4380
|
const legacySkillsDir = path.join(workspaceDir, '.devin', 'skills');
|
|
4346
4381
|
if (!fs.existsSync(legacySkillsDir)) return 0;
|
|
4347
4382
|
|
|
4348
|
-
// Mirror the user-owned list from cleanupCodexSkillMetadataSidecars (#2973).
|
|
4349
|
-
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
4350
4383
|
let removed = 0;
|
|
4351
4384
|
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4385
|
+
// Decide ownership once, BEFORE deleting: the capability-marker leg reads the
|
|
4386
|
+
// dir itself, so asking after a removal would report the removed dir as kept.
|
|
4387
|
+
// USER_OWNED_SKILL_DIRS (#2973) dirs are never candidates.
|
|
4388
|
+
const candidates = fs.readdirSync(legacySkillsDir, { withFileTypes: true })
|
|
4389
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('gsd-') && !USER_OWNED_SKILL_DIRS.includes(entry.name));
|
|
4390
|
+
const notOwned = owns ? candidates.filter((entry) => !owns(entry.name)).map((entry) => entry.name) : [];
|
|
4391
|
+
|
|
4392
|
+
for (const entry of candidates) {
|
|
4393
|
+
if (notOwned.includes(entry.name)) continue;
|
|
4355
4394
|
|
|
4356
4395
|
const dirToRemove = path.join(legacySkillsDir, entry.name);
|
|
4357
4396
|
try {
|
|
@@ -4366,6 +4405,7 @@ function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
|
4366
4405
|
// Fail open — a single bad dir must not block the install.
|
|
4367
4406
|
}
|
|
4368
4407
|
}
|
|
4408
|
+
_warnPreservedSkillDirs(legacySkillsDir, notOwned);
|
|
4369
4409
|
|
|
4370
4410
|
// If .devin/skills/ is now empty, prune it. If .devin/ itself is then empty,
|
|
4371
4411
|
// prune that too — leaves the workspace clean for the new .windsurf/ layout.
|
|
@@ -4392,20 +4432,29 @@ function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
|
4392
4432
|
* relocating skills to ~/.agents/skills) orphans the pre-move dirs at
|
|
4393
4433
|
* ~/.codex/skills. Only managed `<prefix>*` dirs are touched; user-owned content
|
|
4394
4434
|
* (non-prefixed dirs, gsd-dev-preferences, symlinks) is preserved. Fail-open.
|
|
4435
|
+
*
|
|
4436
|
+
* #5161: with `owns`, a `<prefix>*` dir is removed only when GSD owns it; any
|
|
4437
|
+
* other is kept and named by path. Install passes it. Uninstall does not: it
|
|
4438
|
+
* removes every `gsd-*` skill dir, at either location.
|
|
4395
4439
|
* @param {string} oldSkillsDir absolute path to the pre-move skills location
|
|
4396
4440
|
* @param {string} prefix managed skill-dir prefix (e.g. 'gsd-')
|
|
4441
|
+
* @param {(name: string) => boolean} [owns] ownership predicate (createSkillDirOwnership)
|
|
4397
4442
|
* @returns {number} count of stale dirs removed
|
|
4398
4443
|
*/
|
|
4399
|
-
function cleanupMovedSkillsOldLocation(oldSkillsDir, prefix) {
|
|
4444
|
+
function cleanupMovedSkillsOldLocation(oldSkillsDir, prefix, owns) {
|
|
4400
4445
|
if (!fs.existsSync(oldSkillsDir)) return 0;
|
|
4401
4446
|
|
|
4402
|
-
// Mirror the user-owned list from cleanupCodexSkillMetadataSidecars (#2973).
|
|
4403
|
-
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
4404
4447
|
let removed = 0;
|
|
4405
4448
|
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4449
|
+
// Decide ownership once, BEFORE deleting: the capability-marker leg reads the
|
|
4450
|
+
// dir itself, so asking after a removal would report the removed dir as kept.
|
|
4451
|
+
// USER_OWNED_SKILL_DIRS (#2973) dirs are never candidates.
|
|
4452
|
+
const candidates = fs.readdirSync(oldSkillsDir, { withFileTypes: true })
|
|
4453
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith(prefix) && !USER_OWNED_SKILL_DIRS.includes(entry.name));
|
|
4454
|
+
const notOwned = owns ? candidates.filter((entry) => !owns(entry.name)).map((entry) => entry.name) : [];
|
|
4455
|
+
|
|
4456
|
+
for (const entry of candidates) {
|
|
4457
|
+
if (notOwned.includes(entry.name)) continue;
|
|
4409
4458
|
|
|
4410
4459
|
const dirToRemove = path.join(oldSkillsDir, entry.name);
|
|
4411
4460
|
try {
|
|
@@ -4420,6 +4469,7 @@ function cleanupMovedSkillsOldLocation(oldSkillsDir, prefix) {
|
|
|
4420
4469
|
// Fail open — a single bad dir must not block install/uninstall.
|
|
4421
4470
|
}
|
|
4422
4471
|
}
|
|
4472
|
+
_warnPreservedSkillDirs(oldSkillsDir, notOwned);
|
|
4423
4473
|
|
|
4424
4474
|
// Prune the old skills dir if now empty — leaves the configHome clean.
|
|
4425
4475
|
// Never remove a non-empty container (user may keep other content there).
|
|
@@ -6477,7 +6527,28 @@ function splitTopLevelKeys(content) {
|
|
|
6477
6527
|
return { topLevel: content.trim(), rest: '' };
|
|
6478
6528
|
}
|
|
6479
6529
|
|
|
6530
|
+
/**
|
|
6531
|
+
* #5301: the path a config write must target. atomicWriteFileSync ends in a
|
|
6532
|
+
* rename(2) onto its destination, which REPLACES a symlink at that path rather than
|
|
6533
|
+
* writing through it, so a dotfiles-managed `config.toml -> ~/dotfiles/codex.toml`
|
|
6534
|
+
* became a plain file and the user's copy never saw the merge. With
|
|
6535
|
+
* GSD_ALLOW_SYMLINKED_DEST the symlink is intentional: resolve it and write the
|
|
6536
|
+
* file it points at, leaving the link itself alone. Without the opt-in the path is
|
|
6537
|
+
* returned unchanged (the install-root symlink guard refuses such a layout before
|
|
6538
|
+
* any write), and so does a dangling link (the guard cannot resolve it), so only a
|
|
6539
|
+
* link whose target exists reaches the resolution below.
|
|
6540
|
+
*/
|
|
6541
|
+
function resolveSymlinkWriteTarget(filePath) {
|
|
6542
|
+
if (!isSymlinkedDestOptIn()) return filePath;
|
|
6543
|
+
let stat;
|
|
6544
|
+
try { stat = fs.lstatSync(filePath); } catch (_) { return filePath; }
|
|
6545
|
+
if (!stat.isSymbolicLink()) return filePath;
|
|
6546
|
+
try { return fs.realpathSync(filePath); } catch (_) { return filePath; }
|
|
6547
|
+
}
|
|
6548
|
+
|
|
6480
6549
|
function mergeCodexConfig(configPath, gsdBlock) {
|
|
6550
|
+
// #5301: both write sites below land on the symlink's target, never replace the link.
|
|
6551
|
+
configPath = resolveSymlinkWriteTarget(configPath);
|
|
6481
6552
|
// Case 1: No config.toml — create fresh
|
|
6482
6553
|
if (!fs.existsSync(configPath)) {
|
|
6483
6554
|
atomicWriteFileSync(configPath, gsdBlock + '\n');
|
|
@@ -7033,7 +7104,7 @@ function recordDefaultsProvenance(gsdDir, runtime, defaults, setNow) {
|
|
|
7033
7104
|
/** @returns {boolean} true when it removed or reverted something */
|
|
7034
7105
|
function revertNonClaudeDefaults(runtime) {
|
|
7035
7106
|
if (hostBehaviorsFor(runtime).nativeModelAliases || process.env.GSD_TEST_MODE) return false;
|
|
7036
|
-
const gsdDir = path.join(
|
|
7107
|
+
const gsdDir = path.join(resolveGsdHome(), '.gsd'); // #5017: GSD_HOME-aware, like the loader
|
|
7037
7108
|
const provPath = path.join(gsdDir, DEFAULTS_PROVENANCE_NAME);
|
|
7038
7109
|
if (!fs.existsSync(provPath)) return false;
|
|
7039
7110
|
let releaseLock = null;
|
|
@@ -7086,7 +7157,7 @@ function revertNonClaudeDefaults(runtime) {
|
|
|
7086
7157
|
*/
|
|
7087
7158
|
function writeNonClaudeDefaults(runtime) {
|
|
7088
7159
|
if (hostBehaviorsFor(runtime).nativeModelAliases || process.env.GSD_TEST_MODE) return;
|
|
7089
|
-
const gsdDir = path.join(
|
|
7160
|
+
const gsdDir = path.join(resolveGsdHome(), '.gsd'); // #5017: GSD_HOME-aware, like the loader
|
|
7090
7161
|
const defaultsPath = path.join(gsdDir, 'defaults.json');
|
|
7091
7162
|
let releaseLock = null;
|
|
7092
7163
|
try {
|
|
@@ -7690,6 +7761,23 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
|
|
|
7690
7761
|
// combinedFamilyInstall path (installOpencodeFamilyArtifacts) instead of the
|
|
7691
7762
|
// bespoke inline block that used to call this function.
|
|
7692
7763
|
|
|
7764
|
+
/**
|
|
7765
|
+
* Count the gsd-* skill dirs in `skillsDir` for "Installed N skills" and the
|
|
7766
|
+
* non-empty check that detects a failed install (#5161): the dirs GSD owns,
|
|
7767
|
+
* plus any USER_OWNED_SKILL_DIRS entry present (gsd-dev-preferences), which
|
|
7768
|
+
* both counted before #5161. A gsd-* dir the prune kept because GSD does not
|
|
7769
|
+
* own it is not counted, so it can neither inflate the line nor stand in for
|
|
7770
|
+
* an install that wrote nothing.
|
|
7771
|
+
*/
|
|
7772
|
+
function _countOwnedSkillDirs(runtime, configDir, skillsDir) {
|
|
7773
|
+
// A verification count fails closed: an unreadable install source must not
|
|
7774
|
+
// let a user's gsd-* dir stand in for an install that wrote nothing.
|
|
7775
|
+
const owns = createSkillDirOwnership(runtime, configDir, skillsDir, 'gsd-', { includeManifest: false, includeWritten: true });
|
|
7776
|
+
return fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
7777
|
+
.filter((e) => e.isDirectory() && e.name.startsWith('gsd-') && (owns(e.name) || USER_OWNED_SKILL_DIRS.includes(e.name)))
|
|
7778
|
+
.length;
|
|
7779
|
+
}
|
|
7780
|
+
|
|
7693
7781
|
function listCodexSkillNames(skillsDir, prefix = 'gsd-') {
|
|
7694
7782
|
if (!fs.existsSync(skillsDir)) return [];
|
|
7695
7783
|
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
@@ -7921,6 +8009,10 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7921
8009
|
return content;
|
|
7922
8010
|
},
|
|
7923
8011
|
},
|
|
8012
|
+
pi: {
|
|
8013
|
+
// #5569: `Agent(...)` -> `gsd_dispatch(role=..., task=...)`.
|
|
8014
|
+
md: (content, ctx) => convertClaudeToPiMarkdown(content, ctx),
|
|
8015
|
+
},
|
|
7924
8016
|
hermes: {
|
|
7925
8017
|
// #2284: brand-swap alone left the false "Agent tool IS available"
|
|
7926
8018
|
// assertion + literal `Agent(...)` call syntax installed verbatim — see
|
|
@@ -8040,6 +8132,8 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
8040
8132
|
if (shouldCompose(srcPath)) {
|
|
8041
8133
|
content = composeWorkflow(content, { sourcePath: srcPath });
|
|
8042
8134
|
}
|
|
8135
|
+
// #5637: the typed-text capture heredoc ends at a terminator the typed text cannot know.
|
|
8136
|
+
content = stampInstalledContent(content);
|
|
8043
8137
|
|
|
8044
8138
|
content = filterRuntimeNotesForTarget(content, runtime);
|
|
8045
8139
|
|
|
@@ -10158,7 +10252,16 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10158
10252
|
if (!hostBehaviorsFor(runtime).skipCodexSkillsManifest && fs.existsSync(codexSkillsDir)) {
|
|
10159
10253
|
// All runtimes (including Hermes post-#947) use the canonical 'gsd-' prefix.
|
|
10160
10254
|
const skillListPrefix = 'gsd-';
|
|
10255
|
+
// #5161: record only skill dirs GSD owns. A user's gsd-* dir the prune
|
|
10256
|
+
// preserved must not enter the manifest, or the next install reads it back
|
|
10257
|
+
// as GSD-owned and deletes it. includeManifest:false keeps the previous
|
|
10258
|
+
// manifest from vouching for itself; manifestIfSourceUnresolved keeps an
|
|
10259
|
+
// unreadable install source from dropping every GSD skill from the manifest
|
|
10260
|
+
// by carrying the previous manifest's entries forward instead of recording
|
|
10261
|
+
// every gsd-* dir present.
|
|
10262
|
+
const ownsSkillDir = createSkillDirOwnership(runtime, configDir, codexSkillsDir, skillListPrefix, { includeManifest: false, manifestIfSourceUnresolved: true, includeWritten: true });
|
|
10161
10263
|
for (const skillName of listCodexSkillNames(codexSkillsDir, skillListPrefix)) {
|
|
10264
|
+
if (!ownsSkillDir(skillName) && !USER_OWNED_SKILL_DIRS.includes(skillName)) continue;
|
|
10162
10265
|
const skillRoot = path.join(codexSkillsDir, skillName);
|
|
10163
10266
|
const skillHashes = generateManifest(skillRoot);
|
|
10164
10267
|
for (const [rel, hash] of Object.entries(skillHashes)) {
|
|
@@ -10747,7 +10850,8 @@ function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
|
|
10747
10850
|
if (meta.files && meta.files.length > 0) {
|
|
10748
10851
|
const reapplyCommand = hostBehaviorsFor(runtime).reapplyCommand || '/gsd-update --reapply';
|
|
10749
10852
|
console.log('');
|
|
10750
|
-
|
|
10853
|
+
const fromLabel = meta.from_version ? ' (from v' + meta.from_version + ')' : ' (from a first install)';
|
|
10854
|
+
console.log(' ' + yellow + 'Local patches detected' + reset + fromLabel + ':');
|
|
10751
10855
|
for (const f of meta.files) {
|
|
10752
10856
|
console.log(' ' + cyan + f + reset);
|
|
10753
10857
|
}
|
|
@@ -10760,6 +10864,114 @@ function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
|
|
10760
10864
|
return meta.files || [];
|
|
10761
10865
|
}
|
|
10762
10866
|
|
|
10867
|
+
/**
|
|
10868
|
+
* #5522: the first-time baseline reports a user's `skills/gsd-<stem>/SKILL.md` as preserved, but the
|
|
10869
|
+
* skills writer owns a dir by its first-party name and replaces it. Before the writers run, set aside a copy of
|
|
10870
|
+
* every baseline-preserved file in the shipped-name namespaces (bounded to BASELINE_SNAPSHOT_MAX_BYTES) in
|
|
10871
|
+
* gsd-migration-backups/ — an installer-internal dir, so it can never collide with a user's existing
|
|
10872
|
+
* gsd-local-patches/ content — so the user's content is on disk before anything can replace it.
|
|
10873
|
+
* backUpReplacedBaselineFiles then moves the copies of replaced files into gsd-local-patches/.
|
|
10874
|
+
* Returns Map<relPath, { fullPath, content, stagedPath }>.
|
|
10875
|
+
*/
|
|
10876
|
+
const BASELINE_SNAPSHOT_MAX_BYTES = 1024 * 1024;
|
|
10877
|
+
const BASELINE_SNAPSHOT_DIR = path.join('gsd-migration-backups', 'first-install-5522');
|
|
10878
|
+
const SHIPPED_NAME_NAMESPACE = /^(skills|commands?)\/gsd([-/]|$)/;
|
|
10879
|
+
function snapshotBaselinePreservedFiles(configDir, migrationResult) {
|
|
10880
|
+
const snapshots = new Map();
|
|
10881
|
+
const plan = migrationResult && migrationResult.plan;
|
|
10882
|
+
const actions = plan && Array.isArray(plan.actions) ? plan.actions : [];
|
|
10883
|
+
const stagingDir = path.join(configDir, BASELINE_SNAPSHOT_DIR);
|
|
10884
|
+
for (const action of actions) {
|
|
10885
|
+
if (!action || action.type !== 'baseline-preserve-user' || typeof action.relPath !== 'string') continue;
|
|
10886
|
+
const ref = resolveInstallRelativePath(configDir, action.relPath);
|
|
10887
|
+
if (!ref || !SHIPPED_NAME_NAMESPACE.test(ref.relPath)) continue;
|
|
10888
|
+
try {
|
|
10889
|
+
const st = fs.lstatSync(ref.fullPath);
|
|
10890
|
+
if (!st.isFile()) continue;
|
|
10891
|
+
if (st.size > BASELINE_SNAPSHOT_MAX_BYTES) {
|
|
10892
|
+
console.warn(` ${yellow}⚠${reset} ${action.relPath} is larger than ${BASELINE_SNAPSHOT_MAX_BYTES} bytes, so it is not copied aside; if the install replaces it, the original is not kept`);
|
|
10893
|
+
continue;
|
|
10894
|
+
}
|
|
10895
|
+
const content = fs.readFileSync(ref.fullPath);
|
|
10896
|
+
const stagedRef = resolveInstallRelativePath(stagingDir, ref.relPath);
|
|
10897
|
+
if (!stagedRef) continue;
|
|
10898
|
+
fs.mkdirSync(path.dirname(stagedRef.fullPath), { recursive: true });
|
|
10899
|
+
fs.writeFileSync(stagedRef.fullPath, content);
|
|
10900
|
+
snapshots.set(ref.relPath, { fullPath: ref.fullPath, content, stagedPath: stagedRef.fullPath });
|
|
10901
|
+
} catch (err) {
|
|
10902
|
+
console.warn(` ${yellow}⚠${reset} Could not set aside ${action.relPath} before install (${err.message}); if the install replaces it, the original is not kept`);
|
|
10903
|
+
}
|
|
10904
|
+
}
|
|
10905
|
+
return snapshots;
|
|
10906
|
+
}
|
|
10907
|
+
|
|
10908
|
+
/**
|
|
10909
|
+
* #5522: after the artifact writers ran, a snapshotted file that is gone or different was replaced. Its
|
|
10910
|
+
* pre-install copy moves into gsd-local-patches/ (next to, never over, an existing backup) and is recorded in
|
|
10911
|
+
* backup-meta.json, so the standard "Local patches detected" report names it as it does on the update path.
|
|
10912
|
+
* The staging dir is always removed. Never throws: bookkeeping must not abort the install.
|
|
10913
|
+
* Returns [{ relPath, backupRel }] for the files kept.
|
|
10914
|
+
*/
|
|
10915
|
+
function backUpReplacedBaselineFiles(configDir, snapshots) {
|
|
10916
|
+
const replaced = [];
|
|
10917
|
+
const patchesDir = path.join(configDir, PATCHES_DIR_NAME);
|
|
10918
|
+
for (const [relPath, snap] of snapshots) {
|
|
10919
|
+
let current = null;
|
|
10920
|
+
try { current = fs.readFileSync(snap.fullPath); } catch { current = null; }
|
|
10921
|
+
if (current !== null && current.equals(snap.content)) continue;
|
|
10922
|
+
try {
|
|
10923
|
+
let backupRel = relPath;
|
|
10924
|
+
if (fs.existsSync(path.join(patchesDir, backupRel))) backupRel = `${relPath}.first-install`;
|
|
10925
|
+
const backupRef = resolveInstallRelativePath(patchesDir, backupRel);
|
|
10926
|
+
if (!backupRef) throw new Error('backup path escapes the config dir');
|
|
10927
|
+
fs.mkdirSync(path.dirname(backupRef.fullPath), { recursive: true });
|
|
10928
|
+
fs.writeFileSync(backupRef.fullPath, snap.content);
|
|
10929
|
+
replaced.push({ relPath, backupRel });
|
|
10930
|
+
} catch (err) {
|
|
10931
|
+
console.warn(` ${yellow}⚠${reset} Could not back up ${relPath} to ${PATCHES_DIR_NAME}/ (${err.message}); the original is kept in ${BASELINE_SNAPSHOT_DIR}/`);
|
|
10932
|
+
return replaced;
|
|
10933
|
+
}
|
|
10934
|
+
}
|
|
10935
|
+
if (replaced.length > 0) {
|
|
10936
|
+
try {
|
|
10937
|
+
const metaPath = path.join(patchesDir, 'backup-meta.json');
|
|
10938
|
+
let meta = {};
|
|
10939
|
+
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')) || {}; } catch { meta = {}; }
|
|
10940
|
+
const files = Array.from(new Set([...(Array.isArray(meta.files) ? meta.files : []), ...replaced.map((r) => r.backupRel)]));
|
|
10941
|
+
fs.writeFileSync(metaPath, JSON.stringify({
|
|
10942
|
+
...meta,
|
|
10943
|
+
backed_up_at: new Date().toISOString(),
|
|
10944
|
+
from_version: meta.from_version ?? null,
|
|
10945
|
+
files,
|
|
10946
|
+
pristine_hashes: { ...(meta.pristine_hashes || {}) },
|
|
10947
|
+
}, null, 2));
|
|
10948
|
+
} catch (err) {
|
|
10949
|
+
console.warn(` ${yellow}⚠${reset} Could not record ${PATCHES_DIR_NAME}/backup-meta.json (${err.message}); your copies are in ${PATCHES_DIR_NAME}/`);
|
|
10950
|
+
}
|
|
10951
|
+
}
|
|
10952
|
+
try { fs.rmSync(path.join(configDir, BASELINE_SNAPSHOT_DIR), { recursive: true, force: true }); } catch { /* leftover is harmless */ }
|
|
10953
|
+
return replaced;
|
|
10954
|
+
}
|
|
10955
|
+
|
|
10956
|
+
/** relPaths in the snapshot whose name is a shipped skill/command the writers will lay down (#5522). */
|
|
10957
|
+
function predictShippedNameReplacements(snapshots, skillsManifest) {
|
|
10958
|
+
const out = [];
|
|
10959
|
+
for (const relPath of snapshots.keys()) {
|
|
10960
|
+
const m = /^skills\/gsd-([^/]+)\//.exec(relPath) || /^commands?\/gsd\/([^/]+?)\.md$/.exec(relPath);
|
|
10961
|
+
if (m && skillsManifest.has(m[1])) out.push(relPath);
|
|
10962
|
+
}
|
|
10963
|
+
return out;
|
|
10964
|
+
}
|
|
10965
|
+
|
|
10966
|
+
/** A copy of the migration result without the baseline-preserve rows for paths the install replaced (#5522). */
|
|
10967
|
+
function withoutReplacedBaselineFiles(migrationResult, replaced) {
|
|
10968
|
+
if (!replaced || replaced.length === 0 || !migrationResult || !migrationResult.plan) return migrationResult;
|
|
10969
|
+
const norm = (p) => String(p).replace(/\\/g, '/').replace(/^\/+/, '');
|
|
10970
|
+
const gone = new Set(replaced.map((r) => norm(r.relPath)));
|
|
10971
|
+
const actions = (migrationResult.plan.actions || []).filter((a) => !(a && a.type === 'baseline-preserve-user' && gone.has(norm(a.relPath))));
|
|
10972
|
+
return { ...migrationResult, plan: { ...migrationResult.plan, actions } };
|
|
10973
|
+
}
|
|
10974
|
+
|
|
10763
10975
|
function reportInstallerMigrationResult(result) {
|
|
10764
10976
|
const summary = summarizeInstallerMigrationResult(result);
|
|
10765
10977
|
if (!summary.hasReportableActions) return;
|
|
@@ -10893,6 +11105,20 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10893
11105
|
? process.cwd()
|
|
10894
11106
|
: path.join(process.cwd(), dirName);
|
|
10895
11107
|
|
|
11108
|
+
// #5637: this install's typed-text heredoc terminator: the directory's existing nonce on a
|
|
11109
|
+
// reinstall (so it changes nothing), a fresh one otherwise.
|
|
11110
|
+
if (process.env.GSD_ARGS_NONCE_OFF !== '1') {
|
|
11111
|
+
// Only a global install reuses its nonce; a project-local dir may be a committed clone.
|
|
11112
|
+
armArgsNonce(targetDir, isGlobal);
|
|
11113
|
+
} else {
|
|
11114
|
+
disarmArgsNonce();
|
|
11115
|
+
console.warn(' GSD_ARGS_NONCE_OFF=1: the typed-text heredoc terminator is NOT randomized in this install.');
|
|
11116
|
+
}
|
|
11117
|
+
|
|
11118
|
+
// #5161: this install's record of the skill dirs it writes starts empty,
|
|
11119
|
+
// whichever install path it takes.
|
|
11120
|
+
installEngine.clearWrittenSkillDirs();
|
|
11121
|
+
|
|
10896
11122
|
// #3664: a --config-dir destination holding foreign agent files gets an
|
|
10897
11123
|
// explicit install-time warning — never a silent Claude-shaped emit.
|
|
10898
11124
|
if (isGlobal) {
|
|
@@ -10962,8 +11188,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10962
11188
|
const _isCoreProfileAlias = _activeProfileName === 'core';
|
|
10963
11189
|
const _effectiveInstallMode = _isCoreProfileAlias ? 'minimal' : 'full';
|
|
10964
11190
|
// Load the manifest and compute resolved profile for named profiles.
|
|
10965
|
-
// For --minimal/core: use an empty manifest (core profile has no
|
|
10966
|
-
// deps) to produce a resolvedProfile with the core skill set
|
|
11191
|
+
// For --minimal/core: use an empty manifest for the SKILL closure (core profile has no
|
|
11192
|
+
// transitive skill deps) to produce a resolvedProfile with the core skill set; agents
|
|
11193
|
+
// come from the full manifest (#5503, see below). For core/
|
|
10967
11194
|
// standard profiles, resolveProfile's `registry` arg IS consulted (via
|
|
10968
11195
|
// _capabilitySkillsForMode) so tier:core/tier:standard capability skills are
|
|
10969
11196
|
// unioned in when registered. #2322 correction: for the DEFAULT `full`
|
|
@@ -10976,10 +11203,15 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10976
11203
|
// is passed so an INSTALLED third-party capability (not just a first-party
|
|
10977
11204
|
// one) is honored on every profile, `full` included (#2322 blocker 2).
|
|
10978
11205
|
const _commandsDir = path.join(src, 'commands', 'gsd');
|
|
10979
|
-
|
|
11206
|
+
// #5503: the empty manifest only keeps core's SKILL set at the eight core skills (no
|
|
11207
|
+
// `requires:` closure). The agents those skills spawn are still derived from the real
|
|
11208
|
+
// manifest, so a core install can run /gsd-plan-phase and friends.
|
|
11209
|
+
const _fullSkillsManifest = loadSkillsManifest(_commandsDir);
|
|
11210
|
+
const _skillsManifest = _isCoreProfileAlias ? new Map() : _fullSkillsManifest;
|
|
10980
11211
|
let _resolvedProfile = resolveProfile({
|
|
10981
11212
|
modes: [_activeProfileName],
|
|
10982
11213
|
manifest: _skillsManifest,
|
|
11214
|
+
agentManifest: _fullSkillsManifest,
|
|
10983
11215
|
registry: _installedCapabilityRegistry,
|
|
10984
11216
|
});
|
|
10985
11217
|
// Unified staging function: all profiles use stageSkillsForProfile with the
|
|
@@ -10988,9 +11220,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10988
11220
|
return stageSkillsForProfile(commandsGsdDir, _resolvedProfile);
|
|
10989
11221
|
}
|
|
10990
11222
|
function _stageAgents(agentsDir) {
|
|
10991
|
-
|
|
11223
|
+
// #5503: no core-alias shortcut — Codex gets the same profile-filtered agents as every runtime.
|
|
10992
11224
|
return stageAgentsForProfile(agentsDir, _resolvedProfile);
|
|
10993
11225
|
}
|
|
11226
|
+
// #5503: Codex registers agent roles in config.toml. That used to be gated on "minimal mode"
|
|
11227
|
+
// because minimal staged no agents; gate it on whether the resolved profile stages any.
|
|
11228
|
+
const _codexAgentRolesEnabled = () =>
|
|
11229
|
+
!isMinimalMode(_effectiveInstallMode) || (_resolvedProfile.agents instanceof Set && _resolvedProfile.agents.size > 0);
|
|
10994
11230
|
const persistActiveProfileMarker = () => {
|
|
10995
11231
|
try {
|
|
10996
11232
|
writeActiveProfile(targetDir, _activeProfileName);
|
|
@@ -11365,7 +11601,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11365
11601
|
// The full restoreCodexSnapshot() (defined inside the config block) additionally
|
|
11366
11602
|
// handles config.toml and the staged hooks/ tree, which are not yet touched
|
|
11367
11603
|
// at this point in the pipeline.
|
|
11368
|
-
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall ||
|
|
11604
|
+
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall || !_codexAgentRolesEnabled() ? null : () => {
|
|
11369
11605
|
rollbackInstallerMigrations();
|
|
11370
11606
|
// skills/gsd-* — pass 1: restore snapshot entries (may be absent if deleted mid-install).
|
|
11371
11607
|
const _earlySkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
@@ -11467,6 +11703,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11467
11703
|
// scope (outside the try block below) so it is accessible in the settings section later.
|
|
11468
11704
|
// Absent VERSION = fresh install; present VERSION = upgrade/re-install.
|
|
11469
11705
|
const priorInstallExisted = fs.existsSync(path.join(targetDir, 'gsd-core', 'VERSION'));
|
|
11706
|
+
// #5522: files the first-time baseline reported as preserved, snapshotted before the artifact writers run.
|
|
11707
|
+
let baselinePreservedSnapshots = new Map();
|
|
11470
11708
|
try {
|
|
11471
11709
|
installerMigrationResult = runInstallerMigrations({
|
|
11472
11710
|
configDir: targetDir,
|
|
@@ -11520,8 +11758,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11520
11758
|
};
|
|
11521
11759
|
}
|
|
11522
11760
|
}
|
|
11523
|
-
|
|
11761
|
+
if (Array.isArray(installerMigrationResult.blocked) && installerMigrationResult.blocked.length > 0) {
|
|
11762
|
+
reportInstallerMigrationResult(installerMigrationResult); // show what blocked before the assertion throws
|
|
11763
|
+
}
|
|
11524
11764
|
assertInstallerMigrationsUnblocked(installerMigrationResult);
|
|
11765
|
+
baselinePreservedSnapshots = snapshotBaselinePreservedFiles(targetDir, installerMigrationResult);
|
|
11766
|
+
// #5522: the migration report stays ahead of materialization, but a baseline-preserved file the skills
|
|
11767
|
+
// writer is about to replace (its name is a shipped skill's) is not listed as preserved; the
|
|
11768
|
+
// replacement is reported once the writers have run.
|
|
11769
|
+
reportInstallerMigrationResult(withoutReplacedBaselineFiles(
|
|
11770
|
+
installerMigrationResult,
|
|
11771
|
+
predictShippedNameReplacements(baselinePreservedSnapshots, _fullSkillsManifest).map((relPath) => ({ relPath }))
|
|
11772
|
+
));
|
|
11525
11773
|
|
|
11526
11774
|
// Artifact install dispatcher — routes to layout-driven path for all
|
|
11527
11775
|
// skills-based runtimes (both full and minimal/core profiles); keeps
|
|
@@ -11687,7 +11935,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11687
11935
|
// in autocomplete. Cleaning them up fixes the duplication; SKILL.md alone is
|
|
11688
11936
|
// sufficient for Codex discovery. User-owned dirs are never touched.
|
|
11689
11937
|
if (hostBehaviorsFor(runtime).cleanupSkillSidecars) {
|
|
11690
|
-
|
|
11938
|
+
// #5161: no includeWritten here, unlike the count and writeManifest. The
|
|
11939
|
+
// record would add only skills staged from a command (a capability skill
|
|
11940
|
+
// carries the marker, which this predicate already owns). The writer prunes
|
|
11941
|
+
// each of those except gsd-dev-preferences and writes it fresh as a lone
|
|
11942
|
+
// SKILL.md, so it holds no sidecar for this cleanup to remove, and the
|
|
11943
|
+
// cleanup skips gsd-dev-preferences by name (USER_OWNED_SKILL_DIRS).
|
|
11944
|
+
cleanupCodexSkillMetadataSidecars(
|
|
11945
|
+
_skillsRootDir,
|
|
11946
|
+
createSkillDirOwnership(runtime, targetDir, _skillsRootDir, 'gsd-', { includeManifest: false }),
|
|
11947
|
+
);
|
|
11691
11948
|
}
|
|
11692
11949
|
|
|
11693
11950
|
// ADR-1239 split-home migration: when a runtime's skills kind moved to an
|
|
@@ -11695,10 +11952,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11695
11952
|
// managed gsd-* skill dirs at the old configDir-rooted location
|
|
11696
11953
|
// (~/.codex/skills). Reinstalling here writes the new location but would
|
|
11697
11954
|
// otherwise orphan the old one — clean up the stale gsd-* dirs.
|
|
11955
|
+
// #5161: only the ones GSD owns; any other gsd-* dir is kept and named.
|
|
11956
|
+
// The manifest leg stays off: codex records its skills under `skills/`
|
|
11957
|
+
// whichever root they sit in, so a name the manifest recorded for the new
|
|
11958
|
+
// home would otherwise claim a same-named user dir at this old root that
|
|
11959
|
+
// GSD never wrote.
|
|
11698
11960
|
{
|
|
11699
11961
|
const _movedOldSkillsDir = _resolveMovedSkillsOldDir(runtime, targetDir, scope);
|
|
11700
11962
|
if (_movedOldSkillsDir) {
|
|
11701
|
-
const migrated = cleanupMovedSkillsOldLocation(_movedOldSkillsDir, 'gsd-'
|
|
11963
|
+
const migrated = cleanupMovedSkillsOldLocation(_movedOldSkillsDir, 'gsd-',
|
|
11964
|
+
createSkillDirOwnership(runtime, targetDir, _movedOldSkillsDir, 'gsd-', { includeManifest: false }));
|
|
11702
11965
|
if (migrated > 0) {
|
|
11703
11966
|
console.log(` ${green}✓${reset} Migrated ${migrated} skill dir(s) off the legacy ${_movedOldSkillsDir} location`);
|
|
11704
11967
|
}
|
|
@@ -11712,8 +11975,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11712
11975
|
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
|
11713
11976
|
// hostBehaviors.legacyDevinSkillsCleanup (windsurf is the only runtime that
|
|
11714
11977
|
// declares it, so this is byte-parity).
|
|
11978
|
+
// #5161: only the gsd-* dirs GSD owns. The local manifest describes
|
|
11979
|
+
// .windsurf/, never .devin/, so the manifest leg stays off here: a name it
|
|
11980
|
+
// records under one root says nothing about a same-named dir under another.
|
|
11715
11981
|
if (hostBehaviorsFor(runtime).legacyDevinSkillsCleanup && !isGlobal) {
|
|
11716
|
-
const
|
|
11982
|
+
const _devinSkillsDir = path.join(process.cwd(), '.devin', 'skills');
|
|
11983
|
+
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd(),
|
|
11984
|
+
createSkillDirOwnership(runtime, targetDir, _devinSkillsDir, 'gsd-', { includeManifest: false }));
|
|
11717
11985
|
if (removedCount > 0) {
|
|
11718
11986
|
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
|
11719
11987
|
}
|
|
@@ -11729,8 +11997,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11729
11997
|
const hermesSkillsDir = path.join(targetDir, 'skills', 'gsd');
|
|
11730
11998
|
if (fs.existsSync(hermesSkillsDir)) {
|
|
11731
11999
|
// Hermes layout uses prefix: 'gsd-' (#947) — skill dirs have gsd-<stem> names
|
|
11732
|
-
const count =
|
|
11733
|
-
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
12000
|
+
const count = _countOwnedSkillDirs(runtime, targetDir, hermesSkillsDir);
|
|
11734
12001
|
if (count > 0) {
|
|
11735
12002
|
console.log(` ${green}✓${reset} Installed ${count} skills to skills/gsd/`);
|
|
11736
12003
|
} else {
|
|
@@ -11743,8 +12010,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11743
12010
|
const skillsDir = path.join(targetDir, 'skills');
|
|
11744
12011
|
const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml');
|
|
11745
12012
|
if (fs.existsSync(skillsDir)) {
|
|
11746
|
-
const count =
|
|
11747
|
-
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
12013
|
+
const count = _countOwnedSkillDirs(runtime, targetDir, skillsDir);
|
|
11748
12014
|
if (count > 0) {
|
|
11749
12015
|
console.log(` ${green}✓${reset} Installed ${count} Kimi skills to skills/`);
|
|
11750
12016
|
} else {
|
|
@@ -11783,8 +12049,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11783
12049
|
} else {
|
|
11784
12050
|
const skillsDir = _skillsRootDir;
|
|
11785
12051
|
if (fs.existsSync(skillsDir)) {
|
|
11786
|
-
const count =
|
|
11787
|
-
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
12052
|
+
const count = _countOwnedSkillDirs(runtime, targetDir, skillsDir);
|
|
11788
12053
|
if (count > 0) {
|
|
11789
12054
|
console.log(` ${green}✓${reset} Installed ${count} skills to skills/`);
|
|
11790
12055
|
} else {
|
|
@@ -11844,6 +12109,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11844
12109
|
// command/ directories), so writing flat gsd-<cmd>.md files here would be
|
|
11845
12110
|
// dead weight the extension never reads. Skip the flat-commands fallback
|
|
11846
12111
|
// entirely for pluginOnlyInstall runtimes.
|
|
12112
|
+
// NOTE (#5547): pi now declares a skills artifactLayout, so it takes the
|
|
12113
|
+
// `_isSkillsRuntime` branch above; this branch is reached only if that layout
|
|
12114
|
+
// is ever emptied again.
|
|
11847
12115
|
console.log(` ${green}✓${reset} pi: /gsd registered via native extension (no declarative command files)`);
|
|
11848
12116
|
} else {
|
|
11849
12117
|
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
|
@@ -11920,28 +12188,40 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11920
12188
|
}
|
|
11921
12189
|
}
|
|
11922
12190
|
|
|
11923
|
-
// Clean up any stale skills/ from a previous local install
|
|
12191
|
+
// Clean up any stale skills/ from a previous local install. #5161: only
|
|
12192
|
+
// dirs GSD owns — .claude/skills/ is also where a project keeps its own
|
|
12193
|
+
// skills, so a user's gsd-* dir there is preserved and named instead.
|
|
11924
12194
|
const staleSkillsDir = path.join(targetDir, 'skills');
|
|
11925
12195
|
if (fs.existsSync(staleSkillsDir)) {
|
|
11926
|
-
|
|
11927
|
-
|
|
12196
|
+
// Decide ownership once, BEFORE deleting: the capability-marker leg reads
|
|
12197
|
+
// the dir itself, so asking again afterwards reports a deleted dir as kept.
|
|
12198
|
+
// A symlink is never removed here (as before) but is named when unowned.
|
|
12199
|
+
const ownsSkillDir = createSkillDirOwnership(runtime, targetDir, staleSkillsDir, 'gsd-');
|
|
12200
|
+
const prefixed = fs.readdirSync(staleSkillsDir, { withFileTypes: true })
|
|
12201
|
+
.filter(e => (e.isDirectory() || e.isSymbolicLink()) && e.name.startsWith('gsd-'))
|
|
12202
|
+
.map(e => ({ name: e.name, isDir: e.isDirectory(), owned: ownsSkillDir(e.name) }));
|
|
12203
|
+
// gsd-dev-preferences is user content even when an old manifest records
|
|
12204
|
+
// it, and nothing here restores it (#2973), so it is never removed.
|
|
12205
|
+
const staleGsd = prefixed.filter(e => e.isDir && e.owned && !USER_OWNED_SKILL_DIRS.includes(e.name));
|
|
11928
12206
|
for (const e of staleGsd) {
|
|
11929
12207
|
fs.rmSync(path.join(staleSkillsDir, e.name), { recursive: true });
|
|
11930
12208
|
}
|
|
11931
12209
|
if (staleGsd.length > 0) {
|
|
11932
12210
|
console.log(` ${green}✓${reset} Removed ${staleGsd.length} stale GSD skill(s) from skills/`);
|
|
11933
12211
|
}
|
|
12212
|
+
_warnPreservedSkillDirs(staleSkillsDir, prefixed.filter(e => !e.owned).map(e => e.name));
|
|
11934
12213
|
}
|
|
11935
12214
|
}
|
|
11936
12215
|
|
|
11937
12216
|
// Native-extension/plugin staging for runtimes OUTSIDE the layout-driven
|
|
11938
|
-
// _isSkillsRuntime branch above (ADR-1239 / #2102 Stage 1
|
|
12217
|
+
// _isSkillsRuntime branch above (ADR-1239 / #2102 Stage 1). OpenCode/Kilo
|
|
11939
12218
|
// already get their nativePlugin file from installOpencodeFamilyArtifacts
|
|
11940
12219
|
// (called inside the _isSkillsRuntime branch, since both declare a non-empty
|
|
11941
12220
|
// artifactLayout) — guard on `!_isSkillsRuntime` so this standalone call never
|
|
11942
|
-
// double-stages their plugin file.
|
|
11943
|
-
//
|
|
11944
|
-
//
|
|
12221
|
+
// double-stages their plugin file. pi (#5547) now declares a skills layout, so
|
|
12222
|
+
// its extension is staged by installRuntimeArtifacts' own native-plugin
|
|
12223
|
+
// post-step; this standalone call remains for any runtime that declares a
|
|
12224
|
+
// nativePlugin with an intentionally empty artifactLayout.
|
|
11945
12225
|
if (!_isSkillsRuntime && hostBehaviorsFor(runtime).nativePlugin) {
|
|
11946
12226
|
_installNativePluginIfDeclared(runtime, targetDir, hostBehaviorsFor(runtime), src);
|
|
11947
12227
|
}
|
|
@@ -12024,10 +12304,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12024
12304
|
// here explicitly to cover them (install-engine.cts's own doc comment
|
|
12025
12305
|
// explains why; a regression here was caught by the install-tree golden
|
|
12026
12306
|
// fixture, tests/fixtures/install-tree/claude-local.json). It is a no-op
|
|
12027
|
-
// for pi: its capability.json declares
|
|
12028
|
-
// scopes (programmatic dispatch, no named-dispatch subagent toolkit,
|
|
12029
|
-
//
|
|
12030
|
-
//
|
|
12307
|
+
// for pi: its capability.json declares a skills-only artifactLayout for both
|
|
12308
|
+
// scopes (#5547; programmatic dispatch, no named-dispatch subagent toolkit),
|
|
12309
|
+
// so the resolved layout has no `agents` kind to stage and the function
|
|
12310
|
+
// returns `null` without writing anything.
|
|
12031
12311
|
if (hostBehaviorsFor(runtime).pluginOnlyInstall) {
|
|
12032
12312
|
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
|
12033
12313
|
} else if (_isSkillsRuntime) {
|
|
@@ -12063,7 +12343,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12063
12343
|
// advertising the old full agent surface even though the descriptor-driven
|
|
12064
12344
|
// write above already skipped writing the .md files for a minimal-tier
|
|
12065
12345
|
// resolvedProfile. Reuse the same helper that powers `--uninstall`.
|
|
12066
|
-
if (
|
|
12346
|
+
if (!_codexAgentRolesEnabled() && hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
12067
12347
|
const codexConfigPath = path.join(targetDir, 'config.toml');
|
|
12068
12348
|
if (fs.existsSync(codexConfigPath)) {
|
|
12069
12349
|
const existing = fs.readFileSync(codexConfigPath, 'utf8');
|
|
@@ -12472,6 +12752,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12472
12752
|
}
|
|
12473
12753
|
}
|
|
12474
12754
|
|
|
12755
|
+
// #5522: keep the user's copy of any baseline-preserved file the writers replaced (the update path's
|
|
12756
|
+
// gsd-local-patches/ backup), then report the migrations with those paths no longer listed as preserved.
|
|
12757
|
+
const replacedBaselineFiles = backUpReplacedBaselineFiles(targetDir, baselinePreservedSnapshots);
|
|
12758
|
+
for (const { relPath, backupRel } of replacedBaselineFiles) {
|
|
12759
|
+
console.log(` ${yellow}i${reset} ${relPath} was replaced by the install (it has the name of a shipped GSD file) — your copy is backed up to ${PATCHES_DIR_NAME}/${backupRel}`);
|
|
12760
|
+
}
|
|
12761
|
+
|
|
12475
12762
|
if (failures.length > 0) {
|
|
12476
12763
|
console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
|
|
12477
12764
|
process.exit(1);
|
|
@@ -12565,10 +12852,56 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12565
12852
|
}
|
|
12566
12853
|
}
|
|
12567
12854
|
}
|
|
12855
|
+
// #5123: the Copilot and Antigravity converters write their DEFAULT home
|
|
12856
|
+
// into `@` includes and Kimi's prompt converter leaves a prose "GSD core/"
|
|
12857
|
+
// path, none of which exist when the payload lives somewhere else. Point
|
|
12858
|
+
// them at the real install root (the same `$HOME/…` / absolute prefix the
|
|
12859
|
+
// other runtimes use). Global installs only: a local install resolves
|
|
12860
|
+
// against its project, which the converters already handle.
|
|
12861
|
+
const includeRetarget = hostBehaviorsFor(runtime);
|
|
12862
|
+
if (isGlobal && (includeRetarget.defaultHomeIncludeRoot || includeRetarget.retargetsGsdCoreProsePath === true)) {
|
|
12863
|
+
const includePrefix = runtimeArtifactConversion._computePathPrefix({
|
|
12864
|
+
isGlobal: true,
|
|
12865
|
+
isOpencode: false,
|
|
12866
|
+
resolvedTarget: targetDir,
|
|
12867
|
+
homeDir: os.homedir(),
|
|
12868
|
+
});
|
|
12869
|
+
for (const relPath of manifestFiles) {
|
|
12870
|
+
const fileName = path.basename(relPath);
|
|
12871
|
+
if (!(fileName.endsWith('.md') || fileName.endsWith('.toml'))) continue;
|
|
12872
|
+
if (fileName === 'CHANGELOG.md') continue;
|
|
12873
|
+
const retargetPath = path.join(targetDir, relPath);
|
|
12874
|
+
let retargetContent;
|
|
12875
|
+
try {
|
|
12876
|
+
retargetContent = fs.readFileSync(retargetPath, 'utf8');
|
|
12877
|
+
} catch (_retargetReadErr) {
|
|
12878
|
+
continue; // inaccessible or missing — never fail the install over this pass
|
|
12879
|
+
}
|
|
12880
|
+
const retargeted = runtimeArtifactConversion.retargetInstalledIncludes(retargetContent, includeRetarget, includePrefix);
|
|
12881
|
+
if (retargeted !== retargetContent) {
|
|
12882
|
+
try {
|
|
12883
|
+
fs.writeFileSync(retargetPath, retargeted);
|
|
12884
|
+
} catch (_retargetWriteErr) {
|
|
12885
|
+
continue;
|
|
12886
|
+
}
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12568
12890
|
for (const relPath of manifestFiles) {
|
|
12569
12891
|
const fileName = path.basename(relPath);
|
|
12570
12892
|
if (!(fileName.endsWith('.md') || fileName.endsWith('.toml'))) continue;
|
|
12571
12893
|
if (fileName === 'CHANGELOG.md') continue;
|
|
12894
|
+
// #5547: the Runtime Surface corpus is the package's canonical source
|
|
12895
|
+
// bytes, kept byte-identical so a refresh can restore them
|
|
12896
|
+
// (tests/install-runtime-artifacts.test.cjs); the converted artifacts
|
|
12897
|
+
// are what the host reads. Counting the source form as a "leak" made
|
|
12898
|
+
// every skills-runtime install print a warning about files nothing reads.
|
|
12899
|
+
// Windsurf is the exception: its workflow wrappers delegate to the
|
|
12900
|
+
// path-rewritten bodies at gsd-core/commands/gsd/ (#1629), so there they
|
|
12901
|
+
// ARE host-read artifacts and stay in the scan.
|
|
12902
|
+
const posixRelPath = relPath.replace(/\\/g, '/');
|
|
12903
|
+
if (!hostBehaviorsFor(runtime).installsCommandBodiesForWorkflowDelegation
|
|
12904
|
+
&& (posixRelPath.startsWith('gsd-core/commands/') || posixRelPath.startsWith('gsd-core/agents/'))) continue;
|
|
12572
12905
|
const fullPath = path.join(targetDir, relPath);
|
|
12573
12906
|
let content;
|
|
12574
12907
|
try {
|
|
@@ -12631,9 +12964,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12631
12964
|
// #2695: this branch runs for BOTH `core` (minimal) and `full` profiles.
|
|
12632
12965
|
// Hooks are lightweight infrastructure (update-check + context monitor), not the
|
|
12633
12966
|
// "full agent surface" that `core` deliberately omits. The config.toml / agent
|
|
12634
|
-
// generation below is
|
|
12635
|
-
//
|
|
12636
|
-
//
|
|
12967
|
+
// generation below is gated by its own inner `_codexAgentRolesEnabled()` guard (#5503:
|
|
12968
|
+
// it used to be `!isMinimalMode`, which kept `core` from registering the agents its
|
|
12969
|
+
// skills spawn), so `core` enters the branch to receive the hook-file copy + hooks.json
|
|
12970
|
+
// wiring AND the agent roles its own skills spawn. Before #2695 the outer `!isMinimalMode`
|
|
12637
12971
|
// here skipped the whole branch for `core`, so the registered parent hook pointed
|
|
12638
12972
|
// at a worker/registry the same installer never delivered.
|
|
12639
12973
|
if (plan.installSurface === 'codex-toml') {
|
|
@@ -12804,7 +13138,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12804
13138
|
};
|
|
12805
13139
|
|
|
12806
13140
|
let agentCount = 0;
|
|
12807
|
-
if (
|
|
13141
|
+
if (_codexAgentRolesEnabled()) {
|
|
12808
13142
|
// #2834: write ~/.gsd/defaults.json (resolve_model_ids + runtime) BEFORE generating
|
|
12809
13143
|
// agent TOMLs — installCodexConfig reads defaults.json at generation time, so on a
|
|
12810
13144
|
// clean first install the runtime-aware model resolver must already know the runtime.
|
|
@@ -12823,7 +13157,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12823
13157
|
// not include agents/gsd-*.toml because those files did not yet exist.
|
|
12824
13158
|
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
12825
13159
|
} else {
|
|
12826
|
-
console.log(` ${dim}↳${reset} Skipping Codex agent config generation (
|
|
13160
|
+
console.log(` ${dim}↳${reset} Skipping Codex agent config generation (${_resolvedProfile.name} profile stages no agents)`);
|
|
12827
13161
|
}
|
|
12828
13162
|
|
|
12829
13163
|
// Copy only the hook files that Codex actually registers via its hook configuration (#2153).
|
|
@@ -13022,7 +13356,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13022
13356
|
// existing config; the snapshot restore below is a second line of
|
|
13023
13357
|
// defense if even the rename fails.
|
|
13024
13358
|
try {
|
|
13025
|
-
|
|
13359
|
+
// #5301: write through an opted-in symlink instead of replacing it.
|
|
13360
|
+
atomicWriteFileSync(resolveSymlinkWriteTarget(configPath), configContent, 'utf-8');
|
|
13026
13361
|
} catch (writeErr) {
|
|
13027
13362
|
// #2760 CR4 finding 1 — write failure must be loud and fatal. Wrap
|
|
13028
13363
|
// with a `post-write` prefix the outer catch recognises so install
|
|
@@ -13628,10 +13963,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13628
13963
|
// bare basename so an unrelated user command that merely mentions the same
|
|
13629
13964
|
// filename can't false-positive into GSD's validated set.
|
|
13630
13965
|
configuredEntrypoints.push(
|
|
13631
|
-
...settingsEntrypoints
|
|
13632
|
-
const hooksSegment = '/hooks/' + path.basename(entry.scriptPath);
|
|
13633
|
-
return registeredHookCommands.some(command => command.includes(hooksSegment));
|
|
13634
|
-
}),
|
|
13966
|
+
...selectConfiguredEntrypointsForValidation(settingsEntrypoints, registeredHookCommands),
|
|
13635
13967
|
);
|
|
13636
13968
|
const statuslineEntrypoints = settingsEntrypoints.filter(entry => entry.command === statuslineCommand);
|
|
13637
13969
|
const updateBannerEntrypoints = settingsEntrypoints.filter(entry => entry.command === updateBannerCommand);
|
|
@@ -13727,6 +14059,25 @@ function describeEntrypointConsequence(invalidRuntime) {
|
|
|
13727
14059
|
return 'not persisted: this runtime writes its config after this check';
|
|
13728
14060
|
}
|
|
13729
14061
|
|
|
14062
|
+
// #5100: select which tracked entrypoints reach the validation gate. A named
|
|
14063
|
+
// export (see module.exports) so tests can drive the fresh-vs-upgrade split
|
|
14064
|
+
// directly; the install() call site is covered by a child-process test that
|
|
14065
|
+
// runs the installer with win32 emulated.
|
|
14066
|
+
function selectConfiguredEntrypointsForValidation(settingsEntrypoints, registeredHookCommands) {
|
|
14067
|
+
return (settingsEntrypoints || []).filter(entry => {
|
|
14068
|
+
// A command-less entry never produced a registered command, so the
|
|
14069
|
+
// segment match below can never keep it — but it is exactly the
|
|
14070
|
+
// unresolved-bash entry the gate must see (buildHookCommand pushes it
|
|
14071
|
+
// without a `command` when no Git Bash is found). Exempt it: without this
|
|
14072
|
+
// a fresh install silently drops it (gate passes) while an upgrade keeps
|
|
14073
|
+
// it via the persisted command (gate fails) — same host, opposite
|
|
14074
|
+
// verdicts. Surface it in both cases and let the gate decide loudly.
|
|
14075
|
+
if (entry.command === undefined) return true;
|
|
14076
|
+
const hooksSegment = '/hooks/' + path.basename(entry.scriptPath);
|
|
14077
|
+
return registeredHookCommands.some(command => command.includes(hooksSegment));
|
|
14078
|
+
});
|
|
14079
|
+
}
|
|
14080
|
+
|
|
13730
14081
|
function assertConfiguredEntrypoints(entries) {
|
|
13731
14082
|
// #4249: some writers push the same (configPath, scriptPath) pair more than
|
|
13732
14083
|
// once (e.g. Kimi's context-monitor hook registered under several events,
|
|
@@ -14913,6 +15264,8 @@ module.exports = {
|
|
|
14913
15264
|
install,
|
|
14914
15265
|
installAllRuntimes,
|
|
14915
15266
|
uninstall,
|
|
15267
|
+
// #5100 — installer entrypoint-selection seam, exported for direct tests
|
|
15268
|
+
selectConfiguredEntrypointsForValidation,
|
|
14916
15269
|
// #3023 — shared hook bundle directory name, descriptor-driven
|
|
14917
15270
|
SHARED_HOOKS_DIR_DEFAULT,
|
|
14918
15271
|
resolveSharedHooksDirName,
|
|
@@ -14978,6 +15331,8 @@ module.exports = {
|
|
|
14978
15331
|
convertClaudeAgentToClineAgent,
|
|
14979
15332
|
// #2284 — Hermes named-dispatch → delegate_task projection
|
|
14980
15333
|
convertClaudeToHermesMarkdown,
|
|
15334
|
+
convertClaudeToPiMarkdown,
|
|
15335
|
+
PI_DISPATCH_TOOL_CONFIG,
|
|
14981
15336
|
projectNamedDispatchToStructuralDelegate,
|
|
14982
15337
|
_hostIntegrationDispatch,
|
|
14983
15338
|
_resolveAvailableGsdRoles,
|