@opengsd/gsd-core 1.10.0 → 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 +1 -1
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- 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 +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.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 +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -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 +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- 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 +575 -101
- 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 +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- 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 +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- 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-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- 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 +134 -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-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- 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 +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- 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 +56 -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/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
- 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/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -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 +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- 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 +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- 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 +24 -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 +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- 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/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- 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/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- 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.md +2 -1
- 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 +31 -6
- 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 +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- 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/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- 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/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- 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 +77 -2
- 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 +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- 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 +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- 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 +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- 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/changeset/lint.cjs +60 -5
- 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-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- 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 +1 -26
- 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-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-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -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 +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- 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 +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- 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 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -21,12 +21,34 @@ 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
|
|
@@ -270,11 +292,8 @@ function buildKiloAgentPermissionBlock(claudeTools) {
|
|
|
270
292
|
}
|
|
271
293
|
return lines;
|
|
272
294
|
}
|
|
273
|
-
function escapeRegExp(value) {
|
|
274
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
275
|
-
}
|
|
276
295
|
function replaceRelativePathReference(content, fromPath, toPath) {
|
|
277
|
-
const escapedPath =
|
|
296
|
+
const escapedPath = (0, pattern_cjs_1.escapeRegex)(fromPath);
|
|
278
297
|
return content.replace(new RegExp(`(^|[^A-Za-z0-9_./-])${escapedPath}`, 'g'), (_, prefix) => `${prefix}${toPath}`);
|
|
279
298
|
}
|
|
280
299
|
/**
|
|
@@ -367,16 +386,6 @@ function skillFrontmatterName(skillDirName) {
|
|
|
367
386
|
// Return the hyphen form as-is (gsd-<cmd>) — canonical since #2808.
|
|
368
387
|
return skillDirName;
|
|
369
388
|
}
|
|
370
|
-
function normalizeClaudeSkillEffort(effort) {
|
|
371
|
-
// #3039: `max` is rejected by Anthropic models when extended thinking is
|
|
372
|
-
// disabled (400: output_config.effort 'max' is not supported when thinking
|
|
373
|
-
// is disabled). The frontmatter is static at install time and the installer
|
|
374
|
-
// cannot know whether thinking will be on or off at invocation. `high` is the
|
|
375
|
-
// maximum value that works in both states on all supported models.
|
|
376
|
-
if (effort === 'xhigh' || effort === 'max')
|
|
377
|
-
return 'high';
|
|
378
|
-
return effort;
|
|
379
|
-
}
|
|
380
389
|
/**
|
|
381
390
|
* Qwen Code skills accept an optional numeric `priority` frontmatter field.
|
|
382
391
|
* Per the Qwen skills spec (qwen-code/docs/users/features/skills.md, verified
|
|
@@ -431,10 +440,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
431
440
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
432
441
|
const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
|
|
433
442
|
const agent = extractFrontmatterField(frontmatter, 'agent');
|
|
434
|
-
// #769: preserve context:
|
|
435
|
-
//
|
|
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.)
|
|
436
449
|
const context = extractFrontmatterField(frontmatter, 'context');
|
|
437
|
-
const effort = extractFrontmatterField(frontmatter, 'effort');
|
|
438
450
|
// Preserve allowed-tools as YAML multiline list (Claude native format)
|
|
439
451
|
const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
440
452
|
let toolsBlock = '';
|
|
@@ -474,19 +486,100 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
474
486
|
fm += `argument-hint: ${yamlQuote(argumentHint)}\n`;
|
|
475
487
|
if (agent)
|
|
476
488
|
fm += `agent: ${agent}\n`;
|
|
477
|
-
// #769: emit context:
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
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.)
|
|
481
495
|
if (context)
|
|
482
496
|
fm += `context: ${context}\n`;
|
|
483
|
-
if (effort)
|
|
484
|
-
fm += `effort: ${normalizeClaudeSkillEffort(effort)}\n`;
|
|
485
497
|
if (toolsBlock)
|
|
486
498
|
fm += toolsBlock;
|
|
487
499
|
fm += '---';
|
|
488
500
|
return `${fm}\n${normalizedBody}`;
|
|
489
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
|
+
}
|
|
490
583
|
function normalizeKimiSkillName(skillName) {
|
|
491
584
|
let text = String(skillName || '').trim().toLowerCase();
|
|
492
585
|
if (text.startsWith('/'))
|
|
@@ -502,7 +595,7 @@ function normalizeKimiSkillName(skillName) {
|
|
|
502
595
|
function convertGsdCommandReferencesToKimiSkillInvocations(content, cmdNames) {
|
|
503
596
|
if (!Array.isArray(cmdNames) || cmdNames.length === 0)
|
|
504
597
|
return content;
|
|
505
|
-
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);
|
|
506
599
|
const commandGroup = commands.join('|');
|
|
507
600
|
const colonPattern = new RegExp(`(?<![A-Za-z0-9_/:.-])/?gsd:(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
|
|
508
601
|
const hyphenPattern = new RegExp(`(?:/|\\$)gsd-(${commandGroup})(?=[^A-Za-z0-9_-]|$)`, 'g');
|
|
@@ -1533,7 +1626,7 @@ Typed mapping (agent_type-capable schema only):
|
|
|
1533
1626
|
to \`spawn_agent\` when the runtime/tool supports it. Omit missing, empty,
|
|
1534
1627
|
inherited, or unsupported values; do not invent one-off effort literals in
|
|
1535
1628
|
workflow prose.
|
|
1536
|
-
- \`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
|
|
1537
1630
|
- \`task_name\` — required by the collaboration schema; provide a descriptive name for each spawned task
|
|
1538
1631
|
- \`fork_turns\` — optional parameter controlling turn-forking depth; coexists with \`fork_context\` (not a replacement)
|
|
1539
1632
|
- \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct \`spawn_agent\` mapping,
|
|
@@ -2188,6 +2281,106 @@ function convertClaudeAgentToQwenAgent(content) {
|
|
|
2188
2281
|
fm += '---';
|
|
2189
2282
|
return `${fm}\n${body}`;
|
|
2190
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
|
+
}
|
|
2191
2384
|
function convertClaudeAgentToCodebuddyAgent(content) {
|
|
2192
2385
|
const converted = convertClaudeToCodebuddyMarkdown(content);
|
|
2193
2386
|
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
@@ -2208,6 +2401,48 @@ function convertClaudeAgentToClineAgent(content) {
|
|
|
2208
2401
|
const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
|
|
2209
2402
|
return `${cleanFrontmatter}\n${body}`;
|
|
2210
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
|
+
}
|
|
2211
2446
|
/**
|
|
2212
2447
|
* Convert Claude Code agent markdown to Codex agent format.
|
|
2213
2448
|
* Applies base markdown conversions, then adds a <codex_agent_role> header
|
|
@@ -2320,20 +2555,145 @@ function computePathPrefix({ isGlobal, isOpencode, isWindowsHost: _isWindowsHost
|
|
|
2320
2555
|
const NON_CLAUDE_RUNTIMES = Object.keys(capabilityRegistry.runtimes)
|
|
2321
2556
|
.filter((id) => id !== 'claude')
|
|
2322
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
|
+
}
|
|
2323
2595
|
/**
|
|
2324
2596
|
* #1521: Every non-Claude runtime resolves its own runtime identity from a
|
|
2325
|
-
* runtime-neutral config
|
|
2326
|
-
*
|
|
2327
|
-
*
|
|
2328
|
-
*
|
|
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.
|
|
2329
2612
|
*
|
|
2330
2613
|
* @private — exported as `_stampNonClaudeRuntimeDefaults` for tests.
|
|
2331
2614
|
*/
|
|
2332
2615
|
function _stampNonClaudeRuntimeDefaults(content, runtime) {
|
|
2333
|
-
|
|
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
|
+
}
|
|
2334
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}"`);
|
|
2335
2620
|
return content;
|
|
2336
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
|
+
}
|
|
2337
2697
|
/**
|
|
2338
2698
|
* Apply the per-runtime rewrite table to a single content string.
|
|
2339
2699
|
* Relocated from bin/install.js `_applyRuntimeRewrites`.
|
|
@@ -2410,7 +2770,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2410
2770
|
// #2097: dot-dir self-references (~/.augment/…) → resolved prefix,
|
|
2411
2771
|
// dirName-derived (no runtime literal). getDirName('augment') resolves
|
|
2412
2772
|
// to '.augment', so this is byte-identical to the prior hardcoded regexes.
|
|
2413
|
-
const _dd =
|
|
2773
|
+
const _dd = (0, pattern_cjs_1.escapeRegex)(dirName);
|
|
2414
2774
|
content = content.replace(new RegExp('~/' + _dd + '/', 'g'), pathPrefix);
|
|
2415
2775
|
content = content.replace(new RegExp('\\$HOME/' + _dd + '/', 'g'), pathPrefix);
|
|
2416
2776
|
content = content.replace(new RegExp('~/' + _dd + '(?![\\w-])', 'g'), normalizedPathPrefix);
|
|
@@ -2428,7 +2788,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2428
2788
|
// #2094: descriptor-driven — dirName resolves to '.trae' via
|
|
2429
2789
|
// getDirName()/localConfigDir, so this regex is built rather than
|
|
2430
2790
|
// hardcoded as `/~\/\.trae\//g` (byte-identical output for trae).
|
|
2431
|
-
content = content.replace(new RegExp('~/' +
|
|
2791
|
+
content = content.replace(new RegExp('~/' + (0, pattern_cjs_1.escapeRegex)(dirName) + '/', 'g'), pathPrefix);
|
|
2432
2792
|
content = processAttribution(content, attribution);
|
|
2433
2793
|
break;
|
|
2434
2794
|
case 'codebuddy':
|
|
@@ -2454,6 +2814,10 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2454
2814
|
content = content.replace(/~\/\.claude\//g, pathPrefix);
|
|
2455
2815
|
content = content.replace(/\$HOME\/\.claude\//g, pathPrefix);
|
|
2456
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);
|
|
2457
2821
|
content = processAttribution(content, attribution);
|
|
2458
2822
|
break;
|
|
2459
2823
|
// Descriptor-driven brand literals (ADR-1239 / #2092): the qwen/hermes
|
|
@@ -2541,18 +2905,18 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
|
|
2541
2905
|
* @param attribution Co-Authored-By value (string | null | undefined)
|
|
2542
2906
|
*/
|
|
2543
2907
|
function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
2544
|
-
if (!
|
|
2908
|
+
if (!installFs().existsSync(stagedDir))
|
|
2545
2909
|
return;
|
|
2546
2910
|
const walkAndRewrite = (dir) => {
|
|
2547
|
-
for (const entry of
|
|
2911
|
+
for (const entry of installFs().readdirSync(dir, { withFileTypes: true })) {
|
|
2548
2912
|
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
2549
2913
|
if (entry.isDirectory()) {
|
|
2550
2914
|
walkAndRewrite(fullPath);
|
|
2551
2915
|
}
|
|
2552
2916
|
else if (entry.name.endsWith('.md')) {
|
|
2553
|
-
let content =
|
|
2917
|
+
let content = installFs().readFileSync(fullPath, 'utf8');
|
|
2554
2918
|
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
|
|
2555
|
-
|
|
2919
|
+
installFs().writeFileSync(fullPath, content);
|
|
2556
2920
|
}
|
|
2557
2921
|
}
|
|
2558
2922
|
};
|
|
@@ -2577,14 +2941,14 @@ function applyRuntimeContentRewritesInPlace(stagedDir, runtime, pathPrefix, isGl
|
|
|
2577
2941
|
* @returns {string} path to the temp dir (caller is responsible for cleanup)
|
|
2578
2942
|
*/
|
|
2579
2943
|
function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathPrefix, isGlobal = false, attribution = undefined) {
|
|
2580
|
-
if (!
|
|
2944
|
+
if (!installFs().existsSync(stagedDir))
|
|
2581
2945
|
return stagedDir;
|
|
2582
|
-
const tempDir =
|
|
2946
|
+
const tempDir = mkInstallTempDir('gsd-cmd-rewrites-');
|
|
2583
2947
|
try {
|
|
2584
|
-
for (const entry of
|
|
2948
|
+
for (const entry of installFs().readdirSync(stagedDir, { withFileTypes: true })) {
|
|
2585
2949
|
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
2586
2950
|
continue;
|
|
2587
|
-
let content =
|
|
2951
|
+
let content = installFs().readFileSync(node_path_1.default.join(stagedDir, entry.name), 'utf8');
|
|
2588
2952
|
content = _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal, attribution);
|
|
2589
2953
|
// #2097 (ADR-1239): descriptor-driven — commandBodyConverter name comes
|
|
2590
2954
|
// from runtime.hostBehaviors instead of a hardcoded runtime-name branch.
|
|
@@ -2592,18 +2956,52 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
|
|
|
2592
2956
|
if (_cmdConv && COMMAND_BODY_CONVERTERS[_cmdConv]) {
|
|
2593
2957
|
content = COMMAND_BODY_CONVERTERS[_cmdConv](content);
|
|
2594
2958
|
}
|
|
2595
|
-
|
|
2959
|
+
installFs().writeFileSync(node_path_1.default.join(tempDir, entry.name), content);
|
|
2596
2960
|
}
|
|
2597
2961
|
}
|
|
2598
2962
|
catch (err) {
|
|
2599
2963
|
try {
|
|
2600
|
-
|
|
2964
|
+
installFs().rmSync(tempDir, { recursive: true, force: true });
|
|
2601
2965
|
}
|
|
2602
2966
|
catch { /* best-effort */ }
|
|
2603
2967
|
throw err;
|
|
2604
2968
|
}
|
|
2605
2969
|
return tempDir;
|
|
2606
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
|
+
}
|
|
2607
3005
|
/**
|
|
2608
3006
|
* HIGH-LEVEL: In-place fs walk: rewrite all .md files under stagedDir for the given runtime.
|
|
2609
3007
|
*
|
|
@@ -2620,16 +3018,30 @@ function applyRuntimeContentRewritesForCommandsInPlace(stagedDir, runtime, pathP
|
|
|
2620
3018
|
*/
|
|
2621
3019
|
function rewriteStagedSkillBodies(stagedDir, opts) {
|
|
2622
3020
|
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2623
|
-
if (!
|
|
3021
|
+
if (!installFs().existsSync(stagedDir))
|
|
2624
3022
|
return;
|
|
2625
3023
|
const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
|
|
2626
3024
|
const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
|
|
2627
|
-
|
|
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);
|
|
2628
3029
|
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
|
2629
3030
|
const isWindowsHost = platform === 'win32';
|
|
2630
3031
|
const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
|
2631
3032
|
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
|
2632
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
|
+
}
|
|
2633
3045
|
}
|
|
2634
3046
|
/**
|
|
2635
3047
|
* HIGH-LEVEL: Copy-to-temp then rewrite all .md files for the given runtime.
|
|
@@ -2649,11 +3061,14 @@ function rewriteStagedSkillBodies(stagedDir, opts) {
|
|
|
2649
3061
|
*/
|
|
2650
3062
|
function rewriteStagedCommandBodies(stagedDir, opts) {
|
|
2651
3063
|
const { runtime, configDir, scope = 'global', homedir = () => node_os_1.default.homedir(), platform = process.platform, resolveAttribution, } = opts;
|
|
2652
|
-
if (!
|
|
3064
|
+
if (!installFs().existsSync(stagedDir))
|
|
2653
3065
|
return stagedDir;
|
|
2654
3066
|
const resolvedTarget = (0, shell_command_projection_cjs_1.posixNormalize)(node_path_1.default.resolve(configDir));
|
|
2655
3067
|
const homeDir = (0, shell_command_projection_cjs_1.posixNormalize)(homedir());
|
|
2656
|
-
|
|
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);
|
|
2657
3072
|
const isOpencode = false; // #2087: opencode installs via the combined-family engine path, never through the generic rewrite
|
|
2658
3073
|
const isWindowsHost = platform === 'win32';
|
|
2659
3074
|
const pathPrefix = computePathPrefix({ isGlobal, isOpencode, isWindowsHost, resolvedTarget, homeDir });
|
|
@@ -2714,6 +3129,127 @@ function applyAgentPathRewrites(content, runtime, pathPrefix) {
|
|
|
2714
3129
|
return content;
|
|
2715
3130
|
}
|
|
2716
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
|
+
}
|
|
2717
3253
|
/**
|
|
2718
3254
|
* Apply Co-Authored-By attribution policy to file content.
|
|
2719
3255
|
* - null -> remove the Co-Authored-By line and its preceding blank line
|
|
@@ -2756,6 +3292,11 @@ module.exports = {
|
|
|
2756
3292
|
convertClaudeToAntigravityContent,
|
|
2757
3293
|
convertClaudeCommandToAntigravitySkill,
|
|
2758
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,
|
|
2759
3300
|
convertClaudeCommandToKimiSkill,
|
|
2760
3301
|
convertClaudeCommandToKimiCodeSkill,
|
|
2761
3302
|
buildKimiAgentArtifacts,
|
|
@@ -2810,11 +3351,20 @@ module.exports = {
|
|
|
2810
3351
|
convertClaudeAgentToCodebuddyAgent,
|
|
2811
3352
|
convertClaudeAgentToClineAgent,
|
|
2812
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,
|
|
2813
3358
|
// ADR-1239 / #2092 Phase B Upgrade 1: native .qwen/agents/*.md subagent
|
|
2814
3359
|
// projection — registered by name so convertedAgentsKind's
|
|
2815
3360
|
// conversionExports[converterName] dispatch (runtime-artifact-layout.cts)
|
|
2816
3361
|
// can resolve it from capabilities/qwen/capability.json's agents kind.
|
|
2817
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,
|
|
2818
3368
|
// #1511 ADR-1508 Phase 2: rewrite engine deep seam
|
|
2819
3369
|
// Low-level walkers (pathPrefix + attribution pre-resolved by caller):
|
|
2820
3370
|
applyRuntimeContentRewritesInPlace,
|
|
@@ -2825,9 +3375,21 @@ module.exports = {
|
|
|
2825
3375
|
// ADR-1235 §1: descriptor-driven agent cross-cutting
|
|
2826
3376
|
applyAgentPathRewrites,
|
|
2827
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,
|
|
2828
3387
|
_computePathPrefix: computePathPrefix,
|
|
3388
|
+
_restoreClaudeGlobalAtRefTilde: restoreClaudeGlobalAtRefTilde,
|
|
2829
3389
|
_applyRuntimeRewrites,
|
|
2830
3390
|
_stampNonClaudeRuntimeDefaults,
|
|
3391
|
+
// #2652: registry-resolved dispatch isolation, mirroring routeDispatchIsolation
|
|
3392
|
+
_negotiatedDispatchIsolation,
|
|
2831
3393
|
// #1521: canonical non-Claude runtime list for test files and tooling
|
|
2832
3394
|
NON_CLAUDE_RUNTIMES,
|
|
2833
3395
|
};
|