@opengsd/gsd-core 1.12.0 → 1.14.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/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -23,6 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
23
|
const node_os_1 = __importDefault(require("node:os"));
|
|
24
24
|
const node_path_1 = __importDefault(require("node:path"));
|
|
25
25
|
const runtimeArtifactConversion = require("./runtime-artifact-conversion.cjs");
|
|
26
|
+
const security_cjs_1 = require("./security.cjs");
|
|
26
27
|
const runtimeArtifactLayout = require("./runtime-artifact-layout.cjs");
|
|
27
28
|
const runtimeArtifactInstallPlan = require("./runtime-artifact-install-plan.cjs");
|
|
28
29
|
const runtimeNamePolicy = require("./runtime-name-policy.cjs");
|
|
@@ -55,6 +56,148 @@ const { processAttribution } = runtimeArtifactConversion;
|
|
|
55
56
|
// resolveRuntimeArtifactLayout: accessed via module ref (not destructured) so
|
|
56
57
|
// test stubs that monkeypatch the module's exports are seen at call time.
|
|
57
58
|
const { getDirName } = runtimeNamePolicy;
|
|
59
|
+
function withInstallerPackageSource(configDir, fn) {
|
|
60
|
+
// Reuse the existing compatibility-marker contract through the existing fs
|
|
61
|
+
// seam. The marker exists only in this synchronous call tree: no disk state,
|
|
62
|
+
// layout export, stage argument, or caller-settable authority flag is added.
|
|
63
|
+
const markerPath = node_path_1.default.resolve(configDir, '.gsd-source');
|
|
64
|
+
const packageCommandsRoot = runtimeArtifactLayout.findInstallSourceRoot();
|
|
65
|
+
const markerBytes = Buffer.from(packageCommandsRoot + '\n');
|
|
66
|
+
const base = installFs();
|
|
67
|
+
const overlay = {
|
|
68
|
+
...base,
|
|
69
|
+
existsSync: (candidate) => node_path_1.default.resolve(candidate) === markerPath || base.existsSync(candidate),
|
|
70
|
+
lstatSync: (candidate) => node_path_1.default.resolve(candidate) === markerPath
|
|
71
|
+
? { isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false }
|
|
72
|
+
: base.lstatSync(candidate),
|
|
73
|
+
readFileSync: ((candidate, encoding) => {
|
|
74
|
+
if (node_path_1.default.resolve(candidate) !== markerPath) {
|
|
75
|
+
return encoding ? base.readFileSync(candidate, encoding) : base.readFileSync(candidate);
|
|
76
|
+
}
|
|
77
|
+
return encoding ? markerBytes.toString(encoding) : Buffer.from(markerBytes);
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
return withInstallFs(overlay, fn);
|
|
81
|
+
}
|
|
82
|
+
function isRuntimeSurfaceSourceUnavailable(message) {
|
|
83
|
+
return message.startsWith('Runtime Surface source is unavailable or incomplete for ') &&
|
|
84
|
+
message.endsWith('install or upgrade gsd-core before materializing this surface.');
|
|
85
|
+
}
|
|
86
|
+
function assertCorpusTreeHasNoSymlinks(root) {
|
|
87
|
+
if (!installFs().existsSync(root))
|
|
88
|
+
return;
|
|
89
|
+
const stat = installFs().lstatSync(root);
|
|
90
|
+
if (stat.isSymbolicLink()) {
|
|
91
|
+
throw new Error(`Runtime Surface corpus path is a symlink: ${root}`);
|
|
92
|
+
}
|
|
93
|
+
if (!stat.isDirectory())
|
|
94
|
+
return;
|
|
95
|
+
for (const name of installFs().readdirSync(root)) {
|
|
96
|
+
assertCorpusTreeHasNoSymlinks(node_path_1.default.join(root, name));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function previousOwnedCorpusFiles(configDir, prefix) {
|
|
100
|
+
try {
|
|
101
|
+
const files = installerMigrations.readInstallManifest(configDir).files;
|
|
102
|
+
return Object.keys(files)
|
|
103
|
+
.filter((entry) => entry.startsWith(prefix))
|
|
104
|
+
.map((entry) => entry.slice(prefix.length))
|
|
105
|
+
.filter((entry) => entry !== '' && !node_path_1.default.posix.isAbsolute(entry) && !entry.split('/').some((part) => part === '' || part === '.' || part === '..'));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// An absent or unreadable prior manifest provides no ownership evidence.
|
|
109
|
+
// Preserve existing entries rather than guessing that they are stale.
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function pruneEmptyCorpusParents(start, stop) {
|
|
114
|
+
let current = node_path_1.default.dirname(start);
|
|
115
|
+
while (current !== stop && current.startsWith(stop + node_path_1.default.sep)) { // allow-handrolled-containment: ancestor-walk loop condition, not a containment gate
|
|
116
|
+
if (installFs().readdirSync(current).length > 0)
|
|
117
|
+
return;
|
|
118
|
+
installFs().rmdirSync(current);
|
|
119
|
+
current = node_path_1.default.dirname(current);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function syncRuntimeSurfaceCorpus(source, destination, configDir, manifestPrefix) {
|
|
123
|
+
if (hasExistingSymlinkBetween(node_path_1.default.resolve(configDir), destination, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
124
|
+
throw new Error(`syncRuntimeSurfaceCorpus: destination "${destination}" contains a symlink the install root "${configDir}" does not trust — refusing to write.`);
|
|
125
|
+
}
|
|
126
|
+
assertCorpusTreeHasNoSymlinks(destination);
|
|
127
|
+
// Remove only paths the previous manifest proves GSD owned and which the
|
|
128
|
+
// executing package no longer ships. Unknown neighbouring files survive.
|
|
129
|
+
for (const relative of previousOwnedCorpusFiles(configDir, manifestPrefix)) {
|
|
130
|
+
const sourceEntry = node_path_1.default.join(source, ...relative.split('/'));
|
|
131
|
+
let sourceIsFile = false;
|
|
132
|
+
try {
|
|
133
|
+
sourceIsFile = installFs().lstatSync(sourceEntry).isFile();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
sourceIsFile = false;
|
|
137
|
+
}
|
|
138
|
+
if (sourceIsFile)
|
|
139
|
+
continue;
|
|
140
|
+
const target = node_path_1.default.join(destination, ...relative.split('/'));
|
|
141
|
+
if (!installFs().existsSync(target))
|
|
142
|
+
continue;
|
|
143
|
+
const targetStat = installFs().lstatSync(target);
|
|
144
|
+
if (!targetStat.isFile()) {
|
|
145
|
+
throw new Error(`Runtime Surface corpus ownership conflict at ${target}`);
|
|
146
|
+
}
|
|
147
|
+
installFs().rmSync(target, { force: true });
|
|
148
|
+
pruneEmptyCorpusParents(target, destination);
|
|
149
|
+
}
|
|
150
|
+
installFs().mkdirSync(node_path_1.default.dirname(destination), { recursive: true });
|
|
151
|
+
installFs().cpSync(source, destination, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Provision the raw, installation-owned input needed to re-materialize a
|
|
155
|
+
* global Runtime Surface after the executing package tree disappears.
|
|
156
|
+
*
|
|
157
|
+
* The corpus deliberately lives below the already-installed `gsd-core/`
|
|
158
|
+
* tree and is accepted only after its manifest ownership and hashes verify.
|
|
159
|
+
* The compatibility marker does not replace that installed-corpus authority.
|
|
160
|
+
*/
|
|
161
|
+
function provisionRuntimeSurfaceCorpus(layout, configDir, scope) {
|
|
162
|
+
const required = new Set();
|
|
163
|
+
if ((0, install_scope_cjs_1.isGlobalScope)(scope)) {
|
|
164
|
+
for (const kind of layout.kinds) {
|
|
165
|
+
if (kind.kind === 'commands' || kind.kind === 'skills')
|
|
166
|
+
required.add('commands');
|
|
167
|
+
if (kind.kind === 'agents' || kind.kind === 'kimi-agents')
|
|
168
|
+
required.add('agents');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (required.size === 0)
|
|
172
|
+
return;
|
|
173
|
+
const corpusRoot = node_path_1.default.join(configDir, 'gsd-core');
|
|
174
|
+
if (required.has('commands')) {
|
|
175
|
+
const source = runtimeArtifactLayout.findInstallSourceRoot();
|
|
176
|
+
const destination = node_path_1.default.join(corpusRoot, 'commands', 'gsd');
|
|
177
|
+
syncRuntimeSurfaceCorpus(source, destination, configDir, 'gsd-core/commands/gsd/');
|
|
178
|
+
}
|
|
179
|
+
if (required.has('agents')) {
|
|
180
|
+
const source = node_path_1.default.join(executingPackageRoot(), 'agents');
|
|
181
|
+
const destination = node_path_1.default.join(corpusRoot, 'agents');
|
|
182
|
+
syncRuntimeSurfaceCorpus(source, destination, configDir, 'gsd-core/agents/');
|
|
183
|
+
}
|
|
184
|
+
const markerFile = _hostBehaviors(layout.runtime).sourceMarkerFile;
|
|
185
|
+
if (typeof markerFile === 'string' && markerFile !== '' && required.has('commands')) {
|
|
186
|
+
try {
|
|
187
|
+
const markerPath = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, markerFile);
|
|
188
|
+
if (hasExistingSymlinkBetween(node_path_1.default.resolve(configDir), markerPath, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
189
|
+
throw new Error(`compatibility marker "${markerPath}" contains an untrusted symlink`);
|
|
190
|
+
}
|
|
191
|
+
installFs().writeFileSync(markerPath, node_path_1.default.join(corpusRoot, 'commands', 'gsd') + '\n', 'utf8');
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// The existing installer marker writer owns the user-facing warning and
|
|
195
|
+
// keeps marker failure non-fatal. The installed corpus remains usable
|
|
196
|
+
// without the compatibility marker.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function executingPackageRoot() { return node_path_1.default.dirname(node_path_1.default.dirname(runtimeArtifactLayout.findInstallSourceRoot())); }
|
|
58
201
|
// ---------------------------------------------------------------------------
|
|
59
202
|
// USER_OWNED_ARTIFACTS
|
|
60
203
|
// ---------------------------------------------------------------------------
|
|
@@ -205,8 +348,11 @@ function hasExistingSymlinkBetween(root, fullPath, options = {}) {
|
|
|
205
348
|
const resolvedFullPath = node_path_1.default.resolve(fullPath);
|
|
206
349
|
// (a) Path-traversal refusal — ALWAYS enforced, even with opt-in. An untrusted
|
|
207
350
|
// destSubpath string that escapes the install root via '..' is rejected
|
|
208
|
-
// regardless of user opt-in state (ADR-1239 Phase B threat (a)).
|
|
209
|
-
|
|
351
|
+
// regardless of user opt-in state (ADR-1239 Phase B threat (a)). Lexical
|
|
352
|
+
// (ADR-4650 decision 6): this function's whole purpose is to DETECT
|
|
353
|
+
// symlinks between root and target, so resolving them here would erase what
|
|
354
|
+
// it measures.
|
|
355
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(resolvedFullPath, resolvedRoot) === null) {
|
|
210
356
|
return true;
|
|
211
357
|
}
|
|
212
358
|
// #2393 (security-review finding): realpathSync fully resolves all symlink
|
|
@@ -977,6 +1123,7 @@ function _runLegacyUninstallCleanup(runtime, configDir, scope = 'global') {
|
|
|
977
1123
|
*/
|
|
978
1124
|
function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution = () => undefined, capabilityRegistry, deps = {}) {
|
|
979
1125
|
return withInstallFs(deps.fs, () => {
|
|
1126
|
+
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, configDir, scope, capabilityRegistry);
|
|
980
1127
|
// A removed descriptor kind is no longer visited by the layout loop, so it
|
|
981
1128
|
// cannot prune its own previous output. Clean manifest-proven retired files
|
|
982
1129
|
// before materializing the current layout (#2644).
|
|
@@ -986,6 +1133,7 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
986
1133
|
// generic layout-driven loop below, mirroring the bespoke install path that
|
|
987
1134
|
// previously lived inline in bin/install.js.
|
|
988
1135
|
const behaviors = _hostBehaviors(runtime);
|
|
1136
|
+
const projectDir = scope === 'global' ? process.cwd() : configDir;
|
|
989
1137
|
if (behaviors.combinedFamilyInstall) {
|
|
990
1138
|
// #2329: combined-family runtimes (OpenCode/Kilo) bypass
|
|
991
1139
|
// _runLegacyInstallMigrations below entirely (early return), so their
|
|
@@ -994,17 +1142,24 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
994
1142
|
// #2874 design row 2: this early return must ALSO return an executed
|
|
995
1143
|
// plan — installOpencodeFamilyArtifacts reports what it wrote, so a
|
|
996
1144
|
// whole runtime family returning undefined is no longer a hole.
|
|
997
|
-
|
|
1145
|
+
// An injected filesystem supplies its own hermetic corpus fixture. The
|
|
1146
|
+
// real installer is the authority that refreshes package bytes into the
|
|
1147
|
+
// durable installed corpus; attempting that cross-filesystem copy through
|
|
1148
|
+
// an in-memory destination adapter would read from the wrong filesystem.
|
|
1149
|
+
if (!deps.fs)
|
|
1150
|
+
provisionRuntimeSurfaceCorpus(layout, configDir, scope);
|
|
1151
|
+
return installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution, behaviors, capabilityRegistry, deps.packageRoot, projectDir);
|
|
998
1152
|
}
|
|
999
1153
|
// Legacy cleanup before layout-driven writes
|
|
1000
1154
|
_runLegacyInstallMigrations(runtime, configDir, scope);
|
|
1001
|
-
|
|
1155
|
+
if (!deps.fs)
|
|
1156
|
+
provisionRuntimeSurfaceCorpus(layout, configDir, scope);
|
|
1002
1157
|
// #3712: a global `home` override escapes the sandboxed configDir. Refuse to
|
|
1003
1158
|
// execute when a test run would land that escape in the developer's real home.
|
|
1004
1159
|
testHomeGuard.assertTestHomeSandboxed('installRuntimeArtifacts', runtime, layout?.kinds, {
|
|
1005
1160
|
os: deps.os, env: deps.env,
|
|
1006
1161
|
});
|
|
1007
|
-
const
|
|
1162
|
+
const createPlan = () => runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan({
|
|
1008
1163
|
// `Layout` is structurally identical across the layout/install-plan .cjs
|
|
1009
1164
|
// modules but nominally distinct to tsc (untyped .cjs boundary) — bridge it.
|
|
1010
1165
|
layout: layout,
|
|
@@ -1012,7 +1167,16 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
1012
1167
|
homedir: () => node_os_1.default.homedir(),
|
|
1013
1168
|
platform: process.platform,
|
|
1014
1169
|
resolveAttribution,
|
|
1170
|
+
projectDir,
|
|
1015
1171
|
});
|
|
1172
|
+
let planResult = createPlan();
|
|
1173
|
+
if (scope === 'global' &&
|
|
1174
|
+
!planResult.ok &&
|
|
1175
|
+
planResult.kind === 'stage_failed' &&
|
|
1176
|
+
planResult.cleanupDirs.length === 0 &&
|
|
1177
|
+
isRuntimeSurfaceSourceUnavailable(planResult.message)) {
|
|
1178
|
+
planResult = withInstallerPackageSource(configDir, createPlan);
|
|
1179
|
+
}
|
|
1016
1180
|
const cleanupDirs = planResult.ok ? planResult.plan.cleanupDirs : planResult.cleanupDirs;
|
|
1017
1181
|
// #2874 row 1/4/5: per-kind executed-plan entries, appended only as the
|
|
1018
1182
|
// loop below actually finishes writing each kind — a kind that throws
|
|
@@ -1176,8 +1340,10 @@ function installRuntimeArtifacts(runtime, configDir, scope, resolvedProfile, res
|
|
|
1176
1340
|
// is safe even when configDir has no .gsd-source marker (artifactLayout: []).
|
|
1177
1341
|
let nativePluginInstalled = false;
|
|
1178
1342
|
if (behaviors.nativePlugin) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1343
|
+
// Native plugin sources live only in the executing package, never in the
|
|
1344
|
+
// durable Runtime Surface corpus. Do not derive this package root from a
|
|
1345
|
+
// config-scoped provider that may now correctly resolve installed input.
|
|
1346
|
+
const src = deps.packageRoot ?? executingPackageRoot();
|
|
1181
1347
|
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
1182
1348
|
nativePluginInstalled = true;
|
|
1183
1349
|
}
|
|
@@ -1330,6 +1496,15 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1330
1496
|
content = applyOpencodeFamilyPathPrefix(content, runtime, pathPrefix);
|
|
1331
1497
|
content = processAttribution(content, resolveAttribution(runtime));
|
|
1332
1498
|
const skillDir = node_path_1.default.join(dest, skillName);
|
|
1499
|
+
// isPathConfined is lexical and cannot see a symlink. mkdirSync({recursive:true})
|
|
1500
|
+
// does NOT throw when skillDir already exists as a symlink to a directory, so a
|
|
1501
|
+
// pre-planted link would redirect the SKILL.md write outside `dest`. Refuse to
|
|
1502
|
+
// write through a link (epic #4636; mirrors retired-artifact-cleanup.cts:77).
|
|
1503
|
+
try {
|
|
1504
|
+
if (installFs().lstatSync(skillDir).isSymbolicLink())
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
catch { /* ENOENT: not created yet — the normal case */ }
|
|
1333
1508
|
installFs().mkdirSync(skillDir, { recursive: true });
|
|
1334
1509
|
installFs().writeFileSync(node_path_1.default.join(skillDir, 'SKILL.md'), content);
|
|
1335
1510
|
// #2322 HIGH-3 parity: persist the capability-owned marker so a later
|
|
@@ -1388,12 +1563,13 @@ function installOpencodeFamilySkills(runtime, targetDir, rawCommandsDir, pathPre
|
|
|
1388
1563
|
* @param capabilityRegistry - #2362: optional composed capability registry, threaded
|
|
1389
1564
|
* straight through to resolveRuntimeArtifactLayout (unused by the agents kind today,
|
|
1390
1565
|
* but kept for signature parity with the skills/commands siblings on this call tree)
|
|
1566
|
+
* @param projectDir - project/config discovery root, distinct from the artifact destination
|
|
1391
1567
|
* @returns `{ sourceDir, destDir }` describing what was written, or `null` when the
|
|
1392
1568
|
* runtime's layout declares no `agents` kind.
|
|
1393
1569
|
*/
|
|
1394
|
-
function installAgentsKindStandalone(runtime, targetDir, scope, resolvedProfile, pathPrefix, resolveAttribution = () => undefined, capabilityRegistry) {
|
|
1570
|
+
function installAgentsKindStandalone(runtime, targetDir, scope, resolvedProfile, pathPrefix, resolveAttribution = () => undefined, capabilityRegistry, projectDir) {
|
|
1395
1571
|
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, targetDir, scope, capabilityRegistry);
|
|
1396
|
-
const agentsKindEntry = layout.kinds.find((
|
|
1572
|
+
const agentsKindEntry = layout.kinds.find((kind) => kind.kind === 'agents');
|
|
1397
1573
|
if (!agentsKindEntry)
|
|
1398
1574
|
return null;
|
|
1399
1575
|
// #3712: this writer selects `agentsKindEntry.home` over targetDir below and then
|
|
@@ -1408,8 +1584,16 @@ function installAgentsKindStandalone(runtime, targetDir, scope, resolvedProfile,
|
|
|
1408
1584
|
// for the generic layout-driven loop (runtime-artifact-install-plan.cts) —
|
|
1409
1585
|
// targetDir IS the install root the inline agent loop called `targetDir`.
|
|
1410
1586
|
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
|
1411
|
-
const agentCtx = { runtime, pathPrefix, attribution, targetDir };
|
|
1412
|
-
|
|
1587
|
+
const agentCtx = { runtime, pathPrefix, attribution, targetDir, projectDir: projectDir ?? targetDir };
|
|
1588
|
+
let stagedDir;
|
|
1589
|
+
try {
|
|
1590
|
+
stagedDir = agentsKindEntry.stage(resolvedProfile, agentCtx);
|
|
1591
|
+
}
|
|
1592
|
+
catch (err) {
|
|
1593
|
+
if (scope !== 'global' || !isRuntimeSurfaceSourceUnavailable(err.message))
|
|
1594
|
+
throw err;
|
|
1595
|
+
stagedDir = withInstallerPackageSource(targetDir, () => agentsKindEntry.stage(resolvedProfile, agentCtx));
|
|
1596
|
+
}
|
|
1413
1597
|
const stagedAgentFiles = installFs().existsSync(stagedDir)
|
|
1414
1598
|
? installFs().readdirSync(stagedDir).filter((f) => f.endsWith('.md'))
|
|
1415
1599
|
: [];
|
|
@@ -1670,12 +1854,13 @@ function _migrateLegacyOpencodeCommandDir(runtime, configDir, behaviors) {
|
|
|
1670
1854
|
* installOpencodeFamilySkills so an installed third-party capability skill
|
|
1671
1855
|
* materializes for this combined-family (OpenCode/Kilo) install path too.
|
|
1672
1856
|
* Absent -> no third-party skills staged (fail closed).
|
|
1857
|
+
* @param projectDir - project/config discovery root, distinct from configDir for global installs
|
|
1673
1858
|
* @returns #2874 design row 2: an executed-plan value, same top-level shape
|
|
1674
1859
|
* (`runtime`/`scope`/`kinds`/`cleanup`/`postSteps`) as the generic
|
|
1675
1860
|
* `installRuntimeArtifacts` branch — this was the one early return a
|
|
1676
1861
|
* `void`-shaped hole survived unnoticed in.
|
|
1677
1862
|
*/
|
|
1678
|
-
function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution = () => undefined, behaviors = {}, capabilityRegistry) {
|
|
1863
|
+
function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution = () => undefined, behaviors = {}, capabilityRegistry, packageRoot, projectDir) {
|
|
1679
1864
|
// #2870: `scope` keeps its exported required `string` signature (no
|
|
1680
1865
|
// signature change). It is always the `installRuntimeArtifacts`-forwarded
|
|
1681
1866
|
// 'global' | 'local' literal produced by bin/install.js's scope-resolution
|
|
@@ -1688,7 +1873,7 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
1688
1873
|
// into stageSkillsForProfile/stageSkillsForRuntimeAsSkills. The repo/package
|
|
1689
1874
|
// root (needed below for the native plugin source) is two levels up.
|
|
1690
1875
|
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
|
1691
|
-
const src =
|
|
1876
|
+
const src = packageRoot ?? executingPackageRoot();
|
|
1692
1877
|
const rawCommandsDir = installProfiles.stageSkillsForProfile(commandsGsdDir, resolvedProfile);
|
|
1693
1878
|
const pathPrefix = runtimeArtifactConversion._computePathPrefix({
|
|
1694
1879
|
isGlobal,
|
|
@@ -1711,7 +1896,7 @@ function installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfi
|
|
|
1711
1896
|
// generic layout-driven loop uses (see installAgentsKindStandalone's own
|
|
1712
1897
|
// doc). A `null` result means this runtime's layout declares no `agents`
|
|
1713
1898
|
// kind — nothing written, nothing reported (no #1879-F15 inert claim).
|
|
1714
|
-
const agentsResult = installAgentsKindStandalone(runtime, configDir, scope, resolvedProfile, pathPrefix, resolveAttribution, capabilityRegistry);
|
|
1899
|
+
const agentsResult = installAgentsKindStandalone(runtime, configDir, scope, resolvedProfile, pathPrefix, resolveAttribution, capabilityRegistry, projectDir);
|
|
1715
1900
|
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
|
1716
1901
|
// #2874 design row 2: report what this combined-family install wrote,
|
|
1717
1902
|
// mirroring the generic branch's top-level shape. `cleanup` is `[]` — this
|
|
@@ -114,6 +114,50 @@ function readGsdEffectiveModelOverrides(targetDir = null, options = {}) {
|
|
|
114
114
|
// Per-project wins on conflict; preserve non-conflicting global keys.
|
|
115
115
|
return { ...(global || {}), ...(projectOverrides || {}) };
|
|
116
116
|
}
|
|
117
|
+
function readGsdAgentTools(config) {
|
|
118
|
+
const raw = config?.agent_tools;
|
|
119
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
120
|
+
return null;
|
|
121
|
+
const result = {};
|
|
122
|
+
for (const [selector, value] of Object.entries(raw)) {
|
|
123
|
+
// An explicitly present selector always has a verdict. Invalid values are
|
|
124
|
+
// empty so a project config cannot accidentally restore a global grant.
|
|
125
|
+
result[selector] = Array.isArray(value)
|
|
126
|
+
? value.filter((entry) => typeof entry === 'string')
|
|
127
|
+
.map((entry) => entry.trim())
|
|
128
|
+
.filter((entry) => entry.length > 0
|
|
129
|
+
&& !entry.endsWith(':')
|
|
130
|
+
&& !/[\s\u0000-\u001F\u007F-\u009F,#"'\u2028\u2029]/.test(entry))
|
|
131
|
+
: [];
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve valid install-time `agent_tools` grants from global defaults and
|
|
137
|
+
* the nearest project config. Project selectors replace only matching global
|
|
138
|
+
* selectors; malformed whole files remain harmless absence like the existing
|
|
139
|
+
* model override resolver.
|
|
140
|
+
*/
|
|
141
|
+
function readGsdEffectiveAgentTools(targetDir = null, options = {}) {
|
|
142
|
+
const home = options.homedir ? options.homedir() : node_os_1.default.homedir();
|
|
143
|
+
const globalConfig = _readGsdConfigFile(node_path_1.default.join(home, '.gsd', 'defaults.json'), 'global defaults');
|
|
144
|
+
let projectConfig = null;
|
|
145
|
+
if (targetDir) {
|
|
146
|
+
const candidate = _findAncestorGsdConfigPath(targetDir);
|
|
147
|
+
if (candidate) {
|
|
148
|
+
projectConfig = _readGsdConfigFile(candidate, 'project config');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const global = readGsdAgentTools(globalConfig);
|
|
152
|
+
const project = readGsdAgentTools(projectConfig);
|
|
153
|
+
if (projectConfig
|
|
154
|
+
&& Object.prototype.hasOwnProperty.call(projectConfig, 'agent_tools')
|
|
155
|
+
&& project === null)
|
|
156
|
+
return {};
|
|
157
|
+
if (!global && !project)
|
|
158
|
+
return null;
|
|
159
|
+
return { ...(global || {}), ...(project || {}) };
|
|
160
|
+
}
|
|
117
161
|
/**
|
|
118
162
|
* Build a runtime-aware tier resolver for the install path (#2517).
|
|
119
163
|
*
|
|
@@ -230,6 +274,7 @@ function resolveAgentModelOverride(agentName, modelOverrides, runtimeResolver) {
|
|
|
230
274
|
module.exports = {
|
|
231
275
|
readGsdGlobalModelOverrides,
|
|
232
276
|
readGsdEffectiveModelOverrides,
|
|
277
|
+
readGsdEffectiveAgentTools,
|
|
233
278
|
readGsdRuntimeProfileResolver,
|
|
234
279
|
resolveAgentModelOverride,
|
|
235
280
|
};
|
|
@@ -32,7 +32,7 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
32
32
|
const external_descriptor_trust_cjs_1 = require("./external-descriptor-trust.cjs");
|
|
33
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
34
|
const conversionModule = require("./runtime-artifact-conversion.cjs");
|
|
35
|
-
const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, deriveAgentName: _deriveAgentName, applyAgentFrontmatterExtensions: _applyAgentFrontmatterExtensions, } = conversionModule;
|
|
35
|
+
const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, deriveAgentName: _deriveAgentName, applyAgentFrontmatterExtensions: _applyAgentFrontmatterExtensions, appendAgentTools: _appendAgentTools, } = conversionModule;
|
|
36
36
|
// #2995 (epic #1671 Phase 6.4): agent bodies join the fragment model. Markers are
|
|
37
37
|
// stripped at emit BEFORE any path rewrite or converter runs, so a `.claude/` ->
|
|
38
38
|
// `.windsurf/` regex can never reach inside a marker attribute and corrupt it —
|
|
@@ -40,6 +40,8 @@ const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _pr
|
|
|
40
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
41
|
const workflowFragmentsModule = require("./workflow-fragments.cjs");
|
|
42
42
|
const { composeWorkflow: _composeWorkflow } = workflowFragmentsModule;
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
|
+
const installModelOverrideResolver = require("./install-model-override-resolver.cjs");
|
|
43
45
|
// ---------------------------------------------------------------------------
|
|
44
46
|
// Profile definitions
|
|
45
47
|
// ---------------------------------------------------------------------------
|
|
@@ -738,6 +740,11 @@ function readInstalledCapabilitySkill(stem, registry) {
|
|
|
738
740
|
return null;
|
|
739
741
|
const skillPath = node_path_1.default.join(capDir, relSkillPath);
|
|
740
742
|
try {
|
|
743
|
+
// statSync follows symlinks; isPathConfined is lexical and cannot see one.
|
|
744
|
+
// Refuse to read through a link so an outside file's content cannot be
|
|
745
|
+
// installed as a capability skill (epic #4636).
|
|
746
|
+
if (node_fs_1.default.lstatSync(skillPath).isSymbolicLink())
|
|
747
|
+
return null;
|
|
741
748
|
if (!node_fs_1.default.statSync(skillPath).isFile())
|
|
742
749
|
return null;
|
|
743
750
|
return { capId, content: node_fs_1.default.readFileSync(skillPath, 'utf8') };
|
|
@@ -856,6 +863,15 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
856
863
|
if (!(0, external_descriptor_trust_cjs_1.isPathConfined)(skillName, stageDir))
|
|
857
864
|
continue; // defense-in-depth
|
|
858
865
|
const destDir = node_path_1.default.join(stageDir, skillName);
|
|
866
|
+
// isPathConfined is lexical and cannot see a symlink. mkdirSync({recursive:true})
|
|
867
|
+
// does NOT throw when destDir already exists as a symlink to a directory, so a
|
|
868
|
+
// pre-planted link would redirect the SKILL.md write outside `stageDir`. Refuse
|
|
869
|
+
// to write through a link (epic #4636; mirrors retired-artifact-cleanup.cts:77).
|
|
870
|
+
try {
|
|
871
|
+
if (installFs().lstatSync(destDir).isSymbolicLink())
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
catch { /* ENOENT: not created yet — the normal case */ }
|
|
859
875
|
installFs().mkdirSync(destDir, { recursive: true });
|
|
860
876
|
installFs().writeFileSync(node_path_1.default.join(destDir, 'SKILL.md'), found.content);
|
|
861
877
|
// #2322 HIGH-3: persist the capability-owned marker so a later prune
|
|
@@ -896,14 +912,14 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
896
912
|
* agent loop in bin/install.js exactly:
|
|
897
913
|
* 1. applyAgentPathRewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
|
898
914
|
* 2. processAttribution (Co-Authored-By policy)
|
|
899
|
-
* 3.
|
|
900
|
-
* 4.
|
|
915
|
+
* 3. appendAgentTools (#4032: validated agent_tools grants, before host conversion)
|
|
916
|
+
* 4. converter (runtime-specific frontmatter/body transform)
|
|
917
|
+
* 5. applyAgentFrontmatterExtensions (#2875 Part 2: effort/disallowedTools,
|
|
901
918
|
* gated by hostBehaviors.agentFrontmatterExtensions — no-op for a runtime
|
|
902
919
|
* that declares nothing, e.g. every non-Claude runtime today)
|
|
903
|
-
*
|
|
904
|
-
* When `agentCtx` is absent, only
|
|
905
|
-
*
|
|
906
|
-
* that handle cross-cutting inside their converters).
|
|
920
|
+
* 6. normalizeAgentBodyForRuntime (colon→hyphen refs; no-op for trivial group)
|
|
921
|
+
* When `agentCtx` is absent, only global `agent_tools` augmentation and the
|
|
922
|
+
* converter run; other cross-cutting remains absent for backward compatibility.
|
|
907
923
|
*
|
|
908
924
|
* @param srcAgentsDir source agents directory (e.g. agents/)
|
|
909
925
|
* @param resolvedProfile profile filter from resolveProfile()
|
|
@@ -917,8 +933,7 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
917
933
|
* every OTHER converter's contract — converters that don't declare a
|
|
918
934
|
* 3rd parameter simply never read it.
|
|
919
935
|
* @param isGlobal install scope passed through to the converter
|
|
920
|
-
* @param agentCtx optional cross-cutting context (ADR-1235 §1)
|
|
921
|
-
* only the converter is applied (backward compat)
|
|
936
|
+
* @param agentCtx optional cross-cutting context (ADR-1235 §1)
|
|
922
937
|
*/
|
|
923
938
|
function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, converter, isGlobal = false, agentCtx) {
|
|
924
939
|
if (!installFs().existsSync(srcAgentsDir))
|
|
@@ -950,6 +965,7 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
|
|
|
950
965
|
try {
|
|
951
966
|
// Resolve cmdNames once per staging call (not per file) for performance.
|
|
952
967
|
const cmdNames = agentCtx ? _readGsdCommandNames() : [];
|
|
968
|
+
const agentTools = installModelOverrideResolver.readGsdEffectiveAgentTools(agentCtx?.projectDir ?? agentCtx?.targetDir ?? null);
|
|
953
969
|
for (const entry of entries) {
|
|
954
970
|
if (!entry.isFile())
|
|
955
971
|
continue;
|
|
@@ -969,25 +985,31 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
|
|
|
969
985
|
// throws loudly naming the file for a malformed marker, never emitting a
|
|
970
986
|
// half-composed agent.
|
|
971
987
|
content = _composeWorkflow(content, { sourcePath: agentSourcePath });
|
|
988
|
+
const agentName = _deriveAgentName(entry.name);
|
|
989
|
+
const grants = [
|
|
990
|
+
...(agentTools?.['*'] || []),
|
|
991
|
+
...(agentTools?.[agentName] || []),
|
|
992
|
+
];
|
|
972
993
|
if (agentCtx) {
|
|
973
994
|
// #2875 Part 2 / row I3: derived exactly as the inline loop does —
|
|
974
995
|
// single-sourced via deriveAgentName (runtime-artifact-conversion.cts).
|
|
975
|
-
const agentName = _deriveAgentName(entry.name);
|
|
976
996
|
// ADR-1235 §1: pre-converter cross-cutting (matches inline loop order exactly)
|
|
977
997
|
// Step 1: path rewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
|
978
998
|
content = _applyAgentPathRewrites(content, agentCtx.runtime, agentCtx.pathPrefix);
|
|
979
999
|
// Step 2: attribution
|
|
980
1000
|
content = _processAttribution(content, agentCtx.attribution);
|
|
981
|
-
// Step 3:
|
|
1001
|
+
// Step 3: validated canonical grants, before host conversion.
|
|
1002
|
+
content = _appendAgentTools(content, grants);
|
|
1003
|
+
// Step 4: converter (runtime-specific frontmatter/body transform)
|
|
982
1004
|
content = converter(content, isGlobal, { agentName });
|
|
983
|
-
// Step
|
|
1005
|
+
// Step 5: frontmatter extensions (effort/disallowedTools; no-op unless
|
|
984
1006
|
// the runtime declares hostBehaviors.agentFrontmatterExtensions)
|
|
985
1007
|
content = _applyAgentFrontmatterExtensions(content, { runtime: agentCtx.runtime, agentName, targetDir: agentCtx.targetDir });
|
|
986
|
-
// Step
|
|
1008
|
+
// Step 6: normalize colon→hyphen refs (no-op for trivial group)
|
|
987
1009
|
content = _normalizeAgentBodyForRuntime(content, agentCtx.runtime, cmdNames);
|
|
988
1010
|
}
|
|
989
1011
|
else {
|
|
990
|
-
|
|
1012
|
+
content = _appendAgentTools(content, grants);
|
|
991
1013
|
content = converter(content, isGlobal);
|
|
992
1014
|
}
|
|
993
1015
|
installFs().writeFileSync(node_path_1.default.join(stageDir, entry.name), content, 'utf8');
|
|
@@ -55,6 +55,7 @@ exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
|
|
|
55
55
|
'hooks/gsd-prompt-guard.js',
|
|
56
56
|
'hooks/gsd-read-guard.js',
|
|
57
57
|
'hooks/gsd-read-injection-scanner.js',
|
|
58
|
+
'hooks/gsd-secret-read-guard.js',
|
|
58
59
|
'hooks/gsd-session-state.sh',
|
|
59
60
|
'hooks/gsd-statusline.js',
|
|
60
61
|
'hooks/gsd-update-banner.js',
|
|
@@ -17,6 +17,7 @@ const installer_migration_authoring_cjs_1 = require("./installer-migration-autho
|
|
|
17
17
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
18
18
|
const clock_cjs_1 = require("./clock.cjs");
|
|
19
19
|
const install_scope_cjs_1 = require("./install-scope.cjs");
|
|
20
|
+
const security_cjs_1 = require("./security.cjs");
|
|
20
21
|
// #2874 (ADR-58 cleanup phase): this file is the ~1200-line migration
|
|
21
22
|
// plan/apply/rollback/lock/journal engine — almost none of it is on the
|
|
22
23
|
// installRuntimeArtifacts call tree. Only `readInstallManifest` and
|
|
@@ -122,8 +123,17 @@ function evaluateRemoveEmptyDir(configDir, fullPath) {
|
|
|
122
123
|
catch {
|
|
123
124
|
return 'left-in-place';
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
// `resolvedTarget === resolvedRoot` is a DELIBERATE ADDITIONAL rejection,
|
|
127
|
+
// separate from the containment decision: `tryWithinRootLexical` treats
|
|
128
|
+
// target === root as CONTAINED, but removing the config root itself is
|
|
129
|
+
// never in scope for this action (see the doc comment above) — this arm
|
|
130
|
+
// prevents `rmdirSync` from ever being asked to remove `configDir` itself.
|
|
131
|
+
// Kept as its own check per ADR-4650 decision 6 (a wrapper may add its own
|
|
132
|
+
// conditions on top of the canonical predicate, never invert it).
|
|
133
|
+
if (resolvedTarget === resolvedRoot)
|
|
134
|
+
return 'left-in-place';
|
|
135
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(resolvedTarget, resolvedRoot) === null) {
|
|
136
|
+
// Refuses "target escaped configDir".
|
|
127
137
|
return 'left-in-place';
|
|
128
138
|
}
|
|
129
139
|
let entries;
|
|
@@ -598,11 +608,30 @@ function acquireInstallMigrationLock(configDir, { timeoutMs = DEFAULT_LOCK_TIMEO
|
|
|
598
608
|
}
|
|
599
609
|
}
|
|
600
610
|
}
|
|
611
|
+
// DELIBERATELY LEXICAL — the RESOLUTION policy stays lexical, never realpath
|
|
612
|
+
// (`assertWithinRoot` / `tryWithinRoot`, src/security.cts).
|
|
613
|
+
//
|
|
614
|
+
// Reviewed under epic #4636 Phase 3 and reverted after the remote matrix proved
|
|
615
|
+
// the realpath collapse wrong. This module's contract is that a symlinked
|
|
616
|
+
// managed path is treated AS A LINK and never dereferenced — it is snapshotted
|
|
617
|
+
// as a link, restored as a link, and backed up as a link. The realpath-based
|
|
618
|
+
// predicate dereferences exactly the symlinks this module exists to preserve
|
|
619
|
+
// and then rejects them for escaping configDir
|
|
620
|
+
// ("migration path escapes configDir: extensions/gsd.cjs"). Four tests in
|
|
621
|
+
// tests/installer-migrations.test.cjs pin that behavior.
|
|
622
|
+
//
|
|
623
|
+
// The containment DECISION now routes through the canonical LEXICAL predicate
|
|
624
|
+
// (`tryWithinRootLexical`, ADR-4650 decision 6) — only the comparison moved;
|
|
625
|
+
// the lexical policy itself remains this module's own required choice, and
|
|
626
|
+
// the thrown message / returned `fullPath` are unchanged.
|
|
627
|
+
//
|
|
628
|
+
// `normalizeRelPath` is the pre-gate: it throws on absolute paths and on any
|
|
629
|
+
// '..' segment BEFORE this runs, so the check below is defense-in-depth over
|
|
630
|
+
// already-traversal-free input rather than the primary boundary.
|
|
601
631
|
function ensureInsideConfig(configDir, relPath) {
|
|
602
632
|
const normalized = normalizeRelPath(relPath);
|
|
603
633
|
const fullPath = node_path_1.default.resolve(configDir, normalized);
|
|
604
|
-
|
|
605
|
-
if (fullPath !== root && !fullPath.startsWith(root + node_path_1.default.sep)) {
|
|
634
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(fullPath, configDir) === null) {
|
|
606
635
|
throw new Error(`migration path escapes configDir: ${relPath}`);
|
|
607
636
|
}
|
|
608
637
|
return { normalized, fullPath };
|
package/gsd-core/bin/lib/io.cjs
CHANGED
|
@@ -205,6 +205,40 @@ function output(result, raw, rawValue) {
|
|
|
205
205
|
// and skipping process.exit() lets the event loop drain naturally.
|
|
206
206
|
writeAllSync(1, data);
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Single owner of the "a no-op decline reports the real condition" pairing:
|
|
210
|
+
* a `[gsd-tools] WARNING:` stderr disclosure plus the matching
|
|
211
|
+
* `{ [flagKey]: false, ...computed, reason }` JSON payload (#3957, epic
|
|
212
|
+
* #3473 B9). Before this helper, each CLI command's no-op arm hand-wrote
|
|
213
|
+
* both halves independently, and #3957's own sweep found sites where one
|
|
214
|
+
* half drifted: a decline that discarded values it had already computed, a
|
|
215
|
+
* `reason` string naming a condition that hadn't actually fired, or no
|
|
216
|
+
* stderr disclosure at all (missing the `[gsd-tools] WARNING:` convention
|
|
217
|
+
* #3217, ADR-3180 §7.6 rule 4, established for exactly this situation —
|
|
218
|
+
* a JSON `reason` field alone is easy for a caller piping stdout through
|
|
219
|
+
* `--json` to never read). Routing every no-op decline through one call
|
|
220
|
+
* site makes the convention structural — a call site, not a habit a fifth
|
|
221
|
+
* arm can independently forget — rather than leaving it to per-site
|
|
222
|
+
* discipline.
|
|
223
|
+
*
|
|
224
|
+
* `disclosure` is written to stderr VERBATIM. Like `error()`'s `message`
|
|
225
|
+
* argument (see that function's own doc comment above `formatDiagnosticToken`),
|
|
226
|
+
* this function stays a dumb, faithful writer and does not sanitize it — a
|
|
227
|
+
* caller that interpolates an UNTRUSTED substring (a caller-supplied
|
|
228
|
+
* blocker/session-field string, an argv token) into `disclosure` MUST pass
|
|
229
|
+
* that substring through `formatDiagnosticToken` first. Skipping that step
|
|
230
|
+
* lets an embedded `\n` forge a second, attacker-authored
|
|
231
|
+
* `[gsd-tools] WARNING:` line — the same risk `error()`'s doc comment
|
|
232
|
+
* documents for its own stderr write.
|
|
233
|
+
*/
|
|
234
|
+
function declineNoOp(raw, flagKey, reason, disclosure, computed = {}) {
|
|
235
|
+
// `process.stderr.write`, matching the `[gsd-tools] WARNING:` call sites
|
|
236
|
+
// this helper replaces (state.cts's stateReplaceFieldWithFallback and the
|
|
237
|
+
// cmdStateUpdateProgress decline arms) — not the `writeAllSync(2, …)`
|
|
238
|
+
// primitive `error()` uses, which is reserved for the fatal-exit path.
|
|
239
|
+
process.stderr.write(`[gsd-tools] WARNING: ${disclosure}\n`);
|
|
240
|
+
output({ [flagKey]: false, ...computed, reason }, raw, 'false');
|
|
241
|
+
}
|
|
208
242
|
/**
|
|
209
243
|
* Frozen enum of typed reason codes used by error() for structured errors.
|
|
210
244
|
* Each subcommand contributes its own codes; the enum exists so tests can
|
|
@@ -392,6 +426,7 @@ module.exports = {
|
|
|
392
426
|
ensureGsdTempDir,
|
|
393
427
|
reapStaleTempFiles,
|
|
394
428
|
output,
|
|
429
|
+
declineNoOp,
|
|
395
430
|
serializeForOutput,
|
|
396
431
|
ERROR_REASON,
|
|
397
432
|
setJsonErrorMode,
|