@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
|
@@ -34,6 +34,52 @@ const prohibition_enforcement_cjs_1 = require("./prohibition-enforcement.cjs");
|
|
|
34
34
|
function baseOf(p) {
|
|
35
35
|
return typeof p === 'string' ? (p.split(/[\\/]/).pop() ?? p) : '';
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* #4724 — Surefire/Failsafe XML summary parsed by TAG-BOUNDARY scanning, NOT
|
|
39
|
+
* by a lazy `(.*?)<\/testcase>` regex: Surefire writes PASSING cases as
|
|
40
|
+
* self-closing `<testcase … />` elements, so a lazy span from a green case to
|
|
41
|
+
* the next closing tag swallows every case between and misreports names beside
|
|
42
|
+
* the right failure message. The scanner walks `<testcase` start tags; each
|
|
43
|
+
* case's extent is its own tag (self-closing) or the segment up to its
|
|
44
|
+
* `</testcase>` closer (testcases never nest). A `<failure` or `<error` child
|
|
45
|
+
* marks the case failing; the reported name is `classname#name`. Any parse
|
|
46
|
+
* anomaly degrades to "case not failing" — the module stays fail-closed.
|
|
47
|
+
*/
|
|
48
|
+
function parseSurefireSummary(output) {
|
|
49
|
+
let tests = 0;
|
|
50
|
+
let fail = 0;
|
|
51
|
+
const failing_tests = [];
|
|
52
|
+
let idx = output.indexOf('<testcase');
|
|
53
|
+
while (idx !== -1) {
|
|
54
|
+
const tagEnd = output.indexOf('>', idx);
|
|
55
|
+
// #4724 review (HIGH): a `<testcase` with no `>` (truncated/stream-cut
|
|
56
|
+
// output) must terminate the scan — resuming the search at -1 clamps to 0
|
|
57
|
+
// and re-finds the same tag forever. Degrade to what was scanned so far
|
|
58
|
+
// (fail-closed: an incomplete report proves nothing).
|
|
59
|
+
if (tagEnd === -1)
|
|
60
|
+
break;
|
|
61
|
+
tests++;
|
|
62
|
+
const tagText = output.slice(idx, tagEnd + 1);
|
|
63
|
+
// Self-closing (`/>`): the case has NO body — a failing sibling after it
|
|
64
|
+
// must never be attributed to this case (the #4724 spanning trap).
|
|
65
|
+
const selfClosing = output[tagEnd - 1] === '/';
|
|
66
|
+
const closeIdx = selfClosing ? -1 : output.indexOf('</testcase>', tagEnd);
|
|
67
|
+
// CDATA sections are verbatim captured output (e.g. System.out echoing
|
|
68
|
+
// test source) — their content must never be scanned for failure tags
|
|
69
|
+
// (#4724 review: CDATA phantom failures).
|
|
70
|
+
const body = (selfClosing
|
|
71
|
+
? ''
|
|
72
|
+
: output.slice(tagEnd + 1, closeIdx === -1 ? output.length : closeIdx)).replace(/<!\[CDATA\[[\s\S]*?\]\]>/g, '');
|
|
73
|
+
const name = /name="([^"]*)"/.exec(tagText)?.[1] ?? '';
|
|
74
|
+
const classname = /classname="([^"]*)"/.exec(tagText)?.[1] ?? '';
|
|
75
|
+
if (body.includes('<failure') || body.includes('<error')) {
|
|
76
|
+
fail++;
|
|
77
|
+
failing_tests.push(classname ? `${classname}#${name}` : name);
|
|
78
|
+
}
|
|
79
|
+
idx = output.indexOf('<testcase', closeIdx === -1 ? tagEnd : closeIdx);
|
|
80
|
+
}
|
|
81
|
+
return { tests, pass: tests - fail, fail, failing_tests };
|
|
82
|
+
}
|
|
37
83
|
/** Coerce and validate the raw record's scalar fields. Returns null exit_code only when absent/non-numeric. */
|
|
38
84
|
function readInput(input) {
|
|
39
85
|
const command = typeof input?.command === 'string' ? input.command : '';
|
|
@@ -69,8 +115,34 @@ function classifyRedEvidence(input) {
|
|
|
69
115
|
};
|
|
70
116
|
}
|
|
71
117
|
const { command, exitCode, output, targetTest } = parsed;
|
|
72
|
-
|
|
73
|
-
|
|
118
|
+
// #4724: format detection. Surefire/Failsafe XML (a `<testsuite>` element) is
|
|
119
|
+
// parsed by tag-boundary scanning; everything else stays on the proven TAP
|
|
120
|
+
// primitives. Unknown formats keep the TAP parse — which finds nothing — and
|
|
121
|
+
// fail closed below.
|
|
122
|
+
// #4724 review: BOTH element names must appear — a TAP error message that
|
|
123
|
+
// merely quotes "<testsuite>" (e.g. "expected <testsuite> was 2") must not
|
|
124
|
+
// flip a genuine TAP red into the XML path.
|
|
125
|
+
const isSurefireXml = output.includes('<testsuite') && output.includes('<testcase');
|
|
126
|
+
let summary;
|
|
127
|
+
let failing;
|
|
128
|
+
if (isSurefireXml) {
|
|
129
|
+
const sf = parseSurefireSummary(output);
|
|
130
|
+
summary = { tests: sf.tests, pass: sf.pass, fail: sf.fail };
|
|
131
|
+
failing = sf.failing_tests;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
summary = (0, prohibition_enforcement_cjs_1.parseNodeTestSummary)(output);
|
|
135
|
+
failing = (0, prohibition_enforcement_cjs_1.tapFailedTestNames)(output);
|
|
136
|
+
}
|
|
137
|
+
// Surefire target matching is class-level (the plan names the class); the
|
|
138
|
+
// entry's last classname segment or its test name must carry the target.
|
|
139
|
+
const surefireTargetMatches = isSurefireXml
|
|
140
|
+
? failing.some((entry) => {
|
|
141
|
+
const cls = entry.split('#')[0] ?? '';
|
|
142
|
+
const nm = entry.split('#')[1] ?? '';
|
|
143
|
+
return cls === targetTest || cls.endsWith(`.${targetTest}`) || nm === targetTest;
|
|
144
|
+
})
|
|
145
|
+
: failing.includes(targetTest);
|
|
74
146
|
const evidence = {
|
|
75
147
|
command,
|
|
76
148
|
exit_code: exitCode,
|
|
@@ -90,14 +162,15 @@ function classifyRedEvidence(input) {
|
|
|
90
162
|
if (summary.tests === 0) {
|
|
91
163
|
return { verdict: 'INVALID_RED', reason: 'zero_tests_discovered', evidence };
|
|
92
164
|
}
|
|
93
|
-
// Nonzero exit but
|
|
94
|
-
// whose failure never reached a test assertion (or unparseable output).
|
|
165
|
+
// Nonzero exit but the report shows no failing test: harness/setup/parser
|
|
166
|
+
// crash whose failure never reached a test assertion (or unparseable output).
|
|
95
167
|
if (summary.fail === 0 || failing.length === 0) {
|
|
96
168
|
return { verdict: 'INVALID_RED', reason: 'nonzero_exit_without_test_failure', evidence };
|
|
97
169
|
}
|
|
98
170
|
// Fixture/load failure: every failing entry is named like the target FILE —
|
|
99
171
|
// node reports a load-time crash (throw-on-require, syntax error, ENOENT
|
|
100
172
|
// fixture) as a file-named `not ok 1 - <file>`, never the target test.
|
|
173
|
+
// (TAP-only shape: Surefire failures carry class#method names.)
|
|
101
174
|
const targetBase = baseOf(input?.targetFile ?? '');
|
|
102
175
|
const distinctlyNamed = failing.filter((n) => (targetBase ? baseOf(n) !== targetBase : true));
|
|
103
176
|
if (distinctlyNamed.length === 0) {
|
|
@@ -105,7 +178,7 @@ function classifyRedEvidence(input) {
|
|
|
105
178
|
}
|
|
106
179
|
// Unrelated failure: real tests ran and failed, but none is the target test
|
|
107
180
|
// the plan named — an unrelated assertion must not authorize GREEN.
|
|
108
|
-
if (!distinctlyNamed.includes(targetTest)) {
|
|
181
|
+
if (!(isSurefireXml ? surefireTargetMatches : distinctlyNamed.includes(targetTest))) {
|
|
109
182
|
return { verdict: 'INVALID_RED', reason: 'no_target_test_failure', evidence };
|
|
110
183
|
}
|
|
111
184
|
return { verdict: 'RED_EVIDENCE_OK', reason: 'target_test_failed', evidence };
|
|
@@ -47,6 +47,22 @@ const BLOCKING_VERIFICATION_FM_STATUSES = new Set([
|
|
|
47
47
|
]);
|
|
48
48
|
// UAT test-item `result` values that count as passing
|
|
49
49
|
const PASSING_RESULTS = new Set(['passed', 'pass']);
|
|
50
|
+
// #4546 — a `skipped` test-item whose reason carries the verify-work writer's
|
|
51
|
+
// deferral template prefix ("Deferred follow-up: …", #1921) is a deliberately
|
|
52
|
+
// deferred follow-up: non-blocking. Quote-tolerant (the writer wraps the value
|
|
53
|
+
// in double quotes) and case-insensitive (human-edited files vary). Anything
|
|
54
|
+
// else — a reasonless skip, a non-deferral reason — still blocks. PARITY: this
|
|
55
|
+
// matcher and the writer template in gsd-core/workflows/verify-work.md
|
|
56
|
+
// (process_response) are two halves of one contract, pinned together by
|
|
57
|
+
// tests/verify-work-deferred-promotion.test.cjs.
|
|
58
|
+
const DEFERRED_REASON_RE = /^["']?deferred follow-up\b/i;
|
|
59
|
+
// Trust note (#4546 review): the reason line is user-authored state — an
|
|
60
|
+
// author could equally write `result: passed` — so this prefix is an
|
|
61
|
+
// AUTHORING contract with the verify-work writer, not a security boundary.
|
|
62
|
+
// A hand-written deferral that skips the UAT file's ## Deferred Follow-Ups
|
|
63
|
+
// section also bypasses the complete_session promotion offer; the section is
|
|
64
|
+
// the durable project-level record. Variant spellings that do not match
|
|
65
|
+
// ("Deferred follow-ups:", "followup") block — fail-closed by design.
|
|
50
66
|
// ─── stripFalsePositiveContexts ───────────────────────────────────────────────
|
|
51
67
|
/**
|
|
52
68
|
* Remove contexts that can contain `result: ...` lines that are NOT real test results:
|
|
@@ -166,16 +182,28 @@ function parseUatResultItems(cleanContent) {
|
|
|
166
182
|
// ambiguity counting, matching src/uat.cts's contract.
|
|
167
183
|
// Uses [ \t]* (not \s*) so the captured value must sit on the SAME line as result:.
|
|
168
184
|
// A result: key with the value on a subsequent line yields no match → 'missing' (blocker).
|
|
185
|
+
// #4546: the `reason:` line is captured with the same frame and FIRST-match
|
|
186
|
+
// rule — it is the deferral signal the evaluator needs (a `skipped` item
|
|
187
|
+
// whose reason is the verify-work writer's "Deferred follow-up:" template
|
|
188
|
+
// is non-blocking). Quoted values are captured with their quotes so the
|
|
189
|
+
// evaluator's matcher can tolerate them exactly as written.
|
|
169
190
|
const RESULT_LINE_RE = /^result:[ \t]*\[?([\w-]+)\]?/i;
|
|
170
191
|
const resultMatch = blockContent
|
|
171
192
|
.split('\n')
|
|
172
193
|
.map((line) => line.match(RESULT_LINE_RE))
|
|
173
194
|
.find((m) => m !== null) ?? null;
|
|
195
|
+
const REASON_LINE_RE = /^reason:[ \t]*(.*)$/i;
|
|
196
|
+
const reasonMatch = blockContent
|
|
197
|
+
.split('\n')
|
|
198
|
+
.map((line) => line.match(REASON_LINE_RE))
|
|
199
|
+
.find((m) => m !== null) ?? null;
|
|
200
|
+
const reason = reasonMatch ? reasonMatch[1].trim() : '';
|
|
174
201
|
if (resultMatch) {
|
|
175
202
|
items.push({
|
|
176
203
|
test: h.test,
|
|
177
204
|
name: h.name,
|
|
178
205
|
result: resultMatch[1].toLowerCase(),
|
|
206
|
+
reason,
|
|
179
207
|
});
|
|
180
208
|
}
|
|
181
209
|
else {
|
|
@@ -184,6 +212,7 @@ function parseUatResultItems(cleanContent) {
|
|
|
184
212
|
test: h.test,
|
|
185
213
|
name: h.name,
|
|
186
214
|
result: 'missing',
|
|
215
|
+
reason,
|
|
187
216
|
});
|
|
188
217
|
}
|
|
189
218
|
}
|
|
@@ -198,7 +227,13 @@ function parseUatResultItems(cleanContent) {
|
|
|
198
227
|
* Pass requires at least one real passing check AND no blockers.
|
|
199
228
|
*/
|
|
200
229
|
function evaluateUatPassed(phaseFullDir, opts) {
|
|
201
|
-
|
|
230
|
+
// uatOnly (#4663) takes precedence: it evaluates the UAT rows ONLY, skipping
|
|
231
|
+
// the VERIFICATION-file blockers entirely. The verify-work canonicalize
|
|
232
|
+
// pre-check needs exactly that — it runs while the report still reads
|
|
233
|
+
// `human_needed`, which is itself a blocking verification status, so the
|
|
234
|
+
// full predicate could never pass there and the flip would deadlock.
|
|
235
|
+
const uatOnly = opts?.policy?.uatOnly === true;
|
|
236
|
+
const requireVerification = !uatOnly && opts?.policy?.requireVerification === true;
|
|
202
237
|
const blockers = [];
|
|
203
238
|
const checks = [];
|
|
204
239
|
const uatFiles = [];
|
|
@@ -221,7 +256,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
221
256
|
checks: [],
|
|
222
257
|
blockers,
|
|
223
258
|
no_uat_artifacts,
|
|
224
|
-
policy: { require_verification: requireVerification },
|
|
259
|
+
policy: { require_verification: requireVerification, uat_only: uatOnly },
|
|
225
260
|
// readVerificationStatus was never reached on this early-return path.
|
|
226
261
|
verification_stale_check_indeterminate: false,
|
|
227
262
|
};
|
|
@@ -269,13 +304,20 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
269
304
|
const cleanContent = stripFalsePositiveContexts(raw);
|
|
270
305
|
const items = parseUatResultItems(cleanContent);
|
|
271
306
|
for (const item of items) {
|
|
272
|
-
|
|
307
|
+
// #4546: a `skipped` item whose reason matches the verify-work writer's
|
|
308
|
+
// "Deferred follow-up:" template is a deliberately deferred follow-up
|
|
309
|
+
// (#1921) — non-blocking. Quote-tolerant because the writer emits the
|
|
310
|
+
// reason WITH its wrapping quotes. Everything else — pending, blocked,
|
|
311
|
+
// issue, missing, and a plain or non-deferral skipped — still blocks.
|
|
312
|
+
const deferred = item.result === 'skipped' && DEFERRED_REASON_RE.test(item.reason);
|
|
313
|
+
const passing = PASSING_RESULTS.has(item.result) || deferred;
|
|
273
314
|
checks.push({
|
|
274
315
|
file,
|
|
275
316
|
test: item.test,
|
|
276
317
|
name: item.name,
|
|
277
318
|
result: item.result,
|
|
278
319
|
passing,
|
|
320
|
+
deferred,
|
|
279
321
|
});
|
|
280
322
|
if (!passing) {
|
|
281
323
|
blockers.push(`${file}: test ${item.test} (${item.result})`);
|
|
@@ -284,7 +326,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
284
326
|
}
|
|
285
327
|
// ── Process VERIFICATION files ─────────────────────────────────────────────
|
|
286
328
|
let hasPassingVerification = false;
|
|
287
|
-
for (const file of verFileNames) {
|
|
329
|
+
for (const file of uatOnly ? [] : verFileNames) {
|
|
288
330
|
verificationFiles.push(file);
|
|
289
331
|
const verificationFilePath = node_path_1.default.join(phaseFullDir, file);
|
|
290
332
|
let raw = '';
|
|
@@ -340,6 +382,7 @@ function evaluateUatPassed(phaseFullDir, opts) {
|
|
|
340
382
|
no_uat_artifacts,
|
|
341
383
|
policy: {
|
|
342
384
|
require_verification: requireVerification,
|
|
385
|
+
uat_only: uatOnly,
|
|
343
386
|
},
|
|
344
387
|
verification_stale_check_indeterminate: verificationStaleCheckIndeterminate,
|
|
345
388
|
};
|
package/gsd-core/bin/lib/uat.cjs
CHANGED
|
@@ -322,7 +322,7 @@ function cmdRenderCheckpoint(cwd, options = {}, raw) {
|
|
|
322
322
|
if (!filePath) {
|
|
323
323
|
error('UAT file required: use uat render-checkpoint --file <path>');
|
|
324
324
|
}
|
|
325
|
-
const resolvedPath = (0, security_cjs_1.requireSafePath)(filePath, cwd, 'UAT file',
|
|
325
|
+
const resolvedPath = (0, security_cjs_1.requireSafePath)(filePath, cwd, 'UAT file', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
326
326
|
if (!node_fs_1.default.existsSync(resolvedPath)) {
|
|
327
327
|
error(`UAT file not found: ${filePath}`);
|
|
328
328
|
}
|
|
@@ -3579,6 +3579,14 @@ function categorizeItem(rawResult, reason, blockedBy) {
|
|
|
3579
3579
|
return 'blocked';
|
|
3580
3580
|
}
|
|
3581
3581
|
if (result === 'skipped') {
|
|
3582
|
+
// #4546 — a deliberately deferred follow-up (the verify-work writer's
|
|
3583
|
+
// "Deferred follow-up:" template, #1921) is its own category, checked
|
|
3584
|
+
// BEFORE the keyword families so e.g. "…on the release build next
|
|
3585
|
+
// version" is not misfiled as build_needed. Must agree with
|
|
3586
|
+
// uat-predicate.cts's DEFERRED_REASON_RE (gate/audit agreement,
|
|
3587
|
+
// #3078-CR), pinned by tests/uat-predicate.test.cjs.
|
|
3588
|
+
if (reason && /^["']?deferred follow-up\b/i.test(reason))
|
|
3589
|
+
return 'deferred';
|
|
3582
3590
|
if (reason) {
|
|
3583
3591
|
if (/server|not running|not available/i.test(reason))
|
|
3584
3592
|
return 'server_blocked';
|
|
@@ -117,6 +117,15 @@ function validateRequirement(element) {
|
|
|
117
117
|
if (r.elements == null && !(typeof r.text === 'string' && r.text.trim())) {
|
|
118
118
|
throw new Error(`element ${element.id} text must be a non-empty string when no elements override is provided`);
|
|
119
119
|
}
|
|
120
|
+
// text_en (#4657) is optional, but when present it must be a non-empty string. An empty
|
|
121
|
+
// string is NOT caught by `??` (only null/undefined are), so an unvalidated `text_en: ''`
|
|
122
|
+
// would silently win `text_en ?? text` and classify against '' — the same fail-open shape
|
|
123
|
+
// #1110/#2773 already exist to eliminate, just moved one field over. Validated
|
|
124
|
+
// unconditionally (not gated on whether `elements` will make it unused) so bad data fails
|
|
125
|
+
// closed even when it happens to be dead for this particular call.
|
|
126
|
+
if (r.text_en != null && !(typeof r.text_en === 'string' && r.text_en.trim())) {
|
|
127
|
+
throw new Error(`element ${element.id} text_en must be a non-empty string when present`);
|
|
128
|
+
}
|
|
120
129
|
}
|
|
121
130
|
/** Validate a UI-consideration resolution against the UI verification vocabulary (delegated, D-06). */
|
|
122
131
|
function validateResolution(resolution) {
|
|
@@ -143,7 +152,11 @@ function proposeConsiderations(element) {
|
|
|
143
152
|
kinds = element.elements;
|
|
144
153
|
}
|
|
145
154
|
else {
|
|
146
|
-
|
|
155
|
+
// #4657: prefer the English translation when present — UI_CUES are English-only
|
|
156
|
+
// word-boundary patterns, so a non-English `text` (e.g. response_language projects)
|
|
157
|
+
// would otherwise classify to zero kinds. validateRequirement (called above) has
|
|
158
|
+
// already guaranteed text_en, if present, is a non-empty string.
|
|
159
|
+
kinds = classifyElement((element.text_en ?? element.text));
|
|
147
160
|
if (kinds.length === 0) {
|
|
148
161
|
// Prose present but no element cue matched. Do NOT silently drop it (#1110): a UI element
|
|
149
162
|
// whose phrasing missed every cue would otherwise vanish from coverage with no signal — the
|
|
@@ -210,7 +223,7 @@ function proposeElements(elements) {
|
|
|
210
223
|
const considerations = proposeConsiderations(el);
|
|
211
224
|
const kinds = Array.isArray(el.elements)
|
|
212
225
|
? el.elements // already validated inside proposeConsiderations
|
|
213
|
-
: classifyElement(el.text);
|
|
226
|
+
: classifyElement((el.text_en ?? el.text)); // #4657: text_en ?? text
|
|
214
227
|
const unclassified = !Array.isArray(el.elements) && kinds.length === 0;
|
|
215
228
|
const categories = unclassified ? [] : applicableCategories(kinds);
|
|
216
229
|
return { id: el.id, kinds, categories, considerations, unclassified };
|
|
@@ -23,6 +23,19 @@
|
|
|
23
23
|
* html, ...) are deliberately NOT static evidence: docs sites and
|
|
24
24
|
* markdown/bash/config repos routinely carry stray `.html`/`.css`, which
|
|
25
25
|
* is precisely the false-positive class #3312 reports.
|
|
26
|
+
* (c) any `*.xaml` file, or a `*.swift` / `*.kt` / `*.dart` file whose
|
|
27
|
+
* content carries its ecosystem's UI-framework import marker
|
|
28
|
+
* (`import SwiftUI` / `import UIKit`, `androidx.compose`,
|
|
29
|
+
* `package:flutter`) (#4658). Native UI projects (SwiftUI, Jetpack
|
|
30
|
+
* Compose, Flutter, .NET MAUI) carry neither (a) nor (b), which made the
|
|
31
|
+
* gate structurally unreachable for them. Source files match on the
|
|
32
|
+
* IMPORT, not the extension alone — the same unambiguity bar that
|
|
33
|
+
* justifies (b)'s subset and excludes (css, scss, html): a non-UI Swift
|
|
34
|
+
* package (a CLI, a server) imports Foundation, not SwiftUI, and must
|
|
35
|
+
* stay silent. `.xaml` is extension-alone for the same reason `.tsx` is —
|
|
36
|
+
* the extension itself is unambiguous. Marker matching is case-sensitive
|
|
37
|
+
* (imports are case-sensitive in all four ecosystems); extension
|
|
38
|
+
* matching is case-insensitive, mirroring `UI_COMPONENT_FILE_RE`.
|
|
26
39
|
*
|
|
27
40
|
* All I/O failures degrade to `false` (no evidence) — never throw.
|
|
28
41
|
*/
|
|
@@ -30,12 +43,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
30
43
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
44
|
};
|
|
32
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.UI_COMPONENT_FILE_RE = void 0;
|
|
46
|
+
exports.NATIVE_UI_CONTENT_MARKERS = exports.NATIVE_UI_XAML_RE = exports.NATIVE_UI_SOURCE_RE = exports.UI_COMPONENT_FILE_RE = void 0;
|
|
34
47
|
exports.hasStaticFrontendEvidence = hasStaticFrontendEvidence;
|
|
35
48
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
36
49
|
const node_path_1 = __importDefault(require("node:path"));
|
|
37
50
|
/** Component-framework file extensions — the static-evidence subset of UI_FILE_EXTENSIONS_RE. */
|
|
38
51
|
exports.UI_COMPONENT_FILE_RE = /\.(tsx|jsx|vue|svelte)$/i;
|
|
52
|
+
/** Native UI source files whose CONTENT is scanned for an import marker (#4658). */
|
|
53
|
+
exports.NATIVE_UI_SOURCE_RE = /\.(swift|kt|dart)$/i;
|
|
54
|
+
/** Native UI files that are evidence by extension alone — `.xaml`, the `.tsx` analogue. */
|
|
55
|
+
exports.NATIVE_UI_XAML_RE = /\.xaml$/i;
|
|
56
|
+
/**
|
|
57
|
+
* Per-extension UI-framework import markers for `NATIVE_UI_SOURCE_RE` files
|
|
58
|
+
* (#4658). Every marker embeds its ecosystem's import keyword, so a bare
|
|
59
|
+
* framework-name mention in prose or a comment is not evidence; the Dart
|
|
60
|
+
* marker carries both legal quote styles. Case-sensitive: imports are
|
|
61
|
+
* case-sensitive in Swift, Kotlin and Dart.
|
|
62
|
+
*/
|
|
63
|
+
exports.NATIVE_UI_CONTENT_MARKERS = {
|
|
64
|
+
'.swift': ['import SwiftUI', 'import UIKit'],
|
|
65
|
+
'.kt': ['import androidx.compose'],
|
|
66
|
+
'.dart': ["import 'package:flutter", 'import "package:flutter'],
|
|
67
|
+
};
|
|
39
68
|
/**
|
|
40
69
|
* UI-framework package.json dependencies (dependencies OR devDependencies).
|
|
41
70
|
* Component frameworks/renderers only — deliberately excludes meta tooling
|
|
@@ -111,9 +140,16 @@ function packageJsonHasUiFramework(projectDir) {
|
|
|
111
140
|
}
|
|
112
141
|
return false;
|
|
113
142
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Shared bounded BFS over the project tree — the single home of the walk
|
|
145
|
+
* semantics both evidence walks depend on: SKIP_DIRS pruning, the
|
|
146
|
+
* MAX_WALK_ENTRIES entry cap (cap-hit → `false`: the tree is treated as
|
|
147
|
+
* scanned and evidence stays undecided), symlinks never followed
|
|
148
|
+
* (withFileTypes Dirents), unreadable directories skipped. `visit` is called
|
|
149
|
+
* for every regular file with its name and full path; returning `true` stops
|
|
150
|
+
* the walk with `true` (evidence found).
|
|
151
|
+
*/
|
|
152
|
+
function walkProjectFiles(projectDir, visit) {
|
|
117
153
|
const queue = [projectDir];
|
|
118
154
|
let visited = 0;
|
|
119
155
|
while (queue.length > 0 && visited < MAX_WALK_ENTRIES) {
|
|
@@ -133,25 +169,80 @@ function treeHasComponentFile(projectDir) {
|
|
|
133
169
|
if (!SKIP_DIRS.has(entry.name))
|
|
134
170
|
queue.push(node_path_1.default.join(dir, entry.name));
|
|
135
171
|
}
|
|
136
|
-
else if (entry.isFile() &&
|
|
172
|
+
else if (entry.isFile() && visit(entry.name, node_path_1.default.join(dir, entry.name))) {
|
|
137
173
|
return true;
|
|
138
174
|
}
|
|
139
175
|
}
|
|
140
176
|
}
|
|
141
177
|
return false;
|
|
142
178
|
}
|
|
179
|
+
function treeHasComponentFile(projectDir) {
|
|
180
|
+
return walkProjectFiles(projectDir, (name) => exports.UI_COMPONENT_FILE_RE.test(name));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Read at most the first 64 KiB of `file` and report whether any of `markers`
|
|
184
|
+
* occurs in it (#4658). Import sections live at the top of a source file, so a
|
|
185
|
+
* bounded prefix read keeps the gate's plan-time cost profile without reading
|
|
186
|
+
* generated monsters in full. Any I/O failure degrades to false — never throw.
|
|
187
|
+
*/
|
|
188
|
+
function fileHasAnyMarker(file, markers) {
|
|
189
|
+
let fd;
|
|
190
|
+
try {
|
|
191
|
+
fd = node_fs_1.default.openSync(file, 'r');
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const bytes = Buffer.alloc(64 * 1024);
|
|
198
|
+
const read = node_fs_1.default.readSync(fd, bytes, 0, bytes.length, 0);
|
|
199
|
+
const prefix = bytes.toString('utf8', 0, read);
|
|
200
|
+
return markers.some((m) => prefix.includes(m));
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
try {
|
|
207
|
+
node_fs_1.default.closeSync(fd);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// already closed — nothing to degrade
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Native-UI walk over the shared bounded BFS (#4658): `.xaml` is evidence by
|
|
216
|
+
* extension alone; `.swift`/`.kt`/`.dart` are evidence only when the file's
|
|
217
|
+
* content carries its ecosystem's import marker.
|
|
218
|
+
*/
|
|
219
|
+
function treeHasNativeUiFile(projectDir) {
|
|
220
|
+
return walkProjectFiles(projectDir, (name, fullPath) => {
|
|
221
|
+
if (exports.NATIVE_UI_XAML_RE.test(name))
|
|
222
|
+
return true;
|
|
223
|
+
if (exports.NATIVE_UI_SOURCE_RE.test(name)) {
|
|
224
|
+
const markers = exports.NATIVE_UI_CONTENT_MARKERS[node_path_1.default.extname(name).toLowerCase()];
|
|
225
|
+
return markers != null && fileHasAnyMarker(fullPath, markers);
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
143
230
|
/**
|
|
144
231
|
* Does the project tree carry static evidence of a frontend?
|
|
145
232
|
*
|
|
146
233
|
* @param projectDir - Absolute path to the project root (the gate's cwd).
|
|
147
|
-
* @returns true when package.json declares a UI-framework dependency
|
|
148
|
-
*
|
|
149
|
-
*
|
|
234
|
+
* @returns true when package.json declares a UI-framework dependency, the tree
|
|
235
|
+
* contains a component-framework file, or the tree contains native UI
|
|
236
|
+
* evidence (a `.xaml` file, or a `.swift`/`.kt`/`.dart` file carrying
|
|
237
|
+
* its ecosystem's UI import marker — #4658); false otherwise
|
|
238
|
+
* (including on any I/O failure — evidence must be affirmative).
|
|
150
239
|
*/
|
|
151
240
|
function hasStaticFrontendEvidence(projectDir) {
|
|
152
241
|
if (typeof projectDir !== 'string' || projectDir === '')
|
|
153
242
|
return false;
|
|
154
243
|
if (packageJsonHasUiFramework(projectDir))
|
|
155
244
|
return true;
|
|
156
|
-
|
|
245
|
+
if (treeHasComponentFile(projectDir))
|
|
246
|
+
return true;
|
|
247
|
+
return treeHasNativeUiFile(projectDir);
|
|
157
248
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Undo Commit Selection — issue #4661 (absorbed into epic #4906, Phase 5).
|
|
4
|
+
*
|
|
5
|
+
* Pure scope-matching logic for `/gsd:undo --phase N` / `--plan N-M`, replacing
|
|
6
|
+
* the two hand-rolled `git log --oneline | grep -E ...` pipelines that used to
|
|
7
|
+
* live in `gsd-core/workflows/undo.md`. Those greps were unanchored substring
|
|
8
|
+
* matches against a live regex built from unvalidated user input — four
|
|
9
|
+
* documented bug classes (see #4661):
|
|
10
|
+
*
|
|
11
|
+
* 1. The id was interpolated raw into an ERE: `.` and `+` are live
|
|
12
|
+
* metacharacters there, so a malformed or merely differently-shaped id
|
|
13
|
+
* silently matched the WRONG phase.
|
|
14
|
+
* 2. The grep was unanchored, so a commit that only MENTIONS a scope
|
|
15
|
+
* (`docs(99-01): explain feat(03-01): commit convention`) was wrongly
|
|
16
|
+
* selected as a DECLARATION of it.
|
|
17
|
+
* 3. Phase-mode's `\):` suffix and plan-mode's missing suffix disagreed on
|
|
18
|
+
* the same commit (`feat(03-01)!: breaking change`).
|
|
19
|
+
* 4. The "obvious" fix — anchoring `git log --oneline`'s record start —
|
|
20
|
+
* is a trap: it silently drops `fixup! ...` / `Revert "..."` wrapper
|
|
21
|
+
* commits (a worse failure, a silent partial revert) and returns zero
|
|
22
|
+
* matches under `color.ui=always` (ANSI codes precede the sha).
|
|
23
|
+
*
|
|
24
|
+
* This module closes all four by parsing each commit subject through the
|
|
25
|
+
* SAME anchored conventional-commit grammar the changelog/PR-title gate uses
|
|
26
|
+
* (`HEADER_RE`, reused verbatim from `scripts/release-notes/conventional-title.cjs`
|
|
27
|
+
* — see the require() below), then comparing the extracted scope to the
|
|
28
|
+
* caller-supplied target id by exact STRING EQUALITY. No regex is built from
|
|
29
|
+
* user input at any point past `validatePhaseNumber` (src/security.cts),
|
|
30
|
+
* which the CLI caller (`gsd-tools.cjs query select-revert-commits`) runs
|
|
31
|
+
* BEFORE this module ever sees the id.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately no `fs`/`child_process` in this file: it operates purely on
|
|
34
|
+
* `{sha, subject}` records the caller already fetched (e.g. via `git log
|
|
35
|
+
* --format=%H%x00%s`), so it is unit-testable without touching git or the
|
|
36
|
+
* filesystem.
|
|
37
|
+
*
|
|
38
|
+
* KNOWN LIMIT (documented, not a defect): `unwrapSubject` unwraps ONE level
|
|
39
|
+
* of a `fixup! `/`squash! `/`Revert "..."` wrapper. A doubly-wrapped subject
|
|
40
|
+
* (e.g. `Revert "fixup! feat(03-01): work"`) is treated as unparseable (no
|
|
41
|
+
* declared scope) after that single unwrap — it is NOT selected, never
|
|
42
|
+
* mis-selected. This bounds the module to the wrapper shapes git itself
|
|
43
|
+
* produces (`fixup!`/`squash!` from `--fixup`/`--squash`, `Revert "..."` from
|
|
44
|
+
* `git revert`), each of which wraps a single ordinary commit subject, not
|
|
45
|
+
* another wrapper.
|
|
46
|
+
*/
|
|
47
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
48
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49
|
+
};
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.selectCommitsByDeclaredScope = selectCommitsByDeclaredScope;
|
|
52
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
53
|
+
// Reuse the exact HEADER_RE the changelog classifier and PR-title CI gate
|
|
54
|
+
// already depend on (#1549) — never fork a second copy of this grammar. A
|
|
55
|
+
// bare `require()` (not a TS `import ... = require(...)`) is used
|
|
56
|
+
// deliberately: `scripts/` sits outside this build's `rootDir` ("src", see
|
|
57
|
+
// tsconfig.build.json) and carries no `.d.ts`, so a type-checked import-equals
|
|
58
|
+
// would fail module resolution (TS7016) under `strict`/`noImplicitAny`. A
|
|
59
|
+
// plain `require()` resolves through Node's ambient `NodeRequire` type
|
|
60
|
+
// (`(id: string) => any`) instead, sidestepping that check entirely — the
|
|
61
|
+
// cast below is the only place the resulting shape is asserted.
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
63
|
+
const conventionalTitle = require(node_path_1.default.resolve(__dirname, '..', '..', '..', 'scripts', 'release-notes', 'conventional-title.cjs'));
|
|
64
|
+
const { HEADER_RE } = conventionalTitle;
|
|
65
|
+
// ─── Subject unwrapping (Known limit: exactly ONE level) ──────────────────
|
|
66
|
+
const FIXUP_PREFIX = 'fixup! ';
|
|
67
|
+
const SQUASH_PREFIX = 'squash! ';
|
|
68
|
+
const REVERT_RE = /^Revert "(.*)"$/;
|
|
69
|
+
/**
|
|
70
|
+
* Strip exactly one `fixup! `/`squash! `/`Revert "..."` wrapper layer off a
|
|
71
|
+
* commit subject, returning the (possibly) unwrapped remainder. Never
|
|
72
|
+
* recurses — a doubly-wrapped subject is returned with only its outermost
|
|
73
|
+
* layer removed, which is intentionally still unparseable by `HEADER_RE` (see
|
|
74
|
+
* module doc comment).
|
|
75
|
+
*/
|
|
76
|
+
function unwrapSubject(subject) {
|
|
77
|
+
if (subject.startsWith(FIXUP_PREFIX))
|
|
78
|
+
return subject.slice(FIXUP_PREFIX.length);
|
|
79
|
+
if (subject.startsWith(SQUASH_PREFIX))
|
|
80
|
+
return subject.slice(SQUASH_PREFIX.length);
|
|
81
|
+
const revertMatch = REVERT_RE.exec(subject);
|
|
82
|
+
if (revertMatch)
|
|
83
|
+
return revertMatch[1];
|
|
84
|
+
return subject;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parse a (possibly wrapped) commit subject into its declared conventional-
|
|
88
|
+
* commit scope, or `null` when the subject carries no parseable scope: no
|
|
89
|
+
* clean `type[(scope)][!]:` header at all, or a header with no `(scope)`
|
|
90
|
+
* group. Never regex-matches against untrusted `targetId` — this function
|
|
91
|
+
* only ever consumes the commit's OWN subject text.
|
|
92
|
+
*/
|
|
93
|
+
function parseDeclaredScope(subject) {
|
|
94
|
+
const unwrapped = unwrapSubject(subject);
|
|
95
|
+
const m = HEADER_RE.exec(unwrapped);
|
|
96
|
+
if (!m)
|
|
97
|
+
return null;
|
|
98
|
+
const scopeWithParens = m[2]; // e.g. "(03-01)" — includes parens, or undefined
|
|
99
|
+
if (!scopeWithParens)
|
|
100
|
+
return null;
|
|
101
|
+
// HEADER_RE's scope group is `\([^)]*\)` — always at least the two paren
|
|
102
|
+
// characters when present, so slicing them off is safe unconditionally.
|
|
103
|
+
return scopeWithParens.slice(1, -1);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Select commits whose DECLARED conventional-commit scope identifies
|
|
107
|
+
* `targetId`, by exact string equality (`'plan'` mode) or exact-or-phase-
|
|
108
|
+
* prefixed equality (`'phase'` mode). `targetId` is assumed ALREADY
|
|
109
|
+
* VALIDATED/NORMALIZED by the caller (e.g. via `validatePhaseNumber` from
|
|
110
|
+
* `src/security.cts`) — this function performs no validation of its own, and
|
|
111
|
+
* never builds a regex from it (string equality / `startsWith` only), so a
|
|
112
|
+
* malformed or metacharacter-laden `targetId` cannot cause a wildcard-style
|
|
113
|
+
* over-match the way the retired grep pipelines could.
|
|
114
|
+
*/
|
|
115
|
+
function selectCommitsByDeclaredScope(commits, targetId, mode = 'plan') {
|
|
116
|
+
const phasePrefix = `${targetId}-`;
|
|
117
|
+
const classified = commits.map(({ sha, subject }) => {
|
|
118
|
+
const parsedScope = parseDeclaredScope(subject);
|
|
119
|
+
let matched = false;
|
|
120
|
+
if (parsedScope !== null) {
|
|
121
|
+
matched = mode === 'phase'
|
|
122
|
+
? parsedScope === targetId || parsedScope.startsWith(phasePrefix)
|
|
123
|
+
: parsedScope === targetId;
|
|
124
|
+
}
|
|
125
|
+
return { sha, subject, parsedScope, matched };
|
|
126
|
+
});
|
|
127
|
+
const selected = classified
|
|
128
|
+
.filter((c) => c.matched)
|
|
129
|
+
.map(({ sha, subject }) => ({ sha, subject }));
|
|
130
|
+
return { selected, classified };
|
|
131
|
+
}
|