@opengsd/gsd-core 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lint-response-language-coverage.cjs
|
|
4
|
+
*
|
|
5
|
+
* Enforces #2529: every workflow must be covered by the response-language
|
|
6
|
+
* contract, so a workflow can never ship English-only when the user has
|
|
7
|
+
* configured `response_language`.
|
|
8
|
+
*
|
|
9
|
+
* A workflow file passes when it contains EITHER:
|
|
10
|
+
* - a reference to the shared directive
|
|
11
|
+
* (`references/response-language-directive.md`), OR
|
|
12
|
+
* - its own inline `response_language` directive (the ~half of the catalog
|
|
13
|
+
* that already carried one before #2529, plus workflow-specific extracts
|
|
14
|
+
* like `references/execute-phase-response-language.md`).
|
|
15
|
+
*
|
|
16
|
+
* A bare config-field mention is not coverage: the same line must direct how
|
|
17
|
+
* user-facing output is rendered, or the workflow must load a known directive.
|
|
18
|
+
*
|
|
19
|
+
* Nor is a directive that names only the question/answer surface. #2529's stated
|
|
20
|
+
* defect is that inter-tool NARRATION stays in English while the answers around
|
|
21
|
+
* it are translated, so a line saying "all user-facing questions, prompts, and
|
|
22
|
+
* explanations" describes the gap rather than closing it. Coverage therefore
|
|
23
|
+
* requires a narration-class token as well — see `NARRATION_CLASS_RE`.
|
|
24
|
+
*
|
|
25
|
+
* A workflow FRAGMENT (`<workflow>/<modes|steps|templates|detail>/<name>.md`, the
|
|
26
|
+
* shape the #1671 fragment epic extracts — `detail/` is the fourth such
|
|
27
|
+
* subdirectory kind, added by the #4403/ADR-4139 spine+detail split) additionally
|
|
28
|
+
* passes when its parent workflow
|
|
29
|
+
* names that exact fragment path and is itself covered — see
|
|
30
|
+
* `inheritsParentCoverage`, which proves the inheritance per file instead of
|
|
31
|
+
* granting it to a directory.
|
|
32
|
+
*
|
|
33
|
+
* Exit 0 only when workflows were actually found AND every one of them is
|
|
34
|
+
* covered; exit 1 with a per-file listing if not. "No violations" alone is not
|
|
35
|
+
* a pass: a run that inspected nothing has established nothing.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
'use strict';
|
|
39
|
+
|
|
40
|
+
const fs = require('fs');
|
|
41
|
+
const path = require('path');
|
|
42
|
+
|
|
43
|
+
const ROOT = path.join(__dirname, '..');
|
|
44
|
+
const WORKFLOWS_DIR = path.join(ROOT, 'gsd-core', 'workflows');
|
|
45
|
+
// A reference resolves as a sibling of the workflow catalog, so a fixture tree
|
|
46
|
+
// and the real one resolve by one rule.
|
|
47
|
+
const REFERENCE_ROOT = path.join(ROOT, 'gsd-core');
|
|
48
|
+
const DIRECTIVE_REFS = [
|
|
49
|
+
'references/response-language-directive.md',
|
|
50
|
+
'references/execute-phase-response-language.md',
|
|
51
|
+
];
|
|
52
|
+
// Names the narration class explicitly for the same reason the inline directives
|
|
53
|
+
// in the workflow bodies do (see NARRATION_CLASS_RE): "user-facing prose" alone
|
|
54
|
+
// reads, in practice, as the question/answer surface, which is the half of the
|
|
55
|
+
// output #2529 was never about.
|
|
56
|
+
const INLINE_RESPONSE_LANGUAGE_DIRECTIVE =
|
|
57
|
+
'Apply response_language to all user-facing prose — narration between tool calls, status updates, progress notes, and findings included; preserve code, paths, and identifiers.';
|
|
58
|
+
// THE RULE THAT DECIDES THIS SET (#2529, restated in review round 29).
|
|
59
|
+
// A lazy-loaded mode/step/template carries its own directive only when it cannot
|
|
60
|
+
// PROVE inheritance -- no parent dispatches it from a read/execute context, or the
|
|
61
|
+
// parent is itself uncovered. Where inheritance is proven, the parent's directive is
|
|
62
|
+
// already in the loaded context by the time the fragment is read, so a second copy
|
|
63
|
+
// buys no coverage and adds a sentence that can drift. Pinning the exact wording is
|
|
64
|
+
// what makes the first case safe: these files cannot take the eager @-reference (an
|
|
65
|
+
// @-line inside a later Read is inert), so the sentence lives inline, and a partial
|
|
66
|
+
// typo or rewording would otherwise split the contract silently.
|
|
67
|
+
// enforces the rule in both directions:
|
|
68
|
+
// no member of this set may be one that would inherit anyway.
|
|
69
|
+
const EXACT_INLINE_DIRECTIVE_WORKFLOWS = new Set([
|
|
70
|
+
'discuss-phase/modes/advisor.md',
|
|
71
|
+
'discuss-phase/modes/all.md',
|
|
72
|
+
'discuss-phase/modes/analyze.md',
|
|
73
|
+
'discuss-phase/modes/auto.md',
|
|
74
|
+
'discuss-phase/modes/batch.md',
|
|
75
|
+
'discuss-phase/modes/chain.md',
|
|
76
|
+
'discuss-phase/modes/default.md',
|
|
77
|
+
'discuss-phase/modes/power.md',
|
|
78
|
+
'discuss-phase/modes/text.md',
|
|
79
|
+
'discuss-phase/templates/context.md',
|
|
80
|
+
'discuss-phase/templates/discussion-log.md',
|
|
81
|
+
'execute-phase/steps/codebase-drift-gate.md',
|
|
82
|
+
'execute-phase/steps/regression-gate-run.md',
|
|
83
|
+
'execute-phase/steps/worktree-recovery-policy.md',
|
|
84
|
+
'help/modes/brief.md',
|
|
85
|
+
'help/modes/default.md',
|
|
86
|
+
'help/modes/full.md',
|
|
87
|
+
'help/modes/topic.md',
|
|
88
|
+
'plan-phase/steps/prd-express-path.md',
|
|
89
|
+
'quick-batch/steps/plan-checker-loop.md',
|
|
90
|
+
'settings-advanced.md',
|
|
91
|
+
]);
|
|
92
|
+
// This lint once carried a third coverage form, for `verify-phase.md`: a
|
|
93
|
+
// workflow file nothing entered directly, covered instead by the directive
|
|
94
|
+
// execute-phase.md injects into its `gsd-verifier` dispatch prompt. `next`
|
|
95
|
+
// deleted that workflow in #3421 (an orphan that shipped ~40 KB to every runtime
|
|
96
|
+
// and was never loaded) and migrated its live gates into the verifier, so the
|
|
97
|
+
// form has no subject left and is gone with it.
|
|
98
|
+
//
|
|
99
|
+
// The dispatch contract itself is NOT gone — the verifier subagent still runs and
|
|
100
|
+
// still emits user-facing prose, so `references/execute-phase-response-language.md`
|
|
101
|
+
// still tells the orchestrator to carry the directive into that prompt. It is
|
|
102
|
+
// simply no longer a statement about any file in this catalog, which is all this
|
|
103
|
+
// lint reads. `tests/response-language-coverage.test.cjs` pins both ends of it
|
|
104
|
+
// (the reference's directive text and the `Create VERIFICATION.md.` anchor it
|
|
105
|
+
// positions against in execute-phase.md) directly against the real tree.
|
|
106
|
+
// Fragment directories produced by the workflow-fragment epic (#1671). A file
|
|
107
|
+
// under one of these is a SECTION of its parent workflow, never an entry point:
|
|
108
|
+
// it is reached by a `read and execute gsd-core/workflows/<path>` stub, which
|
|
109
|
+
// fires with the parent — and therefore the parent's response-language
|
|
110
|
+
// directive — already loaded.
|
|
111
|
+
//
|
|
112
|
+
// The stub is USUALLY in the top-level parent, which is the only case
|
|
113
|
+
// `inheritsParentCoverage` can prove, and it deliberately proves no more. Four
|
|
114
|
+
// fragments arrive by a different route today, and none of them relies on this
|
|
115
|
+
// function: `execute-phase/steps/regression-gate-run.md`,
|
|
116
|
+
// `plan-phase/steps/prd-express-path.md` and
|
|
117
|
+
// `quick-batch/steps/plan-checker-loop.md` are dispatched by a SIBLING fragment
|
|
118
|
+
// (`regression-gate.md`, `prd-express-gate.md`, `planner-wave.md`), and
|
|
119
|
+
// `help/modes/topic.md` is routed from a table in `help.md` that names the path
|
|
120
|
+
// without a dispatch verb at all. All four carry the pinned inline directive and
|
|
121
|
+
// are listed in `EXACT_INLINE_DIRECTIVE_WORKFLOWS`, so `findViolations` settles
|
|
122
|
+
// them before inheritance is ever consulted.
|
|
123
|
+
//
|
|
124
|
+
// Left as one hop on purpose. Walking the sibling chain would let a fragment
|
|
125
|
+
// inherit through a file this lint has not proven reachable, trading a loud
|
|
126
|
+
// failure for a quiet assumption; as it stands, extracting a fragment-of-a-
|
|
127
|
+
// fragment without pinning it turns the lint RED, which is the correct answer
|
|
128
|
+
// and names the file to fix.
|
|
129
|
+
// `detail` (ADR-4139 §6 Decision 6 / #4403) is the fourth fragment-directory kind,
|
|
130
|
+
// alongside modes/steps/templates from #1671: a spine's `detail/<part>.md` is
|
|
131
|
+
// reached the same way -- a `read and execute` stub in the top-level parent -- so
|
|
132
|
+
// it inherits coverage through the exact same mechanism, not a parallel one.
|
|
133
|
+
const FRAGMENT_DIRS = new Set(['modes', 'steps', 'templates', 'detail']);
|
|
134
|
+
const DIRECTIVE_ACTION_RE = /\b(?:apply|present|render|respond|translate|use|write|must|should)\b/i;
|
|
135
|
+
const USER_OUTPUT_RE = /\b(?:explanations?|language|narration|outputs?|prompts?|prose|questions?|templates?|user-facing)\b/i;
|
|
136
|
+
// The defect #2529 reports is NARRATION, not the question/answer surface: a
|
|
137
|
+
// directive worded as "all user-facing questions, prompts, and explanations" is
|
|
138
|
+
// read as covering what the user is asked and told at a turn boundary, and the
|
|
139
|
+
// running commentary the model emits between tool calls stays in English beside
|
|
140
|
+
// it. A line that names only that surface therefore certifies the exact gap the
|
|
141
|
+
// issue exists to close, so coverage additionally requires a narration-class
|
|
142
|
+
// token — narration, status, progress, findings, or output "between tool calls".
|
|
143
|
+
// Round 21 tightening: the accepted forms are the two that NAME the class — the
|
|
144
|
+
// word "narration", or output described as running "between tool calls". The
|
|
145
|
+
// earlier list also accepted a bare "status", "progress" or "findings", which
|
|
146
|
+
// are words that merely APPEAR in the canonical phrasing: a line reading "Use
|
|
147
|
+
// response_language for all user-facing output; report status." passed while
|
|
148
|
+
// naming nothing about inter-tool narration, i.e. weaker than REQ-LANG-04
|
|
149
|
+
// requires. Verified before tightening: all 79 catalog files that passed under
|
|
150
|
+
// the old list still pass under this one, so no workflow needed rewording — the
|
|
151
|
+
// dropped tokens were reachable only by directives nobody ships.
|
|
152
|
+
// Still deliberately not a phrase match: pinning one sentence would push authors
|
|
153
|
+
// toward copying wording instead of stating the rule (`tests/response-language-coverage.test.cjs`
|
|
154
|
+
// pins the old weak wording as a FAILING case so this cannot silently loosen).
|
|
155
|
+
const NARRATION_CLASS_RE = /\bnarration\b|\bbetween tool calls\b/i;
|
|
156
|
+
// The catalog's extensions, matched case-insensitively and by whole extension.
|
|
157
|
+
// `endsWith('.md')` failed in the one direction this lint cannot afford: it is
|
|
158
|
+
// silent UNDER-enforcement, the same vacuous pass `main()` refuses when
|
|
159
|
+
// discovery returns nothing. `SETTINGS.MD` is the same file to Windows and
|
|
160
|
+
// macOS and a different one to Linux, so a case-sensitive test exempts it on
|
|
161
|
+
// the only platform whose verdict gates the merge — a workflow certified by
|
|
162
|
+
// having gone unseen.
|
|
163
|
+
// `.mdx` stays out deliberately. Admitting an extension states what a workflow
|
|
164
|
+
// IS, and that claim has a second half: `inheritsParentCoverage` resolves a
|
|
165
|
+
// fragment's parent as `<workflow>.md`. If the repo ever emits an `.mdx`
|
|
166
|
+
// workflow, the entry belongs here next to the parent resolution it must move
|
|
167
|
+
// with, not ahead of it.
|
|
168
|
+
const WORKFLOW_EXTENSIONS = new Set(['.md']);
|
|
169
|
+
|
|
170
|
+
function isWorkflowFile(name) {
|
|
171
|
+
return WORKFLOW_EXTENSIONS.has(path.extname(name).toLowerCase());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Walk the catalog, FOLLOWING symlinked subtrees.
|
|
176
|
+
*
|
|
177
|
+
* `Dirent` uses lstat semantics, so a symlinked directory answers false to both
|
|
178
|
+
* `isDirectory()` and `isFile()`. Branching on those alone skipped a symlinked
|
|
179
|
+
* subtree in silence while `files.length > 0` kept the run green — the exact
|
|
180
|
+
* "reported OK while coverage regressed" outcome `main()` claims to make
|
|
181
|
+
* impossible. Each symlink is resolved with `statSync` and followed.
|
|
182
|
+
*
|
|
183
|
+
* Cycles are bounded by the `seen` set of resolved real paths: a link pointing
|
|
184
|
+
* at an ancestor is entered once and not re-entered. A broken link resolves to
|
|
185
|
+
* nothing and is skipped — it contributes no file to inspect, and no coverage
|
|
186
|
+
* claim rides on it.
|
|
187
|
+
*/
|
|
188
|
+
function findMarkdownFilesRecursive(dir, seen = new Set()) {
|
|
189
|
+
const files = [];
|
|
190
|
+
let real;
|
|
191
|
+
try { real = fs.realpathSync(dir); } catch { real = dir; }
|
|
192
|
+
if (seen.has(real)) return files;
|
|
193
|
+
seen.add(real);
|
|
194
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
195
|
+
const full = path.join(dir, entry.name);
|
|
196
|
+
let isDirectory = entry.isDirectory();
|
|
197
|
+
let isFile = entry.isFile();
|
|
198
|
+
if (entry.isSymbolicLink()) {
|
|
199
|
+
let stat;
|
|
200
|
+
try { stat = fs.statSync(full); } catch { continue; }
|
|
201
|
+
isDirectory = stat.isDirectory();
|
|
202
|
+
isFile = stat.isFile();
|
|
203
|
+
}
|
|
204
|
+
if (isDirectory) files.push(...findMarkdownFilesRecursive(full, seen));
|
|
205
|
+
else if (isFile && isWorkflowFile(entry.name)) files.push(full);
|
|
206
|
+
}
|
|
207
|
+
return files.sort();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Does the file LOAD a shared directive, rather than merely name one?
|
|
212
|
+
*
|
|
213
|
+
* The distinction is the same one `namesFragmentAsEntryPoint` draws a level
|
|
214
|
+
* down, and it matters for the same reason: only a real `@`-import puts the
|
|
215
|
+
* directive in context. A changelog line ("#2529: added the shared
|
|
216
|
+
* response-language-directive.md reference"), a deprecation note, or any prose
|
|
217
|
+
* naming the path proves nothing about what loads — but a bare substring test
|
|
218
|
+
* cannot tell those apart, so such a workflow would be certified covered while
|
|
219
|
+
* shipping the very defect #2529 exists to close.
|
|
220
|
+
*
|
|
221
|
+
* The catalog emits the import exactly one way: an `@`-path occupying the whole
|
|
222
|
+
* line — line 1 for the 43 top-level workflows, line 96 for `execute-phase.md`.
|
|
223
|
+
* Both `@~/` and `@$HOME/` spellings are accepted because the repo writes both
|
|
224
|
+
* (see `scripts/strip-prose-atrefs.cjs`).
|
|
225
|
+
*
|
|
226
|
+
* LIMITATION, stated rather than papered over: like the fragment check, this
|
|
227
|
+
* reads Markdown as text. An import inside a fenced block quoted as an example
|
|
228
|
+
* still counts, and one whose line carries a trailing `<!-- ... -->` no longer
|
|
229
|
+
* does. Neither shape occurs in the catalog today, and both would be visible in
|
|
230
|
+
* review as a quoted or edited import.
|
|
231
|
+
*/
|
|
232
|
+
const DIRECTIVE_IMPORT_RES = DIRECTIVE_REFS.map(
|
|
233
|
+
(ref) => new RegExp(`^@(?:~|\\$HOME)/\\S*${ref.replaceAll('.', '\\.')}$`),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
function importsDirectiveReference(content) {
|
|
237
|
+
return importedDirectiveReference(content) !== null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Which known reference does this file import, if any? */
|
|
241
|
+
function importedDirectiveReference(content) {
|
|
242
|
+
const lines = content.split(/\r?\n/).map((line) => line.trim());
|
|
243
|
+
for (const [index, ref] of DIRECTIVE_REFS.entries()) {
|
|
244
|
+
if (lines.some((line) => DIRECTIVE_IMPORT_RES[index].test(line))) return ref;
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The four-predicate test for an actionable directive on a single line.
|
|
251
|
+
*
|
|
252
|
+
* LIMITATION (text, not AST): the four hits are independent, so the predicate
|
|
253
|
+
* reads vocabulary and not polarity. A line that negates the instruction —
|
|
254
|
+
* "Do not translate narration; apply response_language to code only." — satisfies
|
|
255
|
+
* all four and reads as coverage. Contrived rather than plausible: the wording is
|
|
256
|
+
* pinned byte-for-byte in the files that cannot take the reference, and the shared
|
|
257
|
+
* reference is validated against this same predicate, so a negated line would have
|
|
258
|
+
* to be authored deliberately in one of the 44 files that carry their own sentence.
|
|
259
|
+
* Named here because a predicate that cannot see "not" should say so.
|
|
260
|
+
*/
|
|
261
|
+
function carriesInlineDirective(content) {
|
|
262
|
+
return content.split(/\r?\n/).some((line) =>
|
|
263
|
+
/\bresponse_language\b/i.test(line) &&
|
|
264
|
+
DIRECTIVE_ACTION_RE.test(line) &&
|
|
265
|
+
USER_OUTPUT_RE.test(line) &&
|
|
266
|
+
NARRATION_CLASS_RE.test(line)
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Does the SHARED FILE a workflow imports itself carry an actionable directive?
|
|
272
|
+
*
|
|
273
|
+
* Checking only that the `@`-import LINE exists is a hole the size of the
|
|
274
|
+
* bucket: 43 workflows take their entire coverage from one file, and nothing in
|
|
275
|
+
* the repo pinned that file's text. Rewriting it back to the pre-#2529 wording —
|
|
276
|
+
* the sentence this PR's own round-10 finding calls the DEFECT — left the lint
|
|
277
|
+
* green and every workflow "covered". That is the same "the gate certifies the
|
|
278
|
+
* defect" failure the round-10 fix closed, one level up.
|
|
279
|
+
*
|
|
280
|
+
* So an import is coverage only when the referenced file passes the very
|
|
281
|
+
* predicate an inline directive must pass. A missing, unreadable, or weakened
|
|
282
|
+
* reference covers nobody, and `main()` reports it as one systemic failure
|
|
283
|
+
* rather than as 43 identical per-file violations.
|
|
284
|
+
*/
|
|
285
|
+
// Keyed by identity AND version, not by path alone. A path-only key is correct
|
|
286
|
+
// for a one-shot CLI and for the tests, where every fixture gets a fresh mkdtemp
|
|
287
|
+
// root — but it makes the verdict for a path permanent within the process, so a
|
|
288
|
+
// caller that rewrites a reference and re-asks gets the stale answer. Stamping
|
|
289
|
+
// size and mtime into the key costs one stat and removes the class rather than
|
|
290
|
+
// documenting it.
|
|
291
|
+
const referenceDirectiveCache = new Map();
|
|
292
|
+
function referenceCarriesDirective(ref, refRoot) {
|
|
293
|
+
const file = path.join(refRoot, ref);
|
|
294
|
+
let stamp = 'missing';
|
|
295
|
+
try {
|
|
296
|
+
const stat = fs.statSync(file);
|
|
297
|
+
stamp = `${stat.size}:${stat.mtimeMs}`;
|
|
298
|
+
} catch { /* keep the missing stamp; the read below decides the verdict */ }
|
|
299
|
+
const key = [refRoot, ref, stamp].join('\u0000');
|
|
300
|
+
if (!referenceDirectiveCache.has(key)) {
|
|
301
|
+
let ok = false;
|
|
302
|
+
try { ok = carriesInlineDirective(fs.readFileSync(file, 'utf8')); }
|
|
303
|
+
catch { ok = false; }
|
|
304
|
+
referenceDirectiveCache.set(key, ok);
|
|
305
|
+
}
|
|
306
|
+
return referenceDirectiveCache.get(key);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Of the references these files actually import, which are missing, unreadable,
|
|
311
|
+
* or no longer actionable?
|
|
312
|
+
*
|
|
313
|
+
* Scoped to imported references on purpose: a reference nobody imports cannot
|
|
314
|
+
* uncover anybody, so failing on it would red a run over a file it never
|
|
315
|
+
* consulted. Every reference that IS imported is load-bearing for every workflow
|
|
316
|
+
* that imports it.
|
|
317
|
+
*/
|
|
318
|
+
function findBrokenDirectiveReferences(files, refRoot = REFERENCE_ROOT) {
|
|
319
|
+
const imported = new Set();
|
|
320
|
+
for (const file of files) {
|
|
321
|
+
let content;
|
|
322
|
+
try { content = fs.readFileSync(file, 'utf8'); } catch { continue; }
|
|
323
|
+
const ref = importedDirectiveReference(content);
|
|
324
|
+
if (ref !== null) imported.add(ref);
|
|
325
|
+
}
|
|
326
|
+
return [...imported].filter((ref) => !referenceCarriesDirective(ref, refRoot)).sort();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function hasResponseLanguageCoverage(content, refRoot = REFERENCE_ROOT) {
|
|
330
|
+
const ref = importedDirectiveReference(content);
|
|
331
|
+
if (ref !== null) return referenceCarriesDirective(ref, refRoot);
|
|
332
|
+
|
|
333
|
+
return carriesInlineDirective(content);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* How far a read/execute verb may sit from the fragment path it governs on the
|
|
338
|
+
* same line. Sized from the widest shape the catalog actually emits —
|
|
339
|
+
* ``read and execute `gsd-core/workflows/...` `` (14 characters between verb and
|
|
340
|
+
* path) — with room for a variant, and deliberately far short of a sentence, so
|
|
341
|
+
* a verb belonging to a different clause cannot reach across and vouch for a
|
|
342
|
+
* path it never dispatches.
|
|
343
|
+
*/
|
|
344
|
+
const ENTRY_POINT_VERB_WINDOW = 40;
|
|
345
|
+
const ENTRY_POINT_VERB_RE = new RegExp(`\\b(?:read|execute|run)\\b.{0,${ENTRY_POINT_VERB_WINDOW}}$`, 'i');
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Does the parent name this fragment as a live ENTRY POINT, rather than merely
|
|
349
|
+
* mentioning its path?
|
|
350
|
+
*
|
|
351
|
+
* The distinction is the whole basis of inherited coverage: the inheritance is
|
|
352
|
+
* sound only because the fragment cannot be reached except through the parent's
|
|
353
|
+
* dispatch stub, which fires with the parent's directive already loaded. A path
|
|
354
|
+
* that appears in a changelog line, a deprecation note, or a comment proves
|
|
355
|
+
* nothing about how the fragment is reached — but a bare substring test cannot
|
|
356
|
+
* tell those apart, so before this check any mention at all granted coverage.
|
|
357
|
+
*
|
|
358
|
+
* The catalog emits exactly one shape, in five spellings: ``read and execute
|
|
359
|
+
* `<path>` ``, ``Read+execute `<path>` ``, ``Read `<path>` if <condition>``,
|
|
360
|
+
* ``run `<path>` to <effect>`` (the spelling #1689's per-plan executor routing
|
|
361
|
+
* introduced), and the same stub written with the path RELATIVE to the catalog
|
|
362
|
+
* rather than rooted at `gsd-core/workflows/` (#3552's `branching_strategy:
|
|
363
|
+
* none` arm). A read/execute/run verb within `ENTRY_POINT_VERB_WINDOW`
|
|
364
|
+
* characters ahead of the path on the SAME LINE covers all five.
|
|
365
|
+
*
|
|
366
|
+
* LIMITATION, stated rather than papered over: this reads Markdown as text, not
|
|
367
|
+
* as a parsed document. A dispatch stub that a future edit comments out with
|
|
368
|
+
* `<!-- ... -->`, or moves inside a fenced block quoted as an example, still
|
|
369
|
+
* matches — the verb and the path are both still there. Distinguishing those
|
|
370
|
+
* needs a Markdown parser, and the cheap approximations (tracking fence state,
|
|
371
|
+
* skipping `<!--` lines) buy little: neither shape occurs in the catalog today,
|
|
372
|
+
* and both would be visible in review as a deleted dispatch. What this DOES
|
|
373
|
+
* close is the common, invisible case — a path mentioned in prose that never
|
|
374
|
+
* dispatches anything. `tests/response-language-coverage.test.cjs` pins both the
|
|
375
|
+
* accepted spellings and the rejected mention forms.
|
|
376
|
+
*
|
|
377
|
+
* SECOND LIMITATION, same class: a dispatch line may sit inside a
|
|
378
|
+
* `<!-- gsd:section id="X" when="…" -->` guard, and nothing here checks that the
|
|
379
|
+
* guard is satisfiable or that its `id` still matches the fragment it gates.
|
|
380
|
+
* Every such pairing lines up today. If one drifts, the fragment becomes
|
|
381
|
+
* unreachable while this function still calls it dispatched — an orphan the lint
|
|
382
|
+
* reports as covered. That is not the defect #2529 is about: a dispatch that
|
|
383
|
+
* never fires renders nothing, so no English prose reaches the user through it.
|
|
384
|
+
* The gap is a soundness one, worth naming rather than implying.
|
|
385
|
+
*/
|
|
386
|
+
function namesFragmentAsEntryPoint(parent, relative) {
|
|
387
|
+
// Two spellings of the same dispatch: catalog-rooted and catalog-RELATIVE.
|
|
388
|
+
// #3552's `"none"` arm introduced the second one — `Read and execute
|
|
389
|
+
// `execute-phase/steps/protected-branch.md`` — and a rooted-only needle read
|
|
390
|
+
// that live dispatch as no dispatch at all.
|
|
391
|
+
const needles = [`gsd-core/workflows/${relative}`, relative];
|
|
392
|
+
return parent.split(/\r?\n/).some((line) => needles.some((needle) => {
|
|
393
|
+
const at = line.indexOf(needle);
|
|
394
|
+
// A path character immediately before the match means this is the TAIL of
|
|
395
|
+
// some longer path, not the fragment itself: `vendor/<relative>` must not
|
|
396
|
+
// grant `<relative>` coverage. The rooted needle carries its own prefix and
|
|
397
|
+
// is matched on its own, so nothing legitimate is lost here.
|
|
398
|
+
if (at === -1 || (at > 0 && /[\w/.-]/.test(line.charAt(at - 1)))) return false;
|
|
399
|
+
return ENTRY_POINT_VERB_RE.test(line.slice(0, at));
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* A fragment inherits its parent workflow's coverage, but only when the
|
|
405
|
+
* inheritance is PROVEN per file rather than assumed from the directory:
|
|
406
|
+
* 1. the path is `<workflow>/<fragment-dir>/<name>.md`,
|
|
407
|
+
* 2. `<workflow>.md` exists and dispatches this exact fragment path from a
|
|
408
|
+
* read/execute context — i.e. the parent really is the way in, not just a
|
|
409
|
+
* file that happens to spell the name (see `namesFragmentAsEntryPoint`), and
|
|
410
|
+
* 3. the parent is itself covered.
|
|
411
|
+
* A fragment nobody dispatches, or one hanging off an uncovered parent, stays a
|
|
412
|
+
* violation. Without this the lint reds on every new fragment the #1671 epic
|
|
413
|
+
* extracts, even though the extraction moved prose that was already covered.
|
|
414
|
+
*/
|
|
415
|
+
function inheritsParentCoverage(workflowsDir, relative, refRoot = REFERENCE_ROOT) {
|
|
416
|
+
const segments = relative.split('/');
|
|
417
|
+
if (segments.length !== 3 || !FRAGMENT_DIRS.has(segments[1])) return false;
|
|
418
|
+
const parentPath = path.join(workflowsDir, `${segments[0]}.md`);
|
|
419
|
+
if (!fs.existsSync(parentPath)) return false;
|
|
420
|
+
const parent = fs.readFileSync(parentPath, 'utf8');
|
|
421
|
+
if (!namesFragmentAsEntryPoint(parent, relative)) return false;
|
|
422
|
+
return hasResponseLanguageCoverage(parent, refRoot);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function findViolations(workflowsDir, refRoot = REFERENCE_ROOT) {
|
|
426
|
+
return findMarkdownFilesRecursive(workflowsDir).filter((file) => {
|
|
427
|
+
const relative = path.relative(workflowsDir, file).replaceAll(path.sep, '/');
|
|
428
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
429
|
+
if (EXACT_INLINE_DIRECTIVE_WORKFLOWS.has(relative)) {
|
|
430
|
+
if (content.split(/\r?\n/).includes(INLINE_RESPONSE_LANGUAGE_DIRECTIVE)) return false;
|
|
431
|
+
// The pin exists because these files cannot take the eager @-reference, not
|
|
432
|
+
// because the reference is worse. If one ever CAN take it -- a fragment that
|
|
433
|
+
// becomes eagerly loaded -- that conversion is strictly better than the pin and
|
|
434
|
+
// must not read as a violation. Only the reference form is admitted here: its
|
|
435
|
+
// wording is validated in turn by findBrokenDirectiveReferences, so the contract
|
|
436
|
+
// survives the swap. An arbitrary reworded inline line stays a violation, which
|
|
437
|
+
// is the whole point of pinning.
|
|
438
|
+
return !importsDirectiveReference(content) || !hasResponseLanguageCoverage(content, refRoot);
|
|
439
|
+
}
|
|
440
|
+
if (hasResponseLanguageCoverage(content, refRoot)) return false;
|
|
441
|
+
return !inheritsParentCoverage(workflowsDir, relative, refRoot);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function main(workflowsDir = WORKFLOWS_DIR, io = console, refRoot = REFERENCE_ROOT) {
|
|
446
|
+
// The catalog is discovered, not declared, so an unreadable or empty
|
|
447
|
+
// directory yields an empty violation list — indistinguishable from full
|
|
448
|
+
// coverage if the only success condition is `violations.length === 0`. Both
|
|
449
|
+
// discovery failures below are therefore lint failures in their own right:
|
|
450
|
+
// a stripped install tree, a `__dirname`-relative path typo, or an
|
|
451
|
+
// unfollowed symlink must not be able to report OK while coverage silently
|
|
452
|
+
// regresses to the pre-#2529 state.
|
|
453
|
+
let files;
|
|
454
|
+
try {
|
|
455
|
+
files = findMarkdownFilesRecursive(workflowsDir);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') throw error;
|
|
458
|
+
io.error(
|
|
459
|
+
`lint-response-language-coverage: cannot read the workflow directory ${workflowsDir} (${error.code}).\n` +
|
|
460
|
+
`Coverage cannot be established, so this is a failure and not a pass (#2529).`,
|
|
461
|
+
);
|
|
462
|
+
return 1;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (files.length === 0) {
|
|
466
|
+
io.error(
|
|
467
|
+
`lint-response-language-coverage: no workflow files found under ${workflowsDir}.\n` +
|
|
468
|
+
`A run that inspected zero workflows cannot establish coverage (#2529).`,
|
|
469
|
+
);
|
|
470
|
+
return 1;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// The shared references are checked ONCE, before the per-file pass. 43
|
|
474
|
+
// workflows hold no directive of their own, so a reference that has gone
|
|
475
|
+
// missing or been reworded back to the pre-#2529 sentence is a single
|
|
476
|
+
// systemic failure, not 43 identical ones — and reporting it as 43 would bury
|
|
477
|
+
// the cause under its symptoms.
|
|
478
|
+
const brokenReferences = findBrokenDirectiveReferences(files, refRoot);
|
|
479
|
+
if (brokenReferences.length > 0) {
|
|
480
|
+
io.error(
|
|
481
|
+
'lint-response-language-coverage: ' + brokenReferences.length +
|
|
482
|
+
' shared directive reference(s) no longer carry an actionable directive (#2529, REQ-LANG-04).\n' +
|
|
483
|
+
'Workflows that @-import a reference take ALL of their coverage from it, so a missing,\n' +
|
|
484
|
+
'unreadable, or reworded reference silently uncovers every one of them:\n\n' +
|
|
485
|
+
brokenReferences.map((ref) => ' - ' + ref).join('\n') + '\n\n' +
|
|
486
|
+
'The file must itself name the narration class on the same line as response_language\n' +
|
|
487
|
+
'— the same rule an inline directive must satisfy.',
|
|
488
|
+
);
|
|
489
|
+
return 1;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const violations = findViolations(workflowsDir, refRoot);
|
|
493
|
+
|
|
494
|
+
if (violations.length > 0) {
|
|
495
|
+
io.error(
|
|
496
|
+
`lint-response-language-coverage: ${violations.length} workflow(s) have no response-language coverage (#2529).\n` +
|
|
497
|
+
`Each workflow must either @-reference a recognized response-language directive\n` +
|
|
498
|
+
`or carry its own inline \`response_language\` directive (unless its parent dispatches it).\n` +
|
|
499
|
+
`An inline directive names the narration class with the word "narration" or the phrase\n` +
|
|
500
|
+
`"between tool calls" (REQ-LANG-04); enumerating status updates, progress notes or\n` +
|
|
501
|
+
`findings without naming the class does not satisfy it:\n\n` +
|
|
502
|
+
violations.map((file) => ` - ${path.relative(workflowsDir, file).replaceAll(path.sep, '/')}`).join('\n'),
|
|
503
|
+
);
|
|
504
|
+
return 1;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
io.log(`lint-response-language-coverage: OK (${files.length} workflows covered)`);
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (require.main === module) process.exitCode = main();
|
|
512
|
+
|
|
513
|
+
module.exports = {
|
|
514
|
+
EXACT_INLINE_DIRECTIVE_WORKFLOWS,
|
|
515
|
+
INLINE_RESPONSE_LANGUAGE_DIRECTIVE,
|
|
516
|
+
NARRATION_CLASS_RE,
|
|
517
|
+
WORKFLOW_EXTENSIONS,
|
|
518
|
+
WORKFLOWS_DIR,
|
|
519
|
+
REFERENCE_ROOT,
|
|
520
|
+
carriesInlineDirective,
|
|
521
|
+
findBrokenDirectiveReferences,
|
|
522
|
+
findMarkdownFilesRecursive,
|
|
523
|
+
findViolations,
|
|
524
|
+
hasResponseLanguageCoverage,
|
|
525
|
+
importedDirectiveReference,
|
|
526
|
+
importsDirectiveReference,
|
|
527
|
+
inheritsParentCoverage,
|
|
528
|
+
namesFragmentAsEntryPoint,
|
|
529
|
+
main,
|
|
530
|
+
};
|
|
@@ -114,7 +114,7 @@ function toPosix(p) {
|
|
|
114
114
|
function isGeneratedOutput(relPath) {
|
|
115
115
|
const posixRel = toPosix(relPath);
|
|
116
116
|
return GENERATED_OUTPUT_PREFIXES.some(
|
|
117
|
-
(prefix) => posixRel === prefix || posixRel.startsWith(`${prefix}/`)
|
|
117
|
+
(prefix) => posixRel === prefix || posixRel.startsWith(`${prefix}/`) // allow-handrolled-containment: scan-exclusion membership test against a fixed generated-output prefix list, not a filesystem root-confinement gate
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"phase-dependency-levels.test.cjs",
|
|
82
82
|
"phase-resolution-parity.test.cjs",
|
|
83
83
|
"phase-structure.test.cjs",
|
|
84
|
+
"phase-tdd-applicable.test.cjs",
|
|
84
85
|
"phase.test.cjs"
|
|
85
86
|
],
|
|
86
87
|
"issue": "3186"
|
|
@@ -122,6 +123,7 @@
|
|
|
122
123
|
"state-prune.test.cjs",
|
|
123
124
|
"state-rebuild-cli.test.cjs",
|
|
124
125
|
"state-rebuild.test.cjs",
|
|
126
|
+
"state-todos-render.test.cjs",
|
|
125
127
|
"state-write-path-drift-guard.test.cjs",
|
|
126
128
|
"state.test.cjs"
|
|
127
129
|
],
|
|
@@ -260,9 +262,10 @@
|
|
|
260
262
|
"audit-milestone-filename-guard.test.cjs",
|
|
261
263
|
"audit-open-remainder-count.test.cjs",
|
|
262
264
|
"audit-uat-acknowledged.test.cjs",
|
|
265
|
+
"audit-uat-summary-segmentation.test.cjs",
|
|
263
266
|
"audit-workstream-layouts.test.cjs"
|
|
264
267
|
],
|
|
265
|
-
"issue": "#3804/#3805 \u2014 audit-layout and acknowledged-marker suites drive the real audit-uat CLI against on-disk fixtures | #3817 \u2014 regression suite proving countReal counts the truncation remainder marker instead of treating it as one phantom item"
|
|
268
|
+
"issue": "#3804/#3805 \u2014 audit-layout and acknowledged-marker suites drive the real audit-uat CLI against on-disk fixtures | #3817 \u2014 regression suite proving countReal counts the truncation remainder marker instead of treating it as one phantom item | #3783 \u2014 dedicated suite for the summary.current_milestone/archived segmentation, matching this module's one-file-per-feature-slice precedent (e.g. audit-uat-acknowledged.test.cjs) rather than growing the unrelated 6900+ line tests/uat.test.cjs (a different module, keyed off uat.cjs)"
|
|
266
269
|
},
|
|
267
270
|
"shell-command-projection": {
|
|
268
271
|
"files": [
|