@opengsd/gsd-core 1.13.0 → 1.15.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/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- 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 +459 -0
- package/agents/gsd-code-fixer.md +9 -8
- 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 +360 -0
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- 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-auditor.md +1 -1
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +13 -8
- 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-intel-updater.md +1 -1
- 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-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-research-synthesizer.md +1 -1
- 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-auditor.md +155 -17
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +848 -163
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- 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/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +8 -3
- 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/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/review.md +2 -3
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- 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 +672 -146
- package/gsd-core/bin/lib/adr-parser.cjs +4 -2
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +119 -34
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- 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 +96 -189
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -2
- package/gsd-core/bin/lib/check-command-router.cjs +213 -49
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +823 -112
- package/gsd-core/bin/lib/config-loader.cjs +66 -4
- package/gsd-core/bin/lib/config.cjs +186 -45
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +164 -45
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +614 -86
- package/gsd-core/bin/lib/install-engine.cjs +29 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +37 -13
- package/gsd-core/bin/lib/model-resolver.cjs +253 -53
- package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +268 -27
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +393 -88
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- 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 +10 -30
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +356 -42
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +181 -30
- package/gsd-core/bin/lib/state.cjs +265 -27
- package/gsd-core/bin/lib/surface.cjs +77 -3
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +9 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +315 -30
- package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
- package/gsd-core/bin/lib/verify.cjs +320 -48
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +13 -4
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +14 -2
- package/gsd-core/references/tdd.md +30 -4
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +433 -2
- package/gsd-core/templates/README.md +7 -1
- 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 +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +29 -16
- package/gsd-core/workflows/check-todos.md +6 -4
- package/gsd-core/workflows/cleanup.md +5 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +216 -73
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +41 -264
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +17 -158
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +83 -172
- package/gsd-core/workflows/execute-plan.md +24 -10
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +52 -5
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +56 -14
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +39 -209
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +45 -187
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +132 -20
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +29 -10
- package/gsd-core/workflows/reapply-patches.md +86 -6
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +22 -9
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +14 -12
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +101 -196
- package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +71 -19
- package/hooks/dist/gsd-statusline.js +81 -20
- package/hooks/dist/gsd-validate-commit.sh +97 -8
- 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/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +66 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +71 -19
- package/hooks/gsd-statusline.js +81 -20
- package/hooks/gsd-validate-commit.sh +97 -8
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +11 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +131 -12
- 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 +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +35 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- 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 +224 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +623 -13
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +29 -9
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
- package/scripts/prompt-injection-scan.sh +18 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- 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-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +8 -3
- 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-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review/SKILL.md +2 -3
- 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-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
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* When `workflow.windows_enforce` is true, `/gsd-ship` blocks while any entry is
|
|
8
8
|
* `open`; an entry can be `waived` only with a recorded reason or `fixed`.
|
|
9
9
|
*
|
|
10
|
-
* LEAF MODULE — imports
|
|
10
|
+
* LEAF MODULE — imports node:fs + node:path, plus two compiled sibling lib
|
|
11
|
+
* modules require()d at runtime: workstream-inventory.cjs (the #4487
|
|
12
|
+
* milestone stamp) and capability-lock.cjs (the #3780 cross-process ledger
|
|
13
|
+
* lock). No other src/ imports.
|
|
11
14
|
*
|
|
12
15
|
* Storage format (`.planning/WINDOWS.md`):
|
|
13
16
|
* ---
|
|
@@ -58,12 +61,15 @@ exports.parseLedger = parseLedger;
|
|
|
58
61
|
exports.renderLedger = renderLedger;
|
|
59
62
|
exports.renderTable = renderTable;
|
|
60
63
|
exports.extractTableRegion = extractTableRegion;
|
|
64
|
+
exports.withLedgerLock = withLedgerLock;
|
|
61
65
|
exports.cmdWindowsStatus = cmdWindowsStatus;
|
|
62
66
|
exports.cmdWindowsAppend = cmdWindowsAppend;
|
|
63
67
|
exports.cmdWindowsWaive = cmdWindowsWaive;
|
|
64
68
|
exports.cmdWindowsMarkFixed = cmdWindowsMarkFixed;
|
|
65
69
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
66
70
|
const node_path_1 = __importDefault(require("node:path"));
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
72
|
+
const workstreamInventory = require("./workstream-inventory.cjs");
|
|
67
73
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
68
74
|
exports.LEDGER_FILE_NAME = 'WINDOWS.md';
|
|
69
75
|
exports.SCHEMA_VERSION = 1;
|
|
@@ -90,6 +96,11 @@ exports.REASON = Object.freeze({
|
|
|
90
96
|
// sole source of truth) at the pre-write seam — refuse rather than silently
|
|
91
97
|
// reconcile by overwriting the operator's hand-edit or dropping a row.
|
|
92
98
|
WINDOWS_LEDGER_TABLE_DRIFT: 'windows_ledger_table_drift',
|
|
99
|
+
// #3780: the read-compute-write cycle is serialized on a cross-process
|
|
100
|
+
// ledger lock; this fires only when another writer held the lock past the
|
|
101
|
+
// whole bounded retry budget — a typed, actionable refusal instead of a
|
|
102
|
+
// silently-lost mutation reported as success.
|
|
103
|
+
WINDOWS_LEDGER_LOCK: 'windows_ledger_lock',
|
|
93
104
|
});
|
|
94
105
|
/** Allowed window kinds. Aligned with the issue's enumerated sources. */
|
|
95
106
|
exports.KINDS = Object.freeze([
|
|
@@ -219,12 +230,14 @@ function nextId(entries) {
|
|
|
219
230
|
* Append a window to the ledger. Assigns the next dense id (max+1), sets
|
|
220
231
|
* status=open, timestamps via opts.now.
|
|
221
232
|
*
|
|
222
|
-
* Concurrency (issue #1950 review L2
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
233
|
+
* Concurrency (issue #1950 review L2, superseded by #3780): as a PURE
|
|
234
|
+
* function this operates on whatever ledger snapshot it is passed and cannot
|
|
235
|
+
* see concurrent writers — serialization is the CALLER's job. The I/O entry
|
|
236
|
+
* points below (cmdWindowsAppend/Waive/MarkFixed) now discharge that duty by
|
|
237
|
+
* holding the cross-process ledger lock across their whole
|
|
238
|
+
* read-compute-write cycle, so the previously-documented loss (two parallel
|
|
239
|
+
* writers, second rename wins, first entry silently gone) can no longer
|
|
240
|
+
* occur through the CLI.
|
|
228
241
|
*/
|
|
229
242
|
function appendWindow(ledger, input, opts = { now: new Date().toISOString() }) {
|
|
230
243
|
validateKind(input.kind);
|
|
@@ -243,6 +256,7 @@ function appendWindow(ledger, input, opts = { now: new Date().toISOString() }) {
|
|
|
243
256
|
reason: '',
|
|
244
257
|
recorded_at: opts.now,
|
|
245
258
|
resolved_at: null,
|
|
259
|
+
milestone: input.milestone ?? null,
|
|
246
260
|
};
|
|
247
261
|
const entries = [...ledger.entries, entry];
|
|
248
262
|
const result = recomputeCounts({ ...ledger, entries, last_updated: opts.now });
|
|
@@ -475,6 +489,19 @@ function validateEntryShape(e, i) {
|
|
|
475
489
|
reason: o.reason,
|
|
476
490
|
recorded_at: recordedStr,
|
|
477
491
|
resolved_at: resolvedStr,
|
|
492
|
+
// #4487: NOT in `required` above -- an entry recorded before this field
|
|
493
|
+
// existed has no `milestone` key at all, and that must parse cleanly.
|
|
494
|
+
// Preserve the absence itself -- by not materializing the property at
|
|
495
|
+
// all, via the conditional spread below, rather than assigning it
|
|
496
|
+
// `milestone: undefined` (an object literal property set to `undefined`
|
|
497
|
+
// is still an OWN property; `'milestone' in entry` reads true either
|
|
498
|
+
// way) -- so a genuinely absent key stays absent both to `in` and to
|
|
499
|
+
// JSON.stringify on re-render. Collapsing absence to an explicit null
|
|
500
|
+
// instead would stamp every legacy entry with permanent
|
|
501
|
+
// `"milestone": null` noise the moment the ledger is next touched. A
|
|
502
|
+
// genuinely-recorded-but-unresolvable milestone (set by appendWindow)
|
|
503
|
+
// is still an explicit null and round-trips as one.
|
|
504
|
+
...('milestone' in o ? { milestone: typeof o.milestone === 'string' ? o.milestone : null } : {}),
|
|
478
505
|
};
|
|
479
506
|
}
|
|
480
507
|
function parseLedger(raw) {
|
|
@@ -685,6 +712,78 @@ function diffTableRowIds(expectedTable, actualTable) {
|
|
|
685
712
|
function ledgerPath(cwd) {
|
|
686
713
|
return node_path_1.default.join(cwd, '.planning', exports.LEDGER_FILE_NAME);
|
|
687
714
|
}
|
|
715
|
+
/**
|
|
716
|
+
* The SHARED hardened cross-process lock primitive (single source of truth
|
|
717
|
+
* for capability-lifecycle + capability-consent, extracted so locks cannot
|
|
718
|
+
* diverge). Required LAZILY: capability-lock captures the process start time
|
|
719
|
+
* at module load — a `ps` subprocess on macOS, PowerShell on win32 — and
|
|
720
|
+
* this module is loaded by lock-free readers (`windows status`, the
|
|
721
|
+
* /gsd-ship gate) that must not pay that per-invocation cost (#3780
|
|
722
|
+
* review). `require` is cached, so writers pay it once per process.
|
|
723
|
+
*/
|
|
724
|
+
let _lockMod = null;
|
|
725
|
+
function ledgerLock() {
|
|
726
|
+
if (_lockMod === null) {
|
|
727
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
728
|
+
_lockMod = require('./capability-lock.cjs');
|
|
729
|
+
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
730
|
+
}
|
|
731
|
+
return _lockMod;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Budget mirrors capability-consent's CONSENT_LOCK_MAX_ATTEMPTS: two
|
|
735
|
+
* genuinely-racing writers must SERIALIZE, not fail. The ledger's critical
|
|
736
|
+
* section is sub-millisecond and the primitive backs off ~25-50ms per
|
|
737
|
+
* attempt, so 50 attempts is orders of magnitude beyond any real contention
|
|
738
|
+
* while keeping the worst case (a holder that never releases until the
|
|
739
|
+
* primitive's own liveness/deadman protocol reclaims it) bounded at ~2s
|
|
740
|
+
* before the typed refusal below.
|
|
741
|
+
*/
|
|
742
|
+
const LEDGER_LOCK_MAX_ATTEMPTS = 50;
|
|
743
|
+
function ledgerLockPath(cwd) {
|
|
744
|
+
return node_path_1.default.join(cwd, '.planning', '.WINDOWS.lock');
|
|
745
|
+
}
|
|
746
|
+
function acquireLedgerLock(cwd) {
|
|
747
|
+
return ledgerLock().acquireLock(ledgerLockPath(cwd), {
|
|
748
|
+
maxAttempts: LEDGER_LOCK_MAX_ATTEMPTS,
|
|
749
|
+
// A contended fresh/live holder is WAITED FOR (back off + retry), not
|
|
750
|
+
// failed-fast — racing wave-level executors serialize (issue #3780).
|
|
751
|
+
waitForFresh: true,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
function releaseLedgerLock(handle) {
|
|
755
|
+
ledgerLock().releaseLock(handle);
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Run `fn` (a full ledger read-compute-write cycle) while holding the
|
|
759
|
+
* cross-process ledger lock. Throws a typed WindowsError — never falls back
|
|
760
|
+
* to an unlocked mutation — when the lock cannot be acquired within the
|
|
761
|
+
* budget, mirroring capability-consent finding 3: a locked store must refuse
|
|
762
|
+
* the write rather than silently race for it. Readers (cmdWindowsStatus, the
|
|
763
|
+
* ship gate) deliberately do NOT take this lock: the atomic rename already
|
|
764
|
+
* gives them a whole-file snapshot.
|
|
765
|
+
*
|
|
766
|
+
* EXPORTED (#3780) because `withLedgerLock` is the ONE serialization seam
|
|
767
|
+
* for WINDOWS.md: every writer of the ledger — the cmd* entry points here
|
|
768
|
+
* and any sibling module with its own read-compute-write cycle on the same
|
|
769
|
+
* file (refactor-trigger-command-router's strict-window record/resolve) —
|
|
770
|
+
* must hold this lock, or the lost-update race #3780 fixed survives on that
|
|
771
|
+
* path.
|
|
772
|
+
*/
|
|
773
|
+
function withLedgerLock(cwd, fn) {
|
|
774
|
+
const handle = acquireLedgerLock(cwd);
|
|
775
|
+
if (!handle) {
|
|
776
|
+
throw new WindowsError(exports.REASON.WINDOWS_LEDGER_LOCK, `Another writer holds the ledger lock at ${ledgerLockPath(cwd)}; WINDOWS.md ` +
|
|
777
|
+
'mutations are serialized per project. Re-run the command once the other ' +
|
|
778
|
+
'writer finishes — the lock is reclaimed automatically if its holder died.');
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
return fn();
|
|
782
|
+
}
|
|
783
|
+
finally {
|
|
784
|
+
releaseLedgerLock(handle);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
688
787
|
function readLedgerOrNull(cwd) {
|
|
689
788
|
const p = ledgerPath(cwd);
|
|
690
789
|
let raw;
|
|
@@ -910,24 +1009,36 @@ function cmdWindowsAppend(cwd, args, opts = {}) {
|
|
|
910
1009
|
flags: ['--kind', '--phase', '--file', '--line', '--description'],
|
|
911
1010
|
required: ['--kind', '--phase', '--description'],
|
|
912
1011
|
});
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1012
|
+
// #3780: the whole read-compute-write cycle — snapshot, milestone stamp,
|
|
1013
|
+
// id allocation, atomic rename — holds the ledger lock, so two parallel
|
|
1014
|
+
// invocations can no longer compute the same nextId from the same snapshot
|
|
1015
|
+
// and silently lose the first append to the second rename.
|
|
1016
|
+
withLedgerLock(cwd, () => {
|
|
1017
|
+
let ledger;
|
|
1018
|
+
try {
|
|
1019
|
+
ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
|
|
1020
|
+
}
|
|
1021
|
+
catch (e) {
|
|
1022
|
+
if (e instanceof WindowsError)
|
|
1023
|
+
throw e;
|
|
1024
|
+
throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
|
|
1025
|
+
}
|
|
1026
|
+
// #4487: stamp the workstream's resolved milestone at record time -- the
|
|
1027
|
+
// same STATE.md-first, ROADMAP-fallback resolution workstream-inventory.cts
|
|
1028
|
+
// already uses. Best-effort: an unreadable/missing STATE.md or ROADMAP.md
|
|
1029
|
+
// resolves to null, same as an entry recorded before this field existed.
|
|
1030
|
+
const milestone = workstreamInventory.readCurrentMilestoneVersion(node_path_1.default.join(cwd, '.planning', 'STATE.md'), node_path_1.default.join(cwd, '.planning', 'ROADMAP.md'));
|
|
1031
|
+
const result = appendWindow(ledger, {
|
|
1032
|
+
kind: parsed.values['--kind'],
|
|
1033
|
+
phase: parsed.values['--phase'] ?? '',
|
|
1034
|
+
file: parsed.values['--file'] ?? '',
|
|
1035
|
+
line: parsed.values['--line'] == null ? null : Number(parsed.values['--line']),
|
|
1036
|
+
description: parsed.values['--description'] ?? '',
|
|
1037
|
+
milestone,
|
|
1038
|
+
}, { now: nowIso() });
|
|
1039
|
+
writeLedgerAtomic(cwd, result.ledger);
|
|
1040
|
+
emit({ ok: true, ledger: result.ledger, entry: result.entry });
|
|
1041
|
+
});
|
|
931
1042
|
}
|
|
932
1043
|
/** `gsd-tools windows waive <id> "<reason>"`. */
|
|
933
1044
|
function cmdWindowsWaive(cwd, args, opts = {}) {
|
|
@@ -936,36 +1047,44 @@ function cmdWindowsWaive(cwd, args, opts = {}) {
|
|
|
936
1047
|
const idStr = positionals[0];
|
|
937
1048
|
const reason = positionals[1];
|
|
938
1049
|
const id = parseIdOrThrow(idStr);
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1050
|
+
// #3780: same serialization as append — a concurrent append holding a stale
|
|
1051
|
+
// snapshot would otherwise overwrite the waive and resurrect the entry.
|
|
1052
|
+
withLedgerLock(cwd, () => {
|
|
1053
|
+
let ledger;
|
|
1054
|
+
try {
|
|
1055
|
+
ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
|
|
1056
|
+
}
|
|
1057
|
+
catch (e) {
|
|
1058
|
+
if (e instanceof WindowsError)
|
|
1059
|
+
throw e;
|
|
1060
|
+
throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
|
|
1061
|
+
}
|
|
1062
|
+
const updated = markWaived(ledger, id, reason ?? '', { now: nowIso() });
|
|
1063
|
+
writeLedgerAtomic(cwd, updated);
|
|
1064
|
+
emit({ ok: true, ledger: updated });
|
|
1065
|
+
});
|
|
951
1066
|
}
|
|
952
1067
|
/** `gsd-tools windows fixed <id>`. */
|
|
953
1068
|
function cmdWindowsMarkFixed(cwd, args, opts = {}) {
|
|
954
1069
|
void opts;
|
|
955
1070
|
const { positionals } = parseArgs(args, { flags: [], required: [], positionals: 1 });
|
|
956
1071
|
const id = parseIdOrThrow(positionals[0]);
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1072
|
+
// #3780: same serialization as append — a concurrent writer holding a
|
|
1073
|
+
// stale snapshot would otherwise overwrite the resolved status.
|
|
1074
|
+
withLedgerLock(cwd, () => {
|
|
1075
|
+
let ledger;
|
|
1076
|
+
try {
|
|
1077
|
+
ledger = readLedgerOrNull(cwd) ?? emptyLedger(nowIso());
|
|
1078
|
+
}
|
|
1079
|
+
catch (e) {
|
|
1080
|
+
if (e instanceof WindowsError)
|
|
1081
|
+
throw e;
|
|
1082
|
+
throw new WindowsError(exports.REASON.WINDOWS_LEDGER_MALFORMED, e.message);
|
|
1083
|
+
}
|
|
1084
|
+
const updated = markFixed(ledger, id, { now: nowIso() });
|
|
1085
|
+
writeLedgerAtomic(cwd, updated);
|
|
1086
|
+
emit({ ok: true, ledger: updated });
|
|
1087
|
+
});
|
|
969
1088
|
}
|
|
970
1089
|
function parseIdOrThrow(raw) {
|
|
971
1090
|
if (raw == null || raw === '') {
|
|
@@ -25,6 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
25
25
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
26
26
|
const node_path_1 = __importDefault(require("node:path"));
|
|
27
27
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
28
|
+
const security_cjs_1 = require("./security.cjs");
|
|
28
29
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
29
30
|
const sourceMod = require('./capability-source.cjs');
|
|
30
31
|
const ledgerMod = require('./capability-ledger.cjs');
|
|
@@ -202,7 +203,10 @@ function safeRmUnder(runtimeDir, rel) {
|
|
|
202
203
|
catch {
|
|
203
204
|
return false;
|
|
204
205
|
}
|
|
205
|
-
|
|
206
|
+
// Containment decision is the canonical LEXICAL predicate (ADR-4650 decision 6); lexical because
|
|
207
|
+
// both operands are already realpath-resolved here and the final component is deliberately
|
|
208
|
+
// handled as a link (see above).
|
|
209
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(realParent, realRoot) === null)
|
|
206
210
|
return false;
|
|
207
211
|
const realTarget = node_path_1.default.join(realParent, node_path_1.default.basename(target));
|
|
208
212
|
let st;
|
|
@@ -250,11 +254,11 @@ function confinedSharedFile(runtimeDir, relFile) {
|
|
|
250
254
|
catch {
|
|
251
255
|
// Parent does not exist yet (created inside the scope on write): a non-existent path cannot be a
|
|
252
256
|
// symlink escaping the root, so a lexical containment check is sufficient.
|
|
253
|
-
if (
|
|
257
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(parentDir, realRoot) === null)
|
|
254
258
|
return null;
|
|
255
259
|
return target;
|
|
256
260
|
}
|
|
257
|
-
if (
|
|
261
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(realParent, realRoot) === null)
|
|
258
262
|
return null;
|
|
259
263
|
return node_path_1.default.join(realParent, node_path_1.default.basename(target));
|
|
260
264
|
}
|
|
@@ -363,7 +367,7 @@ function confinedBundleScript(capDirPath, script) {
|
|
|
363
367
|
// disk): a non-existent root cannot be a symlink escaping itself, so confine lexically.
|
|
364
368
|
realCapRoot = node_path_1.default.resolve(capDirPath);
|
|
365
369
|
const targetLex = node_path_1.default.resolve(realCapRoot, script);
|
|
366
|
-
if (
|
|
370
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(targetLex, realCapRoot) === null)
|
|
367
371
|
return null;
|
|
368
372
|
return targetLex;
|
|
369
373
|
}
|
|
@@ -376,13 +380,13 @@ function confinedBundleScript(capDirPath, script) {
|
|
|
376
380
|
catch {
|
|
377
381
|
// Parent does not exist yet (created inside the bundle): lexical containment is sufficient
|
|
378
382
|
// because a non-existent path cannot be a symlink escaping the root.
|
|
379
|
-
if (
|
|
383
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(parentDir, realCapRoot) === null)
|
|
380
384
|
return null;
|
|
381
385
|
return target;
|
|
382
386
|
}
|
|
383
387
|
// The realpath'd parent chain must remain inside the bundle — an ancestor symlink escaping the
|
|
384
388
|
// bundle is refused here (the symlink is followed by realpathSync, so its real location is checked).
|
|
385
|
-
if (
|
|
389
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(realParent, realCapRoot) === null)
|
|
386
390
|
return null;
|
|
387
391
|
return node_path_1.default.join(realParent, node_path_1.default.basename(target));
|
|
388
392
|
}
|
|
@@ -70,6 +70,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
70
70
|
})();
|
|
71
71
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
72
72
|
exports.readHostVersion = readHostVersion;
|
|
73
|
+
exports.crossValidationSeed = crossValidationSeed;
|
|
73
74
|
exports.loadRegistry = loadRegistry;
|
|
74
75
|
const fs = __importStar(require("node:fs"));
|
|
75
76
|
const os = __importStar(require("node:os"));
|
|
@@ -366,6 +367,139 @@ function ledgerOverlayIds(ledger, rootDir) {
|
|
|
366
367
|
catch { /* missing/invalid/non-regular/oversized ledger — no pending, no committed (fail closed) */ }
|
|
367
368
|
return { pending, committed };
|
|
368
369
|
}
|
|
370
|
+
// ─── #3929: install-time cross-capability validation seed ──────────────────
|
|
371
|
+
/** First-party capabilities from the frozen registry, keyed by id. */
|
|
372
|
+
function firstPartyCaps() {
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
374
|
+
const base = require('./capability-registry.cjs');
|
|
375
|
+
return base.capabilities ?? {};
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Central config-schema validKeys for the ownership-exclusivity check — the
|
|
379
|
+
* same source `loadRegistry`'s generator path reads, with the same
|
|
380
|
+
* missing-schema fallback (empty set). Memoized per process: the manifest is
|
|
381
|
+
* static for the lifetime of the runtime.
|
|
382
|
+
*/
|
|
383
|
+
let _centralKeysMemo = null;
|
|
384
|
+
function centralConfigKeys() {
|
|
385
|
+
if (_centralKeysMemo === null) {
|
|
386
|
+
// Same generator seam loadRegistry uses (including the test override), so
|
|
387
|
+
// install-time and load-time central keys cannot diverge when the
|
|
388
|
+
// generator is stubbed.
|
|
389
|
+
if (_generatorOverride) {
|
|
390
|
+
_centralKeysMemo = _generatorOverride.loadCentralConfigKeys();
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
try {
|
|
394
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
395
|
+
const mod = require('../../../scripts/gen-capability-registry.cjs');
|
|
396
|
+
_centralKeysMemo = mod.loadCentralConfigKeys();
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
_centralKeysMemo = new Set();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return _centralKeysMemo;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* #3929: the validation seed `capability-source.stageValidated` runs the
|
|
407
|
+
* cross-capability suite against. Built the way `loadRegistry` builds its
|
|
408
|
+
* accepted map: first-party registry capabilities, then each COMMITTED overlay
|
|
409
|
+
* of the TARGET (global) install scope accepted INCREMENTALLY — structural
|
|
410
|
+
* validation, engines.gsd against the running host, then the FULL
|
|
411
|
+
* cross-capability suite; an overlay joins only if the suite stays clean
|
|
412
|
+
* after adding it (load's invariant: first-party alone is clean, so any new
|
|
413
|
+
* error is that overlay's fault — skip it, never fail the seed). The seed is
|
|
414
|
+
* therefore CLEAN BY CONSTRUCTION: pre-existing junk in the install scope
|
|
415
|
+
* (colliding entries, shape-invalid manifests, engines-incompatible bundles)
|
|
416
|
+
* is skipped exactly as load skips it and can never fail or skew a
|
|
417
|
+
* candidate's install decision — a repo-planted ledger cannot veto installs
|
|
418
|
+
* (#1459 CB-3).
|
|
419
|
+
*
|
|
420
|
+
* Scope: only the install TARGET scope is walked — `stageValidated` promotes
|
|
421
|
+
* into `${gsdHome}/.gsd/capabilities`, so target scope == global.
|
|
422
|
+
* Project-scope overlays are deliberately NOT seeded (they additionally
|
|
423
|
+
* require user consent to activate at load; the issue asks for overlays "in
|
|
424
|
+
* the target scope"). The candidate is added by the caller LAST, mirroring
|
|
425
|
+
* `acceptedMap.set(id, cap)`.
|
|
426
|
+
*
|
|
427
|
+
* Exported (not inlined in the installer) so the semantics this reuses —
|
|
428
|
+
* `overlayRoots`, `ledgerOverlayIds`, the shared bounded manifest reader, and
|
|
429
|
+
* the incremental-accept rules — have exactly one owner and cannot drift from
|
|
430
|
+
* the loader's load-time rules.
|
|
431
|
+
*/
|
|
432
|
+
function crossValidationSeed(cwd, gsdHome, hostVersion, validator, semver) {
|
|
433
|
+
const fp = firstPartyCaps();
|
|
434
|
+
const capMap = new Map(Object.entries(fp));
|
|
435
|
+
const centralKeys = centralConfigKeys();
|
|
436
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
437
|
+
const ledger = require('./capability-ledger.cjs');
|
|
438
|
+
for (const root of overlayRoots(cwd, gsdHome)) {
|
|
439
|
+
if (root.scope !== 'global')
|
|
440
|
+
continue; // seed the TARGET scope only
|
|
441
|
+
const { committed } = ledgerOverlayIds(ledger, root.dir);
|
|
442
|
+
for (const id of committed) {
|
|
443
|
+
// First-party always wins (CONTEXT.md capability-loader entry): an
|
|
444
|
+
// overlay claiming a first-party id — or a reserved first-party prefix
|
|
445
|
+
// — is rejected at load, so it must not join the validation set.
|
|
446
|
+
if (Object.prototype.hasOwnProperty.call(fp, id))
|
|
447
|
+
continue;
|
|
448
|
+
if (RESERVED_ID_PREFIX.test(id))
|
|
449
|
+
continue;
|
|
450
|
+
let cap;
|
|
451
|
+
try {
|
|
452
|
+
const manifestPath = path.join(root.dir, id, 'capability.json');
|
|
453
|
+
const raw = ledger.readSmallRegularFile(manifestPath, MANIFEST_MAX_BYTES);
|
|
454
|
+
if (raw === null)
|
|
455
|
+
continue; // missing/non-regular/oversized — skip fail-closed
|
|
456
|
+
cap = JSON.parse(raw);
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
continue; // unreadable overlay — skip (same rule as load)
|
|
460
|
+
}
|
|
461
|
+
// Same per-overlay pre-filters load applies before the cross suite:
|
|
462
|
+
// structural validity, then engines.gsd against the running host.
|
|
463
|
+
// validateCapability itself is not total over malformed array entries
|
|
464
|
+
// (#1461 finding 1) — a throw skips the overlay, as load skips it.
|
|
465
|
+
try {
|
|
466
|
+
if (validator.validateCapability(cap, id).length > 0)
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const engines = cap['engines'];
|
|
473
|
+
if (engines && typeof engines === 'object' && !Array.isArray(engines)) {
|
|
474
|
+
const range = engines['gsd'];
|
|
475
|
+
if (typeof range === 'string' && range && !semver.semverSatisfies(hostVersion, range))
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
// Incremental accept: the overlay joins only if the FULL suite stays
|
|
479
|
+
// clean after adding it; any error is that overlay's fault — skip it.
|
|
480
|
+
capMap.set(id, cap);
|
|
481
|
+
let errs = [];
|
|
482
|
+
try {
|
|
483
|
+
errs = [
|
|
484
|
+
...validator.validateConsumesGlobal(capMap),
|
|
485
|
+
...validator.validateCrossCapability(capMap, centralKeys),
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
// The cross validators are not total over arbitrary shapes (#1461
|
|
490
|
+
// finding 1 — e.g. the duplicate-producer invariant throws). A
|
|
491
|
+
// throwing overlay must be REMOVED here, exactly as load's
|
|
492
|
+
// acceptedMap.delete(id) does — retaining it would leave poisoning
|
|
493
|
+
// content in the seed and attribute pre-existing junk to the
|
|
494
|
+
// candidate.
|
|
495
|
+
capMap.delete(id);
|
|
496
|
+
}
|
|
497
|
+
if (errs.length > 0)
|
|
498
|
+
capMap.delete(id);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return { capMap, centralKeys };
|
|
502
|
+
}
|
|
369
503
|
/** Shallow-attach overlay diagnostics WITHOUT mutating the frozen registry module. */
|
|
370
504
|
function withOverlayMeta(reg, meta) {
|
|
371
505
|
return Object.assign({}, reg, { _overlay: meta });
|
|
@@ -821,4 +955,4 @@ function loadRegistry(options = {}) {
|
|
|
821
955
|
}
|
|
822
956
|
}
|
|
823
957
|
// readHostVersion is exported for the #1920 regression (VERSION-first host-version resolution).
|
|
824
|
-
module.exports = { loadRegistry, readHostVersion, _setValidatorForTest, _setGeneratorForTest };
|
|
958
|
+
module.exports = { loadRegistry, readHostVersion, crossValidationSeed, _setValidatorForTest, _setGeneratorForTest };
|