@opengsd/gsd-core 1.18.0 → 1.20.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-code-fixer.compact.md +7 -7
- package/agents/gsd-code-fixer.md +13 -7
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +179 -30
- 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-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +376 -103
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +32 -0
- package/gsd-core/bin/lib/commands.cjs +27 -17
- 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 +45 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +52 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -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 +245 -68
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-profiles.cjs +9 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- 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 +61 -51
- 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-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +629 -38
- package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +3066 -653
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
- package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
- package/gsd-core/bin/lib/roadmap.cjs +383 -123
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +151 -19
- package/gsd-core/bin/lib/smart-entry.cjs +35 -11
- package/gsd-core/bin/lib/state-contract.cjs +39 -15
- package/gsd-core/bin/lib/state-document.cjs +344 -27
- package/gsd-core/bin/lib/state-transition.cjs +156 -32
- package/gsd-core/bin/lib/state.cjs +527 -186
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +5 -25
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +4 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
- 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/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/necessity-review.md +48 -0
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +2 -2
- 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/audit-fix.md +2 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +64 -7
- 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 +37 -150
- package/gsd-core/workflows/diagnose-issues.md +29 -5
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +6 -10
- 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 +7 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +10 -6
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +14 -14
- package/gsd-core/workflows/pr-branch.md +2 -0
- 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.md +1 -1
- package/gsd-core/workflows/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +70 -154
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +15 -4
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/verify-work.md +4 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-graphify-update.sh +6 -0
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +172 -39
- package/hooks/dist/gsd-statusline.js +37 -3
- package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
- package/hooks/dist/gsd-workflow-guard.js +3 -2
- package/hooks/dist/gsd-worktree-path-guard.js +29 -2
- package/hooks/dist/lib/git-probe.js +16 -0
- 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 +11 -1
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-graphify-update.sh +6 -0
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +37 -3
- package/hooks/gsd-windsurf-pre-write.js +29 -2
- package/hooks/gsd-workflow-guard.js +3 -2
- package/hooks/gsd-worktree-path-guard.js +29 -2
- package/hooks/hooks.json +1 -0
- package/hooks/lib/git-probe.js +16 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +15 -8
- 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 +774 -0
- package/pi/package-agents/gsd-code-reviewer.md +441 -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 +898 -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 +54 -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/build-hooks.js +4 -0
- package/scripts/ci-next-health.cjs +246 -23
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-features.cjs +14 -2
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/mutation-matrix.cjs +23 -1
- package/scripts/npm-audit-baseline.cjs +41 -7
- 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/scripts/sync-next-version.cjs +38 -6
- package/skills/gsd-update/SKILL.md +5 -2
- package/vscode/package.json +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
- package/scripts/diagnose-validate-commit-phases.cjs +0 -348
- package/scripts/xtrace-phase-timing.cjs +0 -205
package/bin/install.js
CHANGED
|
@@ -63,6 +63,8 @@ const { composeWorkflow } = require('../gsd-core/bin/lib/workflow-fragments.cjs'
|
|
|
63
63
|
// #3072: THE shared composition-scope predicate (also consumed by the served
|
|
64
64
|
// MCP catalog, src/mcp-catalog.cts) — see the comment at its call site below.
|
|
65
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');
|
|
66
68
|
const runtimeArtifactConversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
|
67
69
|
const { escapeRegex: escapeRegExp } = require('../gsd-core/bin/lib/pattern.cjs');
|
|
68
70
|
// #2873: cross-scope shadow detection — reports (never fails) when a
|
|
@@ -844,6 +846,9 @@ const {
|
|
|
844
846
|
migrateLegacyDevPreferencesToSkill,
|
|
845
847
|
USER_OWNED_ARTIFACTS,
|
|
846
848
|
_snapshotDir,
|
|
849
|
+
createSkillDirOwnership,
|
|
850
|
+
_warnPreservedSkillDirs,
|
|
851
|
+
USER_OWNED_SKILL_DIRS,
|
|
847
852
|
} = installEngine;
|
|
848
853
|
|
|
849
854
|
// #2875 (epic #2866 Phase 6): durable on-disk staging for USER_OWNED_ARTIFACTS
|
|
@@ -1640,14 +1645,13 @@ const claudeToOpencodeTools = {
|
|
|
1640
1645
|
const claudeToAntigravityTools = {
|
|
1641
1646
|
// #4705: Antigravity-NATIVE tool names (see src/runtime-artifact-conversion.cts)
|
|
1642
1647
|
Read: 'view_file',
|
|
1643
|
-
Write: '
|
|
1648
|
+
Write: 'write_to_file',
|
|
1644
1649
|
Edit: 'replace_file_content',
|
|
1645
1650
|
Bash: 'run_command',
|
|
1646
|
-
Glob: '
|
|
1651
|
+
Glob: 'find_by_name',
|
|
1647
1652
|
Grep: 'grep_search',
|
|
1648
|
-
WebSearch: '
|
|
1649
|
-
WebFetch: '
|
|
1650
|
-
TodoWrite: 'write_todos',
|
|
1653
|
+
WebSearch: 'search_web',
|
|
1654
|
+
WebFetch: 'read_url_content',
|
|
1651
1655
|
}
|
|
1652
1656
|
|
|
1653
1657
|
// Tool name mapping from Claude/GSD agents to Kimi CLI module paths.
|
|
@@ -1716,13 +1720,16 @@ function convertAntigravityToolName(claudeTool) {
|
|
|
1716
1720
|
// built-in tool; the lowercase fallback would emit an invalid
|
|
1717
1721
|
// 'skill'/'slashcommand' name that fails frontmatter validation
|
|
1718
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).
|
|
1719
1725
|
if (
|
|
1720
1726
|
claudeTool === 'Task' ||
|
|
1721
1727
|
claudeTool === 'Agent' ||
|
|
1722
1728
|
claudeTool === 'AskUserQuestion' ||
|
|
1723
1729
|
claudeTool === 'ask_user' ||
|
|
1724
1730
|
claudeTool === 'Skill' ||
|
|
1725
|
-
claudeTool === 'SlashCommand'
|
|
1731
|
+
claudeTool === 'SlashCommand' ||
|
|
1732
|
+
claudeTool === 'TodoWrite'
|
|
1726
1733
|
) {
|
|
1727
1734
|
return null;
|
|
1728
1735
|
}
|
|
@@ -2156,43 +2163,11 @@ function parseKimiAgentSource(source) {
|
|
|
2156
2163
|
};
|
|
2157
2164
|
}
|
|
2158
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.
|
|
2159
2169
|
function parseFrontmatterTools(frontmatter) {
|
|
2160
|
-
|
|
2161
|
-
const lines = frontmatter.split(/\r?\n/);
|
|
2162
|
-
const tools = [];
|
|
2163
|
-
let collecting = false;
|
|
2164
|
-
|
|
2165
|
-
for (const line of lines) {
|
|
2166
|
-
const trimmed = line.trim();
|
|
2167
|
-
if (!trimmed) continue;
|
|
2168
|
-
|
|
2169
|
-
if (collecting) {
|
|
2170
|
-
if (trimmed.startsWith('- ')) {
|
|
2171
|
-
tools.push(trimmed.slice(2).trim());
|
|
2172
|
-
continue;
|
|
2173
|
-
}
|
|
2174
|
-
collecting = false;
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
if (trimmed === 'tools:' || trimmed === 'allowed-tools:') {
|
|
2178
|
-
collecting = true;
|
|
2179
|
-
continue;
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
if (trimmed.startsWith('tools:') || trimmed.startsWith('allowed-tools:')) {
|
|
2183
|
-
const value = trimmed.slice(trimmed.indexOf(':') + 1).trim();
|
|
2184
|
-
if (value) {
|
|
2185
|
-
for (const tool of value.split(',')) {
|
|
2186
|
-
const name = tool.trim();
|
|
2187
|
-
if (name) tools.push(name);
|
|
2188
|
-
}
|
|
2189
|
-
} else {
|
|
2190
|
-
collecting = true;
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
return tools;
|
|
2170
|
+
return runtimeArtifactConversion.parseFrontmatterTools(frontmatter);
|
|
2196
2171
|
}
|
|
2197
2172
|
|
|
2198
2173
|
function addKimiAgentDiagnostic(diagnostics, code, message, value, source = null) {
|
|
@@ -2500,10 +2475,11 @@ function convertClaudeAgentToAntigravityAgent(content, isGlobal = false) {
|
|
|
2500
2475
|
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
2501
2476
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
2502
2477
|
const color = extractFrontmatterField(frontmatter, 'color');
|
|
2503
|
-
const toolsRaw = extractFrontmatterField(frontmatter, 'tools') || '';
|
|
2504
2478
|
|
|
2505
|
-
// Map tools to Antigravity equivalents (reuse existing convertAntigravityToolName)
|
|
2506
|
-
|
|
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);
|
|
2507
2483
|
const mappedTools = claudeTools.map(t => convertAntigravityToolName(t)).filter(Boolean);
|
|
2508
2484
|
|
|
2509
2485
|
// #2876: quote description for the same reason as the skill variant.
|
|
@@ -3275,6 +3251,12 @@ function _stripCallArgument(spanText, matchStart, matchEnd, { consumeLeadingComm
|
|
|
3275
3251
|
*/
|
|
3276
3252
|
function _projectRoleArgument(spanText, matchStart, matchEnd, roleValueExpr, toolConfig, canOrchestrate) {
|
|
3277
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
|
+
}
|
|
3278
3260
|
const lineStart = spanText.lastIndexOf('\n', matchStart - 1) + 1;
|
|
3279
3261
|
const startsOwnLine = /^[ \t]*$/.test(spanText.slice(lineStart, matchStart));
|
|
3280
3262
|
|
|
@@ -3406,6 +3388,20 @@ function _normalizeDispatchCallSpan(spanText, hasBraceWrapper, dispatch, toolCon
|
|
|
3406
3388
|
}
|
|
3407
3389
|
}
|
|
3408
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
|
+
|
|
3409
3405
|
// 2. Per-call model argument (model= / model:) — stripped entirely when the
|
|
3410
3406
|
// target has no per-call model-selection parameter (there is no
|
|
3411
3407
|
// `dispatch` axis for this — it is inherent tool vocabulary, like the
|
|
@@ -3637,6 +3633,15 @@ function projectNamedDispatchToStructuralDelegate(content, dispatch, toolConfig)
|
|
|
3637
3633
|
if (namedDispatch) {
|
|
3638
3634
|
return `The \`${toolName}\` tool IS available in a top-level ${sessionName} session. Always dispatch ${rosterFlat} as separate \`${toolName}()\` calls.`;
|
|
3639
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
|
+
}
|
|
3640
3645
|
return (
|
|
3641
3646
|
`${sessionName} has no \`Agent\` tool. It exposes \`${toolName}\`, which dispatches by structural role — ` +
|
|
3642
3647
|
'it has no concept of a named subagent identity. GSD projects each named gsd-* role onto this primitive ' +
|
|
@@ -3794,6 +3799,31 @@ function convertClaudeToHermesMarkdown(content, ctx) {
|
|
|
3794
3799
|
|
|
3795
3800
|
// ── End Hermes converters ────────────────────────────────────────────────────
|
|
3796
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
|
+
|
|
3797
3827
|
function convertSlashCommandsToCodexSkillMentions(content) {
|
|
3798
3828
|
// Colon-style /gsd: never appears as a filesystem path segment, so no boundary guard is needed (unlike the hyphen-style below).
|
|
3799
3829
|
let converted = content.replace(/\/gsd:([a-z0-9-]+)/gi, (_, commandName) => {
|
|
@@ -4285,8 +4315,8 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
4285
4315
|
* - Returns immediately if skillsDir does not exist (fails open).
|
|
4286
4316
|
* - Only touches directories whose names start with "gsd-".
|
|
4287
4317
|
* - Skips user-owned dirs (gsd-dev-preferences) — their agents/ content is
|
|
4288
|
-
* never modified,
|
|
4289
|
-
* installOpencodeFamilySkills.
|
|
4318
|
+
* never modified, by the same USER_OWNED_SKILL_DIRS guard
|
|
4319
|
+
* installOpencodeFamilySkills uses.
|
|
4290
4320
|
* - For each managed gsd-* dir, if agents/openai.yaml exists, deletes it.
|
|
4291
4321
|
* - If agents/ is now empty, removes the directory; if it still contains
|
|
4292
4322
|
* other files (e.g. user-added content), leaves it in place.
|
|
@@ -4296,15 +4326,17 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
4296
4326
|
*
|
|
4297
4327
|
* @param {string} skillsDir - Path to the skills/ directory (e.g. ~/.codex/skills)
|
|
4298
4328
|
*/
|
|
4299
|
-
function cleanupCodexSkillMetadataSidecars(skillsDir) {
|
|
4329
|
+
function cleanupCodexSkillMetadataSidecars(skillsDir, owns) {
|
|
4300
4330
|
if (!fs.existsSync(skillsDir)) return;
|
|
4301
|
-
//
|
|
4302
|
-
//
|
|
4303
|
-
//
|
|
4304
|
-
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.
|
|
4305
4334
|
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
4306
4335
|
if (!entry.isDirectory() || !entry.name.startsWith('gsd-')) continue;
|
|
4307
|
-
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;
|
|
4308
4340
|
const agentsSubdir = path.join(skillsDir, entry.name, 'agents');
|
|
4309
4341
|
const sidecarPath = path.join(agentsSubdir, 'openai.yaml');
|
|
4310
4342
|
try {
|
|
@@ -4338,21 +4370,27 @@ function cleanupCodexSkillMetadataSidecars(skillsDir) {
|
|
|
4338
4370
|
* - non-gsd-* dirs under .devin/skills/ (user-authored skills)
|
|
4339
4371
|
* - gsd-dev-preferences/ (user-owned per #2973)
|
|
4340
4372
|
* - any files (not dirs) under .devin/skills/
|
|
4373
|
+
* - with `owns` (#5161), any gsd-* dir GSD does not own, which is named by path
|
|
4341
4374
|
*
|
|
4342
4375
|
* @param {string} workspaceDir - workspace root (process.cwd() for local installs)
|
|
4376
|
+
* @param {(name: string) => boolean} [owns] ownership predicate (createSkillDirOwnership)
|
|
4343
4377
|
* @returns {number} count of removed legacy gsd-* skill directories
|
|
4344
4378
|
*/
|
|
4345
|
-
function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
4379
|
+
function cleanupWindsurfLegacyDevinSkills(workspaceDir, owns) {
|
|
4346
4380
|
const legacySkillsDir = path.join(workspaceDir, '.devin', 'skills');
|
|
4347
4381
|
if (!fs.existsSync(legacySkillsDir)) return 0;
|
|
4348
4382
|
|
|
4349
|
-
// Mirror the user-owned list from cleanupCodexSkillMetadataSidecars (#2973).
|
|
4350
|
-
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
4351
4383
|
let removed = 0;
|
|
4352
4384
|
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
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;
|
|
4356
4394
|
|
|
4357
4395
|
const dirToRemove = path.join(legacySkillsDir, entry.name);
|
|
4358
4396
|
try {
|
|
@@ -4367,6 +4405,7 @@ function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
|
4367
4405
|
// Fail open — a single bad dir must not block the install.
|
|
4368
4406
|
}
|
|
4369
4407
|
}
|
|
4408
|
+
_warnPreservedSkillDirs(legacySkillsDir, notOwned);
|
|
4370
4409
|
|
|
4371
4410
|
// If .devin/skills/ is now empty, prune it. If .devin/ itself is then empty,
|
|
4372
4411
|
// prune that too — leaves the workspace clean for the new .windsurf/ layout.
|
|
@@ -4393,20 +4432,29 @@ function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
|
4393
4432
|
* relocating skills to ~/.agents/skills) orphans the pre-move dirs at
|
|
4394
4433
|
* ~/.codex/skills. Only managed `<prefix>*` dirs are touched; user-owned content
|
|
4395
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.
|
|
4396
4439
|
* @param {string} oldSkillsDir absolute path to the pre-move skills location
|
|
4397
4440
|
* @param {string} prefix managed skill-dir prefix (e.g. 'gsd-')
|
|
4441
|
+
* @param {(name: string) => boolean} [owns] ownership predicate (createSkillDirOwnership)
|
|
4398
4442
|
* @returns {number} count of stale dirs removed
|
|
4399
4443
|
*/
|
|
4400
|
-
function cleanupMovedSkillsOldLocation(oldSkillsDir, prefix) {
|
|
4444
|
+
function cleanupMovedSkillsOldLocation(oldSkillsDir, prefix, owns) {
|
|
4401
4445
|
if (!fs.existsSync(oldSkillsDir)) return 0;
|
|
4402
4446
|
|
|
4403
|
-
// Mirror the user-owned list from cleanupCodexSkillMetadataSidecars (#2973).
|
|
4404
|
-
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
4405
4447
|
let removed = 0;
|
|
4406
4448
|
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
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;
|
|
4410
4458
|
|
|
4411
4459
|
const dirToRemove = path.join(oldSkillsDir, entry.name);
|
|
4412
4460
|
try {
|
|
@@ -4421,6 +4469,7 @@ function cleanupMovedSkillsOldLocation(oldSkillsDir, prefix) {
|
|
|
4421
4469
|
// Fail open — a single bad dir must not block install/uninstall.
|
|
4422
4470
|
}
|
|
4423
4471
|
}
|
|
4472
|
+
_warnPreservedSkillDirs(oldSkillsDir, notOwned);
|
|
4424
4473
|
|
|
4425
4474
|
// Prune the old skills dir if now empty — leaves the configHome clean.
|
|
4426
4475
|
// Never remove a non-empty container (user may keep other content there).
|
|
@@ -6478,7 +6527,28 @@ function splitTopLevelKeys(content) {
|
|
|
6478
6527
|
return { topLevel: content.trim(), rest: '' };
|
|
6479
6528
|
}
|
|
6480
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
|
+
|
|
6481
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);
|
|
6482
6552
|
// Case 1: No config.toml — create fresh
|
|
6483
6553
|
if (!fs.existsSync(configPath)) {
|
|
6484
6554
|
atomicWriteFileSync(configPath, gsdBlock + '\n');
|
|
@@ -7691,6 +7761,23 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
|
|
|
7691
7761
|
// combinedFamilyInstall path (installOpencodeFamilyArtifacts) instead of the
|
|
7692
7762
|
// bespoke inline block that used to call this function.
|
|
7693
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
|
+
|
|
7694
7781
|
function listCodexSkillNames(skillsDir, prefix = 'gsd-') {
|
|
7695
7782
|
if (!fs.existsSync(skillsDir)) return [];
|
|
7696
7783
|
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
@@ -7922,6 +8009,10 @@ const RUNTIME_CONTENT_DISPATCH = {
|
|
|
7922
8009
|
return content;
|
|
7923
8010
|
},
|
|
7924
8011
|
},
|
|
8012
|
+
pi: {
|
|
8013
|
+
// #5569: `Agent(...)` -> `gsd_dispatch(role=..., task=...)`.
|
|
8014
|
+
md: (content, ctx) => convertClaudeToPiMarkdown(content, ctx),
|
|
8015
|
+
},
|
|
7925
8016
|
hermes: {
|
|
7926
8017
|
// #2284: brand-swap alone left the false "Agent tool IS available"
|
|
7927
8018
|
// assertion + literal `Agent(...)` call syntax installed verbatim — see
|
|
@@ -8041,6 +8132,8 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
8041
8132
|
if (shouldCompose(srcPath)) {
|
|
8042
8133
|
content = composeWorkflow(content, { sourcePath: srcPath });
|
|
8043
8134
|
}
|
|
8135
|
+
// #5637: the typed-text capture heredoc ends at a terminator the typed text cannot know.
|
|
8136
|
+
content = stampInstalledContent(content);
|
|
8044
8137
|
|
|
8045
8138
|
content = filterRuntimeNotesForTarget(content, runtime);
|
|
8046
8139
|
|
|
@@ -10159,7 +10252,16 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10159
10252
|
if (!hostBehaviorsFor(runtime).skipCodexSkillsManifest && fs.existsSync(codexSkillsDir)) {
|
|
10160
10253
|
// All runtimes (including Hermes post-#947) use the canonical 'gsd-' prefix.
|
|
10161
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 });
|
|
10162
10263
|
for (const skillName of listCodexSkillNames(codexSkillsDir, skillListPrefix)) {
|
|
10264
|
+
if (!ownsSkillDir(skillName) && !USER_OWNED_SKILL_DIRS.includes(skillName)) continue;
|
|
10163
10265
|
const skillRoot = path.join(codexSkillsDir, skillName);
|
|
10164
10266
|
const skillHashes = generateManifest(skillRoot);
|
|
10165
10267
|
for (const [rel, hash] of Object.entries(skillHashes)) {
|
|
@@ -10748,7 +10850,8 @@ function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
|
|
10748
10850
|
if (meta.files && meta.files.length > 0) {
|
|
10749
10851
|
const reapplyCommand = hostBehaviorsFor(runtime).reapplyCommand || '/gsd-update --reapply';
|
|
10750
10852
|
console.log('');
|
|
10751
|
-
|
|
10853
|
+
const fromLabel = meta.from_version ? ' (from v' + meta.from_version + ')' : ' (from a first install)';
|
|
10854
|
+
console.log(' ' + yellow + 'Local patches detected' + reset + fromLabel + ':');
|
|
10752
10855
|
for (const f of meta.files) {
|
|
10753
10856
|
console.log(' ' + cyan + f + reset);
|
|
10754
10857
|
}
|
|
@@ -10761,6 +10864,114 @@ function reportLocalPatches(configDir, runtime = DEFAULT_RUNTIME) {
|
|
|
10761
10864
|
return meta.files || [];
|
|
10762
10865
|
}
|
|
10763
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
|
+
|
|
10764
10975
|
function reportInstallerMigrationResult(result) {
|
|
10765
10976
|
const summary = summarizeInstallerMigrationResult(result);
|
|
10766
10977
|
if (!summary.hasReportableActions) return;
|
|
@@ -10894,6 +11105,20 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10894
11105
|
? process.cwd()
|
|
10895
11106
|
: path.join(process.cwd(), dirName);
|
|
10896
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
|
+
|
|
10897
11122
|
// #3664: a --config-dir destination holding foreign agent files gets an
|
|
10898
11123
|
// explicit install-time warning — never a silent Claude-shaped emit.
|
|
10899
11124
|
if (isGlobal) {
|
|
@@ -10963,8 +11188,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10963
11188
|
const _isCoreProfileAlias = _activeProfileName === 'core';
|
|
10964
11189
|
const _effectiveInstallMode = _isCoreProfileAlias ? 'minimal' : 'full';
|
|
10965
11190
|
// Load the manifest and compute resolved profile for named profiles.
|
|
10966
|
-
// For --minimal/core: use an empty manifest (core profile has no
|
|
10967
|
-
// 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/
|
|
10968
11194
|
// standard profiles, resolveProfile's `registry` arg IS consulted (via
|
|
10969
11195
|
// _capabilitySkillsForMode) so tier:core/tier:standard capability skills are
|
|
10970
11196
|
// unioned in when registered. #2322 correction: for the DEFAULT `full`
|
|
@@ -10977,10 +11203,15 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10977
11203
|
// is passed so an INSTALLED third-party capability (not just a first-party
|
|
10978
11204
|
// one) is honored on every profile, `full` included (#2322 blocker 2).
|
|
10979
11205
|
const _commandsDir = path.join(src, 'commands', 'gsd');
|
|
10980
|
-
|
|
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;
|
|
10981
11211
|
let _resolvedProfile = resolveProfile({
|
|
10982
11212
|
modes: [_activeProfileName],
|
|
10983
11213
|
manifest: _skillsManifest,
|
|
11214
|
+
agentManifest: _fullSkillsManifest,
|
|
10984
11215
|
registry: _installedCapabilityRegistry,
|
|
10985
11216
|
});
|
|
10986
11217
|
// Unified staging function: all profiles use stageSkillsForProfile with the
|
|
@@ -10989,9 +11220,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10989
11220
|
return stageSkillsForProfile(commandsGsdDir, _resolvedProfile);
|
|
10990
11221
|
}
|
|
10991
11222
|
function _stageAgents(agentsDir) {
|
|
10992
|
-
|
|
11223
|
+
// #5503: no core-alias shortcut — Codex gets the same profile-filtered agents as every runtime.
|
|
10993
11224
|
return stageAgentsForProfile(agentsDir, _resolvedProfile);
|
|
10994
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);
|
|
10995
11230
|
const persistActiveProfileMarker = () => {
|
|
10996
11231
|
try {
|
|
10997
11232
|
writeActiveProfile(targetDir, _activeProfileName);
|
|
@@ -11366,7 +11601,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11366
11601
|
// The full restoreCodexSnapshot() (defined inside the config block) additionally
|
|
11367
11602
|
// handles config.toml and the staged hooks/ tree, which are not yet touched
|
|
11368
11603
|
// at this point in the pipeline.
|
|
11369
|
-
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall ||
|
|
11604
|
+
const _codexPreConfigRollback = !hostBehaviorsFor(runtime).tomlConfigInstall || !_codexAgentRolesEnabled() ? null : () => {
|
|
11370
11605
|
rollbackInstallerMigrations();
|
|
11371
11606
|
// skills/gsd-* — pass 1: restore snapshot entries (may be absent if deleted mid-install).
|
|
11372
11607
|
const _earlySkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
@@ -11468,6 +11703,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11468
11703
|
// scope (outside the try block below) so it is accessible in the settings section later.
|
|
11469
11704
|
// Absent VERSION = fresh install; present VERSION = upgrade/re-install.
|
|
11470
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();
|
|
11471
11708
|
try {
|
|
11472
11709
|
installerMigrationResult = runInstallerMigrations({
|
|
11473
11710
|
configDir: targetDir,
|
|
@@ -11521,8 +11758,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11521
11758
|
};
|
|
11522
11759
|
}
|
|
11523
11760
|
}
|
|
11524
|
-
|
|
11761
|
+
if (Array.isArray(installerMigrationResult.blocked) && installerMigrationResult.blocked.length > 0) {
|
|
11762
|
+
reportInstallerMigrationResult(installerMigrationResult); // show what blocked before the assertion throws
|
|
11763
|
+
}
|
|
11525
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
|
+
));
|
|
11526
11773
|
|
|
11527
11774
|
// Artifact install dispatcher — routes to layout-driven path for all
|
|
11528
11775
|
// skills-based runtimes (both full and minimal/core profiles); keeps
|
|
@@ -11688,7 +11935,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11688
11935
|
// in autocomplete. Cleaning them up fixes the duplication; SKILL.md alone is
|
|
11689
11936
|
// sufficient for Codex discovery. User-owned dirs are never touched.
|
|
11690
11937
|
if (hostBehaviorsFor(runtime).cleanupSkillSidecars) {
|
|
11691
|
-
|
|
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
|
+
);
|
|
11692
11948
|
}
|
|
11693
11949
|
|
|
11694
11950
|
// ADR-1239 split-home migration: when a runtime's skills kind moved to an
|
|
@@ -11696,10 +11952,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11696
11952
|
// managed gsd-* skill dirs at the old configDir-rooted location
|
|
11697
11953
|
// (~/.codex/skills). Reinstalling here writes the new location but would
|
|
11698
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.
|
|
11699
11960
|
{
|
|
11700
11961
|
const _movedOldSkillsDir = _resolveMovedSkillsOldDir(runtime, targetDir, scope);
|
|
11701
11962
|
if (_movedOldSkillsDir) {
|
|
11702
|
-
const migrated = cleanupMovedSkillsOldLocation(_movedOldSkillsDir, 'gsd-'
|
|
11963
|
+
const migrated = cleanupMovedSkillsOldLocation(_movedOldSkillsDir, 'gsd-',
|
|
11964
|
+
createSkillDirOwnership(runtime, targetDir, _movedOldSkillsDir, 'gsd-', { includeManifest: false }));
|
|
11703
11965
|
if (migrated > 0) {
|
|
11704
11966
|
console.log(` ${green}✓${reset} Migrated ${migrated} skill dir(s) off the legacy ${_movedOldSkillsDir} location`);
|
|
11705
11967
|
}
|
|
@@ -11713,8 +11975,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11713
11975
|
// Descriptor-driven (ADR-1239 / #2100): folded from `isWindsurf` into
|
|
11714
11976
|
// hostBehaviors.legacyDevinSkillsCleanup (windsurf is the only runtime that
|
|
11715
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.
|
|
11716
11981
|
if (hostBehaviorsFor(runtime).legacyDevinSkillsCleanup && !isGlobal) {
|
|
11717
|
-
const
|
|
11982
|
+
const _devinSkillsDir = path.join(process.cwd(), '.devin', 'skills');
|
|
11983
|
+
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd(),
|
|
11984
|
+
createSkillDirOwnership(runtime, targetDir, _devinSkillsDir, 'gsd-', { includeManifest: false }));
|
|
11718
11985
|
if (removedCount > 0) {
|
|
11719
11986
|
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
|
11720
11987
|
}
|
|
@@ -11730,8 +11997,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11730
11997
|
const hermesSkillsDir = path.join(targetDir, 'skills', 'gsd');
|
|
11731
11998
|
if (fs.existsSync(hermesSkillsDir)) {
|
|
11732
11999
|
// Hermes layout uses prefix: 'gsd-' (#947) — skill dirs have gsd-<stem> names
|
|
11733
|
-
const count =
|
|
11734
|
-
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
12000
|
+
const count = _countOwnedSkillDirs(runtime, targetDir, hermesSkillsDir);
|
|
11735
12001
|
if (count > 0) {
|
|
11736
12002
|
console.log(` ${green}✓${reset} Installed ${count} skills to skills/gsd/`);
|
|
11737
12003
|
} else {
|
|
@@ -11744,8 +12010,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11744
12010
|
const skillsDir = path.join(targetDir, 'skills');
|
|
11745
12011
|
const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml');
|
|
11746
12012
|
if (fs.existsSync(skillsDir)) {
|
|
11747
|
-
const count =
|
|
11748
|
-
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
12013
|
+
const count = _countOwnedSkillDirs(runtime, targetDir, skillsDir);
|
|
11749
12014
|
if (count > 0) {
|
|
11750
12015
|
console.log(` ${green}✓${reset} Installed ${count} Kimi skills to skills/`);
|
|
11751
12016
|
} else {
|
|
@@ -11784,8 +12049,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11784
12049
|
} else {
|
|
11785
12050
|
const skillsDir = _skillsRootDir;
|
|
11786
12051
|
if (fs.existsSync(skillsDir)) {
|
|
11787
|
-
const count =
|
|
11788
|
-
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
|
|
12052
|
+
const count = _countOwnedSkillDirs(runtime, targetDir, skillsDir);
|
|
11789
12053
|
if (count > 0) {
|
|
11790
12054
|
console.log(` ${green}✓${reset} Installed ${count} skills to skills/`);
|
|
11791
12055
|
} else {
|
|
@@ -11845,6 +12109,9 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11845
12109
|
// command/ directories), so writing flat gsd-<cmd>.md files here would be
|
|
11846
12110
|
// dead weight the extension never reads. Skip the flat-commands fallback
|
|
11847
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.
|
|
11848
12115
|
console.log(` ${green}✓${reset} pi: /gsd registered via native extension (no declarative command files)`);
|
|
11849
12116
|
} else {
|
|
11850
12117
|
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
|
@@ -11921,28 +12188,40 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11921
12188
|
}
|
|
11922
12189
|
}
|
|
11923
12190
|
|
|
11924
|
-
// 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.
|
|
11925
12194
|
const staleSkillsDir = path.join(targetDir, 'skills');
|
|
11926
12195
|
if (fs.existsSync(staleSkillsDir)) {
|
|
11927
|
-
|
|
11928
|
-
|
|
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));
|
|
11929
12206
|
for (const e of staleGsd) {
|
|
11930
12207
|
fs.rmSync(path.join(staleSkillsDir, e.name), { recursive: true });
|
|
11931
12208
|
}
|
|
11932
12209
|
if (staleGsd.length > 0) {
|
|
11933
12210
|
console.log(` ${green}✓${reset} Removed ${staleGsd.length} stale GSD skill(s) from skills/`);
|
|
11934
12211
|
}
|
|
12212
|
+
_warnPreservedSkillDirs(staleSkillsDir, prefixed.filter(e => !e.owned).map(e => e.name));
|
|
11935
12213
|
}
|
|
11936
12214
|
}
|
|
11937
12215
|
|
|
11938
12216
|
// Native-extension/plugin staging for runtimes OUTSIDE the layout-driven
|
|
11939
|
-
// _isSkillsRuntime branch above (ADR-1239 / #2102 Stage 1
|
|
12217
|
+
// _isSkillsRuntime branch above (ADR-1239 / #2102 Stage 1). OpenCode/Kilo
|
|
11940
12218
|
// already get their nativePlugin file from installOpencodeFamilyArtifacts
|
|
11941
12219
|
// (called inside the _isSkillsRuntime branch, since both declare a non-empty
|
|
11942
12220
|
// artifactLayout) — guard on `!_isSkillsRuntime` so this standalone call never
|
|
11943
|
-
// double-stages their plugin file.
|
|
11944
|
-
//
|
|
11945
|
-
//
|
|
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.
|
|
11946
12225
|
if (!_isSkillsRuntime && hostBehaviorsFor(runtime).nativePlugin) {
|
|
11947
12226
|
_installNativePluginIfDeclared(runtime, targetDir, hostBehaviorsFor(runtime), src);
|
|
11948
12227
|
}
|
|
@@ -12025,10 +12304,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12025
12304
|
// here explicitly to cover them (install-engine.cts's own doc comment
|
|
12026
12305
|
// explains why; a regression here was caught by the install-tree golden
|
|
12027
12306
|
// fixture, tests/fixtures/install-tree/claude-local.json). It is a no-op
|
|
12028
|
-
// for pi: its capability.json declares
|
|
12029
|
-
// scopes (programmatic dispatch, no named-dispatch subagent toolkit,
|
|
12030
|
-
//
|
|
12031
|
-
//
|
|
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.
|
|
12032
12311
|
if (hostBehaviorsFor(runtime).pluginOnlyInstall) {
|
|
12033
12312
|
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
|
12034
12313
|
} else if (_isSkillsRuntime) {
|
|
@@ -12064,7 +12343,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12064
12343
|
// advertising the old full agent surface even though the descriptor-driven
|
|
12065
12344
|
// write above already skipped writing the .md files for a minimal-tier
|
|
12066
12345
|
// resolvedProfile. Reuse the same helper that powers `--uninstall`.
|
|
12067
|
-
if (
|
|
12346
|
+
if (!_codexAgentRolesEnabled() && hostBehaviorsFor(runtime).tomlConfigInstall) {
|
|
12068
12347
|
const codexConfigPath = path.join(targetDir, 'config.toml');
|
|
12069
12348
|
if (fs.existsSync(codexConfigPath)) {
|
|
12070
12349
|
const existing = fs.readFileSync(codexConfigPath, 'utf8');
|
|
@@ -12473,6 +12752,13 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12473
12752
|
}
|
|
12474
12753
|
}
|
|
12475
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
|
+
|
|
12476
12762
|
if (failures.length > 0) {
|
|
12477
12763
|
console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
|
|
12478
12764
|
process.exit(1);
|
|
@@ -12605,6 +12891,17 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12605
12891
|
const fileName = path.basename(relPath);
|
|
12606
12892
|
if (!(fileName.endsWith('.md') || fileName.endsWith('.toml'))) continue;
|
|
12607
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;
|
|
12608
12905
|
const fullPath = path.join(targetDir, relPath);
|
|
12609
12906
|
let content;
|
|
12610
12907
|
try {
|
|
@@ -12667,9 +12964,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12667
12964
|
// #2695: this branch runs for BOTH `core` (minimal) and `full` profiles.
|
|
12668
12965
|
// Hooks are lightweight infrastructure (update-check + context monitor), not the
|
|
12669
12966
|
// "full agent surface" that `core` deliberately omits. The config.toml / agent
|
|
12670
|
-
// generation below is
|
|
12671
|
-
//
|
|
12672
|
-
//
|
|
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`
|
|
12673
12971
|
// here skipped the whole branch for `core`, so the registered parent hook pointed
|
|
12674
12972
|
// at a worker/registry the same installer never delivered.
|
|
12675
12973
|
if (plan.installSurface === 'codex-toml') {
|
|
@@ -12840,7 +13138,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12840
13138
|
};
|
|
12841
13139
|
|
|
12842
13140
|
let agentCount = 0;
|
|
12843
|
-
if (
|
|
13141
|
+
if (_codexAgentRolesEnabled()) {
|
|
12844
13142
|
// #2834: write ~/.gsd/defaults.json (resolve_model_ids + runtime) BEFORE generating
|
|
12845
13143
|
// agent TOMLs — installCodexConfig reads defaults.json at generation time, so on a
|
|
12846
13144
|
// clean first install the runtime-aware model resolver must already know the runtime.
|
|
@@ -12859,7 +13157,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12859
13157
|
// not include agents/gsd-*.toml because those files did not yet exist.
|
|
12860
13158
|
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
12861
13159
|
} else {
|
|
12862
|
-
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)`);
|
|
12863
13161
|
}
|
|
12864
13162
|
|
|
12865
13163
|
// Copy only the hook files that Codex actually registers via its hook configuration (#2153).
|
|
@@ -13058,7 +13356,8 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13058
13356
|
// existing config; the snapshot restore below is a second line of
|
|
13059
13357
|
// defense if even the rename fails.
|
|
13060
13358
|
try {
|
|
13061
|
-
|
|
13359
|
+
// #5301: write through an opted-in symlink instead of replacing it.
|
|
13360
|
+
atomicWriteFileSync(resolveSymlinkWriteTarget(configPath), configContent, 'utf-8');
|
|
13062
13361
|
} catch (writeErr) {
|
|
13063
13362
|
// #2760 CR4 finding 1 — write failure must be loud and fatal. Wrap
|
|
13064
13363
|
// with a `post-write` prefix the outer catch recognises so install
|
|
@@ -13664,10 +13963,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
13664
13963
|
// bare basename so an unrelated user command that merely mentions the same
|
|
13665
13964
|
// filename can't false-positive into GSD's validated set.
|
|
13666
13965
|
configuredEntrypoints.push(
|
|
13667
|
-
...settingsEntrypoints
|
|
13668
|
-
const hooksSegment = '/hooks/' + path.basename(entry.scriptPath);
|
|
13669
|
-
return registeredHookCommands.some(command => command.includes(hooksSegment));
|
|
13670
|
-
}),
|
|
13966
|
+
...selectConfiguredEntrypointsForValidation(settingsEntrypoints, registeredHookCommands),
|
|
13671
13967
|
);
|
|
13672
13968
|
const statuslineEntrypoints = settingsEntrypoints.filter(entry => entry.command === statuslineCommand);
|
|
13673
13969
|
const updateBannerEntrypoints = settingsEntrypoints.filter(entry => entry.command === updateBannerCommand);
|
|
@@ -13763,6 +14059,25 @@ function describeEntrypointConsequence(invalidRuntime) {
|
|
|
13763
14059
|
return 'not persisted: this runtime writes its config after this check';
|
|
13764
14060
|
}
|
|
13765
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
|
+
|
|
13766
14081
|
function assertConfiguredEntrypoints(entries) {
|
|
13767
14082
|
// #4249: some writers push the same (configPath, scriptPath) pair more than
|
|
13768
14083
|
// once (e.g. Kimi's context-monitor hook registered under several events,
|
|
@@ -14949,6 +15264,8 @@ module.exports = {
|
|
|
14949
15264
|
install,
|
|
14950
15265
|
installAllRuntimes,
|
|
14951
15266
|
uninstall,
|
|
15267
|
+
// #5100 — installer entrypoint-selection seam, exported for direct tests
|
|
15268
|
+
selectConfiguredEntrypointsForValidation,
|
|
14952
15269
|
// #3023 — shared hook bundle directory name, descriptor-driven
|
|
14953
15270
|
SHARED_HOOKS_DIR_DEFAULT,
|
|
14954
15271
|
resolveSharedHooksDirName,
|
|
@@ -15014,6 +15331,8 @@ module.exports = {
|
|
|
15014
15331
|
convertClaudeAgentToClineAgent,
|
|
15015
15332
|
// #2284 — Hermes named-dispatch → delegate_task projection
|
|
15016
15333
|
convertClaudeToHermesMarkdown,
|
|
15334
|
+
convertClaudeToPiMarkdown,
|
|
15335
|
+
PI_DISPATCH_TOOL_CONFIG,
|
|
15017
15336
|
projectNamedDispatchToStructuralDelegate,
|
|
15018
15337
|
_hostIntegrationDispatch,
|
|
15019
15338
|
_resolveAvailableGsdRoles,
|