@opengsd/gsd-core 1.9.1 → 1.11.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 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +608 -148
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +160 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +33 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -21,21 +21,46 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
21
21
|
const node_path_1 = __importDefault(require("node:path"));
|
|
22
22
|
const node_os_1 = __importDefault(require("node:os"));
|
|
23
23
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
24
|
+
// #2874 (ADR-58 cleanup phase): route this module's content-rewrite-pass fs
|
|
25
|
+
// calls through the installRuntimeArtifacts call tree's injectable seam —
|
|
26
|
+
// see install-fs-adapter.cts's module doc. Resolves to real `node:fs` unless
|
|
27
|
+
// the top-level installRuntimeArtifacts call injected a `deps.fs`. These
|
|
28
|
+
// walkers operate on already-staged temp directories (never the real GSD
|
|
29
|
+
// source tree or the real install destination directly), so routing them is
|
|
30
|
+
// unconditionally safe.
|
|
31
|
+
const installFsAdapter = require("./install-fs-adapter.cjs");
|
|
32
|
+
const { installFs, mkInstallTempDir } = installFsAdapter;
|
|
24
33
|
const commandRoster = require("./command-roster.cjs");
|
|
25
34
|
const { readGsdCommandNames, transformContentToHyphen } = commandRoster;
|
|
26
35
|
const runtimeNamePolicy = require("./runtime-name-policy.cjs");
|
|
27
36
|
const { getDirName } = runtimeNamePolicy;
|
|
28
37
|
const capabilityRegistry = require("./capability-registry.cjs");
|
|
38
|
+
const hostIntegration = require("./host-integration.cjs");
|
|
29
39
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
40
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
41
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
42
|
+
// #2870: install-scope.cts is a leaf-tier sibling (imports only
|
|
43
|
+
// runtime-homes.cjs + node builtins, never this module) — no cycle. See the
|
|
44
|
+
// isGlobal sites below for why the boolean projection is centralized here too.
|
|
45
|
+
const install_scope_cjs_1 = require("./install-scope.cjs");
|
|
46
|
+
// #2875 Part 2: install-effort-resolver.cjs is a leaf-tier sibling (#2071) —
|
|
47
|
+
// used by applyAgentFrontmatterExtensions below to read the SAME merged
|
|
48
|
+
// effort config the install-time Claude .md injection has always read,
|
|
49
|
+
// without this module reaching upward into bin/install.js (ADR-1508).
|
|
50
|
+
const installEffortResolver = require("./install-effort-resolver.cjs");
|
|
51
|
+
const { readGsdEffectiveEffortConfig, resolveInstallTimeEffort, _getGsdEffortCatalog } = installEffortResolver;
|
|
30
52
|
// #1383: resolve GSD's version WITHOUT a top-level
|
|
31
53
|
// `require('../../../package.json')`. That require ran at module load on every
|
|
32
54
|
// gsd-tools invocation (this module sits in the gsd-tools loader chain) and
|
|
33
55
|
// threw `Cannot find module '../../../package.json'` on runtimes whose root has
|
|
34
|
-
// no package.json —
|
|
35
|
-
// package.json
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
56
|
+
// no package.json — originally just Codex, where the installer never wrote the
|
|
57
|
+
// synthetic root package.json; since #2544 that is true of EVERY runtime, as
|
|
58
|
+
// GSD's markers moved into `hooks/` and the native plugin dir and the config
|
|
59
|
+
// root is no longer written at all — taking the entire CLI down before it did
|
|
60
|
+
// anything. And even where it used to resolve (the synthetic
|
|
61
|
+
// `{"type":"commonjs"}`), there is no `version` field, so the single consumer
|
|
62
|
+
// below already emitted `version: undefined`. Resolve lazily and defensively
|
|
63
|
+
// instead:
|
|
39
64
|
// 1. Installed trees carry <root>/gsd-core/VERSION (written by the installer);
|
|
40
65
|
// this module lives at <root>/gsd-core/bin/lib, so VERSION is two dirs up.
|
|
41
66
|
// 2. The source / npm-package tree has no gsd-core/VERSION but carries a real
|
|
@@ -267,11 +292,8 @@ function buildKiloAgentPermissionBlock(claudeTools) {
|
|
|
267
292
|
}
|
|
268
293
|
return lines;
|
|
269
294
|
}
|
|
270
|
-
function escapeRegExp(value) {
|
|
271
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
272
|
-
}
|
|
273
295
|
function replaceRelativePathReference(content, fromPath, toPath) {
|
|
274
|
-
const escapedPath =
|
|
296
|
+
const escapedPath = (0, pattern_cjs_1.escapeRegex)(fromPath);
|
|
275
297
|
return content.replace(new RegExp(`(^|[^A-Za-z0-9_./-])${escapedPath}`, 'g'), (_, prefix) => `${prefix}${toPath}`);
|
|
276
298
|
}
|
|
277
299
|
/**
|
|
@@ -364,9 +386,6 @@ function skillFrontmatterName(skillDirName) {
|
|
|
364
386
|
// Return the hyphen form as-is (gsd-<cmd>) — canonical since #2808.
|
|
365
387
|
return skillDirName;
|
|
366
388
|
}
|
|
367
|
-
function normalizeClaudeSkillEffort(effort) {
|
|
368
|
-
return effort === 'xhigh' ? 'max' : effort;
|
|
369
|
-
}
|
|
370
389
|
/**
|
|
371
390
|
* Qwen Code skills accept an optional numeric `priority` frontmatter field.
|
|
372
391
|
* Per the Qwen skills spec (qwen-code/docs/users/features/skills.md, verified
|
|
@@ -421,10 +440,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
421
440
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
422
441
|
const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
|
|
423
442
|
const agent = extractFrontmatterField(frontmatter, 'agent');
|
|
424
|
-
// #769: preserve context:
|
|
425
|
-
//
|
|
443
|
+
// #769: preserve context: from source command files so it is emitted into
|
|
444
|
+
// the installed SKILL.md frontmatter unchanged. (#3151: effort: is no longer
|
|
445
|
+
// emitted into skill frontmatter — a static effort value changes
|
|
446
|
+
// output_config.effort on invocation and invalidates the caller's prompt
|
|
447
|
+
// cache at both scope boundaries; the reporter's owned measurement confirms
|
|
448
|
+
// the mechanism. The separate agent-effort surface is tracked by #3160.)
|
|
426
449
|
const context = extractFrontmatterField(frontmatter, 'context');
|
|
427
|
-
const effort = extractFrontmatterField(frontmatter, 'effort');
|
|
428
450
|
// Preserve allowed-tools as YAML multiline list (Claude native format)
|
|
429
451
|
const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
430
452
|
let toolsBlock = '';
|
|
@@ -464,19 +486,100 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
464
486
|
fm += `argument-hint: ${yamlQuote(argumentHint)}\n`;
|
|
465
487
|
if (agent)
|
|
466
488
|
fm += `agent: ${agent}\n`;
|
|
467
|
-
// #769: emit context:
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
//
|
|
489
|
+
// #769: emit context: when present so the runtime can honour it natively
|
|
490
|
+
// (context: fork = isolated subagent window). Claude-specific; unknown
|
|
491
|
+
// frontmatter fields are silently ignored by other runtimes (backward-compatible).
|
|
492
|
+
// (#3151: effort: is intentionally NOT emitted into skill frontmatter — a
|
|
493
|
+
// static effort value changes output_config.effort on invocation and
|
|
494
|
+
// invalidates the caller's prompt cache at both scope boundaries.)
|
|
471
495
|
if (context)
|
|
472
496
|
fm += `context: ${context}\n`;
|
|
473
|
-
if (effort)
|
|
474
|
-
fm += `effort: ${normalizeClaudeSkillEffort(effort)}\n`;
|
|
475
497
|
if (toolsBlock)
|
|
476
498
|
fm += toolsBlock;
|
|
477
499
|
fm += '---';
|
|
478
500
|
return `${fm}\n${normalizedBody}`;
|
|
479
501
|
}
|
|
502
|
+
// #2873 (4b) — spec-root reachability. Matches ONLY a line that is a real
|
|
503
|
+
// `@~/.claude/gsd-core/workflows/<stem>.md` include: line-start `@`, exact
|
|
504
|
+
// spec-root shape, nothing else on the line. This is deliberately narrower
|
|
505
|
+
// than "any line mentioning gsd-core/workflows" so prose mentions and
|
|
506
|
+
// `references/`/`templates/`/`@.planning/...` includes are never touched
|
|
507
|
+
// (rows 24/25). CRLF-safe: an optional trailing `\r` is captured and
|
|
508
|
+
// preserved rather than dropped.
|
|
509
|
+
const WORKFLOW_SPEC_ROOT_INCLUDE_RE = /^@~\/\.claude\/gsd-core\/workflows\/([A-Za-z0-9._-]+)\.md[ \t]*(\r?)$/gm;
|
|
510
|
+
/**
|
|
511
|
+
* Rewrite a static global-scope Claude skill `@`-include of the command's own
|
|
512
|
+
* workflow spec into an imperative two-step resolution the agent performs at
|
|
513
|
+
* runtime: prefer the project-local spec (cwd-relative), fall back to the
|
|
514
|
+
* global spec, and treat "neither exists" as a visible failure rather than a
|
|
515
|
+
* silent no-spec proceed.
|
|
516
|
+
*
|
|
517
|
+
* WHY this can't stay a static `@`-include (even a relative one): Claude Code
|
|
518
|
+
* documents relative `@`-paths as resolving against the file *containing* the
|
|
519
|
+
* import, which for a global skill is `~/.claude/skills/gsd-<stem>/` — not
|
|
520
|
+
* the project's working directory. `@./.claude/...` would therefore always
|
|
521
|
+
* resolve inside the skill's own install directory, never the project, so
|
|
522
|
+
* there is no static include syntax that can express "prefer local, fall
|
|
523
|
+
* back to global". This function exists precisely so that resolution can be
|
|
524
|
+
* performed by the agent, not the host's pre-expansion.
|
|
525
|
+
*
|
|
526
|
+
* Scope-free by design: this function does not know or care whether it is
|
|
527
|
+
* being applied to a global or local artifact, or which runtime — that
|
|
528
|
+
* judgment belongs to the caller (`skillsKind` in
|
|
529
|
+
* `runtime-artifact-layout.cts`, the one site that knows install scope).
|
|
530
|
+
* Applying it to a body with no workflow include is a no-op (row 26); a body
|
|
531
|
+
* with two independent workflow includes has each rewritten independently
|
|
532
|
+
* (row 27); an include inside a fenced code block or wrapped in inline
|
|
533
|
+
* backticks is left untouched (the backtick case is already excluded by the
|
|
534
|
+
* line-start anchor, since a backtick-wrapped line does not begin with `@`).
|
|
535
|
+
* Idempotent: the replacement text never begins with `@` and never matches
|
|
536
|
+
* `WORKFLOW_SPEC_ROOT_INCLUDE_RE`, so re-applying this function to its own
|
|
537
|
+
* output is a no-op.
|
|
538
|
+
*
|
|
539
|
+
* Fence detection reuses `scanFencedBlocks` (markdown-sectionizer.cts) — the
|
|
540
|
+
* same CommonMark-correct state machine `stripFencedCode`/`extractFencedBlock`
|
|
541
|
+
* are built on — instead of a hand-rolled "any delimiter line toggles
|
|
542
|
+
* open/closed" tracker. A naive toggle is wrong under CommonMark: a fence
|
|
543
|
+
* opened with ``` is NOT closed by a ~~~ line (closer must share the
|
|
544
|
+
* opener's delimiter character and have run length >= the opener's), so a
|
|
545
|
+
* mismatched delimiter is fence CONTENT, not a boundary. #2873 review.
|
|
546
|
+
*/
|
|
547
|
+
function resolveSpecRootReference(body) {
|
|
548
|
+
if (typeof body !== 'string' || body.length === 0)
|
|
549
|
+
return body;
|
|
550
|
+
if (!body.includes('@~/.claude/gsd-core/workflows/'))
|
|
551
|
+
return body;
|
|
552
|
+
// Collect [start, end) character-offset ranges covered by fenced code
|
|
553
|
+
// blocks so matches inside them are skipped. An unterminated trailing
|
|
554
|
+
// fence covers to the end of the string (still "inside a fence").
|
|
555
|
+
const lines = body.split('\n');
|
|
556
|
+
const lineStartOffsets = [];
|
|
557
|
+
{
|
|
558
|
+
let offset = 0;
|
|
559
|
+
for (const line of lines) {
|
|
560
|
+
lineStartOffsets.push(offset);
|
|
561
|
+
offset += line.length + 1; // +1 for the '\n' separator
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const fenceRanges = (0, markdown_sectionizer_cjs_1.scanFencedBlocks)(lines).map(({ openLineIdx, closeLineIdx }) => {
|
|
565
|
+
const start = lineStartOffsets[openLineIdx];
|
|
566
|
+
const end = closeLineIdx === -1
|
|
567
|
+
? body.length
|
|
568
|
+
: lineStartOffsets[closeLineIdx] + lines[closeLineIdx].length;
|
|
569
|
+
return [start, end];
|
|
570
|
+
});
|
|
571
|
+
const isInsideFence = (offset) => fenceRanges.some(([start, end]) => offset >= start && offset < end);
|
|
572
|
+
return body.replace(WORKFLOW_SPEC_ROOT_INCLUDE_RE, (match, stem, cr, offset) => {
|
|
573
|
+
if (isInsideFence(offset))
|
|
574
|
+
return match;
|
|
575
|
+
return (`To load this command's workflow spec: check for ` +
|
|
576
|
+
`\`.claude/gsd-core/workflows/${stem}.md\` relative to the current working ` +
|
|
577
|
+
`directory first (project-local); if it is not there, fall back to ` +
|
|
578
|
+
`\`~/.claude/gsd-core/workflows/${stem}.md\` (the global install). If ` +
|
|
579
|
+
`neither file exists, stop — a workflow spec is required and none was found.` +
|
|
580
|
+
cr);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
480
583
|
function normalizeKimiSkillName(skillName) {
|
|
481
584
|
let text = String(skillName || '').trim().toLowerCase();
|
|
482
585
|
if (text.startsWith('/'))
|
|
@@ -492,7 +595,7 @@ function normalizeKimiSkillName(skillName) {
|
|
|
492
595
|
function convertGsdCommandReferencesToKimiSkillInvocations(content, cmdNames) {
|
|
493
596
|
if (!Array.isArray(cmdNames) || cmdNames.length === 0)
|
|
494
597
|
return content;
|
|
495
|
-
const commands = [...cmdNames].sort((a, b) => b.length - a.length).map(
|
|
598
|
+
const commands = [...cmdNames].sort((a, b) => b.length - a.length).map(pattern_cjs_1.escapeRegex);
|
|
496
599
|
const commandGroup = commands.join('|');
|
|
497
600
|
const colonPattern = new RegExp(`(?<![A-Za-z0-9_/:.-])/?gsd:(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
|
|
498
601
|
const hyphenPattern = new RegExp(`(?:/|\\$)gsd-(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
|
|
@@ -848,8 +951,9 @@ function convertClaudeToCursorMarkdown(content) {
|
|
|
848
951
|
// Remove Claude Code-specific bug workarounds before brand replacement
|
|
849
952
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
850
953
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
851
|
-
// Replace "Claude Code" brand references with "Cursor"
|
|
852
|
-
|
|
954
|
+
// Replace "Claude Code" brand references with "Cursor" — #2284(b): skips
|
|
955
|
+
// <runtime_compatibility> comparison-table content (protected region).
|
|
956
|
+
converted = applyClaudeCodeBrandSwap(converted, 'Cursor');
|
|
853
957
|
return converted;
|
|
854
958
|
}
|
|
855
959
|
function getCursorSkillAdapterHeader(skillName) {
|
|
@@ -890,39 +994,41 @@ function convertClaudeCommandToCursorSkill(content, skillName) {
|
|
|
890
994
|
description = toSingleLine(description);
|
|
891
995
|
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
892
996
|
const adapter = getCursorSkillAdapterHeader(skillName);
|
|
893
|
-
//
|
|
894
|
-
//
|
|
895
|
-
//
|
|
896
|
-
//
|
|
897
|
-
|
|
898
|
-
// '/' while keeping model invocation — distinct from disable-model-invocation.)
|
|
899
|
-
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\nuser-invocable: false\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
900
|
-
}
|
|
901
|
-
/**
|
|
902
|
-
* Convert a Claude Code command to a Cursor 1.6 slash command (#785).
|
|
903
|
-
*
|
|
904
|
-
* Cursor slash commands live in `.cursor/commands/<name>.md` and are
|
|
905
|
-
* plain markdown — no YAML frontmatter, no adapter header. The filename
|
|
906
|
-
* becomes the command name (e.g. `gsd-help.md` → `/gsd-help`).
|
|
907
|
-
*
|
|
908
|
-
* Applies the same `convertClaudeToCursorMarkdown` transforms as the skill
|
|
909
|
-
* converter (tool renames, brand substitution, slash-command normalisation),
|
|
910
|
-
* then strips the YAML frontmatter block so only the prose body remains.
|
|
911
|
-
*
|
|
912
|
-
* @param {string} content raw Claude Code command markdown (may have frontmatter)
|
|
913
|
-
* @param {string} _commandName the target command name (unused; present for
|
|
914
|
-
* API symmetry with other converters so the runtime-artifact-layout stage
|
|
915
|
-
* function can call it uniformly)
|
|
916
|
-
* @returns {string} plain markdown body, no frontmatter
|
|
917
|
-
*/
|
|
918
|
-
function convertClaudeCommandToCursorCommand(content, _commandName) {
|
|
919
|
-
const converted = convertClaudeToCursorMarkdown(content);
|
|
920
|
-
const { body } = extractFrontmatterAndBody(converted);
|
|
921
|
-
return body.trimStart();
|
|
997
|
+
// Cursor skills are both slash-invocable and model-invocable. Do not emit the
|
|
998
|
+
// unsupported `user-invocable` field: it is ignored by Cursor and previously
|
|
999
|
+
// hid the real cause of duplicate entries, the parallel commands/ surface
|
|
1000
|
+
// retired in #2644.
|
|
1001
|
+
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
922
1002
|
}
|
|
923
1003
|
// --- Windsurf converters ---
|
|
924
1004
|
// Windsurf uses a tool set similar to Cursor.
|
|
925
1005
|
// Config lives in .windsurf/ (local) and ~/.codeium/windsurf/ (global).
|
|
1006
|
+
// #2931: ported from bin/install.js's local Windsurf converter copy, which had
|
|
1007
|
+
// picked up the #2284(b) protected-region fix that this exported source never
|
|
1008
|
+
// received. Binding bin/install.js's Windsurf family to these exports (see
|
|
1009
|
+
// tests/install-runtime-artifacts.test.cjs reference-identity assertions)
|
|
1010
|
+
// without this would have silently regressed live installs: `Claude Code`
|
|
1011
|
+
// mentions inside a `<runtime_compatibility>` comparison table would start
|
|
1012
|
+
// getting brand-swapped again. Split `content` on the protected-block regex,
|
|
1013
|
+
// brand-swap only the gap text between (and around) matches, then rejoin
|
|
1014
|
+
// gap+block alternately — no placeholder/sentinel token involved.
|
|
1015
|
+
const RUNTIME_COMPATIBILITY_BLOCK_RE = /<runtime_compatibility>[\s\S]*?<\/runtime_compatibility>/g;
|
|
1016
|
+
function applyClaudeCodeBrandSwap(content, brandName) {
|
|
1017
|
+
if (!brandName)
|
|
1018
|
+
return content;
|
|
1019
|
+
let result = '';
|
|
1020
|
+
let lastIndex = 0;
|
|
1021
|
+
RUNTIME_COMPATIBILITY_BLOCK_RE.lastIndex = 0; // reset shared global-regex state before each use
|
|
1022
|
+
let m;
|
|
1023
|
+
while ((m = RUNTIME_COMPATIBILITY_BLOCK_RE.exec(content))) {
|
|
1024
|
+
const gap = content.slice(lastIndex, m.index);
|
|
1025
|
+
result += gap.replace(/\bClaude Code\b/g, brandName);
|
|
1026
|
+
result += m[0]; // protected block, verbatim — never brand-swapped
|
|
1027
|
+
lastIndex = m.index + m[0].length;
|
|
1028
|
+
}
|
|
1029
|
+
result += content.slice(lastIndex).replace(/\bClaude Code\b/g, brandName);
|
|
1030
|
+
return result;
|
|
1031
|
+
}
|
|
926
1032
|
function convertSlashCommandsToWindsurfSkillMentions(content) {
|
|
927
1033
|
// Keep leading "/" for slash commands; only normalize gsd: -> gsd-.
|
|
928
1034
|
return content.replace(/gsd:/gi, 'gsd-');
|
|
@@ -953,8 +1059,9 @@ function convertClaudeToWindsurfMarkdown(content) {
|
|
|
953
1059
|
// Remove Claude Code-specific bug workarounds before brand replacement
|
|
954
1060
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
955
1061
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
956
|
-
// Replace "Claude Code" brand references with "Windsurf"
|
|
957
|
-
|
|
1062
|
+
// Replace "Claude Code" brand references with "Windsurf" — #2284(b): skips
|
|
1063
|
+
// <runtime_compatibility> comparison-table content (protected region).
|
|
1064
|
+
converted = applyClaudeCodeBrandSwap(converted, 'Windsurf');
|
|
958
1065
|
return converted;
|
|
959
1066
|
}
|
|
960
1067
|
function getWindsurfSkillAdapterHeader(skillName) {
|
|
@@ -993,10 +1100,46 @@ function convertClaudeCommandToWindsurfSkill(content, skillName) {
|
|
|
993
1100
|
}
|
|
994
1101
|
}
|
|
995
1102
|
description = toSingleLine(description);
|
|
996
|
-
|
|
1103
|
+
// #2931: code-point-safe truncation (see truncateWindsurfWorkflowDescription
|
|
1104
|
+
// below) — a raw UTF-16 `slice(0, 177)` can bisect a surrogate pair and
|
|
1105
|
+
// emit a lone surrogate on re-encode. Same exact bounds as before (>180
|
|
1106
|
+
// chars -> first 177 code points + '...'), just harmonized with the
|
|
1107
|
+
// sibling Windsurf workflow converter's helper instead of duplicating the
|
|
1108
|
+
// surrogate-splitting idiom here.
|
|
1109
|
+
const shortDescription = truncateWindsurfWorkflowDescription(description);
|
|
997
1110
|
const adapter = getWindsurfSkillAdapterHeader(skillName);
|
|
998
1111
|
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
999
1112
|
}
|
|
1113
|
+
// #2931: cap on the Windsurf workflow's only unbounded input (the frontmatter
|
|
1114
|
+
// `description`). Shared (not just mirrored) by convertClaudeCommandToWindsurfSkill
|
|
1115
|
+
// above — both converters call truncateWindsurfWorkflowDescription below so
|
|
1116
|
+
// there is exactly one code-point-safe truncation idiom, not two.
|
|
1117
|
+
const WINDSURF_WORKFLOW_DESCRIPTION_MAX = 180;
|
|
1118
|
+
function truncateWindsurfWorkflowDescription(description) {
|
|
1119
|
+
// Multi-byte safe: slice by Unicode code points (`Array.from`), never by
|
|
1120
|
+
// raw UTF-16 index — an index-based slice can bisect a surrogate pair and
|
|
1121
|
+
// emit a lone surrogate / U+FFFD on re-encode. See #2931.
|
|
1122
|
+
const codePoints = Array.from(description);
|
|
1123
|
+
if (codePoints.length <= WINDSURF_WORKFLOW_DESCRIPTION_MAX)
|
|
1124
|
+
return description;
|
|
1125
|
+
return `${codePoints.slice(0, WINDSURF_WORKFLOW_DESCRIPTION_MAX - 3).join('')}...`;
|
|
1126
|
+
}
|
|
1127
|
+
// #2931: SEPARATE size control from the #1615 security regex below — do not
|
|
1128
|
+
// fold the two together or make either conditional on the other. The #1615
|
|
1129
|
+
// regex constrains commandName's CHARACTER CLASS but not its LENGTH, and
|
|
1130
|
+
// commandName is interpolated into the emitted template three times (the
|
|
1131
|
+
// `# <commandName>` heading, the `@.../<stem>.md` @-reference target, and the
|
|
1132
|
+
// trailing "after /<commandName>" mention) — so an unbounded commandName
|
|
1133
|
+
// reopens the byte-cap hole the removed 12000-byte throw used to close
|
|
1134
|
+
// (verified: commandName length 246 -> 900 bytes, 5000 -> 15,162 bytes,
|
|
1135
|
+
// 20000 -> 60,162 bytes — all silently over the old 12000 cap). THROW rather
|
|
1136
|
+
// than truncate: a truncated commandName would silently point the workflow's
|
|
1137
|
+
// `@~/.claude/gsd-core/commands/gsd/<stem>.md` reference at a file that does
|
|
1138
|
+
// not exist (see DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED) — a name
|
|
1139
|
+
// too long to represent is a genuine error, not something to degrade. 128 is
|
|
1140
|
+
// deliberately generous: the longest real shipped command name is
|
|
1141
|
+
// `gsd-plan-review-convergence` at 27 characters.
|
|
1142
|
+
const WINDSURF_COMMAND_NAME_MAX = 128;
|
|
1000
1143
|
function convertClaudeCommandToWindsurfWorkflow(content, commandName) {
|
|
1001
1144
|
// #1615 security: commandName flows unsanitized into a markdown body that
|
|
1002
1145
|
// Windsurf loads as an LLM-readable workflow. Validate at entry to prevent
|
|
@@ -1005,21 +1148,43 @@ function convertClaudeCommandToWindsurfWorkflow(content, commandName) {
|
|
|
1005
1148
|
// Pattern: optional gsd- prefix + lowercase alphanumeric + dashes; rejects
|
|
1006
1149
|
// everything else. See DEFECT.PROMPT-INJECTION-SCAN-COLLISION and the
|
|
1007
1150
|
// PR #1622 security review.
|
|
1151
|
+
// #2931: this is a SECURITY control, not a size control — do not weaken,
|
|
1152
|
+
// reorder, or make it conditional on the description-truncation logic
|
|
1153
|
+
// added below. Keep the two concerns independent even though both happen
|
|
1154
|
+
// to run in this function.
|
|
1008
1155
|
if (typeof commandName !== 'string' || !/^(?:gsd-)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(commandName)) {
|
|
1009
1156
|
const preview = typeof commandName === 'string' ? JSON.stringify(commandName.slice(0, 60)) : String(commandName);
|
|
1010
1157
|
throw new Error(`convertClaudeCommandToWindsurfWorkflow: rejected commandName ${preview}; ` +
|
|
1011
1158
|
'must match /^(?:gsd-)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/ (no slashes, backslashes, spaces, dots, trailing dash, or control chars — prevents prompt injection and path-component injection into the workflow body)');
|
|
1012
1159
|
}
|
|
1160
|
+
// #2931: SEPARATE size control — see WINDSURF_COMMAND_NAME_MAX above for
|
|
1161
|
+
// why this exists and why it throws instead of truncating. Kept as an
|
|
1162
|
+
// independent check from the #1615 regex above (not folded into it, not
|
|
1163
|
+
// conditional on it).
|
|
1164
|
+
if (commandName.length > WINDSURF_COMMAND_NAME_MAX) {
|
|
1165
|
+
const preview = JSON.stringify(commandName.slice(0, 60));
|
|
1166
|
+
throw new Error(`convertClaudeCommandToWindsurfWorkflow: commandName too long (${commandName.length} chars, ` +
|
|
1167
|
+
`preview ${preview}...); max ${WINDSURF_COMMAND_NAME_MAX} chars (see WINDSURF_COMMAND_NAME_MAX)`);
|
|
1168
|
+
}
|
|
1013
1169
|
const converted = convertClaudeToWindsurfMarkdown(content);
|
|
1014
1170
|
const { frontmatter } = extractFrontmatterAndBody(converted);
|
|
1015
|
-
const
|
|
1171
|
+
const rawDescription = frontmatter ? extractFrontmatterField(frontmatter, 'description') : '';
|
|
1172
|
+
// #2931: a whitespace-only description is truthy (`description || fallback`
|
|
1173
|
+
// would keep it) but toSingleLine() collapses it to ''. Treat it as absent
|
|
1174
|
+
// so the fallback is used instead of emitting a blank line.
|
|
1175
|
+
const singleLineDescription = rawDescription ? toSingleLine(rawDescription) : '';
|
|
1176
|
+
const effectiveDescription = truncateWindsurfWorkflowDescription(singleLineDescription || `Run ${commandName}.`);
|
|
1016
1177
|
const stem = commandName.startsWith('gsd-') ? commandName.slice(4) : commandName;
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1178
|
+
// #2931: total emission size is bounded by (fixed template text) +
|
|
1179
|
+
// (3 x WINDSURF_COMMAND_NAME_MAX, one per commandName/stem interpolation
|
|
1180
|
+
// above) + (WINDSURF_WORKFLOW_DESCRIPTION_MAX Unicode code points, up to 4
|
|
1181
|
+
// UTF-8 bytes each). Both inputs are validated/truncated above — commandName
|
|
1182
|
+
// is length-capped-and-thrown by WINDSURF_COMMAND_NAME_MAX, description is
|
|
1183
|
+
// truncated by truncateWindsurfWorkflowDescription — so this bound holds by
|
|
1184
|
+
// construction, not by measurement. The 12000-byte figure itself lives in
|
|
1185
|
+
// exactly one place — the cap table in tests/helpers/emitted-caps.cjs —
|
|
1186
|
+
// this comment only justifies why the actual emitted size stays under it.
|
|
1187
|
+
return `# ${commandName}\n\n${effectiveDescription}\n\nRead and execute the GSD command at @~/.claude/gsd-core/commands/gsd/${stem}.md end-to-end. Treat the user's message after /${commandName} as the command arguments.`;
|
|
1023
1188
|
}
|
|
1024
1189
|
// --- Augment converters ---
|
|
1025
1190
|
// Augment uses a tool set similar to Cursor/Windsurf.
|
|
@@ -1047,8 +1212,9 @@ function convertClaudeToAugmentMarkdown(content) {
|
|
|
1047
1212
|
// Remove Claude Code-specific bug workarounds before brand replacement
|
|
1048
1213
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
1049
1214
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
1050
|
-
// Replace "Claude Code" brand references with "Augment"
|
|
1051
|
-
|
|
1215
|
+
// Replace "Claude Code" brand references with "Augment" — #2284(b): skips
|
|
1216
|
+
// <runtime_compatibility> comparison-table content (protected region).
|
|
1217
|
+
converted = applyClaudeCodeBrandSwap(converted, 'Augment');
|
|
1052
1218
|
return converted;
|
|
1053
1219
|
}
|
|
1054
1220
|
// #2097 (ADR-1239): command-body converters selected by descriptor
|
|
@@ -1111,10 +1277,55 @@ function convertClaudeToTraeMarkdown(content) {
|
|
|
1111
1277
|
// Replace general-purpose subagent type with Trae's equivalent "general_purpose_task"
|
|
1112
1278
|
converted = converted.replace(/subagent_type="general-purpose"/g, 'subagent_type="general_purpose_task"');
|
|
1113
1279
|
converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1280
|
+
// #2658: full-path forms (with a leading dot-claude-slash prefix) MUST be
|
|
1281
|
+
// replaced before the bare Claude-instruction-file pattern and before the
|
|
1282
|
+
// generic dot-claude-slash rewrite below — otherwise the bare pattern
|
|
1283
|
+
// consumes only the instruction-filename tail, leaving that prefix stale
|
|
1284
|
+
// in place, and the generic rewrite then mutates the stale leftover too,
|
|
1285
|
+
// producing a doubled trae-prefix segment ahead of the rules path instead
|
|
1286
|
+
// of a single clean one. (Deliberately never spelling the instruction
|
|
1287
|
+
// filename as one contiguous "CLAUDE" + dot + "md" token, and never
|
|
1288
|
+
// spelling either malformed shape out as a literal contiguous string, in
|
|
1289
|
+
// ANY comment in this function: this file ships verbatim into local
|
|
1290
|
+
// `--trae` installs, where it is itself run through this same class of
|
|
1291
|
+
// find/replace — a literal instruction-filename token sitting in a
|
|
1292
|
+
// comment gets "fixed" right along with real code, and the emitted-content
|
|
1293
|
+
// regression test added alongside this fix asserts neither malformed
|
|
1294
|
+
// shape appears anywhere in the installed tree, comments included; this
|
|
1295
|
+
// bit the fix itself twice during development.) All forms converge on the
|
|
1296
|
+
// same concrete file (never a bare directory) so this stays in parity
|
|
1297
|
+
// with the `trae.js` RUNTIME_CONTENT_DISPATCH entry.
|
|
1298
|
+
converted = converted.replace(/`\.\/\.claude\/CLAUDE\.md`/g, '`.trae/rules/rules.md`');
|
|
1299
|
+
converted = converted.replace(/\.\/\.claude\/CLAUDE\.md/g, '.trae/rules/rules.md');
|
|
1300
|
+
converted = converted.replace(/`\.claude\/CLAUDE\.md`/g, '`.trae/rules/rules.md`');
|
|
1301
|
+
converted = converted.replace(/\.claude\/CLAUDE\.md/g, '.trae/rules/rules.md');
|
|
1302
|
+
// #2658 (found via the end-to-end install regression test, not the static
|
|
1303
|
+
// trace above): `copyWithPathReplacement` runs a GENERIC dot-claude-slash
|
|
1304
|
+
// -> runtime-config-dir rewrite on every .md file before calling this
|
|
1305
|
+
// converter — for `~/.claude/`, `$HOME/.claude/`, AND `./.claude/` alike —
|
|
1306
|
+
// substituting a runtime-appropriate `pathPrefix` this function is never
|
|
1307
|
+
// given and cannot itself compute (it differs per install invocation: a
|
|
1308
|
+
// relative `./.trae/` for a project-local install, an arbitrary absolute
|
|
1309
|
+
// path for a local install rooted elsewhere, `~/.trae/` for a global one).
|
|
1310
|
+
// So for source using any of those prefixed forms, the patterns above
|
|
1311
|
+
// never fire here — this converter only ever sees the ALREADY-rewritten
|
|
1312
|
+
// "<runtime-config-dir>/" + instruction-filename shape, with whatever
|
|
1313
|
+
// prefix the install actually used. The generic pattern below preserves
|
|
1314
|
+
// that prefix verbatim (via the capture group) and only fixes the
|
|
1315
|
+
// filename suffix, rather than assuming a fixed `./.trae/` shape — a
|
|
1316
|
+
// narrower fixed-prefix version of this pattern shipped first and still
|
|
1317
|
+
// left the doubled-prefix defect live for the `$HOME/.claude/` and
|
|
1318
|
+
// `~/.claude/` forms specifically (found the same way, one regression-test
|
|
1319
|
+
// run later). Scoped to a `.trae/` tail so it cannot also swallow the
|
|
1320
|
+
// unprefixed `./CLAUDE.md` form the very next pattern handles differently
|
|
1321
|
+
// (discarding the prefix entirely, not preserving it). Must run before
|
|
1322
|
+
// the bare pattern for the same consume-the-full-match-first reason.
|
|
1323
|
+
converted = converted.replace(/`([^\s`]*\.trae\/)CLAUDE\.md`/g, '`$1rules/rules.md`');
|
|
1324
|
+
converted = converted.replace(/([^\s`]*\.trae\/)CLAUDE\.md/g, '$1rules/rules.md');
|
|
1325
|
+
converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.trae/rules/rules.md`');
|
|
1326
|
+
converted = converted.replace(/\.\/CLAUDE\.md/g, '.trae/rules/rules.md');
|
|
1327
|
+
converted = converted.replace(/`CLAUDE\.md`/g, '`.trae/rules/rules.md`');
|
|
1328
|
+
converted = converted.replace(/\bCLAUDE\.md\b/g, '.trae/rules/rules.md');
|
|
1118
1329
|
converted = converted.replace(/\.claude\/skills\//g, '.trae/skills/');
|
|
1119
1330
|
converted = converted.replace(/\.\/\.claude\//g, './.trae/');
|
|
1120
1331
|
converted = converted.replace(/\.claude\//g, '.trae/');
|
|
@@ -1126,7 +1337,8 @@ function convertClaudeToTraeMarkdown(content) {
|
|
|
1126
1337
|
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'TRAE_CONFIG_DIR');
|
|
1127
1338
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
1128
1339
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
1129
|
-
|
|
1340
|
+
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
1341
|
+
converted = applyClaudeCodeBrandSwap(converted, 'Trae');
|
|
1130
1342
|
return converted;
|
|
1131
1343
|
}
|
|
1132
1344
|
// DEFECT.GENERATIVE-FIX: this body is mirrored in bin/install.js's
|
|
@@ -1180,7 +1392,8 @@ function convertClaudeToCodebuddyMarkdown(content) {
|
|
|
1180
1392
|
converted = converted.replace(/\.claude\//g, '.codebuddy/');
|
|
1181
1393
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
1182
1394
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
1183
|
-
|
|
1395
|
+
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
1396
|
+
converted = applyClaudeCodeBrandSwap(converted, 'CodeBuddy');
|
|
1184
1397
|
return converted;
|
|
1185
1398
|
}
|
|
1186
1399
|
function convertClaudeCommandToCodebuddySkill(content, skillName) {
|
|
@@ -1260,7 +1473,8 @@ function convertClaudeToCliineMarkdown(content) {
|
|
|
1260
1473
|
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'CLINE_CONFIG_DIR');
|
|
1261
1474
|
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
|
|
1262
1475
|
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
|
|
1263
|
-
|
|
1476
|
+
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
1477
|
+
converted = applyClaudeCodeBrandSwap(converted, 'Cline');
|
|
1264
1478
|
return converted;
|
|
1265
1479
|
}
|
|
1266
1480
|
/**
|
|
@@ -1412,7 +1626,9 @@ Typed mapping (agent_type-capable schema only):
|
|
|
1412
1626
|
to \`spawn_agent\` when the runtime/tool supports it. Omit missing, empty,
|
|
1413
1627
|
inherited, or unsupported values; do not invent one-off effort literals in
|
|
1414
1628
|
workflow prose.
|
|
1415
|
-
- \`fork_context: false\` by default — GSD agents load their own context via \`<
|
|
1629
|
+
- \`fork_context: false\` by default — GSD agents load their own context via \`<required_reading>\` blocks
|
|
1630
|
+
- \`task_name\` — required by the collaboration schema; provide a descriptive name for each spawned task
|
|
1631
|
+
- \`fork_turns\` — optional parameter controlling turn-forking depth; coexists with \`fork_context\` (not a replacement)
|
|
1416
1632
|
- \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct \`spawn_agent\` mapping,
|
|
1417
1633
|
but Codex declares \`dispatch.isolation: orchestrator-worktree\` (#2584). Codex
|
|
1418
1634
|
\`spawn_agent\` still does not create or bind a git worktree; instead GSD itself
|
|
@@ -1449,11 +1665,13 @@ Spawn restriction:
|
|
|
1449
1665
|
defaulting to inline execution.
|
|
1450
1666
|
|
|
1451
1667
|
Parallel fan-out:
|
|
1452
|
-
- Spawn multiple agents → collect agent IDs → \`
|
|
1668
|
+
- Spawn multiple agents → collect agent IDs → \`collaboration.wait_agent(timeout_ms=...)\` for each to complete
|
|
1669
|
+
- Do NOT use \`functions.wait(cell_id=...)\` — that is an unrelated exec-cell tool, not the collaboration wait
|
|
1453
1670
|
|
|
1454
1671
|
Result parsing:
|
|
1455
1672
|
- Look for structured markers in agent output: \`CHECKPOINT\`, \`PLAN COMPLETE\`, \`SUMMARY\`, etc.
|
|
1456
|
-
- \`close_agent(id)\` after collecting results
|
|
1673
|
+
- \`close_agent(id)\` after collecting results — but only if \`close_agent\` is visible in the current
|
|
1674
|
+
tool schema (check via \`tool_search\` first, same schema-detection gate as \`spawn_agent\` above)
|
|
1457
1675
|
</codex_skill_adapter>`;
|
|
1458
1676
|
}
|
|
1459
1677
|
function convertClaudeCommandToCodexSkill(content, skillName) {
|
|
@@ -2038,8 +2256,9 @@ function convertClaudeAgentToQwenAgent(content) {
|
|
|
2038
2256
|
let converted = content;
|
|
2039
2257
|
if (_b['CLAUDE.md'])
|
|
2040
2258
|
converted = converted.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
2259
|
+
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
2041
2260
|
if (_b['Claude Code'])
|
|
2042
|
-
converted = converted
|
|
2261
|
+
converted = applyClaudeCodeBrandSwap(converted, _b['Claude Code']);
|
|
2043
2262
|
if (_b['.claude/'])
|
|
2044
2263
|
converted = converted.replace(/\.claude\//g, _b['.claude/']);
|
|
2045
2264
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
@@ -2062,6 +2281,106 @@ function convertClaudeAgentToQwenAgent(content) {
|
|
|
2062
2281
|
fm += '---';
|
|
2063
2282
|
return `${fm}\n${body}`;
|
|
2064
2283
|
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Convert a Claude Code agent .md for ZCode (#3384).
|
|
2286
|
+
*
|
|
2287
|
+
* ZCode is Claude-shaped (same frontmatter, same named-dispatch subagents), so
|
|
2288
|
+
* the file is preserved verbatim EXCEPT the `tools:` grant list: ZCode's
|
|
2289
|
+
* dispatcher treats every `mcp__<server>__*` entry as a REQUIRED MCP server and
|
|
2290
|
+
* hard-fails the subagent spawn (CONFIGURATION_ERROR: "Required MCP server is
|
|
2291
|
+
* not connected") whenever it is not connected, whereas Claude Code treats the
|
|
2292
|
+
* same entries as an optional allowlist. The `mcp__*` entries are stripped at
|
|
2293
|
+
* install time — the same exclusion Kimi's converter applies via
|
|
2294
|
+
* convertKimiToolName — so subagent spawns succeed with zero MCP servers
|
|
2295
|
+
* configured; connected servers' tools remain reachable (auto-discovered by the
|
|
2296
|
+
* host, not granted by frontmatter).
|
|
2297
|
+
*
|
|
2298
|
+
* Line-surgical by design: ONLY `tools:` lines inside the frontmatter are
|
|
2299
|
+
* touched, so every other byte (description, color, commented-out blocks, the
|
|
2300
|
+
* body) survives identically. Handles both shapes GSD emits — the inline comma
|
|
2301
|
+
* list (`tools: A, B, C`) and the YAML block list (`tools:` + `- A` items).
|
|
2302
|
+
* An agent whose filtered grant list becomes empty (every grant was `mcp__*`)
|
|
2303
|
+
* drops the `tools:` key entirely: an absent key inherits the full toolkit,
|
|
2304
|
+
* which is the degrade-gracefully outcome, never a toolless subagent.
|
|
2305
|
+
*
|
|
2306
|
+
* Byte-identical for an agent with no `mcp__*` grants (the common case) and
|
|
2307
|
+
* for an agent with no frontmatter at all.
|
|
2308
|
+
*/
|
|
2309
|
+
function convertClaudeAgentToZcodeAgent(content) {
|
|
2310
|
+
// Fast path: no MCP grant token anywhere means nothing to strip. (A body
|
|
2311
|
+
// mention alone is not a grant — the line scan below finds no tools-line
|
|
2312
|
+
// change and returns `content` unchanged anyway; this just skips the scan.)
|
|
2313
|
+
if (!content.includes('mcp__'))
|
|
2314
|
+
return content;
|
|
2315
|
+
const lines = content.split('\n');
|
|
2316
|
+
if (lines[0] !== '---')
|
|
2317
|
+
return content;
|
|
2318
|
+
let fmEnd = -1;
|
|
2319
|
+
for (let i = 1; i < lines.length; i++) {
|
|
2320
|
+
if (lines[i] === '---') {
|
|
2321
|
+
fmEnd = i;
|
|
2322
|
+
break;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (fmEnd === -1)
|
|
2326
|
+
return content; // unterminated frontmatter — leave verbatim
|
|
2327
|
+
const out = [];
|
|
2328
|
+
let changed = false;
|
|
2329
|
+
let i = 1;
|
|
2330
|
+
while (i < fmEnd) {
|
|
2331
|
+
const line = lines[i];
|
|
2332
|
+
const inlineTools = /^tools:[ \t]*(.+)$/.exec(line);
|
|
2333
|
+
if (inlineTools) {
|
|
2334
|
+
const grants = inlineTools[1].split(',').map((tool) => tool.trim()).filter((tool) => tool !== '');
|
|
2335
|
+
const kept = grants.filter((tool) => !tool.startsWith('mcp__'));
|
|
2336
|
+
if (kept.length === grants.length) {
|
|
2337
|
+
out.push(line); // no mcp__* grants — keep the line byte-identical
|
|
2338
|
+
}
|
|
2339
|
+
else if (kept.length > 0) {
|
|
2340
|
+
out.push(`tools: ${kept.join(', ')}`);
|
|
2341
|
+
changed = true;
|
|
2342
|
+
}
|
|
2343
|
+
else {
|
|
2344
|
+
changed = true; // every grant was mcp__*: drop the tools key entirely
|
|
2345
|
+
}
|
|
2346
|
+
i++;
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
if (/^tools:[ \t]*$/.test(line)) {
|
|
2350
|
+
// Block-list form: collect the following `- item` lines.
|
|
2351
|
+
const items = [];
|
|
2352
|
+
let j = i + 1;
|
|
2353
|
+
while (j < fmEnd && /^([ \t]*)-[ \t]*(\S.*)$/.test(lines[j])) {
|
|
2354
|
+
items.push(lines[j]);
|
|
2355
|
+
j++;
|
|
2356
|
+
}
|
|
2357
|
+
const kept = items.filter((item) => {
|
|
2358
|
+
const name = /^([ \t]*)-[ \t]*(\S.*)$/.exec(item)[2].trim();
|
|
2359
|
+
return !name.startsWith('mcp__');
|
|
2360
|
+
});
|
|
2361
|
+
if (kept.length !== items.length) {
|
|
2362
|
+
changed = true;
|
|
2363
|
+
if (kept.length > 0) {
|
|
2364
|
+
out.push(line);
|
|
2365
|
+
out.push(...kept);
|
|
2366
|
+
} // else: drop the tools key and all its items
|
|
2367
|
+
}
|
|
2368
|
+
else {
|
|
2369
|
+
out.push(line, ...items);
|
|
2370
|
+
}
|
|
2371
|
+
i = j;
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
out.push(line);
|
|
2375
|
+
i++;
|
|
2376
|
+
}
|
|
2377
|
+
if (!changed)
|
|
2378
|
+
return content;
|
|
2379
|
+
// Opening delimiter + transformed frontmatter + closing delimiter + body.
|
|
2380
|
+
out.unshift(lines[0]);
|
|
2381
|
+
out.push(...lines.slice(fmEnd));
|
|
2382
|
+
return out.join('\n');
|
|
2383
|
+
}
|
|
2065
2384
|
function convertClaudeAgentToCodebuddyAgent(content) {
|
|
2066
2385
|
const converted = convertClaudeToCodebuddyMarkdown(content);
|
|
2067
2386
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
@@ -2082,6 +2401,48 @@ function convertClaudeAgentToClineAgent(content) {
|
|
|
2082
2401
|
const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
|
|
2083
2402
|
return `${cleanFrontmatter}\n${body}`;
|
|
2084
2403
|
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Apply a runtime's descriptor-declared `hostBehaviors.brandingRewrites` to an
|
|
2406
|
+
* agent body — the three literal-substring replaces the inline agent loop
|
|
2407
|
+
* (bin/install.js) previously hardcoded per-branding-runtime (qwen/hermes):
|
|
2408
|
+
* CLAUDE.md -> brandingRewrites['CLAUDE.md']
|
|
2409
|
+
* Claude Code -> brandingRewrites['Claude Code'] (word-boundary, \bClaude Code\b)
|
|
2410
|
+
* .claude/ -> brandingRewrites['.claude/']
|
|
2411
|
+
*
|
|
2412
|
+
* Data-driven (#2875 Part 2 / J10): reads the rewrite table from the
|
|
2413
|
+
* runtime's OWN descriptor rather than hardcoding any runtime's strings, so a
|
|
2414
|
+
* runtime declaring a different `brandingRewrites` table gets its own
|
|
2415
|
+
* rewrites applied automatically. A runtime with no `brandingRewrites`
|
|
2416
|
+
* declared returns `content` unchanged (no rewrite table to apply).
|
|
2417
|
+
*
|
|
2418
|
+
* Byte-identical to the inline loop's `else if (_hostBehaviors(runtime).brandingRewrites)`
|
|
2419
|
+
* branch, including plain (non-word-boundary) `.replace(/\bClaude Code\b/g, ...)`
|
|
2420
|
+
* semantics — J9.
|
|
2421
|
+
*/
|
|
2422
|
+
function applyAgentBrandingRewrites(content, runtime) {
|
|
2423
|
+
const _b = _hostBehaviors(runtime).brandingRewrites;
|
|
2424
|
+
if (!_b)
|
|
2425
|
+
return content;
|
|
2426
|
+
let converted = content;
|
|
2427
|
+
if (_b['CLAUDE.md'])
|
|
2428
|
+
converted = converted.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
2429
|
+
if (_b['Claude Code'])
|
|
2430
|
+
converted = converted.replace(/\bClaude Code\b/g, _b['Claude Code']);
|
|
2431
|
+
if (_b['.claude/'])
|
|
2432
|
+
converted = converted.replace(/\.claude\//g, _b['.claude/']);
|
|
2433
|
+
return converted;
|
|
2434
|
+
}
|
|
2435
|
+
/**
|
|
2436
|
+
* Named branding converter for Hermes agents (#2875 Part 2 / J9-J10).
|
|
2437
|
+
* `convertedAgentsKind` dispatches converters by exported name, so a named
|
|
2438
|
+
* export is required even though the transform itself is fully generic
|
|
2439
|
+
* (`applyAgentBrandingRewrites`) — resolved from
|
|
2440
|
+
* `capabilities/hermes/capability.json`'s `hostBehaviors.brandingRewrites`,
|
|
2441
|
+
* never hardcoded here.
|
|
2442
|
+
*/
|
|
2443
|
+
function convertClaudeAgentToHermesAgent(content) {
|
|
2444
|
+
return applyAgentBrandingRewrites(content, 'hermes');
|
|
2445
|
+
}
|
|
2085
2446
|
/**
|
|
2086
2447
|
* Convert Claude Code agent markdown to Codex agent format.
|
|
2087
2448
|
* Applies base markdown conversions, then adds a <codex_agent_role> header
|
|
@@ -2194,20 +2555,145 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
|
|
|
2194
2555
|
const NON_CLAUDE_RUNTIMES = Object.keys(capabilityRegistry.runtimes)
|
|
2195
2556
|
.filter((id) => id !== 'claude')
|
|
2196
2557
|
.sort();
|
|
2558
|
+
/**
|
|
2559
|
+
* #2652: The isolation a runtime can actually negotiate at dispatch time,
|
|
2560
|
+
* resolved from the registry exactly as `gsd_run query dispatch-isolation`
|
|
2561
|
+
* resolves it at runtime (`routeDispatchIsolation`, gsd-core/bin/gsd-tools.cjs):
|
|
2562
|
+
* the declared value must be in the closed vocabulary, a `harness-worktree`
|
|
2563
|
+
* host must also declare the flag the scheduler passes, and an
|
|
2564
|
+
* `orchestrator-worktree` host must carry a descriptor that resolves. Anything
|
|
2565
|
+
* else — unknown runtime, `undocumented`, out-of-vocabulary, a throw — is
|
|
2566
|
+
* `none` (ADR-1239, "Fail-closed").
|
|
2567
|
+
*
|
|
2568
|
+
* Install time cannot know the worktree path a future dispatch will target, so
|
|
2569
|
+
* the descriptor is probed with a placeholder; `resolveOrchestratorExec` fails
|
|
2570
|
+
* only on descriptor shape, never on a well-formed target's value.
|
|
2571
|
+
*
|
|
2572
|
+
* @private — exported as `_negotiatedDispatchIsolation` for tests.
|
|
2573
|
+
*/
|
|
2574
|
+
function _negotiatedDispatchIsolation(runtime) {
|
|
2575
|
+
try {
|
|
2576
|
+
const runtimeEntry = capabilityRegistry?.runtimes?.[runtime] ?? null;
|
|
2577
|
+
const declared = runtimeEntry?.runtime?.hostIntegration?.dispatch?.isolation ?? null;
|
|
2578
|
+
if (declared === 'harness-worktree') {
|
|
2579
|
+
const declaredFlag = runtimeEntry?.runtime?.harnessIsolationFlag ?? null;
|
|
2580
|
+
return typeof declaredFlag === 'string' && declaredFlag.length > 0
|
|
2581
|
+
? 'harness-worktree'
|
|
2582
|
+
: 'none';
|
|
2583
|
+
}
|
|
2584
|
+
if (declared === 'orchestrator-worktree') {
|
|
2585
|
+
return hostIntegration.resolveOrchestratorExec(runtimeEntry?.runtime?.orchestratorExec, '/gsd-orchestrator-worktree-probe').ok
|
|
2586
|
+
? 'orchestrator-worktree'
|
|
2587
|
+
: 'none';
|
|
2588
|
+
}
|
|
2589
|
+
return 'none';
|
|
2590
|
+
}
|
|
2591
|
+
catch {
|
|
2592
|
+
return 'none';
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2197
2595
|
/**
|
|
2198
2596
|
* #1521: Every non-Claude runtime resolves its own runtime identity from a
|
|
2199
|
-
* runtime-neutral config
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2597
|
+
* runtime-neutral config. Stamped into the emitted workflow runtime-resolution
|
|
2598
|
+
* blocks. (Generalizes the Codex-only #1515 fix.)
|
|
2599
|
+
*
|
|
2600
|
+
* #1521 also stamped `workflow.use_worktrees` to default false for every
|
|
2601
|
+
* non-Claude runtime, because GSD's worktree isolation was Claude Code's
|
|
2602
|
+
* `isolation="worktree"` spawn parameter and no other runtime honored it.
|
|
2603
|
+
* #2584 removed that premise: isolation is now a negotiated capability
|
|
2604
|
+
* (`dispatch.isolation`), and Cursor declares `harness-worktree` while Codex,
|
|
2605
|
+
* OpenCode, Kimi and Kimi Code declare `orchestrator-worktree`. Stamping the
|
|
2606
|
+
* false default for those hosts resolved `USE_WORKTREES=false` before
|
|
2607
|
+
* `dispatch.isolation` was ever consulted, so a runtime that declares worktree
|
|
2608
|
+
* support still got `ISOLATION=none` — judged by its name after all, which is
|
|
2609
|
+
* the defect #2652 exists to remove. The stamp is therefore scoped to the
|
|
2610
|
+
* runtimes whose negotiated isolation really is `none`, where the default it
|
|
2611
|
+
* writes is the outcome the resolver would reach anyway.
|
|
2203
2612
|
*
|
|
2204
2613
|
* @private — exported as `_stampNonClaudeRuntimeDefaults` for tests.
|
|
2205
2614
|
*/
|
|
2206
2615
|
function _stampNonClaudeRuntimeDefaults(content, runtime) {
|
|
2207
|
-
|
|
2616
|
+
if (_negotiatedDispatchIsolation(runtime) === 'none') {
|
|
2617
|
+
content = content.replace(/config-get workflow\.use_worktrees --raw 2>\/dev\/null \|\| echo "true"/g, 'config-get workflow.use_worktrees --default false --raw 2>/dev/null || echo "false"');
|
|
2618
|
+
}
|
|
2208
2619
|
content = content.replace(/config-get runtime --default claude --raw 2>\/dev\/null \|\| echo "claude"/g, `config-get runtime --default ${runtime} --raw 2>/dev/null || echo "${runtime}"`);
|
|
2209
2620
|
return content;
|
|
2210
2621
|
}
|
|
2622
|
+
/**
|
|
2623
|
+
* #3544 (extending #3133's fix): restore `@$HOME<suffix>` `@`-file-reference
|
|
2624
|
+
* lines back to their tilde equivalent (`@~<suffix>`) in Claude-emitted
|
|
2625
|
+
* content whose pathPrefix is the `$HOME` form. This is a NARROW,
|
|
2626
|
+
* context-sensitive correction layered on top of the blanket `~/.claude/` /
|
|
2627
|
+
* `$HOME/.claude/` -> pathPrefix substitution every Claude emit path
|
|
2628
|
+
* applies: that blanket substitution MUST keep emitting `$HOME` for global
|
|
2629
|
+
* installs — shell commands embedded in workflow/command bodies (e.g.
|
|
2630
|
+
* `node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"`) need it, since `~` does
|
|
2631
|
+
* not expand inside double-quoted shell strings (#1284). But Claude Code's
|
|
2632
|
+
* own `@`-import resolver does the opposite: it documents `~` expansion and
|
|
2633
|
+
* does NOT expand `$HOME`. That is not merely undocumented — a controlled
|
|
2634
|
+
* `/context` measurement showed an `@$HOME/…` import loading nothing (see
|
|
2635
|
+
* .gsd/bug/fix-3544-home-expansion-spec-tree/10-diagnosis.md's ADDENDUM). No
|
|
2636
|
+
* automated test can verify *resolution* inside a live Claude Code session
|
|
2637
|
+
* (nothing in CI can spawn one and read `/context`); every test here — unit
|
|
2638
|
+
* and spawned-installer alike — verifies only the emitted STRING takes the
|
|
2639
|
+
* `~` form Claude Code documents as expanding. A single pathPrefix string
|
|
2640
|
+
* cannot satisfy both the shell and the `@`-import consumer, so this runs as
|
|
2641
|
+
* a second, `@`-anchored pass AFTER the blanket substitution.
|
|
2642
|
+
*
|
|
2643
|
+
* #3133 first applied this restore inline in `_applyRuntimeRewrites`'s
|
|
2644
|
+
* `case 'claude'` below (the skill/command staging pipeline). #3544 found
|
|
2645
|
+
* the identical defect in bin/install.js's `copyWithPathReplacement` — the
|
|
2646
|
+
* `gsd-core/` spec-tree emit path, which never had the restore step, so
|
|
2647
|
+
* every `@~/.claude/gsd-core/…` include in a global install's workflows/
|
|
2648
|
+
* references tree silently resolved to nothing (54 includes across 22 files
|
|
2649
|
+
* on a live install, per the diagnosis). Both call sites now share this one
|
|
2650
|
+
* implementation instead of drifting independently (DEFECT.GENERATIVE-FIX).
|
|
2651
|
+
*
|
|
2652
|
+
* No-op unless `pathPrefix` is the `$HOME` form — local installs already
|
|
2653
|
+
* bake an absolute, `@`-resolvable pathPrefix and are unaffected, as are
|
|
2654
|
+
* every non-Claude runtime (never called for them).
|
|
2655
|
+
*
|
|
2656
|
+
* #3544 review (2nd pass): the first cut of this function hardcoded the
|
|
2657
|
+
* literal `.claude/` segment, so it silently no-opped for any global install
|
|
2658
|
+
* under a non-default `--config-dir` (e.g. `~/.claude-work`) — reproducing
|
|
2659
|
+
* the exact defect #3544 fixes, just one directory name later. This ALSO
|
|
2660
|
+
* corrects the same latent gap in #3133's original path, since both call
|
|
2661
|
+
* sites share this one implementation. Fixed by deriving the rewrite from
|
|
2662
|
+
* `pathPrefix` itself rather than a hardcoded directory name: the tilde
|
|
2663
|
+
* equivalent of any `$HOME`-form prefix is `'~' + pathPrefix.slice(5)`
|
|
2664
|
+
* (`'$HOME'.length === 5`), so the transform generalizes to any config-dir
|
|
2665
|
+
* name with no runtime-specific literal.
|
|
2666
|
+
*
|
|
2667
|
+
* #3544 review (2nd pass), quote-awareness: the anchor is a negative
|
|
2668
|
+
* lookbehind for a preceding quote character, NOT a line-start anchor —
|
|
2669
|
+
* Claude Code documents `@`-references as valid "anywhere in your
|
|
2670
|
+
* CLAUDE.md" (e.g. `See @README for project overview`), so anchoring to
|
|
2671
|
+
* line-start would miss a legitimate mid-line reference. The lookbehind
|
|
2672
|
+
* instead guards the one demonstrated false-positive: a quoted shell string
|
|
2673
|
+
* like `echo "@$HOME/.claude/x"`, where rewriting `$HOME` to `~` inside
|
|
2674
|
+
* double quotes reintroduces the #1284 failure mode (`~` does not expand in
|
|
2675
|
+
* double-quoted shell). Deliberately NOT fenced-code-block aware (unlike
|
|
2676
|
+
* `resolveSpecRootReference`'s `scanFencedBlocks` use above): this pass
|
|
2677
|
+
* targets genuine `@`-import lines and inline shell references across the
|
|
2678
|
+
* whole emitted corpus, and today there are zero occurrences anywhere in the
|
|
2679
|
+
* tree of an `@$HOME<suffix>` sequence inside a fenced code block (the
|
|
2680
|
+
* quote-guard already closes the one reachable false-positive class).
|
|
2681
|
+
* Layering `scanFencedBlocks` on top would roughly double this function's
|
|
2682
|
+
* size to guard an undemonstrated case — the opposite of the brief's
|
|
2683
|
+
* "simpler, not more complex" direction. If a fenced example ever needs this
|
|
2684
|
+
* literal sequence, add fence-awareness then, with a regression test proving
|
|
2685
|
+
* the fence is real.
|
|
2686
|
+
*
|
|
2687
|
+
* @private — exported as `_restoreClaudeGlobalAtRefTilde` for tests and for
|
|
2688
|
+
* bin/install.js's `copyWithPathReplacement`.
|
|
2689
|
+
*/
|
|
2690
|
+
function restoreClaudeGlobalAtRefTilde(content, pathPrefix) {
|
|
2691
|
+
if (typeof pathPrefix !== 'string' || !pathPrefix.startsWith('$HOME'))
|
|
2692
|
+
return content;
|
|
2693
|
+
const tildeEquivalent = '~' + pathPrefix.slice('$HOME'.length);
|
|
2694
|
+
const atRefRe = new RegExp(`(?<!["'])@${(0, pattern_cjs_1.escapeRegex)(pathPrefix)}`, 'g');
|
|
2695
|
+
return content.replace(atRefRe, `@${tildeEquivalent}`);
|
|
2696
|
+
}
|
|
2211
2697
|
/**
|
|
2212
2698
|
* Apply the per-runtime rewrite table to a single content string.
|
|
2213
2699
|
* Relocated from bin/install.js `_applyRuntimeRewrites`.
|
|
@@ -2284,7 +2770,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2284
2770
|
// #2097: dot-dir self-references (~/.augment/…) → resolved prefix,
|
|
2285
2771
|
// dirName-derived (no runtime literal). getDirName('augment') resolves
|
|
2286
2772
|
// to '.augment', so this is byte-identical to the prior hardcoded regexes.
|
|
2287
|
-
const _dd =
|
|
2773
|
+
const _dd = (0, pattern_cjs_1.escapeRegex)(dirName);
|
|
2288
2774
|
content = content.replace(new RegExp('~/' + _dd + '/', 'g'), pathPrefix);
|
|
2289
2775
|
content = content.replace(new RegExp('\\$HOME/' + _dd + '/', 'g'), pathPrefix);
|
|
2290
2776
|
content = content.replace(new RegExp('~/' + _dd + '(?![\\w-])', 'g'), normalizedPathPrefix);
|
|
@@ -2302,7 +2788,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2302
2788
|
// #2094: descriptor-driven — dirName resolves to '.trae' via
|
|
2303
2789
|
// getDirName()/localConfigDir, so this regex is built rather than
|
|
2304
2790
|
// hardcoded as `/~\/\.trae\//g` (byte-identical output for trae).
|
|
2305
|
-
content = content.replace(new RegExp('~/' +
|
|
2791
|
+
content = content.replace(new RegExp('~/' + (0, pattern_cjs_1.escapeRegex)(dirName) + '/', 'g'), pathPrefix);
|
|
2306
2792
|
content = processAttribution(content, attribution);
|
|
2307
2793
|
break;
|
|
2308
2794
|
case 'codebuddy':
|
|
@@ -2328,6 +2814,10 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2328
2814
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2329
2815
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2330
2816
|
content = content.replace(/\.\/\.claude\//g, `./${dirName}/`);
|
|
2817
|
+
// #3133 / #3544: restore @-file-reference lines to the tilde form
|
|
2818
|
+
// Claude actually expands — see restoreClaudeGlobalAtRefTilde's doc
|
|
2819
|
+
// comment above for why this must be a separate, @-anchored pass.
|
|
2820
|
+
content = restoreClaudeGlobalAtRefTilde(content, pathPrefix);
|
|
2331
2821
|
content = processAttribution(content, attribution);
|
|
2332
2822
|
break;
|
|
2333
2823
|
// Descriptor-driven brand literals (ADR-1239 / #2092): the qwen/hermes
|
|
@@ -2342,7 +2832,8 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2342
2832
|
const _b = _hostBehaviors(runtime).brandingRewrites;
|
|
2343
2833
|
if (_b) {
|
|
2344
2834
|
content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
2345
|
-
|
|
2835
|
+
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
2836
|
+
content = applyClaudeCodeBrandSwap(content, _b['Claude Code']);
|
|
2346
2837
|
}
|
|
2347
2838
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2348
2839
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
@@ -2366,7 +2857,8 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2366
2857
|
const _b = _hostBehaviors(runtime).brandingRewrites;
|
|
2367
2858
|
if (_b) {
|
|
2368
2859
|
content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
2369
|
-
|
|
2860
|
+
// #2284(b): skips <runtime_compatibility> comparison-table content (protected region).
|
|
2861
|
+
content = applyClaudeCodeBrandSwap(content, _b['Claude Code']);
|
|
2370
2862
|
}
|
|
2371
2863
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2372
2864
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
@@ -2413,18 +2905,18 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2413
2905
|
* @param attribution Co-Authored-By value (string | null | undefined)
|
|
2414
2906
|
*/
|
|
2415
2907
|
function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
2416
|
-
if (!
|
|
2908
|
+
if (!installFs().existsSync(stagedDir))
|
|
2417
2909
|
return;
|
|
2418
2910
|
const walkAndRewrite = (dir) => {
|
|
2419
|
-
for (const entry of
|
|
2911
|
+
for (const entry of installFs().readdirSync(dir, { withFileTypes: true })) {
|
|
2420
2912
|
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
2421
2913
|
if (entry.isDirectory()) {
|
|
2422
2914
|
walkAndRewrite(fullPath);
|
|
2423
2915
|
}
|
|
2424
2916
|
else if (entry.name.endsWith('.md')) {
|
|
2425
|
-
let content =
|
|
2917
|
+
let content = installFs().readFileSync(fullPath, 'utf8');
|
|
2426
2918
|
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
|
|
2427
|
-
|
|
2919
|
+
installFs().writeFileSync(fullPath, content);
|
|
2428
2920
|
}
|
|
2429
2921
|
}
|
|
2430
2922
|
};
|
|
@@ -2449,14 +2941,14 @@ function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGl
|
|
|
2449
2941
|
* @returns {string} path to the temp dir (caller is responsible for cleanup)
|
|
2450
2942
|
*/
|
|
2451
2943
|
function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
2452
|
-
if (!
|
|
2944
|
+
if (!installFs().existsSync(stagedDir))
|
|
2453
2945
|
return stagedDir;
|
|
2454
|
-
const tempDir =
|
|
2946
|
+
const tempDir = mkInstallTempDir('gsd-cmd-rewrites-');
|
|
2455
2947
|
try {
|
|
2456
|
-
for (const entry of
|
|
2948
|
+
for (const entry of installFs().readdirSync(stagedDir, { withFileTypes: true })) {
|
|
2457
2949
|
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
2458
2950
|
continue;
|
|
2459
|
-
let content =
|
|
2951
|
+
let content = installFs().readFileSync(node_path_1.default.join(stagedDir, entry.name), 'utf8');
|
|
2460
2952
|
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
|
|
2461
2953
|
// #2097 (ADR-1239): descriptor-driven — commandBodyConverter name comes
|
|
2462
2954
|
// from runtime.hostBehaviors instead of a hardcoded runtime-name branch.
|
|
@@ -2464,18 +2956,52 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
|
|
|
2464
2956
|
if (_cmdConv && COMMAND_BODY_CONVERTERS[_cmdConv]) {
|
|
2465
2957
|
content = COMMAND_BODY_CONVERTERS[_cmdConv](content);
|
|
2466
2958
|
}
|
|
2467
|
-
|
|
2959
|
+
installFs().writeFileSync(node_path_1.default.join(tempDir, entry.name), content);
|
|
2468
2960
|
}
|
|
2469
2961
|
}
|
|
2470
2962
|
catch (err) {
|
|
2471
2963
|
try {
|
|
2472
|
-
|
|
2964
|
+
installFs().rmSync(tempDir, { recursive: true, force: true });
|
|
2473
2965
|
}
|
|
2474
2966
|
catch { /* best-effort */ }
|
|
2475
2967
|
throw err;
|
|
2476
2968
|
}
|
|
2477
2969
|
return tempDir;
|
|
2478
2970
|
}
|
|
2971
|
+
/**
|
|
2972
|
+
* #2873 (4b) — second pass over a staged skills directory, run strictly AFTER
|
|
2973
|
+
* `applyRuntimeContentRewritesInPlace`. That pass's `case 'claude':` branch
|
|
2974
|
+
* unconditionally rewrites any bare (non-`@`-prefixed) `~/.claude/` substring
|
|
2975
|
+
* in the body to the computed pathPrefix (`$HOME/.claude/` for a global
|
|
2976
|
+
* install) and restores ONLY the `@`-prefixed form back to `~`
|
|
2977
|
+
* (`@$HOME/.claude/` → `@~/.claude/`). `resolveSpecRootReference`'s
|
|
2978
|
+
* replacement text is deliberately imperative prose containing a literal,
|
|
2979
|
+
* non-`@`-prefixed `~/.claude/gsd-core/workflows/<stem>.md` — running it
|
|
2980
|
+
* BEFORE the pass above would let that literal tilde text get silently
|
|
2981
|
+
* mangled into the undocumented `$HOME/` form the design explicitly rejects.
|
|
2982
|
+
* Running it here, after, means it only ever sees the FINAL
|
|
2983
|
+
* `@~/.claude/gsd-core/workflows/<stem>.md` include line (which survives the
|
|
2984
|
+
* pass above intact via its own `@`-guarded restore).
|
|
2985
|
+
*/
|
|
2986
|
+
function applySpecRootReferenceToStagedSkills(stagedDir) {
|
|
2987
|
+
if (!installFs().existsSync(stagedDir))
|
|
2988
|
+
return;
|
|
2989
|
+
const walk = (dir) => {
|
|
2990
|
+
for (const entry of installFs().readdirSync(dir, { withFileTypes: true })) {
|
|
2991
|
+
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
2992
|
+
if (entry.isDirectory()) {
|
|
2993
|
+
walk(fullPath);
|
|
2994
|
+
}
|
|
2995
|
+
else if (entry.name === 'SKILL.md') {
|
|
2996
|
+
const content = installFs().readFileSync(fullPath, 'utf8');
|
|
2997
|
+
const rewritten = resolveSpecRootReference(content);
|
|
2998
|
+
if (rewritten !== content)
|
|
2999
|
+
installFs().writeFileSync(fullPath, rewritten);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
};
|
|
3003
|
+
walk(stagedDir);
|
|
3004
|
+
}
|
|
2479
3005
|
/**
|
|
2480
3006
|
* HIGH-LEVEL: In-place fs walk: rewrite all .md files under stagedDir for the given runtime.
|
|
2481
3007
|
*
|
|
@@ -2492,16 +3018,30 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
|
|
|
2492
3018
|
*/
|
|
2493
3019
|
function rewriteStagedSkillBodies(stagedDir, opts) {
|
|
2494
3020
|
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2495
|
-
if (!
|
|
3021
|
+
if (!installFs().existsSync(stagedDir))
|
|
2496
3022
|
return;
|
|
2497
3023
|
const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
|
|
2498
3024
|
const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
|
|
2499
|
-
|
|
3025
|
+
// #2870: `scope` is defaulted to 'global' above, so it is never undefined
|
|
3026
|
+
// here, and every reachable caller passes 'global' | 'local' | undefined —
|
|
3027
|
+
// isGlobalScope's throw-on-out-of-union case is unreachable at this site.
|
|
3028
|
+
const isGlobal = (0, install_scope_cjs_1.isGlobalScope)(scope);
|
|
2500
3029
|
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
|
2501
3030
|
const isWindowsHost = platform === 'win32';
|
|
2502
3031
|
const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
|
2503
3032
|
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
|
2504
3033
|
applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal, attribution);
|
|
3034
|
+
// #2873 (4b): claude, global scope only — see
|
|
3035
|
+
// applySpecRootReferenceToStagedSkills's doc comment for why this MUST run
|
|
3036
|
+
// after the rewrite pass above, not before. `rewriteStagedSkillBodies` is
|
|
3037
|
+
// the skills-kind seam (`kind.kind === 'skills'`), so this never touches a
|
|
3038
|
+
// 'commands' or 'agents' kind body (rows 24/25 unaffected), and claude has
|
|
3039
|
+
// no skills-kind entry at local scope, so this is already structurally
|
|
3040
|
+
// scoped to global (row 23) — the explicit isGlobal check is defense-in-depth
|
|
3041
|
+
// against that descriptor wiring ever changing.
|
|
3042
|
+
if (runtime === 'claude' && isGlobal) {
|
|
3043
|
+
applySpecRootReferenceToStagedSkills(stagedDir);
|
|
3044
|
+
}
|
|
2505
3045
|
}
|
|
2506
3046
|
/**
|
|
2507
3047
|
* HIGH-LEVEL: Copy-to-temp then rewrite all .md files for the given runtime.
|
|
@@ -2521,11 +3061,14 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
|
|
|
2521
3061
|
*/
|
|
2522
3062
|
function rewriteStagedCommandBodies(stagedDir, opts) {
|
|
2523
3063
|
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2524
|
-
if (!
|
|
3064
|
+
if (!installFs().existsSync(stagedDir))
|
|
2525
3065
|
return stagedDir;
|
|
2526
3066
|
const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
|
|
2527
3067
|
const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
|
|
2528
|
-
|
|
3068
|
+
// #2870: `scope` is defaulted to 'global' above, so it is never undefined
|
|
3069
|
+
// here, and every reachable caller passes 'global' | 'local' | undefined —
|
|
3070
|
+
// isGlobalScope's throw-on-out-of-union case is unreachable at this site.
|
|
3071
|
+
const isGlobal = (0, install_scope_cjs_1.isGlobalScope)(scope);
|
|
2529
3072
|
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
|
2530
3073
|
const isWindowsHost = platform === 'win32';
|
|
2531
3074
|
const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
|
@@ -2586,6 +3129,127 @@ function applyAgentPathRewrites(content, runtime, pathPrefix) {
|
|
|
2586
3129
|
return content;
|
|
2587
3130
|
}
|
|
2588
3131
|
// ── End rewrite engine ────────────────────────────────────────────────────────
|
|
3132
|
+
/**
|
|
3133
|
+
* Derive an agent's stem name from its source `.md` filename. Byte-identical
|
|
3134
|
+
* to the inline agent loop's `entry.name.replace(/\.md$/, '')` (bin/install.js)
|
|
3135
|
+
* — single-sourced here so the descriptor pipeline's per-agent resolution
|
|
3136
|
+
* context (`agentCtx.agentName`, ADR-1235 §1 / #2875 Part 2 row I3) can never
|
|
3137
|
+
* diverge from it. A filename with no trailing `.md` is returned unchanged
|
|
3138
|
+
* (the regex has nothing to match) — I3's boundary row.
|
|
3139
|
+
*/
|
|
3140
|
+
function deriveAgentName(fileName) {
|
|
3141
|
+
return fileName.replace(/\.md$/, '');
|
|
3142
|
+
}
|
|
3143
|
+
/**
|
|
3144
|
+
* #443 — Inject `effort: <value>` into YAML frontmatter of a Claude .md agent
|
|
3145
|
+
* file in a newline-agnostic way (LF and CRLF source files are both handled).
|
|
3146
|
+
* Relocated verbatim from bin/install.js (#2875 Part 2) — see
|
|
3147
|
+
* `applyAgentFrontmatterExtensions` below for the orchestration that calls it.
|
|
3148
|
+
*
|
|
3149
|
+
* The function:
|
|
3150
|
+
* - Detects the file's EOL (CRLF if the first `---` line ends with \r\n,
|
|
3151
|
+
* otherwise LF).
|
|
3152
|
+
* - Skips injection if an `effort:` key already exists in the frontmatter
|
|
3153
|
+
* (idempotent).
|
|
3154
|
+
* - Inserts `effort: <value>` immediately before the closing `---` delimiter,
|
|
3155
|
+
* using the same EOL as the surrounding frontmatter so the output file
|
|
3156
|
+
* stays EOL-consistent.
|
|
3157
|
+
* - Returns the original content unchanged when no YAML frontmatter is found.
|
|
3158
|
+
*/
|
|
3159
|
+
function injectEffortFrontmatter(content, effortValue) {
|
|
3160
|
+
const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
|
|
3161
|
+
const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
|
|
3162
|
+
const match = fmRe.exec(content);
|
|
3163
|
+
if (!match)
|
|
3164
|
+
return content; // no YAML frontmatter — leave unchanged
|
|
3165
|
+
const fmBody = match[1]; // content between the two `---` lines
|
|
3166
|
+
if (/^effort:/m.test(fmBody))
|
|
3167
|
+
return content;
|
|
3168
|
+
const openLen = 3 + eol.length; // "---" + eol
|
|
3169
|
+
const closingStart = match.index + openLen + fmBody.length;
|
|
3170
|
+
const before = content.slice(0, closingStart);
|
|
3171
|
+
const after = content.slice(closingStart);
|
|
3172
|
+
return `${before}effort: ${effortValue}${eol}${after}`;
|
|
3173
|
+
}
|
|
3174
|
+
/**
|
|
3175
|
+
* #767 — Inject `disallowedTools: <value>` into the YAML frontmatter of a
|
|
3176
|
+
* Claude .md agent. Mirrors injectEffortFrontmatter: idempotent (skips if
|
|
3177
|
+
* disallowedTools: already present), inserts immediately before the closing
|
|
3178
|
+
* `---`. Claude-only — never call for other runtimes, which break on unknown
|
|
3179
|
+
* frontmatter keys. Relocated verbatim from bin/install.js (#2875 Part 2).
|
|
3180
|
+
*/
|
|
3181
|
+
function injectDisallowedToolsFrontmatter(content, disallowedValue) {
|
|
3182
|
+
const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
|
|
3183
|
+
const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
|
|
3184
|
+
const match = fmRe.exec(content);
|
|
3185
|
+
if (!match)
|
|
3186
|
+
return content; // no YAML frontmatter — leave unchanged
|
|
3187
|
+
const fmBody = match[1]; // content between the two `---` lines
|
|
3188
|
+
if (/^disallowedTools:/m.test(fmBody))
|
|
3189
|
+
return content;
|
|
3190
|
+
const openLen = 3 + eol.length; // "---" + eol
|
|
3191
|
+
const closingStart = match.index + openLen + fmBody.length;
|
|
3192
|
+
const before = content.slice(0, closingStart);
|
|
3193
|
+
const after = content.slice(closingStart);
|
|
3194
|
+
return `${before}disallowedTools: ${disallowedValue}${eol}${after}`;
|
|
3195
|
+
}
|
|
3196
|
+
// #767 — Read-only verifier/auditor agents get a Claude-Code disallowedTools deny-list.
|
|
3197
|
+
// Group A (pure read-only) deny Write,Edit,MultiEdit. Group B report-writers Write one
|
|
3198
|
+
// output file so they deny only Edit,MultiEdit. gsd-nyquist-auditor is intentionally
|
|
3199
|
+
// excluded (it legitimately uses Write AND Edit to create/patch test files). Relocated
|
|
3200
|
+
// verbatim from bin/install.js (#2875 Part 2) — single source of truth for both the
|
|
3201
|
+
// inline loop (which now requires this export) and the descriptor pipeline.
|
|
3202
|
+
const READONLY_AGENT_DISALLOWED_TOOLS = {
|
|
3203
|
+
'gsd-plan-checker': 'Write, Edit, MultiEdit',
|
|
3204
|
+
'gsd-integration-checker': 'Write, Edit, MultiEdit',
|
|
3205
|
+
'gsd-ui-checker': 'Write, Edit, MultiEdit',
|
|
3206
|
+
'gsd-verifier': 'Edit, MultiEdit',
|
|
3207
|
+
'gsd-doc-verifier': 'Edit, MultiEdit',
|
|
3208
|
+
'gsd-eval-auditor': 'Edit, MultiEdit',
|
|
3209
|
+
'gsd-ui-auditor': 'Edit, MultiEdit',
|
|
3210
|
+
};
|
|
3211
|
+
/**
|
|
3212
|
+
* Post-converter frontmatter-extensions step (#2875 Part 2 / ADR-1235 §1
|
|
3213
|
+
* follow-up). Driven by the runtime descriptor's
|
|
3214
|
+
* `hostBehaviors.agentFrontmatterExtensions` allow-list — Claude is its only
|
|
3215
|
+
* declared consumer today (`agentFrontmatterExtensions: ["effort"]`).
|
|
3216
|
+
* A runtime that does NOT declare the extension gets nothing injected (J3):
|
|
3217
|
+
* OpenCode/Qwen/Hermes reject unknown frontmatter keys.
|
|
3218
|
+
*
|
|
3219
|
+
* Byte-identical to the inline agent loop's
|
|
3220
|
+
* `if ((_hostBehaviors(runtime).agentFrontmatterExtensions || []).includes('effort'))`
|
|
3221
|
+
* block (bin/install.js): both the effort injection AND the disallowedTools
|
|
3222
|
+
* injection are gated behind the SAME `'effort'` extension flag — there is no
|
|
3223
|
+
* separate `'disallowedTools'` extension key, mirroring the loop exactly.
|
|
3224
|
+
*
|
|
3225
|
+
* J2 (the trap row): when the resolved effort is `'inherit'`, NO `effort:`
|
|
3226
|
+
* key is written at all — the absence of the key IS the behavior (#3533).
|
|
3227
|
+
* Writing `effort: inherit` would be a regression that looks like success.
|
|
3228
|
+
*
|
|
3229
|
+
* @param content agent .md content, already converter-transformed
|
|
3230
|
+
* @param runtime canonical runtime ID
|
|
3231
|
+
* @param agentName agent stem (from deriveAgentName), e.g. 'gsd-planner'
|
|
3232
|
+
* @param targetDir install root — resolves .planning/config.json + ~/.gsd/defaults.json
|
|
3233
|
+
*/
|
|
3234
|
+
function applyAgentFrontmatterExtensions(content, { runtime, agentName, targetDir }) {
|
|
3235
|
+
const extensions = _hostBehaviors(runtime).agentFrontmatterExtensions || [];
|
|
3236
|
+
if (!extensions.includes('effort'))
|
|
3237
|
+
return content;
|
|
3238
|
+
let result = content;
|
|
3239
|
+
const effortCfg = readGsdEffectiveEffortConfig(targetDir ?? null);
|
|
3240
|
+
const universalEffort = resolveInstallTimeEffort(effortCfg, agentName);
|
|
3241
|
+
// #3533 (10d): 'inherit' means the effort: key must NOT exist — Claude Code
|
|
3242
|
+
// then follows the session effort. The canonical source agents carry no
|
|
3243
|
+
// effort key, so skipping injection is the whole job.
|
|
3244
|
+
if (universalEffort !== 'inherit') {
|
|
3245
|
+
const renderedEffort = _getGsdEffortCatalog().renderEffortForRuntime(runtime, universalEffort).value;
|
|
3246
|
+
result = injectEffortFrontmatter(result, renderedEffort);
|
|
3247
|
+
}
|
|
3248
|
+
const disallowedTools = READONLY_AGENT_DISALLOWED_TOOLS[agentName];
|
|
3249
|
+
if (disallowedTools)
|
|
3250
|
+
result = injectDisallowedToolsFrontmatter(result, disallowedTools);
|
|
3251
|
+
return result;
|
|
3252
|
+
}
|
|
2589
3253
|
/**
|
|
2590
3254
|
* Apply Co-Authored-By attribution policy to file content.
|
|
2591
3255
|
* - null -> remove the Co-Authored-By line and its preceding blank line
|
|
@@ -2628,15 +3292,25 @@ module.exports = {
|
|
|
2628
3292
|
convertClaudeToAntigravityContent,
|
|
2629
3293
|
convertClaudeCommandToAntigravitySkill,
|
|
2630
3294
|
convertClaudeCommandToClaudeSkill,
|
|
3295
|
+
// #2873 (4b): pure, scope-free transform — applied by the one call site
|
|
3296
|
+
// that knows install scope (skillsKind's stage() in
|
|
3297
|
+
// runtime-artifact-layout.cts), never inside convertClaudeCommandToClaudeSkill
|
|
3298
|
+
// itself.
|
|
3299
|
+
resolveSpecRootReference,
|
|
2631
3300
|
convertClaudeCommandToKimiSkill,
|
|
2632
3301
|
convertClaudeCommandToKimiCodeSkill,
|
|
2633
3302
|
buildKimiAgentArtifacts,
|
|
2634
3303
|
convertClaudeToCursorMarkdown,
|
|
2635
3304
|
convertClaudeCommandToCursorSkill,
|
|
2636
|
-
convertClaudeCommandToCursorCommand,
|
|
2637
3305
|
convertClaudeToWindsurfMarkdown,
|
|
2638
3306
|
convertClaudeCommandToWindsurfSkill,
|
|
2639
3307
|
convertClaudeCommandToWindsurfWorkflow,
|
|
3308
|
+
// #2931: single-sourced brand-swap helper (was duplicated verbatim in
|
|
3309
|
+
// bin/install.js — the exact drift class this PR exists to reduce). Used
|
|
3310
|
+
// internally by convertClaudeToWindsurfMarkdown/convertClaudeToAugmentMarkdown
|
|
3311
|
+
// above and bound from here by the remaining bin/install.js converters
|
|
3312
|
+
// (Cursor/Trae/CodeBuddy/Cline) that still brand-swap inline.
|
|
3313
|
+
applyClaudeCodeBrandSwap,
|
|
2640
3314
|
convertClaudeToAugmentMarkdown,
|
|
2641
3315
|
convertClaudeCommandToAugmentSkill,
|
|
2642
3316
|
convertClaudeToTraeMarkdown,
|
|
@@ -2677,11 +3351,20 @@ module.exports = {
|
|
|
2677
3351
|
convertClaudeAgentToCodebuddyAgent,
|
|
2678
3352
|
convertClaudeAgentToClineAgent,
|
|
2679
3353
|
convertClaudeAgentToCodexAgent,
|
|
3354
|
+
// #2875 Part 2 (J10): Hermes named branding converter, generic underlying
|
|
3355
|
+
// transform exported alongside it for direct reuse/testing.
|
|
3356
|
+
convertClaudeAgentToHermesAgent,
|
|
3357
|
+
applyAgentBrandingRewrites,
|
|
2680
3358
|
// ADR-1239 / #2092 Phase B Upgrade 1: native .qwen/agents/*.md subagent
|
|
2681
3359
|
// projection — registered by name so convertedAgentsKind's
|
|
2682
3360
|
// conversionExports[converterName] dispatch (runtime-artifact-layout.cts)
|
|
2683
3361
|
// can resolve it from capabilities/qwen/capability.json's agents kind.
|
|
2684
3362
|
convertClaudeAgentToQwenAgent,
|
|
3363
|
+
// #3384: ZCode agents are Claude-shaped but its dispatcher treats mcp__*
|
|
3364
|
+
// tools grants as required MCP servers — registered by name for the same
|
|
3365
|
+
// conversionExports[converterName] dispatch, resolved from
|
|
3366
|
+
// capabilities/zcode/capability.json's agents kind.
|
|
3367
|
+
convertClaudeAgentToZcodeAgent,
|
|
2685
3368
|
// #1511 ADR-1508 Phase 2: rewrite engine deep seam
|
|
2686
3369
|
// Low-level walkers (pathPrefix + attribution pre-resolved by caller):
|
|
2687
3370
|
applyRuntimeContentRewritesInPlace,
|
|
@@ -2692,9 +3375,21 @@ module.exports = {
|
|
|
2692
3375
|
// ADR-1235 §1: descriptor-driven agent cross-cutting
|
|
2693
3376
|
applyAgentPathRewrites,
|
|
2694
3377
|
normalizeAgentBodyForRuntime,
|
|
3378
|
+
// #2875 Part 2: descriptor-driven agent frontmatter-extensions step + its
|
|
3379
|
+
// single-sourced building blocks (also required back by bin/install.js so
|
|
3380
|
+
// the inline loop and the descriptor pipeline resolve through the SAME
|
|
3381
|
+
// code — no drift between the two byte-parity-gated pipelines).
|
|
3382
|
+
deriveAgentName,
|
|
3383
|
+
injectEffortFrontmatter,
|
|
3384
|
+
injectDisallowedToolsFrontmatter,
|
|
3385
|
+
READONLY_AGENT_DISALLOWED_TOOLS,
|
|
3386
|
+
applyAgentFrontmatterExtensions,
|
|
2695
3387
|
_computePathPrefix: computePathPrefix,
|
|
3388
|
+
_restoreClaudeGlobalAtRefTilde: restoreClaudeGlobalAtRefTilde,
|
|
2696
3389
|
_applyRuntimeRewrites,
|
|
2697
3390
|
_stampNonClaudeRuntimeDefaults,
|
|
3391
|
+
// #2652: registry-resolved dispatch isolation, mirroring routeDispatchIsolation
|
|
3392
|
+
_negotiatedDispatchIsolation,
|
|
2698
3393
|
// #1521: canonical non-Claude runtime list for test files and tooling
|
|
2699
3394
|
NON_CLAUDE_RUNTIMES,
|
|
2700
3395
|
};
|