@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
|
@@ -677,8 +677,25 @@ function stageValidated(opts) {
|
|
|
677
677
|
throw new Error(`Hook fragment validation failed: ${fragErrs.join('; ')}`);
|
|
678
678
|
}
|
|
679
679
|
// Cross-capability validations (contract, consumes, cross-capability).
|
|
680
|
-
|
|
681
|
-
|
|
680
|
+
//
|
|
681
|
+
// #3929: seed the validation set the way the loader builds its accepted
|
|
682
|
+
// map — frozen first-party registry, then each committed overlay of the
|
|
683
|
+
// TARGET (global) install scope accepted incrementally (structural +
|
|
684
|
+
// engines + full-suite-clean), then the candidate LAST (mirroring
|
|
685
|
+
// `acceptedMap.set(id, cap)`). The singleton seed
|
|
686
|
+
// `new Map([[id, cap]])` this replaced made every non-empty `requires`
|
|
687
|
+
// unsatisfiable (membership is checked against the map) and left cycles,
|
|
688
|
+
// tier-monotone and central config-key exclusivity vacuous at install.
|
|
689
|
+
// The seed builder lives in capability-loader so the overlay semantics
|
|
690
|
+
// have one owner and are clean by construction — pre-existing junk in the
|
|
691
|
+
// install scope is skipped, never attributed to the candidate. No swallow:
|
|
692
|
+
// if the loader cannot build the seed the install fails loudly — silently
|
|
693
|
+
// degrading to the singleton map would re-hide #3929.
|
|
694
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
695
|
+
const seedLoader = require('./capability-loader.cjs');
|
|
696
|
+
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
697
|
+
const { capMap, centralKeys } = seedLoader.crossValidationSeed(process.cwd(), gsdHome, hostVersion, capValidator, semverMod);
|
|
698
|
+
capMap.set(id, cap);
|
|
682
699
|
const crossErrs = [
|
|
683
700
|
...capValidator.validateAgainstContract(cap, id),
|
|
684
701
|
...capValidator.validateConsumesGlobal(capMap),
|
|
@@ -1947,7 +1947,6 @@ const KNOWN_HOST_BEHAVIORS = new Set([
|
|
|
1947
1947
|
'skillPriorityFrontmatter',
|
|
1948
1948
|
'skillsGlobalOnboarding',
|
|
1949
1949
|
'skillsManifestPrefix',
|
|
1950
|
-
'skipCodexSkillsManifest',
|
|
1951
1950
|
'skipHomePrefixSubstitution',
|
|
1952
1951
|
'skipSettingsUi',
|
|
1953
1952
|
'skipSharedHooksInstall',
|
|
@@ -1956,6 +1955,11 @@ const KNOWN_HOST_BEHAVIORS = new Set([
|
|
|
1956
1955
|
'sourceMarkerFile',
|
|
1957
1956
|
'tomlConfigInstall',
|
|
1958
1957
|
'trackCategoryDescription',
|
|
1958
|
+
// #2586: declares runtime-level feature axes GSD does not/cannot support on
|
|
1959
|
+
// this host (e.g. Codex's `["context-warnings","phase-lifecycle-display"]`)
|
|
1960
|
+
// — present-and-populated / absent-is-unsupported-empty convention, so
|
|
1961
|
+
// omitting the key on every other runtime carries no inverted meaning.
|
|
1962
|
+
'unsupportedFeatures',
|
|
1959
1963
|
'verificationStyle',
|
|
1960
1964
|
'writeCategoryDescription',
|
|
1961
1965
|
]);
|
|
@@ -2796,7 +2800,7 @@ function materializeHookFragments(cap, capDir) {
|
|
|
2796
2800
|
|
|
2797
2801
|
const abs = path.resolve(capDir, fragment.path);
|
|
2798
2802
|
const capRoot = path.resolve(capDir);
|
|
2799
|
-
if (abs !== capRoot && !abs.startsWith(capRoot + path.sep)) {
|
|
2803
|
+
if (abs !== capRoot && !abs.startsWith(capRoot + path.sep)) { // allow-handrolled-containment: committed pre-build .cjs; compiled security.cjs is untracked build output
|
|
2800
2804
|
errors.push(
|
|
2801
2805
|
cap.id + '/' + groupName + '[' + i + '].fragment.path escapes capability directory: ' +
|
|
2802
2806
|
fragment.path,
|
|
@@ -2948,6 +2952,14 @@ function validateStep(step, prefix, declaredSkills, declaredAgents) {
|
|
|
2948
2952
|
errors.push(prefix + '.pointFrom must be a string if present');
|
|
2949
2953
|
}
|
|
2950
2954
|
|
|
2955
|
+
// #4209 DISP-02: strict optional boolean opt-in trait. Absent or false is
|
|
2956
|
+
// inert; only a literal `true` reaches the projected active hook. Reject
|
|
2957
|
+
// every other type (including truthy non-boolean values) so a typo can
|
|
2958
|
+
// never silently opt a step into reviewer-lane dispatch.
|
|
2959
|
+
if (step.supportsReviewerLanes !== undefined && typeof step.supportsReviewerLanes !== 'boolean') {
|
|
2960
|
+
errors.push(prefix + '.supportsReviewerLanes must be a boolean if present');
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2951
2963
|
if (step.fragment !== undefined) {
|
|
2952
2964
|
errors.push(...validateFragment(step.fragment, prefix + '.fragment'));
|
|
2953
2965
|
}
|
|
@@ -14,7 +14,13 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const node_child_process_1 = require("node:child_process");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
16
|
const io = require("./io.cjs");
|
|
17
|
-
const { output,
|
|
17
|
+
const { output, ERROR_REASON } = io;
|
|
18
|
+
// Explicitly annotated so TypeScript applies never-return control-flow narrowing.
|
|
19
|
+
// A destructured `const { error } = io` is a const WITHOUT a type annotation, and TS
|
|
20
|
+
// only narrows after a never-returning call when the callee is a function declaration
|
|
21
|
+
// or an annotated const. Without the annotation every `error(...)` guard below would
|
|
22
|
+
// need a dead `throw` after it to convince the checker that the value is non-null.
|
|
23
|
+
const error = io.error;
|
|
18
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
25
|
const planningWorkspaceMod = require("./planning-workspace.cjs");
|
|
20
26
|
const { planningDir } = planningWorkspaceMod;
|
|
@@ -89,7 +95,12 @@ function readIfExists(filePath) {
|
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
function resolvePath(inputPath, projectDir) {
|
|
92
|
-
|
|
98
|
+
const candidate = node_path_1.default.isAbsolute(inputPath) ? inputPath : node_path_1.default.join(projectDir, inputPath);
|
|
99
|
+
const contained = (0, security_cjs_1.tryWithinRoot)(candidate, projectDir, security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
100
|
+
if (contained === null) {
|
|
101
|
+
error(`path escapes its allowed directory: ${inputPath}`, ERROR_REASON.USAGE);
|
|
102
|
+
}
|
|
103
|
+
return contained;
|
|
93
104
|
}
|
|
94
105
|
function readWorkflowConfig(projectDir) {
|
|
95
106
|
const configPath = node_path_1.default.join(projectDir, '.planning', 'config.json');
|
|
@@ -270,11 +281,37 @@ function loadDecisionExtraction(contextPath) {
|
|
|
270
281
|
return {
|
|
271
282
|
trackable: extraction.decisions.filter((d) => d.trackable),
|
|
272
283
|
outcome: extraction.outcome,
|
|
284
|
+
unreadableIds: extraction.unreadableIds ?? [],
|
|
273
285
|
};
|
|
274
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* `check decision-coverage-plan` — blocking plan-phase decision-coverage gate
|
|
289
|
+
* (#2492, #1365 fail-loud, #2770 empty-arg fail-closed).
|
|
290
|
+
*
|
|
291
|
+
* Invocation (the context path may be supplied EITHER way; #4130 follow-up):
|
|
292
|
+
* gsd_run check decision-coverage-plan <phase-dir> <context-path> (positional, the workflow caller's form)
|
|
293
|
+
* gsd_run check decision-coverage-plan --context <path> [<phase-dir>]
|
|
294
|
+
*
|
|
295
|
+
* `--context <path>` follows the sibling flag convention (`check predicate`,
|
|
296
|
+
* #2008): `--flag value` pairs parsed by the shared partitionPredicateArgs
|
|
297
|
+
* pass, the flag WINNING over a same-purpose positional when both appear,
|
|
298
|
+
* and a valueless `--context` counting as no context at all (it falls
|
|
299
|
+
* through to the #2770 caller-error branch, not to the "CONTEXT.md missing"
|
|
300
|
+
* green skip). The positional form keeps working unchanged — no sibling
|
|
301
|
+
* check verb deprecates positionals and the plan-phase workflow passes them.
|
|
302
|
+
*/
|
|
275
303
|
function cmdDecisionCoveragePlan(projectDir, args, raw) {
|
|
276
|
-
|
|
277
|
-
|
|
304
|
+
// args[0]='check', args[1]=subcommand — partition the REST so flag tokens
|
|
305
|
+
// and their values never land in a positional slot.
|
|
306
|
+
const { flags, positionals } = partitionPredicateArgs(args.slice(2));
|
|
307
|
+
const phaseDir = positionals[0] ? resolvePath(positionals[0], projectDir) : '';
|
|
308
|
+
// A VALUELESS `--context` stays a bare token in the positionals (sibling
|
|
309
|
+
// parser semantics); it must not then be read as the context PATH — a
|
|
310
|
+
// `--`-prefixed "path" is a caller mistake, and #2770's law says a missing
|
|
311
|
+
// context argument fails CLOSED, never a silent "CONTEXT.md missing" green
|
|
312
|
+
// skip. So only a non-flag positional may serve as the context.
|
|
313
|
+
const positionalContext = positionals[1] && !positionals[1].startsWith('--') ? positionals[1] : '';
|
|
314
|
+
const contextArg = flags['context'] ?? positionalContext ?? '';
|
|
278
315
|
const contextPath = contextArg ? resolvePath(contextArg, projectDir) : '';
|
|
279
316
|
if (!gateEnabled(projectDir)) {
|
|
280
317
|
output({ passed: true, skipped: true, reason: 'workflow.context_coverage_gate is false', total: 0, covered: 0, uncovered: [], message: 'Decision coverage gate disabled by config.' }, raw, undefined);
|
|
@@ -293,28 +330,59 @@ function cmdDecisionCoveragePlan(projectDir, args, raw) {
|
|
|
293
330
|
output({ passed: true, skipped: true, reason: 'CONTEXT.md missing', total: 0, covered: 0, uncovered: [], message: 'No CONTEXT.md - nothing to check.' }, raw, undefined);
|
|
294
331
|
return;
|
|
295
332
|
}
|
|
296
|
-
|
|
333
|
+
// #4794: a NON-FILE path (a directory — the adjacent same-looking positional
|
|
334
|
+
// swapped, the issue's repro 2) is a caller error like #2770's empty argument:
|
|
335
|
+
// fs.existsSync is true, the read yields nothing, and the gate used to
|
|
336
|
+
// certify passed:true on a phase full of decisions. Fail closed, naming it.
|
|
337
|
+
// The stat is wrapped: a path that vanishes between existsSync and statSync
|
|
338
|
+
// (or any stat failure) must answer the SAME fail-closed JSON, never a throw.
|
|
339
|
+
let contextIsFile = false;
|
|
340
|
+
let contextKind = 'non-file entry';
|
|
341
|
+
try {
|
|
342
|
+
const st = node_fs_1.default.statSync(contextPath);
|
|
343
|
+
contextIsFile = st.isFile();
|
|
344
|
+
if (st.isDirectory())
|
|
345
|
+
contextKind = 'directory';
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
contextIsFile = false;
|
|
349
|
+
contextKind = 'unreadable path';
|
|
350
|
+
}
|
|
351
|
+
if (!contextIsFile) {
|
|
352
|
+
output({ passed: false, skipped: false, reason: 'context path is not a file', total: null, covered: null, message: `Decision coverage gate: the context path "${contextArg}" is not a readable file (${contextKind}). Swap the adjacent positionals or pass --context <path-to-CONTEXT.md>.` }, raw, undefined);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const { trackable: decisions, outcome, unreadableIds } = loadDecisionExtraction(contextPath);
|
|
297
356
|
// #1365 fail-loud gate: any could-not-parse outcome must NOT silently pass —
|
|
298
357
|
// even when some decisions were extracted (e.g. D-01 valid but D-02 malformed).
|
|
299
358
|
// A parse-miss on ANY bullet means the gate cannot certify full coverage.
|
|
300
359
|
// Fire independent of decisions.length so a partial-parse still blocks.
|
|
301
360
|
if (outcome === 'could-not-parse') {
|
|
361
|
+
// #4794: nothing was measured — the answer must not carry the fields of a
|
|
362
|
+
// gate that did. total/covered are null (a type change is the point:
|
|
363
|
+
// 0 reads as data, null does not), `uncovered` is OMITTED (the list was
|
|
364
|
+
// never built), and the ids that failed to parse are carried so a caller
|
|
365
|
+
// capturing stdout knows which decision to fix.
|
|
302
366
|
const partialParse = decisions.length > 0;
|
|
303
367
|
output({
|
|
304
368
|
passed: false,
|
|
305
369
|
skipped: false,
|
|
306
370
|
reason: 'could-not-parse',
|
|
307
|
-
total:
|
|
308
|
-
covered:
|
|
309
|
-
|
|
310
|
-
message: partialParse
|
|
371
|
+
total: null,
|
|
372
|
+
covered: null,
|
|
373
|
+
unreadable: unreadableIds,
|
|
374
|
+
message: (partialParse
|
|
311
375
|
? 'Decision coverage gate: decisions could not be fully parsed — one or more ' +
|
|
312
|
-
'`- **D-NN ...**` bullets appear malformed (missing `:` or ` — ` separator
|
|
313
|
-
'
|
|
376
|
+
'`- **D-NN ...**` bullets appear malformed (missing `:` or ` — ` separator, or a phase ' +
|
|
377
|
+
'prefix that is not a digit run, e.g. `D4x-01`). Fix the bullet format so all decisions ' +
|
|
378
|
+
'can be read before re-running the gate.'
|
|
314
379
|
: 'Decision coverage gate: could not parse decisions — possible format mismatch. ' +
|
|
315
380
|
'The CONTEXT.md appears to be decision-shaped (has a <decisions> block, a decisions heading, ' +
|
|
316
|
-
'or D- tokens) but no
|
|
317
|
-
'block and ensure bullets follow the `- **D-NN:** text
|
|
381
|
+
'or D- tokens) but no decision bullets could be extracted. Check the formatting of the decisions ' +
|
|
382
|
+
'block and ensure bullets follow the `- **D-NN:** text`, `- **D4-NN:** text` (phase-prefixed), ' +
|
|
383
|
+
'or `- **D-NN — title** body` form. An ID grammar the parser does not support (e.g. `DEC-01`) ' +
|
|
384
|
+
'also lands here.')
|
|
385
|
+
+ (unreadableIds.length > 0 ? ' Unreadable ids: ' + unreadableIds.join(', ') + '.' : ''),
|
|
318
386
|
}, raw, undefined);
|
|
319
387
|
return;
|
|
320
388
|
}
|
|
@@ -354,11 +422,6 @@ function recentCommitMessages(projectDir) {
|
|
|
354
422
|
return '';
|
|
355
423
|
}
|
|
356
424
|
}
|
|
357
|
-
function isInsideRoot(candidatePath, rootDir) {
|
|
358
|
-
const root = node_path_1.default.resolve(rootDir);
|
|
359
|
-
const target = node_path_1.default.resolve(root, candidatePath);
|
|
360
|
-
return target === root || target.startsWith(`${root}${node_path_1.default.sep}`);
|
|
361
|
-
}
|
|
362
425
|
function readModifiedFilesContent(projectDir, summaries) {
|
|
363
426
|
const out = [];
|
|
364
427
|
let total = 0;
|
|
@@ -371,9 +434,17 @@ function readModifiedFilesContent(projectDir, summaries) {
|
|
|
371
434
|
for (const file of files) {
|
|
372
435
|
if (total >= 50)
|
|
373
436
|
break;
|
|
374
|
-
if (!file
|
|
437
|
+
if (!file)
|
|
375
438
|
continue;
|
|
376
|
-
|
|
439
|
+
// Migrated off the hand-rolled prefix check (ADR-4650): resolve+contain in one
|
|
440
|
+
// step via the canonical realpath predicate — the eventual read below follows
|
|
441
|
+
// symlinks, so containment must be decided on the resolved target, not a lexical
|
|
442
|
+
// prefix. Read the value the predicate RETURNED; do not re-derive the path.
|
|
443
|
+
const candidate = node_path_1.default.isAbsolute(file) ? file : node_path_1.default.join(projectDir, file);
|
|
444
|
+
const contained = (0, security_cjs_1.tryWithinRoot)(candidate, projectDir, security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
445
|
+
if (contained === null)
|
|
446
|
+
continue;
|
|
447
|
+
const raw = readIfExists(contained);
|
|
377
448
|
out.push(raw.length > 256 * 1024 ? raw.slice(0, 256 * 1024) : raw);
|
|
378
449
|
total++;
|
|
379
450
|
}
|
|
@@ -411,9 +482,11 @@ function cmdDecisionCoverageVerify(projectDir, args, raw) {
|
|
|
411
482
|
not_honored: [],
|
|
412
483
|
message: partialParse
|
|
413
484
|
? 'Decision coverage verify (warning): decisions could not be fully parsed — one or more ' +
|
|
414
|
-
'`- **D-NN ...**` bullets appear malformed
|
|
485
|
+
'`- **D-NN ...**` bullets appear malformed (missing `:` or ` — ` separator, or a phase ' +
|
|
486
|
+
'prefix that is not a digit run). Fix the bullet format in the CONTEXT.md decisions block.'
|
|
415
487
|
: 'Decision coverage verify (warning): could not parse decisions — possible format mismatch. ' +
|
|
416
|
-
'Check the formatting of the CONTEXT.md decisions block
|
|
488
|
+
'Check the formatting of the CONTEXT.md decisions block (accepted forms: `- **D-NN:** text`, ' +
|
|
489
|
+
'`- **D4-NN:** text` (phase-prefixed), `- **D-NN — title** body`).',
|
|
417
490
|
}, raw, undefined);
|
|
418
491
|
return;
|
|
419
492
|
}
|
|
@@ -502,8 +575,10 @@ function findUiSpecInDir(phaseDir) {
|
|
|
502
575
|
* matches the token `dashboard` exactly like the real compound `micro-frontend`
|
|
503
576
|
* (the boundary rule of #3718 is intentional and untouched). The gate therefore
|
|
504
577
|
* blocks only when the token match is corroborated by static frontend evidence
|
|
505
|
-
* in the repo tree (hasStaticFrontendEvidence: package.json UI-framework dep
|
|
506
|
-
*
|
|
578
|
+
* in the repo tree (hasStaticFrontendEvidence: package.json UI-framework dep, a
|
|
579
|
+
* component-framework file, or native UI evidence — a `.xaml` file or a
|
|
580
|
+
* `.swift`/`.kt`/`.dart` file carrying its ecosystem's UI import marker,
|
|
581
|
+
* #4658). This mirrors the sibling post-wave gate
|
|
507
582
|
* computeUiSafetyGate, which requires `hasUiFiles` (git diff) before blocking.
|
|
508
583
|
* matchedToken/matchedLine surface what tripped the sniffer so an operator can
|
|
509
584
|
* judge the flag in one second instead of reaching for --skip-ui.
|
|
@@ -925,8 +1000,22 @@ function resolvePhaseDirOrEmpty(projectDir, phase) {
|
|
|
925
1000
|
* phase's `-PLAN.md` files against the filesystem WITHOUT executing anything —
|
|
926
1001
|
* see verify-command-grounding.cjs for the recognizer contract.
|
|
927
1002
|
*
|
|
928
|
-
* Args: check verify-command-paths <phase>
|
|
1003
|
+
* Args: check verify-command-paths <phase> | check verify-command-paths --dir <plan-dir>
|
|
929
1004
|
* Invocable as: gsd_run check verify-command-paths <phase>
|
|
1005
|
+
* gsd_run check verify-command-paths --dir <plan-dir>
|
|
1006
|
+
*
|
|
1007
|
+
* `--dir` (#4767) names a directory holding `-PLAN.md` files directly, for
|
|
1008
|
+
* plans that live outside `.planning/phases/` — quick mode's
|
|
1009
|
+
* `.planning/quick/<id>/` is the motivating caller, which until #4767 never ran
|
|
1010
|
+
* this probe at all. The directory is resolved against the project root AND
|
|
1011
|
+
* CONTAINED WITHIN IT — an absolute or climbing `--dir` that lands outside the
|
|
1012
|
+
* root is `unresolvable`, never read — then probed exactly as a phase directory
|
|
1013
|
+
* is; `projectRoot` stays the project root in both forms. `--dir <value>` is the
|
|
1014
|
+
* only accepted spelling: `--dir=<value>` yields no `dir` flag and falls through to
|
|
1015
|
+
* the no-argument arm, as does an empty value. Both are `partitionPredicateArgs`
|
|
1016
|
+
* behaviour, inherited and unchanged. (How that parser resolves a REPEATED `--dir`
|
|
1017
|
+
* is deliberately not characterised here — a malformed later occurrence does not
|
|
1018
|
+
* displace an earlier valid one, so the obvious "last one wins" gloss is wrong.)
|
|
930
1019
|
*
|
|
931
1020
|
* When the phase cannot be resolved to a directory, this emits a non-throwing
|
|
932
1021
|
* degraded JSON payload (status/commands/counts all zeroed, `readError`
|
|
@@ -935,18 +1024,60 @@ function resolvePhaseDirOrEmpty(projectDir, phase) {
|
|
|
935
1024
|
* look", which a non-zero exit / thrown error would collapse.
|
|
936
1025
|
*/
|
|
937
1026
|
function cmdVerifyCommandPaths(projectDir, args, raw) {
|
|
938
|
-
// args[0] = 'check', args[1] = 'verify-command-paths',
|
|
939
|
-
|
|
940
|
-
|
|
1027
|
+
// args[0] = 'check', args[1] = 'verify-command-paths', then either a phase
|
|
1028
|
+
// positional or `--dir <plan-dir>` (#4767).
|
|
1029
|
+
const { flags, positionals } = partitionPredicateArgs(args.slice(2));
|
|
1030
|
+
const dirFlag = typeof flags['dir'] === 'string' ? flags['dir'] : '';
|
|
1031
|
+
// First non-flag positional: `--raw` (valueless) lands in positionals too, and its position
|
|
1032
|
+
// relative to the phase argument is the caller's choice.
|
|
1033
|
+
const phase = positionals.find(p => !p.startsWith('--')) ?? '';
|
|
1034
|
+
if (!phase && !dirFlag) {
|
|
941
1035
|
output({
|
|
942
1036
|
status: 'unresolvable',
|
|
943
1037
|
commands: [],
|
|
944
1038
|
counts: { blocker: 0, warning: 0, total: 0 },
|
|
945
|
-
readError: 'verify-command-paths requires a phase argument: check verify-command-paths <phase>',
|
|
1039
|
+
readError: 'verify-command-paths requires a phase argument or --dir: check verify-command-paths <phase> | --dir <plan-dir>',
|
|
946
1040
|
}, raw, undefined);
|
|
947
1041
|
return;
|
|
948
1042
|
}
|
|
949
|
-
|
|
1043
|
+
// `--dir` is CALLER-SUPPLIED, so it is contained before it reaches the
|
|
1044
|
+
// `readdirSync`/`readFileSync` calls in probePhaseVerifyCommands (#4785 review).
|
|
1045
|
+
// Same predicate and policy as `resolvePath` above, and for the reason ADR-4650
|
|
1046
|
+
// gives at the other read site: the reads below FOLLOW SYMLINKS, so containment
|
|
1047
|
+
// must be decided on the resolved target, not a lexical prefix — a link inside
|
|
1048
|
+
// the root pointing outside it passes `tryWithinRootLexical` and is then read.
|
|
1049
|
+
// Read the value the predicate RETURNED; never re-derive the path. An escape
|
|
1050
|
+
// degrades to the same non-throwing payload the unresolvable-phase arm emits,
|
|
1051
|
+
// because a consumer must be able to tell "could not look" from "nothing to
|
|
1052
|
+
// report" (and `error()` would collapse them).
|
|
1053
|
+
//
|
|
1054
|
+
// RESIDUAL, stated rather than left to be rediscovered: this is check-then-use, so
|
|
1055
|
+
// a symlink planted at the resolved path BETWEEN this call and the reads inside
|
|
1056
|
+
// probePhaseVerifyCommands would be followed. A link already in place when the
|
|
1057
|
+
// command runs IS refused — the predicate resolves it and returns null (driven) —
|
|
1058
|
+
// so the window is the in-process gap, not the ordinary case. It is a property of
|
|
1059
|
+
// every `tryWithinRoot` call site in this repo, including `resolvePath` above and
|
|
1060
|
+
// the artifact scan below, not of this arm; closing it needs O_NOFOLLOW/dirfd
|
|
1061
|
+
// semantics inside the ADR-4650 predicate, which is a wider change than the bug
|
|
1062
|
+
// this fixes.
|
|
1063
|
+
let phaseDir;
|
|
1064
|
+
if (dirFlag) {
|
|
1065
|
+
const candidate = node_path_1.default.isAbsolute(dirFlag) ? dirFlag : node_path_1.default.join(projectDir, dirFlag);
|
|
1066
|
+
const contained = (0, security_cjs_1.tryWithinRoot)(candidate, projectDir, security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
1067
|
+
if (contained === null) {
|
|
1068
|
+
output({
|
|
1069
|
+
status: 'unresolvable',
|
|
1070
|
+
commands: [],
|
|
1071
|
+
counts: { blocker: 0, warning: 0, total: 0 },
|
|
1072
|
+
readError: `--dir resolves outside the project root: ${dirFlag}`,
|
|
1073
|
+
}, raw, undefined);
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
phaseDir = contained;
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
phaseDir = resolvePhaseDirOrEmpty(projectDir, phase);
|
|
1080
|
+
}
|
|
950
1081
|
if (!phaseDir) {
|
|
951
1082
|
output({
|
|
952
1083
|
status: 'unresolvable',
|
|
@@ -1019,8 +1150,9 @@ function cmdGapAnalysisPlanPost(projectDir, args, raw) {
|
|
|
1019
1150
|
error('gap-analysis.plan-post requires a phase-dir argument: check gap-analysis.plan-post <phase-dir> [phase-req-ids]', ERROR_REASON.SDK_MISSING_ARG);
|
|
1020
1151
|
return;
|
|
1021
1152
|
}
|
|
1153
|
+
const resolvedPhaseDir = resolvePath(phaseDir, projectDir);
|
|
1022
1154
|
const phaseReqIds = args[3] ?? undefined;
|
|
1023
|
-
const result = runGapAnalysis(projectDir,
|
|
1155
|
+
const result = runGapAnalysis(projectDir, resolvedPhaseDir, { phaseReqIds });
|
|
1024
1156
|
// Uniform gate contract: block = false (gap-analysis is always advisory, never blocks).
|
|
1025
1157
|
// `message` carries the human-readable gap analysis report so the dispatch's
|
|
1026
1158
|
// advisory branch can surface it. --raw emits JSON (rawValue=undefined), not
|
|
@@ -1071,21 +1203,21 @@ function buildPredicateDeps() {
|
|
|
1071
1203
|
node_path_1.default.win32.basename(artifactSuffix) !== artifactSuffix) {
|
|
1072
1204
|
return null;
|
|
1073
1205
|
}
|
|
1074
|
-
const
|
|
1075
|
-
if (
|
|
1076
|
-
return
|
|
1206
|
+
const directContained = (0, security_cjs_1.tryWithinRoot)(artifactSuffix, phaseDir);
|
|
1207
|
+
if (directContained !== null && node_fs_1.default.existsSync(directContained) && node_fs_1.default.statSync(directContained).isFile()) {
|
|
1208
|
+
return directContained;
|
|
1077
1209
|
}
|
|
1078
|
-
const
|
|
1079
|
-
if (
|
|
1080
|
-
return
|
|
1210
|
+
const planningContained = (0, security_cjs_1.tryWithinRoot)(node_path_1.default.join('.planning', artifactSuffix), phaseDir);
|
|
1211
|
+
if (planningContained !== null && node_fs_1.default.existsSync(planningContained) && node_fs_1.default.statSync(planningContained).isFile()) {
|
|
1212
|
+
return planningContained;
|
|
1081
1213
|
}
|
|
1082
1214
|
try {
|
|
1083
1215
|
const files = node_fs_1.default.readdirSync(phaseDir);
|
|
1084
1216
|
for (const f of files) {
|
|
1085
1217
|
if (f.endsWith('-' + artifactSuffix) || f === artifactSuffix) {
|
|
1086
|
-
const
|
|
1087
|
-
if (
|
|
1088
|
-
return
|
|
1218
|
+
const candidateContained = (0, security_cjs_1.tryWithinRoot)(f, phaseDir);
|
|
1219
|
+
if (candidateContained !== null && node_fs_1.default.statSync(candidateContained).isFile())
|
|
1220
|
+
return candidateContained;
|
|
1089
1221
|
}
|
|
1090
1222
|
}
|
|
1091
1223
|
}
|
|
@@ -1101,23 +1233,42 @@ function buildPredicateDeps() {
|
|
|
1101
1233
|
}
|
|
1102
1234
|
};
|
|
1103
1235
|
}
|
|
1104
|
-
/**
|
|
1105
|
-
|
|
1106
|
-
|
|
1236
|
+
/**
|
|
1237
|
+
* Split an args array into `--flag value` pairs and the leftover positional
|
|
1238
|
+
* tokens, in ONE pass, with the semantics `check predicate` established
|
|
1239
|
+
* (#2008): a `--flag` followed by a non-`--` token consumes it as the value
|
|
1240
|
+
* (last write wins); a `--flag` with no value stays a bare token and moves to
|
|
1241
|
+
* the positionals; everything else is positional. `parsePredicateFlags` is
|
|
1242
|
+
* the flags half of this same pass — there is exactly one parser, so the
|
|
1243
|
+
* flag-taking check verbs cannot drift apart (#4130 follow-up: `check
|
|
1244
|
+
* decision-coverage-plan --context <path>` shares it).
|
|
1245
|
+
*/
|
|
1246
|
+
function partitionPredicateArgs(args) {
|
|
1247
|
+
const flags = {};
|
|
1248
|
+
const positionals = [];
|
|
1107
1249
|
for (let i = 0; i < args.length; i++) {
|
|
1108
1250
|
const a = args[i];
|
|
1109
1251
|
if (typeof a !== 'string')
|
|
1110
1252
|
continue;
|
|
1111
|
-
if (!a.startsWith('--'))
|
|
1253
|
+
if (!a.startsWith('--')) {
|
|
1254
|
+
positionals.push(a);
|
|
1112
1255
|
continue;
|
|
1256
|
+
}
|
|
1113
1257
|
const key = a.slice(2);
|
|
1114
1258
|
const next = args[i + 1];
|
|
1115
1259
|
if (key.length > 0 && typeof next === 'string' && !next.startsWith('--')) {
|
|
1116
|
-
|
|
1260
|
+
flags[key] = next;
|
|
1117
1261
|
i++;
|
|
1118
1262
|
}
|
|
1263
|
+
else {
|
|
1264
|
+
positionals.push(a);
|
|
1265
|
+
}
|
|
1119
1266
|
}
|
|
1120
|
-
return
|
|
1267
|
+
return { flags, positionals };
|
|
1268
|
+
}
|
|
1269
|
+
/** Parse `--flag value` pairs from an args array into a map (last write wins). */
|
|
1270
|
+
function parsePredicateFlags(args) {
|
|
1271
|
+
return partitionPredicateArgs(args).flags;
|
|
1121
1272
|
}
|
|
1122
1273
|
/**
|
|
1123
1274
|
* `check predicate` — generic evaluator for capability gate `check.predicate`
|
|
@@ -1152,10 +1303,15 @@ function cmdCheckPredicate(projectDir, args, raw) {
|
|
|
1152
1303
|
error('predicate --predicate value must be valid JSON', ERROR_REASON.USAGE);
|
|
1153
1304
|
return;
|
|
1154
1305
|
}
|
|
1306
|
+
const rawPhaseDir = flags['phase-dir'];
|
|
1307
|
+
let resolvedPhaseDir = rawPhaseDir;
|
|
1308
|
+
if (typeof rawPhaseDir === 'string' && rawPhaseDir !== '') {
|
|
1309
|
+
resolvedPhaseDir = resolvePath(rawPhaseDir, projectDir);
|
|
1310
|
+
}
|
|
1155
1311
|
const ctx = {
|
|
1156
1312
|
cwd: projectDir,
|
|
1157
1313
|
phaseNumber: flags['phase-number'],
|
|
1158
|
-
phaseDir:
|
|
1314
|
+
phaseDir: resolvedPhaseDir,
|
|
1159
1315
|
phaseReqIds: flags['phase-req-ids'],
|
|
1160
1316
|
};
|
|
1161
1317
|
let result;
|
|
@@ -1249,7 +1405,12 @@ function cmdApiCoverageVerifyPre(projectDir, args, raw) {
|
|
|
1249
1405
|
// Defense-in-depth: the resolved dir must be inside the phases root (or a
|
|
1250
1406
|
// milestone archive under .planning/milestones).
|
|
1251
1407
|
const milestonesRoot = node_path_1.default.join(pDir, 'milestones');
|
|
1252
|
-
|
|
1408
|
+
// Lexical containment (ADR-4650): resolvedDir is a directory path, not read
|
|
1409
|
+
// through here — mirrors the prior path.resolve(root, candidate)-based check
|
|
1410
|
+
// without introducing a filesystem/realpath dependency this defense-in-depth
|
|
1411
|
+
// recheck never had.
|
|
1412
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(resolvedDir, phasesRoot) === null &&
|
|
1413
|
+
(0, security_cjs_1.tryWithinRootLexical)(resolvedDir, milestonesRoot) === null) {
|
|
1253
1414
|
output({
|
|
1254
1415
|
block: true,
|
|
1255
1416
|
passed: false,
|
|
@@ -1578,7 +1739,9 @@ function routeCheckCommand({ args, cwd, raw }) {
|
|
|
1578
1739
|
// this for any gate whose `check` carries a `predicate` (instead of a `query`),
|
|
1579
1740
|
// passing the predicate object as --predicate '<json>'. NOTE: unlike the
|
|
1580
1741
|
// `check.query` subcommands above (which take positional phase args), this
|
|
1581
|
-
// subcommand
|
|
1742
|
+
// subcommand is flag-driven. `decision-coverage-plan` above now ALSO accepts
|
|
1743
|
+
// `--context <path>` (its positionals still work) — both share
|
|
1744
|
+
// partitionPredicateArgs, the one flag parser.
|
|
1582
1745
|
cmdCheckPredicate(cwd, args, raw);
|
|
1583
1746
|
return;
|
|
1584
1747
|
}
|
|
@@ -1606,6 +1769,7 @@ module.exports = {
|
|
|
1606
1769
|
cmdCheckPredicate,
|
|
1607
1770
|
buildPredicateDeps,
|
|
1608
1771
|
parsePredicateFlags,
|
|
1772
|
+
partitionPredicateArgs,
|
|
1609
1773
|
// Fail-closed phase-scope reader for the api-coverage gate — exported for
|
|
1610
1774
|
// in-process failure-injection tests (#2365 review).
|
|
1611
1775
|
readPhaseScope,
|
|
@@ -61,7 +61,7 @@ function normalizeRelPath(p, repoRoot) {
|
|
|
61
61
|
let relativized = false;
|
|
62
62
|
if (typeof repoRoot === 'string' && repoRoot !== '') {
|
|
63
63
|
const rootNormalized = repoRoot.trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
64
|
-
if (rootNormalized !== '' && value.startsWith(`${rootNormalized}/`)) {
|
|
64
|
+
if (rootNormalized !== '' && value.startsWith(`${rootNormalized}/`)) { // allow-handrolled-containment: display-path normalization — strips a caller-declared repoRoot prefix so a path renders repo-relative, not a security root-confinement decision
|
|
65
65
|
value = value.slice(rootNormalized.length + 1);
|
|
66
66
|
relativized = true;
|
|
67
67
|
}
|
|
@@ -86,7 +86,7 @@ function normalizeRelPath(p, repoRoot) {
|
|
|
86
86
|
* with `rulePath + '/'`. Both arguments must already be normalized. Case-sensitive.
|
|
87
87
|
*/
|
|
88
88
|
function ruleMatchesFile(rulePath, filePath) {
|
|
89
|
-
return filePath === rulePath || filePath.startsWith(`${rulePath}/`);
|
|
89
|
+
return filePath === rulePath || filePath.startsWith(`${rulePath}/`); // allow-handrolled-containment: rule-to-file segment match for selecting which review-depth rule applies — not a filesystem root-confinement gate
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Validate + normalize a single rule path (not yet matched against files).
|
|
@@ -382,9 +382,9 @@ function stripReasoningEffort(doc) {
|
|
|
382
382
|
/**
|
|
383
383
|
* The 17 roles measured as widening under derivation (declare Write/Edit,
|
|
384
384
|
* never in the pre-#3897 `CODEX_AGENT_SANDBOX` map, so the old
|
|
385
|
-
* `|| 'read-only'` fallback silently under-granted them)
|
|
386
|
-
* `read-only`
|
|
387
|
-
*
|
|
385
|
+
* `|| 'read-only'` fallback silently under-granted them) — pinned to
|
|
386
|
+
* `read-only` from 2026-09-08 (HALT.md) until #4770 lifted the hold on
|
|
387
|
+
* 2026-09-21. This list is CLOSED and
|
|
388
388
|
* SHRINK-ONLY: a new writing role never lands here (S6, T26); it is validated
|
|
389
389
|
* against the live tool contract every time it is consulted
|
|
390
390
|
* ({@link _deriveCodexSandboxModeFromTools}) and against the real
|
|
@@ -401,29 +401,25 @@ function stripReasoningEffort(doc) {
|
|
|
401
401
|
* parses the list correctly, so this role genuinely derives
|
|
402
402
|
* `workspace-write` from its tool contract — HALT.md's original 16-role
|
|
403
403
|
* count measured against the pre-fix (single-line) readers and undercounted
|
|
404
|
-
* this role.
|
|
405
|
-
*
|
|
406
|
-
*
|
|
404
|
+
* this role.
|
|
405
|
+
*
|
|
406
|
+
* **LIFTED 2026-09-21 (#4770, maintainer decision: documented enforcement
|
|
407
|
+
* suffices).** The map is empty: the rung-3 hold's recorded reopen condition
|
|
408
|
+
* — official OpenAI documentation establishing Codex `sandbox_mode` as an
|
|
409
|
+
* enforced technical boundary that custom subagent TOML files honor — is
|
|
410
|
+
* satisfied, so every role now derives `sandbox_mode` purely from its own
|
|
411
|
+
* `tools:` frontmatter (`workspace-write` iff Write/Edit is declared). The
|
|
412
|
+
* shrink-to-zero invariant (ADR-3473 §8.3) is satisfied by reaching zero;
|
|
413
|
+
* the map is kept as an empty frozen structure so a future re-hold has a
|
|
414
|
+
* shape to land in, and {@link validateCodexSandboxHolds} keeps failing if
|
|
415
|
+
* the list ever grows a role that no longer exists in `agents/`. The
|
|
416
|
+
* #3897 security-review F1/F3 fail-closed pins are unchanged and
|
|
417
|
+
* map-independent: `suspicious` identities (non-ASCII after
|
|
418
|
+
* normalization) still pin `read-only`, and post-lift the sandbox derives
|
|
419
|
+
* from an artifact's own CONTENT, so the F1 identity-confusion attack no
|
|
420
|
+
* longer has a hold to ride.
|
|
407
421
|
*/
|
|
408
|
-
exports.CODEX_SANDBOX_HOLDS = Object.freeze({
|
|
409
|
-
'gsd-ai-researcher': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
410
|
-
'gsd-code-fixer': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
411
|
-
'gsd-code-reviewer': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
412
|
-
'gsd-debug-session-manager': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
413
|
-
'gsd-doc-classifier': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
414
|
-
'gsd-doc-synthesizer': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
415
|
-
'gsd-doc-verifier': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
416
|
-
'gsd-doc-writer': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
417
|
-
'gsd-dom-verifier': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
418
|
-
'gsd-domain-researcher': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
419
|
-
'gsd-eval-auditor': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
420
|
-
'gsd-eval-planner': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
421
|
-
'gsd-intel-updater': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
422
|
-
'gsd-pattern-mapper': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
423
|
-
'gsd-ui-auditor': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
424
|
-
'gsd-ui-researcher': 'declares Write/Edit; pending Codex sandbox_mode enforcement decision',
|
|
425
|
-
'gsd-nyquist-auditor': 'declares Write/Edit (YAML list-form tools:, surfaced by the list-form parse fix); pending Codex sandbox_mode enforcement decision',
|
|
426
|
-
});
|
|
422
|
+
exports.CODEX_SANDBOX_HOLDS = Object.freeze({});
|
|
427
423
|
// True iff a `tools:` frontmatter value declares Write or Edit as a whole
|
|
428
424
|
// token (never a substring match, so a hypothetical "Edith"-named tool could
|
|
429
425
|
// never collide). Single predicate owner for both the emitter
|