@opengsd/gsd-core 1.10.0 → 1.11.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/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +575 -101
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +134 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +24 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* lint-vendored-deps.cjs — freshness gate for gsd-core/bin/lib/vendor/.
|
|
6
|
+
*
|
|
7
|
+
* #3477 follow-up: gsd-core/bin/** is copied by the installer into trees
|
|
8
|
+
* that have NO node_modules, so it must carry zero external requires
|
|
9
|
+
* (local/no-external-require-in-bin, eslint-rules/no-external-require-in-bin.cjs).
|
|
10
|
+
* `re2js` (src/pattern.cts's RE2 engine) is vendored verbatim under
|
|
11
|
+
* gsd-core/bin/lib/vendor/ instead — see gsd-core/bin/lib/vendor/README.md.
|
|
12
|
+
*
|
|
13
|
+
* A vendored artifact that silently drifts from its upstream package is
|
|
14
|
+
* just as dangerous as never vendoring it in the first place (a stale
|
|
15
|
+
* copy ships a different engine than the one actually reviewed/audited).
|
|
16
|
+
* This guard fails CI when:
|
|
17
|
+
* 1. gsd-core/bin/lib/vendor/re2js.cjs no longer matches
|
|
18
|
+
* node_modules/re2js/build/index.cjs byte-for-byte.
|
|
19
|
+
* 2. gsd-core/bin/lib/vendor/re2js.d.cts no longer matches
|
|
20
|
+
* node_modules/re2js/build/index.d.cts byte-for-byte.
|
|
21
|
+
* 3. src/vendor/re2js.d.cts (the source-side twin tsc needs to resolve
|
|
22
|
+
* types for src/pattern.cts's relative './vendor/re2js.cjs' import —
|
|
23
|
+
* module resolution for a .cts source is relative to src/, not the
|
|
24
|
+
* output dir) no longer matches gsd-core/bin/lib/vendor/re2js.d.cts.
|
|
25
|
+
* 4. The `re2js` version pinned in package.json `devDependencies` no
|
|
26
|
+
* longer matches the version actually installed at
|
|
27
|
+
* node_modules/re2js/package.json (read there, per the dispatch
|
|
28
|
+
* brief, rather than duplicating a second pin).
|
|
29
|
+
*
|
|
30
|
+
* Usage: node scripts/lint-vendored-deps.cjs
|
|
31
|
+
* Exit 0 when every vendored copy is fresh; 1 otherwise.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const fs = require('node:fs');
|
|
35
|
+
const path = require('node:path');
|
|
36
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
37
|
+
|
|
38
|
+
const ROOT = path.join(__dirname, '..');
|
|
39
|
+
|
|
40
|
+
const REFRESH_COMMAND =
|
|
41
|
+
'cp node_modules/re2js/build/index.cjs gsd-core/bin/lib/vendor/re2js.cjs && '
|
|
42
|
+
+ 'cp node_modules/re2js/build/index.d.cts gsd-core/bin/lib/vendor/re2js.d.cts && '
|
|
43
|
+
+ 'cp node_modules/re2js/build/index.d.cts src/vendor/re2js.d.cts';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compare two files byte-for-byte. Returns null when equal, or a short
|
|
47
|
+
* mismatch description (missing file / byte-length delta) otherwise.
|
|
48
|
+
* @param {string} relA
|
|
49
|
+
* @param {string} relB
|
|
50
|
+
* @returns {string | null}
|
|
51
|
+
*/
|
|
52
|
+
function compareFiles(relA, relB) {
|
|
53
|
+
const absA = path.join(ROOT, relA);
|
|
54
|
+
const absB = path.join(ROOT, relB);
|
|
55
|
+
if (!fs.existsSync(absA)) return `${relA} does not exist`;
|
|
56
|
+
if (!fs.existsSync(absB)) return `${relB} does not exist`;
|
|
57
|
+
const a = fs.readFileSync(absA);
|
|
58
|
+
const b = fs.readFileSync(absB);
|
|
59
|
+
if (a.equals(b)) return null;
|
|
60
|
+
return `${relA} (${a.length} bytes) != ${relB} (${b.length} bytes)`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Strip a leading semver range operator (^, ~, >=, >, <=, <, =) from a
|
|
65
|
+
* package.json dependency spec, leaving a bare version.
|
|
66
|
+
* @param {string} spec
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
69
|
+
function stripRangeOperator(spec) {
|
|
70
|
+
return String(spec || '').trim().replace(/^[\^~]|^>=|^<=|^>|^<|^=/, '').trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function main() {
|
|
74
|
+
const findings = [];
|
|
75
|
+
|
|
76
|
+
const cjsDrift = compareFiles('gsd-core/bin/lib/vendor/re2js.cjs', 'node_modules/re2js/build/index.cjs');
|
|
77
|
+
if (cjsDrift) findings.push(cjsDrift);
|
|
78
|
+
|
|
79
|
+
const dctsDrift = compareFiles('gsd-core/bin/lib/vendor/re2js.d.cts', 'node_modules/re2js/build/index.d.cts');
|
|
80
|
+
if (dctsDrift) findings.push(dctsDrift);
|
|
81
|
+
|
|
82
|
+
const srcTwinDrift = compareFiles('src/vendor/re2js.d.cts', 'gsd-core/bin/lib/vendor/re2js.d.cts');
|
|
83
|
+
if (srcTwinDrift) findings.push(srcTwinDrift);
|
|
84
|
+
|
|
85
|
+
const pkgPath = path.join(ROOT, 'package.json');
|
|
86
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
87
|
+
const pinnedSpec = pkg.devDependencies && pkg.devDependencies.re2js;
|
|
88
|
+
if (!pinnedSpec) {
|
|
89
|
+
findings.push('package.json devDependencies.re2js is missing');
|
|
90
|
+
} else {
|
|
91
|
+
const installedPkgPath = path.join(ROOT, 'node_modules', 're2js', 'package.json');
|
|
92
|
+
if (!fs.existsSync(installedPkgPath)) {
|
|
93
|
+
findings.push('node_modules/re2js/package.json does not exist (run npm install)');
|
|
94
|
+
} else {
|
|
95
|
+
const installed = JSON.parse(fs.readFileSync(installedPkgPath, 'utf8'));
|
|
96
|
+
const pinned = stripRangeOperator(pinnedSpec);
|
|
97
|
+
if (pinned !== installed.version) {
|
|
98
|
+
findings.push(
|
|
99
|
+
`package.json devDependencies.re2js ("${pinnedSpec}" -> "${pinned}") != `
|
|
100
|
+
+ `node_modules/re2js/package.json version ("${installed.version}")`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (findings.length > 0) {
|
|
107
|
+
const detail = findings.map((f) => ` ${f}`).join('\n');
|
|
108
|
+
throw new ExitError(
|
|
109
|
+
1,
|
|
110
|
+
'lint-vendored-deps: gsd-core/bin/lib/vendor/re2js.* has drifted from its\n'
|
|
111
|
+
+ 'upstream package (or its version pin). Refresh with:\n'
|
|
112
|
+
+ ` ${REFRESH_COMMAND}\n`
|
|
113
|
+
+ 'Findings:\n'
|
|
114
|
+
+ detail,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
process.stdout.write('ok lint-vendored-deps: gsd-core/bin/lib/vendor/re2js.* matches node_modules/re2js and its pinned version\n');
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (require.main === module) runMain(main);
|
|
123
|
+
|
|
124
|
+
module.exports = { compareFiles, stripRangeOperator };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared truncation-detection helper for `gh pr view --json files` (#3211).
|
|
5
|
+
*
|
|
6
|
+
* GitHub's GraphQL `files` connection on a pull request is a paginated
|
|
7
|
+
* connection. `gh pr view --json files` requests it with a single page at
|
|
8
|
+
* `first: 100` and does NOT paginate through the rest — so any PR touching
|
|
9
|
+
* more than 100 files silently returns only the first 100, with no error and
|
|
10
|
+
* no indication in the `files` array itself that entries are missing.
|
|
11
|
+
* `changedFiles` (a separate, non-paginated scalar field) reports the true
|
|
12
|
+
* total. A policy that relaxes a gate based on "every changed file matches
|
|
13
|
+
* pattern X" must treat a `files` list it cannot confirm is complete as
|
|
14
|
+
* incomplete — i.e. fail closed — rather than silently approving a PR whose
|
|
15
|
+
* 101st+ file might violate the policy.
|
|
16
|
+
*
|
|
17
|
+
* This file lives at the top level of scripts/ rather than in scripts/lib/
|
|
18
|
+
* because scripts/lib/** is enumerated in bin/install.js and ships to users
|
|
19
|
+
* on install; this is CI-only tooling with no reason to be installed.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// GitHub's GraphQL `files` connection is requested at `first: 100` by
|
|
23
|
+
// `gh pr view --json files`; `gh` does not paginate this field.
|
|
24
|
+
const FILE_LIST_PAGE_LIMIT = 100;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns true iff `changedFiles` can be trusted as the COMPLETE list of
|
|
28
|
+
* files changed in the PR (i.e. it was not silently truncated).
|
|
29
|
+
*
|
|
30
|
+
* - An empty/non-array list cannot confirm anything about the PR — mirror
|
|
31
|
+
* the fail-closed stance `allPathsAreTooling` already takes on an empty
|
|
32
|
+
* list in scripts/pr-template-policy.cjs.
|
|
33
|
+
* - When the true total is known it is the AUTHORITY, at every list size —
|
|
34
|
+
* not just when the list has hit the page cap. The 100-entry page cap is
|
|
35
|
+
* only ONE way a list can be short of the truth; a `$GITHUB_OUTPUT`
|
|
36
|
+
* heredoc terminated early by an attacker-named file, or a path
|
|
37
|
+
* containing a newline, truncates or inflates the list just as
|
|
38
|
+
* effectively, and at any length. Comparing against the total catches all
|
|
39
|
+
* of them without enumerating the mechanisms.
|
|
40
|
+
* - Only when no total is supplied do we fall back to the page-cap
|
|
41
|
+
* heuristic: a list below the cap cannot have been truncated BY THE CAP,
|
|
42
|
+
* which is the only mechanism a caller without a total can rule out.
|
|
43
|
+
*/
|
|
44
|
+
function fileListIsComplete(changedFiles, changedFilesTotal) {
|
|
45
|
+
if (!Array.isArray(changedFiles) || changedFiles.length === 0) return false;
|
|
46
|
+
if (Number.isInteger(changedFilesTotal)) return changedFilesTotal === changedFiles.length;
|
|
47
|
+
return changedFiles.length < FILE_LIST_PAGE_LIMIT;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parses a newline-delimited env var (as produced by `git diff --name-only`
|
|
52
|
+
* or `gh pr view --json files -q '.files[].path'`) into a trimmed,
|
|
53
|
+
* empty-line-free array. Returns [] for null/undefined/empty input.
|
|
54
|
+
*/
|
|
55
|
+
function parseChangedFilesEnv(value) {
|
|
56
|
+
if (!value) return [];
|
|
57
|
+
return String(value)
|
|
58
|
+
.split(/\r?\n/)
|
|
59
|
+
.map((line) => line.trim())
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { fileListIsComplete, FILE_LIST_PAGE_LIMIT, parseChangedFilesEnv };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { matchesGlob } = require('path');
|
|
4
|
+
const { fileListIsComplete, parseChangedFilesEnv } = require('./pr-changed-files.cjs');
|
|
4
5
|
|
|
5
6
|
const TRUSTED_AUTHOR_ASSOCIATIONS = new Set([
|
|
6
7
|
'CONTRIBUTOR',
|
|
@@ -142,8 +143,12 @@ function matchingTemplate(body) {
|
|
|
142
143
|
* pattern in the allowlist. Returns false for an empty file list (no
|
|
143
144
|
* files means we cannot confirm it is a tooling-only PR).
|
|
144
145
|
*/
|
|
145
|
-
function allPathsAreTooling(changedFiles, allowlist) {
|
|
146
|
+
function allPathsAreTooling(changedFiles, allowlist, changedFilesTotal) {
|
|
146
147
|
if (!Array.isArray(changedFiles) || changedFiles.length === 0) return false;
|
|
148
|
+
// #3211: `gh pr view --json files` truncates at 100 entries with no
|
|
149
|
+
// in-band signal. A truncated list must not relax enforcement — an unseen
|
|
150
|
+
// 101st+ file could fall outside the tooling allowlist entirely.
|
|
151
|
+
if (!fileListIsComplete(changedFiles, changedFilesTotal)) return false;
|
|
147
152
|
return changedFiles.every((file) =>
|
|
148
153
|
allowlist.some((pattern) => matchesGlob(file, pattern)),
|
|
149
154
|
);
|
|
@@ -159,13 +164,13 @@ function hasExemptMarker(body, regex) {
|
|
|
159
164
|
return match[1].trim().length > 0;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
function evaluatePrTemplate(body, authorAssociation, changedFiles) {
|
|
167
|
+
function evaluatePrTemplate(body, authorAssociation, changedFiles, changedFilesTotal) {
|
|
163
168
|
const association = String(authorAssociation || '').toUpperCase();
|
|
164
169
|
const trusted = TRUSTED_AUTHOR_ASSOCIATIONS.has(association);
|
|
165
170
|
const normalizedBody = String(body || '').trim();
|
|
166
171
|
|
|
167
172
|
// --- Carve-out 1: all changed files are in the tooling allowlist ---
|
|
168
|
-
if (allPathsAreTooling(changedFiles, TOOLING_PATH_ALLOWLIST)) {
|
|
173
|
+
if (allPathsAreTooling(changedFiles, TOOLING_PATH_ALLOWLIST, changedFilesTotal)) {
|
|
169
174
|
return {
|
|
170
175
|
valid: true,
|
|
171
176
|
action: 'pass',
|
|
@@ -235,13 +240,18 @@ function evaluatePrTemplate(body, authorAssociation, changedFiles) {
|
|
|
235
240
|
}
|
|
236
241
|
|
|
237
242
|
function main() {
|
|
243
|
+
// Preserve the existing distinction between "unset" (undefined) and
|
|
244
|
+
// "set but empty" ([]) — allPathsAreTooling treats them differently.
|
|
238
245
|
const changedFiles = process.env.CHANGED_FILES
|
|
239
|
-
? process.env.CHANGED_FILES
|
|
246
|
+
? parseChangedFilesEnv(process.env.CHANGED_FILES)
|
|
240
247
|
: undefined;
|
|
248
|
+
const parsedTotal = Number.parseInt(process.env.CHANGED_FILES_TOTAL, 10);
|
|
249
|
+
const changedFilesTotal = Number.isNaN(parsedTotal) ? undefined : parsedTotal;
|
|
241
250
|
const result = evaluatePrTemplate(
|
|
242
251
|
process.env.PR_BODY || '',
|
|
243
252
|
process.env.AUTHOR_ASSOCIATION || '',
|
|
244
253
|
changedFiles,
|
|
254
|
+
changedFilesTotal,
|
|
245
255
|
);
|
|
246
256
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
247
257
|
if (process.env.GITHUB_OUTPUT) {
|
|
@@ -72,6 +72,15 @@ PATTERNS=(
|
|
|
72
72
|
# `eval('...')` (single-quoted) silently went undetected on macOS while
|
|
73
73
|
# passing on GNU-grep CI runners. Found auditing #3175; fixed here since it
|
|
74
74
|
# is the same unanchored/portability defect class as the boundary fix.
|
|
75
|
+
#
|
|
76
|
+
# `exec` stays receiver-blind on purpose. A left boundary that excludes a
|
|
77
|
+
# preceding `.` would drop every member-position `.exec('…')` — including
|
|
78
|
+
# `require('child_process').exec('…')`, the single most common Node spelling
|
|
79
|
+
# of the vector this pattern exists to catch — and a receiver allowlist
|
|
80
|
+
# cannot restore it, because the literal `child_process` is not adjacent to
|
|
81
|
+
# `.exec`. The cost is that `RegExp.prototype.exec`, which takes a subject
|
|
82
|
+
# string rather than code, also matches; files that legitimately call it are
|
|
83
|
+
# handled by ALLOWLIST below, never by narrowing the pattern.
|
|
75
84
|
'(^|[^[:alnum:]])eval[[:space:]]*\([[:space:]]*["'"'"']'
|
|
76
85
|
'exec[[:space:]]*\([[:space:]]*["'"'"']'
|
|
77
86
|
'(^|[^[:alnum:]])Function[[:space:]]*\([[:space:]]*["'"'"'].*return'
|
|
@@ -129,6 +138,22 @@ ALLOWLIST=(
|
|
|
129
138
|
# asserts nothing: it is the payload the guard is required to catch, carried
|
|
130
139
|
# as test DATA. Same class as the read-injection-scanner suites above.
|
|
131
140
|
'tests/kimi-payload-field-shadowing.security.test.cjs'
|
|
141
|
+
# Phase-ID grammar regression tests exercise `RegExp.prototype.exec` via
|
|
142
|
+
# `re.exec('<phase-id>')` against fixtures like 'MANIFOLD-64-auth' / 'CK-64-auth'.
|
|
143
|
+
# The scanner's `exec('` code-execution pattern matches that benign method call,
|
|
144
|
+
# not an attack vector — same DEFECT.PROMPT-INJECTION-SCAN-COLLISION class as the
|
|
145
|
+
# test fixtures above. Pre-existing content (16 such calls on `next`); it surfaces
|
|
146
|
+
# here only because #2573's W024 `state_head` assertions make the file appear in
|
|
147
|
+
# the changed-file set the diff-mode scan walks.
|
|
148
|
+
'tests/health-validation.test.cjs'
|
|
149
|
+
# #2528 — same collision, same disposition: the continuation-grammar suite
|
|
150
|
+
# drives the tokenizer regexes directly via `re.exec('05-80-20')`, so the
|
|
151
|
+
# argument is the subject string, not a command. Exempted per file rather
|
|
152
|
+
# than by narrowing the `exec(` pattern: a left boundary excluding a preceding
|
|
153
|
+
# `.` would drop `require('child_process').exec('…')`, and a receiver
|
|
154
|
+
# allowlist cannot reach it either, because the literal `child_process` is
|
|
155
|
+
# not adjacent to `.exec`. See the note at the pattern itself.
|
|
156
|
+
'tests/continuation-grammar-parity.test.cjs'
|
|
132
157
|
)
|
|
133
158
|
|
|
134
159
|
is_allowlisted() {
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Require-issue-link policy (#3211, preserving #1389).
|
|
6
|
+
*
|
|
7
|
+
* Replaces the shell-only `grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'`
|
|
8
|
+
* step with a pure, testable `evaluateIssueLink` verdict function so a PR
|
|
9
|
+
* that only REFERENCES an issue (rather than closing it — e.g. a follow-up
|
|
10
|
+
* regression-coverage PR) is not forced to fabricate a closing keyword, while
|
|
11
|
+
* two pre-existing constraints are preserved exactly:
|
|
12
|
+
*
|
|
13
|
+
* 1. (#1389, anti-forgery) The backmerge exemption only fires when the
|
|
14
|
+
* head ref carries the backmerge prefix AND the PR is same-repo (not a
|
|
15
|
+
* fork) — dropping the `sameRepo` conjunct would let a fork forge a
|
|
16
|
+
* branch name to bypass the issue-link requirement entirely.
|
|
17
|
+
* 2. (#3211, truncation) `gh pr view --json files` truncates the file list
|
|
18
|
+
* at 100 entries with no in-band signal that it did so (see
|
|
19
|
+
* scripts/pr-changed-files.cjs). The reference-only carve-out below
|
|
20
|
+
* only applies when every changed file is a test/doc file, so a PR
|
|
21
|
+
* whose file list may be truncated must fail closed rather than let an
|
|
22
|
+
* unseen 101st+ file (which could touch `src/`) slip through.
|
|
23
|
+
*
|
|
24
|
+
* Tests assert on the typed ISSUE_LINK_REASON enum, never on free text.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const { fileListIsComplete, parseChangedFilesEnv } = require('./pr-changed-files.cjs');
|
|
28
|
+
const { runMain } = require('./lib/cli-exit.cjs');
|
|
29
|
+
|
|
30
|
+
const ISSUE_LINK_REASON = Object.freeze({
|
|
31
|
+
OK_CLOSING_KEYWORD: 'ok_closing_keyword',
|
|
32
|
+
OK_BACKMERGE_EXEMPT: 'ok_backmerge_exempt',
|
|
33
|
+
OK_FOLLOWUP_REFERENCE: 'ok_followup_reference',
|
|
34
|
+
FAIL_NO_ISSUE_REFERENCE: 'fail_no_issue_reference',
|
|
35
|
+
FAIL_REFERENCE_NEEDS_CLOSING: 'fail_reference_needs_closing',
|
|
36
|
+
FAIL_FILE_LIST_INCOMPLETE: 'fail_file_list_incomplete',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// #1389: backmerge PRs are opened by CI against `next`, never by a human or a
|
|
40
|
+
// fork, so they are exempt from the issue-link requirement outright — but
|
|
41
|
+
// ONLY when combined with `sameRepo === true` below (see header comment).
|
|
42
|
+
const BACKMERGE_BRANCH_PREFIX = 'chore/backmerge-main-to-next-';
|
|
43
|
+
|
|
44
|
+
// A follow-up-only PR (references an issue without closing it) is only
|
|
45
|
+
// allowed to skip the closing keyword when every changed file is a test or
|
|
46
|
+
// doc file — i.e. it cannot be the PR that actually implements the fix.
|
|
47
|
+
const EXEMPT_PATH_PREFIXES = ['tests/', 'docs/'];
|
|
48
|
+
|
|
49
|
+
// Root-level markdown is documentation — this mirrors the repo's own doc-only
|
|
50
|
+
// classifier (.claude/hooks/pre-pr-gate.sh:111), whose `[^/]+\.md` anchor is
|
|
51
|
+
// deliberately root-only so runtime-loaded text under a subdirectory
|
|
52
|
+
// (gsd-core/workflows/*.md, agents/*.md, commands/**/*.md) stays gated.
|
|
53
|
+
// CHANGELOG.md is excluded: scripts/changeset/lint.cjs classes a direct edit to
|
|
54
|
+
// it as user-facing precisely to close a bypass, so it must not ride in on the
|
|
55
|
+
// docs carve-out either.
|
|
56
|
+
const EXCLUDED_ROOT_DOCS = new Set(['CHANGELOG.md']);
|
|
57
|
+
|
|
58
|
+
// Case-insensitive lookup set derived from EXCLUDED_ROOT_DOCS. The exclusion
|
|
59
|
+
// check below is case-insensitive because the `.md` extension test above it
|
|
60
|
+
// already is (`/\.md$/i`) — `changelog.md` or `CHANGELOG.MD` would otherwise
|
|
61
|
+
// slip past the exclusion while still passing the extension test. In
|
|
62
|
+
// practice this is defense in depth rather than a live bypass: the GitHub
|
|
63
|
+
// API always reports the real path with its actual, fixed casing (the
|
|
64
|
+
// filesystem is case-sensitive on the runners this executes on), so a PR
|
|
65
|
+
// cannot rename CHANGELOG.md to bypass the check by casing alone.
|
|
66
|
+
const EXCLUDED_ROOT_DOCS_UPPER = new Set(
|
|
67
|
+
Array.from(EXCLUDED_ROOT_DOCS, (name) => name.toUpperCase()),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
function isRootLevelDoc(normalizedPath) {
|
|
71
|
+
if (!normalizedPath) return false;
|
|
72
|
+
if (normalizedPath.includes('/')) return false;
|
|
73
|
+
if (!/\.md$/i.test(normalizedPath)) return false;
|
|
74
|
+
if (EXCLUDED_ROOT_DOCS_UPPER.has(normalizedPath.toUpperCase())) return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Mirrors the shipped `grep -qiE '(closes|fixes|resolves)\s+#[0-9]+'` exactly:
|
|
79
|
+
// no additional keywords, no `\b` anchors (the shell grep it replaces has
|
|
80
|
+
// none either) — see the corpus-parity test in
|
|
81
|
+
// tests/require-issue-link-policy.test.cjs.
|
|
82
|
+
const CLOSING_KEYWORD_REGEX = /(?:closes|fixes|resolves)\s+#[0-9]+/i;
|
|
83
|
+
|
|
84
|
+
// Accepts a soft "this PR relates to #N" reference without claiming to close
|
|
85
|
+
// it. The leading `\b` prevents matching inside a longer word (e.g. `xref#1`,
|
|
86
|
+
// `prefs #1`) because there is no word-boundary transition between the
|
|
87
|
+
// preceding word character and the start of the alternative — verified
|
|
88
|
+
// explicitly for `preferences #1` / `unreferenced #1` in the test suite.
|
|
89
|
+
const FOLLOWUP_REFERENCE_REGEX = /\b(?:refs?|references?|relates\s+to|related\s+to|follow[-\s]?up\s+to)\s+#[0-9]+/i;
|
|
90
|
+
|
|
91
|
+
function hasClosingKeyword(body) {
|
|
92
|
+
return CLOSING_KEYWORD_REGEX.test(String(body || ''));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function hasFollowUpReference(body) {
|
|
96
|
+
return FOLLOWUP_REFERENCE_REGEX.test(String(body || ''));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Path separator normalization — unconditional, per repo convention (see
|
|
100
|
+
// CLAUDE.md "Path-sep normalization"), so a Windows-style diff entry like
|
|
101
|
+
// `tests\windows\a.test.cjs` is still recognized as tests/-prefixed.
|
|
102
|
+
function normalizePath(p) {
|
|
103
|
+
return String(p).replace(/\\/g, '/');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Returns true iff every changed file qualifies as a test/doc file, i.e. each
|
|
108
|
+
* one is one of:
|
|
109
|
+
* 1. under one of EXEMPT_PATH_PREFIXES (`tests/`, `docs/`) — the trailing
|
|
110
|
+
* slash on each prefix makes this directory-boundary aware, so
|
|
111
|
+
* lookalikes like `tests-e2e/`, `testsuite/`, or `docsite/` are
|
|
112
|
+
* correctly rejected (they are not `tests/` or `docs/`); or
|
|
113
|
+
* 2. a root-level markdown file (no `/`, `.md` extension, case-insensitive)
|
|
114
|
+
* that is not in EXCLUDED_ROOT_DOCS — see isRootLevelDoc.
|
|
115
|
+
*/
|
|
116
|
+
function allPathsAreTestsOrDocs(changedFiles) {
|
|
117
|
+
if (!Array.isArray(changedFiles) || changedFiles.length === 0) return false;
|
|
118
|
+
return changedFiles.every((file) => {
|
|
119
|
+
const normalized = normalizePath(file);
|
|
120
|
+
if (EXEMPT_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return true;
|
|
121
|
+
return isRootLevelDoc(normalized);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function evaluateIssueLink({ prBody, headRef, sameRepo, changedFiles, changedFilesTotal }) {
|
|
126
|
+
if (hasClosingKeyword(prBody)) {
|
|
127
|
+
return { ok: true, reason: ISSUE_LINK_REASON.OK_CLOSING_KEYWORD };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// #1389: `sameRepo === true` is required, not just the branch-name prefix —
|
|
131
|
+
// a fork PR could otherwise name its branch to forge this exemption.
|
|
132
|
+
if (String(headRef || '').startsWith(BACKMERGE_BRANCH_PREFIX) && sameRepo === true) {
|
|
133
|
+
return { ok: true, reason: ISSUE_LINK_REASON.OK_BACKMERGE_EXEMPT };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!hasFollowUpReference(prBody)) {
|
|
137
|
+
return { ok: false, reason: ISSUE_LINK_REASON.FAIL_NO_ISSUE_REFERENCE };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// #3211: a truncated file list cannot be trusted to prove "tests/docs
|
|
141
|
+
// only" — fail closed rather than risk approving on an unseen file.
|
|
142
|
+
if (!fileListIsComplete(changedFiles, changedFilesTotal)) {
|
|
143
|
+
return { ok: false, reason: ISSUE_LINK_REASON.FAIL_FILE_LIST_INCOMPLETE };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (allPathsAreTestsOrDocs(changedFiles)) {
|
|
147
|
+
return { ok: true, reason: ISSUE_LINK_REASON.OK_FOLLOWUP_REFERENCE };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { ok: false, reason: ISSUE_LINK_REASON.FAIL_REFERENCE_NEEDS_CLOSING };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function main() {
|
|
154
|
+
const changedFiles = parseChangedFilesEnv(process.env.CHANGED_FILES);
|
|
155
|
+
const parsedTotal = Number.parseInt(process.env.CHANGED_FILES_TOTAL, 10);
|
|
156
|
+
const changedFilesTotal = Number.isNaN(parsedTotal) ? undefined : parsedTotal;
|
|
157
|
+
|
|
158
|
+
const result = evaluateIssueLink({
|
|
159
|
+
prBody: process.env.PR_BODY || '',
|
|
160
|
+
headRef: process.env.HEAD_REF || '',
|
|
161
|
+
sameRepo: process.env.SAME_REPO === 'true',
|
|
162
|
+
changedFiles,
|
|
163
|
+
changedFilesTotal,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
167
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
168
|
+
const fs = require('node:fs');
|
|
169
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `ok=${result.ok ? 'true' : 'false'}\n`);
|
|
170
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `reason=${result.reason}\n`);
|
|
171
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `result=${JSON.stringify(result)}\n`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result.ok ? 0 : 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (require.main === module) runMain(main);
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
ISSUE_LINK_REASON,
|
|
181
|
+
BACKMERGE_BRANCH_PREFIX,
|
|
182
|
+
EXEMPT_PATH_PREFIXES,
|
|
183
|
+
EXCLUDED_ROOT_DOCS,
|
|
184
|
+
CLOSING_KEYWORD_REGEX,
|
|
185
|
+
FOLLOWUP_REFERENCE_REGEX,
|
|
186
|
+
hasClosingKeyword,
|
|
187
|
+
hasFollowUpReference,
|
|
188
|
+
normalizePath,
|
|
189
|
+
isRootLevelDoc,
|
|
190
|
+
allPathsAreTestsOrDocs,
|
|
191
|
+
evaluateIssueLink,
|
|
192
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "ADR-3408 Decision 5 write-seam ratchet baseline (issue #3468, Phase 1; Phase 2 / #3469 lands the single write seam and Amendment 2). Every entry here is a `writeStateMd(`/`syncStateFrontmatter(`/`applyPostSyncPreservation(` bypass this guard found by a whole-repo scan (Decision 4(a)) — it is ACKNOWLEDGED, not endorsed: acknowledgment is in writing (this file), with the issue owning its removal recorded in the entry's \"owner\" field. This baseline is SHRINK-ONLY — an entry that stops firing goes STALE and fails the plain run until `--baseline` is re-run to drop it (ADR-3180 Decision 4(e)'s \"the baseline may only shrink\", adopted verbatim by ADR-3408). Phase 2 (#3469) removed the `cmdPhaseComplete` (`src/phase.cts`) and `cmdMilestoneComplete` (`src/milestone.cts`) entries by routing both through the single write-seam composition (`syncAndPreserveStateMd`, `src/state.cts`). ADR-3408 Amendment 2: \"0 bypasses\" was never this baseline's target — TWO entries are SANCTIONED PERMANENT, not debt, and Phase 4 (#3471) does NOT drive this file to empty: `cmdStateSync` (`src/state.cts`) exists precisely to let the body win (#905 — `state sync` re-derives frontmatter FROM the body), so routing it through preservation would invert the command rather than fix a bug; `REGENERATE_STATE` (`src/health-diagnostic.cts`) is `/gsd-health --repair`'s factory reset, which rebuilds STATE.md from scratch, so preservation would restore exactly the values it was invoked to discard. Neither entry may be \"consolidated\" away — a guard reporting them is reporting correctly, and a change that removes one is a regression, not progress.",
|
|
3
|
+
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"file": "src/health-diagnostic.cts",
|
|
6
|
+
"source": "writeStateMd(statePath, stateContent, cwd);",
|
|
7
|
+
"symbol": "writeStateMd",
|
|
8
|
+
"count": 1,
|
|
9
|
+
"owner": "sanctioned-permanent"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"file": "src/state.cts",
|
|
13
|
+
"source": "writeStateMd(statePath, modified, cwd);",
|
|
14
|
+
"symbol": "writeStateMd",
|
|
15
|
+
"count": 1,
|
|
16
|
+
"owner": "sanctioned-permanent"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
const fs = require('node:fs');
|
|
20
20
|
const path = require('node:path');
|
|
21
21
|
|
|
22
|
+
const { escapeRegex: escapeRegExp } = require('../gsd-core/bin/lib/pattern.cjs');
|
|
23
|
+
|
|
22
24
|
const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
|
|
23
25
|
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
|
|
24
26
|
const SNIPPET_FILE = path.join(WORKFLOWS_DIR, '_runtime-launcher.snippet.sh');
|
|
@@ -373,10 +375,6 @@ function transformFile(content, preamble) {
|
|
|
373
375
|
return outputLines.join('\n');
|
|
374
376
|
}
|
|
375
377
|
|
|
376
|
-
function escapeRegExp(str) {
|
|
377
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
378
|
-
}
|
|
379
|
-
|
|
380
378
|
/**
|
|
381
379
|
* A file "delegates to the shared resolver" when it pulls the canonical gsd_run
|
|
382
380
|
* preamble in from gsd-core/references/gsd-run-resolver.md via an @-include
|
|
@@ -41,7 +41,7 @@ Optional flags parsed from $ARGUMENTS:
|
|
|
41
41
|
- `--depth=VALUE` — Depth override (quick|standard|deep). If provided, overrides workflow.code_review_depth config.
|
|
42
42
|
- `--files=file1,file2,...` — Explicit file list override. Has highest precedence for file scoping per D-08. When provided, workflow skips SUMMARY.md extraction and git diff fallback entirely.
|
|
43
43
|
|
|
44
|
-
Context files (CLAUDE.md, SUMMARY.md, phase state) are resolved inside the workflow via `gsd-tools query init.phase-op` and delegated to agent via `<
|
|
44
|
+
Context files (CLAUDE.md, SUMMARY.md, phase state) are resolved inside the workflow via `gsd-tools query init.phase-op` and delegated to agent via `<required_reading>` blocks.
|
|
45
45
|
</context>
|
|
46
46
|
|
|
47
47
|
<process>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: gsd-execute-phase
|
|
3
3
|
description: "Execute all plans in a phase with wave-based parallelization"
|
|
4
4
|
argument-hint: "<phase-number> [--wave N] [--gaps-only] [--interactive] [--tdd]"
|
|
5
|
-
effort: high
|
|
6
5
|
allowed-tools:
|
|
7
6
|
- Read
|
|
8
7
|
- Write
|
|
@@ -56,7 +55,7 @@ Phase: $ARGUMENTS
|
|
|
56
55
|
- If none of these tokens appear, run the standard full-phase execution flow with no flag-specific filtering
|
|
57
56
|
- Do not infer that a flag is active just because it is documented in this prompt
|
|
58
57
|
|
|
59
|
-
Context files are resolved inside the workflow via `gsd-tools query init.execute-phase` and per-subagent `<
|
|
58
|
+
Context files are resolved inside the workflow via `gsd-tools query init.execute-phase` and per-subagent `<required_reading>` blocks.
|
|
60
59
|
</context>
|
|
61
60
|
|
|
62
61
|
<process>
|
|
@@ -34,7 +34,7 @@ Output: .planning/codebase/ folder with 7 structured documents about the codebas
|
|
|
34
34
|
Arguments: $ARGUMENTS
|
|
35
35
|
|
|
36
36
|
Parse the first token of $ARGUMENTS:
|
|
37
|
-
- If it is `--fast`: strip the flag,
|
|
37
|
+
- If it is `--fast`: strip the flag, then read and execute `~/.claude/gsd-core/workflows/scan.md` (passing remaining args including optional --focus). Load it on demand here — it is deliberately not in `<execution_context>`, so the common full-map path does not pay for it.
|
|
38
38
|
- If it is `--query`: strip the flag, run the intel workflow (passing remaining args as the subcommand).
|
|
39
39
|
- Otherwise: pass all of $ARGUMENTS as focus area to the map-codebase workflow.
|
|
40
40
|
|
|
@@ -86,7 +86,7 @@ On any error or timeout, stop and let the phase continue -- capture is best-effo
|
|
|
86
86
|
# Mine with --wing only — no --room flag; detect_room() assigns from the folder path
|
|
87
87
|
mempalace mine "$STAGE" --wing <wing>
|
|
88
88
|
```
|
|
89
|
-
3. **Mirror KG facts**
|
|
89
|
+
3. **Mirror KG facts** unless `config.mempalace.mirror_kg === false` (registry default is true — an absent key means enabled): extract decision/delivery facts and `mempalace_kg_add` them with `valid_from` = the phase date (e.g. `(<project>, decided, <decision>)` from CONTEXT; `(<phase>, delivered, <capability>)` from SUMMARY). Under `augment` these are an *additive* mirror of GSD's native `.planning/graphs/`. Under `kg_backend`/`replace` the palace KG is the *authoritative* fact store — GSD still produces `.planning/graphs/` through its normal graphify, so an unreachable palace never loses a fact.
|
|
90
90
|
4. Re-running a phase MUST NOT create duplicate drawers (deterministic ids + `check_duplicate`).
|
|
91
91
|
|
|
92
92
|
## Step 4 -- Report
|
|
@@ -66,7 +66,7 @@ All calls in this step are side-effect-free. On any error or timeout, stop retri
|
|
|
66
66
|
2. **Targeted search:**
|
|
67
67
|
- Interactive: `mempalace_search(query=<topic>, wing=<wing>)`.
|
|
68
68
|
- Headless: `mempalace search "<topic>" --wing <wing>`.
|
|
69
|
-
3. **Knowledge-graph facts** (
|
|
69
|
+
3. **Knowledge-graph facts** (unless `config.mempalace.mirror_kg !== false` — registry default is true, an absent key means enabled): `mempalace_kg_query` / `mempalace_kg_timeline` for decisions relevant to the topic and their validity windows. Under `augment` the palace KG *supplements* GSD's native `.planning/graphs/` — combine both, do not treat the palace as the sole source. Under `kg_backend` or `replace` the palace KG is the *primary* graph source — query it first and use `.planning/graphs/` only as a fallback when the palace is unreachable.
|
|
70
70
|
4. **Dedup** the returned drawers/facts; keep the top results.
|
|
71
71
|
|
|
72
72
|
## Step 4 -- Write MEMORY-RECALL.md
|
|
@@ -36,7 +36,7 @@ Brownfield equivalent of new-project. Project exists, PROJECT.md has history. Ga
|
|
|
36
36
|
<context>
|
|
37
37
|
Milestone name: $ARGUMENTS (optional - will prompt if not provided)
|
|
38
38
|
|
|
39
|
-
Project and milestone context files are resolved inside the workflow (`init new-milestone`) and delegated via `<
|
|
39
|
+
Project and milestone context files are resolved inside the workflow (`init new-milestone`) and delegated via `<required_reading>` blocks where subagents are used.
|
|
40
40
|
</context>
|
|
41
41
|
|
|
42
42
|
<process>
|
package/skills/gsd-next/SKILL.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: gsd-plan-phase
|
|
3
3
|
description: "Create detailed phase plan (PLAN.md) with verification loop"
|
|
4
4
|
argument-hint: "[phase] [--auto] [--research] [--skip-research] [--research-phase <N>] [--view] [--gaps] [--skip-verify] [--skip-ui] [--prd <file>] [--ingest <path-or-glob>] [--ingest-format <auto|nygard|madr|narrative>] [--reviews] [--text] [--bounce] [--skip-bounce] [--chunked] [--tdd] [--mvp] [--granularity <coarse|standard|fine>] [--no-tracer] [--no-reversibility-gates]"
|
|
5
|
-
effort: high
|
|
6
5
|
allowed-tools:
|
|
7
6
|
- Read
|
|
8
7
|
- Write
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: gsd-progress
|
|
3
3
|
description: "Check progress, advance workflow, or dispatch freeform intent — the unified GSD situational command"
|
|
4
4
|
argument-hint: "[--forensic | --next [--auto] [--converge] | --do \\\"task description\\\"]"
|
|
5
|
-
effort: low
|
|
6
5
|
allowed-tools:
|
|
7
6
|
- Read
|
|
8
7
|
- Bash
|
|
@@ -46,7 +46,7 @@ Granular flags are composable: `--discuss --research --validate` gives the same
|
|
|
46
46
|
<context>
|
|
47
47
|
$ARGUMENTS
|
|
48
48
|
|
|
49
|
-
Context files are resolved inside the workflow (`init quick`) and delegated via `<
|
|
49
|
+
Context files are resolved inside the workflow (`init quick`) and delegated via `<required_reading>` blocks.
|
|
50
50
|
</context>
|
|
51
51
|
|
|
52
52
|
<process>
|
|
@@ -18,7 +18,8 @@ milestone sequence or remove stale entries.
|
|
|
18
18
|
|
|
19
19
|
1. **List backlog items:**
|
|
20
20
|
```bash
|
|
21
|
-
|
|
21
|
+
_BACKLOG=( .planning/phases/999* )
|
|
22
|
+
if [ -e "${_BACKLOG[0]}" ]; then ls -d "${_BACKLOG[@]}"; else echo "No backlog items found"; fi
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
2. **Read ROADMAP.md** and extract all 999.x phase entries:
|