@opengsd/gsd-core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- 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 +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -53,6 +53,35 @@ const TOKEN_DRIFT_RE = /(?:\\{1,2}d|\[0-9\])\+\[A-Z(?:a-z)?\]\??\(\?:(?:\\{1,2}\
|
|
|
53
53
|
const OWNER_RE = /^\s*\/\/.*phase-id-owner:/;
|
|
54
54
|
const CANON_REF = 'PHASE_NUMBER_TOKEN_SOURCE';
|
|
55
55
|
|
|
56
|
+
// #2761 M3 (trek-e review): the SECOND grammar this seam owns — the BRACKET
|
|
57
|
+
// project-code class of `[CODE.MM]`, spelled `[A-Z][A-Z0-9_]*` (with its
|
|
58
|
+
// case-widened `[A-Za-z]`/`[A-Za-z0-9_]` variant tolerated so a trivial rewrite
|
|
59
|
+
// does not evade the rule). The token guard above only ever knew the phase-
|
|
60
|
+
// NUMBER grammar, so three files re-typed this class verbatim — roadmap-parser's
|
|
61
|
+
// bracket-fallback selector, state's `isMilestoneBounded`, verify's
|
|
62
|
+
// `checkBracketCoherence` — and `check:phase-id-drift` reported clean the whole
|
|
63
|
+
// time. That is the blind spot which let #2761's own "no token literal outside
|
|
64
|
+
// src/phase-id.cts" gate pass while being violated. Build from
|
|
65
|
+
// `BRACKET_PROJECT_CODE_SRC`, `BRACKET_ID_SRC`, `bracketMilestoneIntroSrcFor`
|
|
66
|
+
// or `BRACKET_MILESTONE_INTRO_CAPTURING_SRC` instead.
|
|
67
|
+
const BRACKET_CODE_DRIFT_RE = /\[A-Z(?:a-z)?\]\[A-Z(?:a-z)?0-9_\]\*/;
|
|
68
|
+
|
|
69
|
+
// This rule has NO counterpart to the token rule's `line.includes(CANON_REF)`
|
|
70
|
+
// escape, and that omission is the point.
|
|
71
|
+
//
|
|
72
|
+
// That escape is LINE-level: a line naming the canonical source anywhere on it
|
|
73
|
+
// is taken as built-from-the-owner. verify.cts's copy read
|
|
74
|
+
//
|
|
75
|
+
// new RegExp(`^\\[[A-Z][A-Z0-9_]*\\.(${BRACKET_MILESTONE_NUMERIC_SRC})\\]`, 'i')
|
|
76
|
+
//
|
|
77
|
+
// — an owner reference for the MILESTONE field sharing a line with a re-typed
|
|
78
|
+
// PROJECT-CODE class. A line-level escape waves that through, so a bracket rule
|
|
79
|
+
// that copied it would have kept reporting clean on the very site under review.
|
|
80
|
+
// Partial ownership IS the drift. Only a `// phase-id-owner:` sanction
|
|
81
|
+
// suppresses this rule, and a sanction has to state which half is deliberate.
|
|
82
|
+
const BRACKET_OWNER_HINT =
|
|
83
|
+
'BRACKET_PROJECT_CODE_SRC / BRACKET_ID_SRC / bracketMilestoneIntroSrcFor / BRACKET_MILESTONE_INTRO_CAPTURING_SRC';
|
|
84
|
+
|
|
56
85
|
/**
|
|
57
86
|
* Pure: find every literal re-derivation of the canonical phase-number token in
|
|
58
87
|
* `text` that is NOT sanctioned. A site is sanctioned when the nearest preceding
|
|
@@ -79,6 +108,28 @@ function findPhaseIdRegexDrift(text) {
|
|
|
79
108
|
return out;
|
|
80
109
|
}
|
|
81
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Pure: find every literal re-derivation of the BRACKET project-code grammar in
|
|
113
|
+
* `text` that is NOT sanctioned. Same sanction mechanism as the token rule — a
|
|
114
|
+
* dedicated `// phase-id-owner:` comment on the nearest preceding non-blank
|
|
115
|
+
* line — but deliberately WITHOUT its line-level owner-reference escape, so a
|
|
116
|
+
* site that references the owner for one field while re-typing the other is
|
|
117
|
+
* still reported (see BRACKET_CODE_DRIFT_RE's note). Returns [{ line, found }].
|
|
118
|
+
*/
|
|
119
|
+
function findBracketGrammarDrift(text) {
|
|
120
|
+
const out = [];
|
|
121
|
+
const lines = text.split('\n');
|
|
122
|
+
for (let i = 0; i < lines.length; i++) {
|
|
123
|
+
const m = BRACKET_CODE_DRIFT_RE.exec(lines[i]);
|
|
124
|
+
if (!m) continue;
|
|
125
|
+
let j = i - 1;
|
|
126
|
+
while (j >= 0 && lines[j].trim() === '') j--; // nearest preceding non-blank line
|
|
127
|
+
if (j >= 0 && OWNER_RE.test(lines[j])) continue;
|
|
128
|
+
out.push({ line: i + 1, found: m[0] });
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
82
133
|
// Authored TypeScript source only (the generated bin/lib/*.cjs mirror it).
|
|
83
134
|
const SCAN_DIRS = ['src'];
|
|
84
135
|
const SCAN_EXT = new Set(['.cts', '.ts', '.mts']);
|
|
@@ -104,6 +155,67 @@ function walk(dir, acc) {
|
|
|
104
155
|
return acc;
|
|
105
156
|
}
|
|
106
157
|
|
|
158
|
+
// ─── #2761 M4: the heading-baseline selector census ────────────────────────
|
|
159
|
+
//
|
|
160
|
+
// `phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.<MODE>)` is the other half
|
|
161
|
+
// of this seam: it decides which intro grammar a call site compiles, and the
|
|
162
|
+
// MODE argument is a fact about that site's history that no behavioural test
|
|
163
|
+
// can recover — flipping verify's milestone-complete site from LABEL_ONLY to
|
|
164
|
+
// ANY_BRACKET grants a tolerance it has never had, and every behavioural test
|
|
165
|
+
// still passes. Pinning it therefore requires reading the authored source.
|
|
166
|
+
//
|
|
167
|
+
// That reading lives HERE, not in the test suite. `tests/**` runs
|
|
168
|
+
// `local/no-source-grep` at ERROR, and its documented exemption
|
|
169
|
+
// (CONTEXT.md: RULESET.TESTS.no-source-grep.exemption) is reserved for tests
|
|
170
|
+
// whose subject is a runtime CONTRACT FILE — STATE.md, config.toml,
|
|
171
|
+
// hooks.json, agent .md — which `src/*.cts` is not. The suite had claimed that
|
|
172
|
+
// exemption anyway. Scripts are the sanctioned home for source scanning (the
|
|
173
|
+
// rule runs at `warn` in `scripts/**`, and this file already scans src/ for the
|
|
174
|
+
// grammar rules above), so the scan is exported as structured data and the test
|
|
175
|
+
// asserts on the returned census instead of on file text.
|
|
176
|
+
const SELECTOR_CALL_RE = /phaseHeadingPrefixSrcFor\(/g;
|
|
177
|
+
const SELECTOR_BASELINE_RE = /phaseHeadingPrefixSrcFor\(\s*PHASE_HEADING_BASELINE\.(ANY_BRACKET|LABEL_ONLY)/g;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Pure: census the heading-baseline selector calls in `text`.
|
|
181
|
+
*
|
|
182
|
+
* `total` counts EVERY invocation, so a call that does not name a
|
|
183
|
+
* `PHASE_HEADING_BASELINE` member shows up as `total > ANY_BRACKET +
|
|
184
|
+
* LABEL_ONLY` — a hole in the pin rather than a silently uncounted site.
|
|
185
|
+
* Returns { ANY_BRACKET, LABEL_ONLY, total }.
|
|
186
|
+
*/
|
|
187
|
+
function countSelectorBaselines(text) {
|
|
188
|
+
const out = { ANY_BRACKET: 0, LABEL_ONLY: 0, total: 0 };
|
|
189
|
+
for (const m of text.matchAll(SELECTOR_BASELINE_RE)) out[m[1]] += 1;
|
|
190
|
+
out.total = (text.match(SELECTOR_CALL_RE) || []).length;
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Scan the authored source tree and return the selector census keyed by
|
|
196
|
+
* repo-relative path, for every file that consumes the selector at least once.
|
|
197
|
+
* `phase-id.cts` is excluded: it DEFINES the selector, so its own occurrences
|
|
198
|
+
* are the declaration, not a consumer's choice of baseline.
|
|
199
|
+
*/
|
|
200
|
+
function scanSelectorBaselines(root) {
|
|
201
|
+
const census = {};
|
|
202
|
+
for (const dir of SCAN_DIRS) {
|
|
203
|
+
for (const file of walk(path.join(root, dir), [])) {
|
|
204
|
+
const rel = path.relative(root, file);
|
|
205
|
+
if (EXEMPT.has(rel)) continue;
|
|
206
|
+
let text;
|
|
207
|
+
try {
|
|
208
|
+
text = fs.readFileSync(file, 'utf8');
|
|
209
|
+
} catch {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const counts = countSelectorBaselines(text);
|
|
213
|
+
if (counts.total > 0) census[path.basename(file)] = counts;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return census;
|
|
217
|
+
}
|
|
218
|
+
|
|
107
219
|
/**
|
|
108
220
|
* Scan the authored source tree and return every unsanctioned phase-token
|
|
109
221
|
* re-derivation, each annotated with the repo-relative file path.
|
|
@@ -121,7 +233,11 @@ function scanRepo(root) {
|
|
|
121
233
|
continue;
|
|
122
234
|
}
|
|
123
235
|
for (const d of findPhaseIdRegexDrift(text)) {
|
|
124
|
-
violations.push({ file: rel, ...d });
|
|
236
|
+
violations.push({ file: rel, kind: 'token', ...d });
|
|
237
|
+
}
|
|
238
|
+
// #2761 M3: the bracket grammar is the second thing this seam owns.
|
|
239
|
+
for (const d of findBracketGrammarDrift(text)) {
|
|
240
|
+
violations.push({ file: rel, kind: 'bracket', ...d });
|
|
125
241
|
}
|
|
126
242
|
}
|
|
127
243
|
}
|
|
@@ -132,19 +248,28 @@ function main() {
|
|
|
132
248
|
const root = path.join(__dirname, '..');
|
|
133
249
|
const violations = scanRepo(root);
|
|
134
250
|
if (violations.length === 0) {
|
|
135
|
-
process.stdout.write('ok phase-id-drift: no unsanctioned phase-token re-derivations outside phase-id.cts\n');
|
|
251
|
+
process.stdout.write('ok phase-id-drift: no unsanctioned phase-token or bracket-grammar re-derivations outside phase-id.cts\n');
|
|
136
252
|
return;
|
|
137
253
|
}
|
|
138
|
-
process.stderr.write('phase-id-drift: literal re-derivation(s) of
|
|
139
|
-
process.stderr.write(
|
|
140
|
-
process.stderr.write(
|
|
141
|
-
process.stderr.write('
|
|
254
|
+
process.stderr.write('phase-id-drift: literal re-derivation(s) of a canonical grammar found.\n');
|
|
255
|
+
process.stderr.write(`Build the regex from phase-id.cjs \`${CANON_REF}\` (or phaseMarkdownRegexSource for a\n`);
|
|
256
|
+
process.stderr.write(`known number) for the phase-number token, or from ${BRACKET_OWNER_HINT}\n`);
|
|
257
|
+
process.stderr.write('for the bracket grammar — or sanction the site with a dedicated\n');
|
|
258
|
+
process.stderr.write('`// phase-id-owner: <reason>` comment on the line directly above the regex:\n');
|
|
142
259
|
for (const d of violations) {
|
|
143
|
-
process.stderr.write(` ${d.file}:${d.line} ${d.found}\n`);
|
|
260
|
+
process.stderr.write(` [${d.kind}] ${d.file}:${d.line} ${d.found}\n`);
|
|
144
261
|
}
|
|
145
262
|
process.exitCode = 1;
|
|
146
263
|
}
|
|
147
264
|
|
|
148
265
|
if (require.main === module) main();
|
|
149
266
|
|
|
150
|
-
module.exports = {
|
|
267
|
+
module.exports = {
|
|
268
|
+
findPhaseIdRegexDrift,
|
|
269
|
+
findBracketGrammarDrift,
|
|
270
|
+
scanRepo,
|
|
271
|
+
countSelectorBaselines,
|
|
272
|
+
scanSelectorBaselines,
|
|
273
|
+
TOKEN_DRIFT_RE,
|
|
274
|
+
BRACKET_CODE_DRIFT_RE,
|
|
275
|
+
};
|
|
@@ -370,8 +370,45 @@ function writeBaseline(root, violations) {
|
|
|
370
370
|
return entries;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Resolve the scan root for a CLI run. Default: this repo — exactly what
|
|
375
|
+
* `lint:ci` invokes. `--root <dir>` overrides it (#3640) so the CLI
|
|
376
|
+
* end-to-end test can prove the guard's FAIL path against an isolated temp
|
|
377
|
+
* tree instead of writing its fixture into the shared `gsd-core/workflows/`
|
|
378
|
+
* that parallel test chunks observe mid-lifecycle (the emitted-provenance
|
|
379
|
+
* manifest build copies the transient file into all 19 runtime manifests;
|
|
380
|
+
* the #3333 TOCTOU ENOENT crash was the same writer's first symptom).
|
|
381
|
+
*
|
|
382
|
+
* Usage errors exit 2 — a code distinct from the guard's own findings exit
|
|
383
|
+
* (1), so tests and CI can tell "bad invocation" from "drift found" without
|
|
384
|
+
* parsing stderr. Fail-closed on every bad shape, because a wrong root is
|
|
385
|
+
* not harmless: `--root --update` would otherwise resolve the flag itself
|
|
386
|
+
* into a cwd-relative path and --update would happily `mkdir -p` a stray
|
|
387
|
+
* baseline tree inside the shared source directory (#3640's write class).
|
|
388
|
+
* A value that starts with `-` (another flag) or does not name an existing
|
|
389
|
+
* directory is therefore a usage error, never a scan root. With repeated
|
|
390
|
+
* `--root` flags the first wins (same first-match convention as the boolean
|
|
391
|
+
* `--update` parse).
|
|
392
|
+
*/
|
|
393
|
+
function resolveScanRoot() {
|
|
394
|
+
const rootIdx = process.argv.indexOf('--root');
|
|
395
|
+
if (rootIdx === -1) return path.join(__dirname, '..');
|
|
396
|
+
const value = process.argv[rootIdx + 1];
|
|
397
|
+
const isDir = value !== undefined
|
|
398
|
+
&& !value.startsWith('-')
|
|
399
|
+
&& fs.existsSync(value)
|
|
400
|
+
&& fs.statSync(value).isDirectory();
|
|
401
|
+
if (!isDir) {
|
|
402
|
+
process.stderr.write('planning-prompt-drift: --root requires an existing directory argument (usage: --root <dir>)\n');
|
|
403
|
+
process.exitCode = 2;
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return path.resolve(value);
|
|
407
|
+
}
|
|
408
|
+
|
|
373
409
|
function main() {
|
|
374
|
-
const root =
|
|
410
|
+
const root = resolveScanRoot();
|
|
411
|
+
if (root === null) return;
|
|
375
412
|
const update = process.argv.includes('--update');
|
|
376
413
|
const violations = scanRepo(root);
|
|
377
414
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* lint-portable-grep.cjs — ban GNU-only `grep -P`/`--perl-regexp` in gsd
|
|
6
|
+
* workflow / agent / reference / command markdown (#4112 macOS regression).
|
|
7
|
+
*
|
|
8
|
+
* ## Why
|
|
9
|
+
*
|
|
10
|
+
* `-P`/`--perl-regexp` selects GNU grep's PCRE engine. Stock macOS ships BSD
|
|
11
|
+
* grep, which does not implement `-P` at all — the call errors out ("grep:
|
|
12
|
+
* invalid option -- P" or "unknown option") on that host, and a pipeline like
|
|
13
|
+
* `... | grep -oP '...' || true` swallows that error and silently resolves to
|
|
14
|
+
* an empty string instead of failing loudly.
|
|
15
|
+
*
|
|
16
|
+
* This is exactly what happened in `gsd-core/workflows/pause-work.md`'s
|
|
17
|
+
* Context Detection step (#4112, merged as #4140): the fix for the `$((`
|
|
18
|
+
* shell-syntax bug left `grep -oP 'phases/\K[^/]+'` in place. That construct
|
|
19
|
+
* had never been exercised on macOS before (the syntax error masked it), so
|
|
20
|
+
* fixing the syntax bug newly exposed a second, pre-existing macOS-only
|
|
21
|
+
* defect — phase/spike/sketch detection silently resolving to "" on macOS —
|
|
22
|
+
* which the PR's own new regression test then caught, but only in the
|
|
23
|
+
* post-merge `full test (macos-latest, ...)` matrix. That lane runs on push
|
|
24
|
+
* to the base branch, not on the pull_request event, so nothing pre-merge
|
|
25
|
+
* (gsd-test's Linux-only benches included) could have caught it before `next`
|
|
26
|
+
* went red.
|
|
27
|
+
*
|
|
28
|
+
* This is the same class of defect `lint-portable-timeout.cjs` (#2351) exists
|
|
29
|
+
* for: a GNU-coreutils assumption baked into workflow markdown that every
|
|
30
|
+
* pre-merge gate is blind to because none of them run on macOS. The fix here
|
|
31
|
+
* is the same shape — a static ratchet that catches the assumption in the
|
|
32
|
+
* markdown itself, before a shell ever executes it on the platform that lacks
|
|
33
|
+
* the flag.
|
|
34
|
+
*
|
|
35
|
+
* ## What PASSES
|
|
36
|
+
*
|
|
37
|
+
* - `grep -o '...'`, `grep -E '...'`, `egrep`/`fgrep` without `-P`.
|
|
38
|
+
* - `-P` appearing as part of an unrelated long option (`--path`, `--perl`) —
|
|
39
|
+
* only a `-P`/short-cluster-containing-`P` token or the literal
|
|
40
|
+
* `--perl-regexp` counts.
|
|
41
|
+
* - Prose that merely mentions "grep -P" outside of a fenced command context
|
|
42
|
+
* is still flagged (this lint does not parse fences) — the roots below are
|
|
43
|
+
* restricted to directive markdown where a `grep -P` mention is always
|
|
44
|
+
* either a real invocation or a specimen to fix, never neutral prose.
|
|
45
|
+
*
|
|
46
|
+
* ## What FAILS
|
|
47
|
+
*
|
|
48
|
+
* A `grep`/`egrep`/`fgrep` invocation whose flag cluster includes `P`
|
|
49
|
+
* (`-P`, `-oP`, `-Po`, `-iP`, ...) or the long form `--perl-regexp`.
|
|
50
|
+
*
|
|
51
|
+
* ## Portable replacements
|
|
52
|
+
*
|
|
53
|
+
* - Extracting a path component: `basename "$(dirname "$path")")` instead of
|
|
54
|
+
* `grep -oP 'seam/\K[^/]+'`.
|
|
55
|
+
* - Extracting a token after a flag: `sed -E 's#.*--flag[[:space:]]+([^[:space:]]+).*#\1#'`
|
|
56
|
+
* instead of `grep -oP '(?<=--flag )\S+'` — POSIX ERE (`-E`) has no
|
|
57
|
+
* lookbehind, so capture the group and let `sed` emit just it. `-E` (not
|
|
58
|
+
* GNU-only `-r`) is supported by both BSD and GNU `sed`. (A `/`-delimited
|
|
59
|
+
* sed command ending in a star-quantifier would place a star immediately
|
|
60
|
+
* before a slash, closing this very JSDoc block comment early — hence the
|
|
61
|
+
* `#`-delimited form used above.)
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
const fs = require('fs');
|
|
65
|
+
const path = require('path');
|
|
66
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
67
|
+
|
|
68
|
+
const ROOT = path.join(__dirname, '..');
|
|
69
|
+
|
|
70
|
+
// Surfaces whose markdown carries agent-executed bash. Mirrors
|
|
71
|
+
// lint-portable-timeout.cjs's roots — the same files that can ship shell
|
|
72
|
+
// snippets which a bash/zsh host on macOS actually executes.
|
|
73
|
+
const DEFAULT_ROOTS = ['gsd-core/workflows', 'gsd-core/references', 'agents', 'commands'];
|
|
74
|
+
|
|
75
|
+
// A `grep`/`egrep`/`fgrep` token, anchored to a command position (line start,
|
|
76
|
+
// right after `| & ; ( \` {`, or right after a shell keyword that opens a new
|
|
77
|
+
// command — `then`/`do`/`else`/`elif`) so prose mentioning "grep -P" in
|
|
78
|
+
// running text is still caught in these directive-markdown roots (see file
|
|
79
|
+
// header) without also matching an unrelated word ending in "grep". The
|
|
80
|
+
// keyword alternative is `\b`-bounded on both sides, so a bare mention of the
|
|
81
|
+
// word "then"/"do"/etc. with no following grep never matches on its own.
|
|
82
|
+
const GREP_INVOCATION_RE = /(?:^|[|&;(`{]|\b(?:then|do|else|elif)\b)[ \t]*(?:e|f)?grep\b/g;
|
|
83
|
+
|
|
84
|
+
// A `-P` short-flag cluster (e.g. `-P`, `-oP`, `-Po`, `-iP`) or the long form
|
|
85
|
+
// `--perl-regexp`, as a standalone token.
|
|
86
|
+
const PERL_FLAG_RE = /(?:^|\s)-[a-zA-Z]*P[a-zA-Z]*(?=\s|$)|--perl-regexp\b/;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Locate `grep -P`/`--perl-regexp` invocations in a block of text.
|
|
90
|
+
*
|
|
91
|
+
* Pure (no I/O): callers pass file contents; the caller reads files. For each
|
|
92
|
+
* `grep`-family invocation on a line, inspects only the segment from that
|
|
93
|
+
* invocation to the next `|`/`;`/end-of-line, so a `-P`-bearing token in a
|
|
94
|
+
* later, unrelated command on the same line is never mistaken for a grep flag.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} text file contents
|
|
97
|
+
* @returns {{ line: number, snippet: string }[]} findings (empty array = clean)
|
|
98
|
+
*/
|
|
99
|
+
function findPerlGrepInvocations(text) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
const lines = String(text).split(/\r?\n/);
|
|
102
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
103
|
+
const line = lines[i];
|
|
104
|
+
GREP_INVOCATION_RE.lastIndex = 0;
|
|
105
|
+
let match;
|
|
106
|
+
while ((match = GREP_INVOCATION_RE.exec(line)) !== null) {
|
|
107
|
+
const startAt = match.index + match[0].length;
|
|
108
|
+
const rest = line.slice(startAt);
|
|
109
|
+
const nextPipe = rest.search(/[|;`]/);
|
|
110
|
+
const segment = nextPipe === -1 ? rest : rest.slice(0, nextPipe);
|
|
111
|
+
if (PERL_FLAG_RE.test(segment)) {
|
|
112
|
+
findings.push({ line: i + 1, snippet: line.trim() });
|
|
113
|
+
break; // one finding per line is enough context to fix it
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function walkMarkdown(dir) {
|
|
121
|
+
const out = [];
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
125
|
+
} catch {
|
|
126
|
+
return out; // a missing root is not an error — some surfaces are optional
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
const full = path.join(dir, entry.name);
|
|
130
|
+
if (entry.isDirectory()) out.push(...walkMarkdown(full));
|
|
131
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) out.push(full);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Scan the given roots (repo-relative) for `grep -P`/`--perl-regexp` invocations.
|
|
138
|
+
* @param {string[]} roots
|
|
139
|
+
* @returns {{ file: string, line: number, snippet: string }[]}
|
|
140
|
+
*/
|
|
141
|
+
function scan(roots = DEFAULT_ROOTS) {
|
|
142
|
+
const offenders = [];
|
|
143
|
+
for (const rel of roots) {
|
|
144
|
+
const abs = path.isAbsolute(rel) ? rel : path.join(ROOT, rel);
|
|
145
|
+
for (const file of walkMarkdown(abs)) {
|
|
146
|
+
const findings = findPerlGrepInvocations(fs.readFileSync(file, 'utf8'));
|
|
147
|
+
for (const f of findings) {
|
|
148
|
+
offenders.push({ file: path.relative(ROOT, file), line: f.line, snippet: f.snippet });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return offenders;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function main() {
|
|
156
|
+
const rootsEnv = process.env.GSD_LINT_PORTABLE_GREP_ROOTS;
|
|
157
|
+
const roots = rootsEnv ? rootsEnv.split(path.delimiter).filter(Boolean) : DEFAULT_ROOTS;
|
|
158
|
+
const offenders = scan(roots);
|
|
159
|
+
if (offenders.length > 0) {
|
|
160
|
+
const detail = offenders.map((o) => ` ${o.file}:${o.line} ${o.snippet}`).join('\n');
|
|
161
|
+
throw new ExitError(
|
|
162
|
+
1,
|
|
163
|
+
'lint-portable-grep: `grep -P`/`--perl-regexp` is not portable — stock macOS ships\n' +
|
|
164
|
+
'BSD grep, which has no `-P`, so the call errors and a `|| true`/`$(...)` fallback\n' +
|
|
165
|
+
'silently resolves to empty output instead of failing loudly (#4112). Use\n' +
|
|
166
|
+
'`basename "$(dirname "$path")")` for path-component extraction, or\n' +
|
|
167
|
+
'`sed -E \'s/.../\\1/\'` (POSIX ERE, no lookbehind needed) for token extraction:\n' +
|
|
168
|
+
detail,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
console.log(`ok lint-portable-grep: no GNU-only grep -P invocations in ${roots.length} root(s)`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = { findPerlGrepInvocations, scan, DEFAULT_ROOTS };
|
|
175
|
+
|
|
176
|
+
if (require.main === module) runMain(main);
|