@opengsd/gsd-core 1.13.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.
Files changed (257) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-advisor-researcher.compact.md +85 -0
  4. package/agents/gsd-ai-researcher.compact.md +96 -0
  5. package/agents/gsd-assumptions-analyzer.compact.md +81 -0
  6. package/agents/gsd-code-fixer.compact.md +458 -0
  7. package/agents/gsd-code-fixer.md +5 -5
  8. package/agents/gsd-code-reviewer.compact.md +269 -0
  9. package/agents/gsd-code-reviewer.md +15 -3
  10. package/agents/gsd-codebase-mapper.compact.md +760 -0
  11. package/agents/gsd-debug-session-manager.compact.md +345 -0
  12. package/agents/gsd-doc-classifier.compact.md +192 -0
  13. package/agents/gsd-doc-synthesizer.compact.md +200 -0
  14. package/agents/gsd-doc-verifier.compact.md +143 -0
  15. package/agents/gsd-doc-writer.compact.md +440 -0
  16. package/agents/gsd-dom-verifier.compact.md +138 -0
  17. package/agents/gsd-domain-researcher.compact.md +141 -0
  18. package/agents/gsd-eval-auditor.compact.md +160 -0
  19. package/agents/gsd-eval-planner.compact.md +137 -0
  20. package/agents/gsd-framework-selector.compact.md +82 -0
  21. package/agents/gsd-integration-checker.compact.md +245 -0
  22. package/agents/gsd-intel-updater.compact.md +226 -0
  23. package/agents/gsd-mempalace-curator.compact.md +45 -0
  24. package/agents/gsd-nyquist-auditor.compact.md +179 -0
  25. package/agents/gsd-pattern-mapper.compact.md +275 -0
  26. package/agents/gsd-project-researcher.compact.md +587 -0
  27. package/agents/gsd-research-synthesizer.compact.md +212 -0
  28. package/agents/gsd-roadmapper.compact.md +454 -0
  29. package/agents/gsd-roadmapper.md +13 -0
  30. package/agents/gsd-security-auditor.compact.md +162 -0
  31. package/agents/gsd-ui-auditor.compact.md +404 -0
  32. package/agents/gsd-ui-checker.compact.md +277 -0
  33. package/agents/gsd-ui-researcher.compact.md +282 -0
  34. package/agents/gsd-user-profiler.compact.md +108 -0
  35. package/bin/install.js +206 -68
  36. package/commands/gsd/cleanup.md +1 -0
  37. package/commands/gsd/code-review.md +2 -1
  38. package/commands/gsd/complete-milestone.md +1 -0
  39. package/commands/gsd/config.md +1 -0
  40. package/commands/gsd/debug.md +1 -0
  41. package/commands/gsd/graphify.md +1 -0
  42. package/commands/gsd/health.md +1 -0
  43. package/commands/gsd/mempalace-capture.md +1 -0
  44. package/commands/gsd/mempalace-recall.md +1 -0
  45. package/commands/gsd/new-milestone.md +1 -0
  46. package/commands/gsd/new-project.md +1 -0
  47. package/commands/gsd/next.md +1 -0
  48. package/commands/gsd/pause-work.md +1 -0
  49. package/commands/gsd/phase.md +1 -0
  50. package/commands/gsd/pr-branch.md +1 -0
  51. package/commands/gsd/resume-work.md +1 -0
  52. package/commands/gsd/review-backlog.md +1 -0
  53. package/commands/gsd/settings.md +2 -1
  54. package/commands/gsd/stats.md +1 -0
  55. package/commands/gsd/thread.md +1 -0
  56. package/commands/gsd/workspace.md +1 -0
  57. package/commands/gsd/workstreams.md +1 -0
  58. package/gsd-core/bin/check-latest-version.cjs +8 -3
  59. package/gsd-core/bin/gsd-tools.cjs +338 -125
  60. package/gsd-core/bin/lib/adr-parser.cjs +1 -1
  61. package/gsd-core/bin/lib/artifacts.cjs +2 -1
  62. package/gsd-core/bin/lib/audit.cjs +39 -22
  63. package/gsd-core/bin/lib/broken-windows.cjs +168 -49
  64. package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
  65. package/gsd-core/bin/lib/capability-loader.cjs +135 -1
  66. package/gsd-core/bin/lib/capability-registry.cjs +79 -67
  67. package/gsd-core/bin/lib/capability-source.cjs +19 -2
  68. package/gsd-core/bin/lib/capability-validator.cjs +14 -1
  69. package/gsd-core/bin/lib/check-command-router.cjs +113 -36
  70. package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
  71. package/gsd-core/bin/lib/commands.cjs +650 -72
  72. package/gsd-core/bin/lib/config-loader.cjs +1 -0
  73. package/gsd-core/bin/lib/config.cjs +153 -38
  74. package/gsd-core/bin/lib/coverage.cjs +1 -1
  75. package/gsd-core/bin/lib/decisions.cjs +137 -34
  76. package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
  77. package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
  78. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
  79. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
  80. package/gsd-core/bin/lib/init.cjs +409 -47
  81. package/gsd-core/bin/lib/install-engine.cjs +16 -3
  82. package/gsd-core/bin/lib/install-profiles.cjs +14 -0
  83. package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
  84. package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
  85. package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
  86. package/gsd-core/bin/lib/milestone.cjs +19 -8
  87. package/gsd-core/bin/lib/model-resolver.cjs +101 -10
  88. package/gsd-core/bin/lib/phase-command-router.cjs +7 -1
  89. package/gsd-core/bin/lib/phase-id.cjs +161 -22
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
  91. package/gsd-core/bin/lib/phase.cjs +167 -63
  92. package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
  93. package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
  94. package/gsd-core/bin/lib/planning-workspace.cjs +50 -1
  95. package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
  96. package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
  97. package/gsd-core/bin/lib/quick-batch.cjs +1 -1
  98. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
  99. package/gsd-core/bin/lib/research-store.cjs +11 -12
  100. package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
  101. package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
  102. package/gsd-core/bin/lib/roadmap-parser.cjs +56 -15
  103. package/gsd-core/bin/lib/roadmap.cjs +108 -14
  104. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +27 -10
  105. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +12 -3
  106. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
  107. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +193 -4
  108. package/gsd-core/bin/lib/security.cjs +126 -7
  109. package/gsd-core/bin/lib/state-document.cjs +130 -28
  110. package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
  111. package/gsd-core/bin/lib/state-transition.cjs +142 -28
  112. package/gsd-core/bin/lib/state.cjs +223 -27
  113. package/gsd-core/bin/lib/surface.cjs +60 -2
  114. package/gsd-core/bin/lib/task-command-router.cjs +12 -6
  115. package/gsd-core/bin/lib/uat.cjs +1 -1
  116. package/gsd-core/bin/lib/update-context.cjs +30 -24
  117. package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
  118. package/gsd-core/bin/lib/verification.cjs +47 -15
  119. package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
  120. package/gsd-core/bin/lib/verify.cjs +188 -23
  121. package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
  122. package/gsd-core/bin/lib/worktree-safety.cjs +13 -7
  123. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  124. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  125. package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
  126. package/gsd-core/references/compact-content-gate.md +66 -0
  127. package/gsd-core/references/loop-hook-dispatch.md +18 -0
  128. package/gsd-core/references/model-profiles.md +12 -3
  129. package/gsd-core/references/planning-config.md +3 -0
  130. package/gsd-core/references/tdd.md +5 -2
  131. package/gsd-core/references/thinking-models-planning.md +18 -2
  132. package/gsd-core/references/verification-patterns.md +17 -4
  133. package/gsd-core/references/worktree-path-safety.md +112 -2
  134. package/gsd-core/templates/README.md +7 -1
  135. package/gsd-core/templates/state.md +6 -3
  136. package/gsd-core/templates/summary.compact.md +212 -0
  137. package/gsd-core/templates/user-setup.compact.md +199 -0
  138. package/gsd-core/templates/user-setup.md +0 -9
  139. package/gsd-core/workflows/add-todo.md +3 -2
  140. package/gsd-core/workflows/autonomous.md +13 -10
  141. package/gsd-core/workflows/check-todos.md +4 -2
  142. package/gsd-core/workflows/cleanup.md +3 -1
  143. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +7 -0
  144. package/gsd-core/workflows/code-review-fix.md +3 -3
  145. package/gsd-core/workflows/code-review.md +156 -30
  146. package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
  147. package/gsd-core/workflows/complete-milestone.md +39 -262
  148. package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
  149. package/gsd-core/workflows/docs-update.md +14 -155
  150. package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
  151. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +18 -3
  152. package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
  153. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +7 -2
  154. package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
  155. package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
  156. package/gsd-core/workflows/execute-phase.md +53 -152
  157. package/gsd-core/workflows/execute-plan.md +20 -7
  158. package/gsd-core/workflows/help/modes/full.compact.md +398 -0
  159. package/gsd-core/workflows/help.md +1 -1
  160. package/gsd-core/workflows/map-codebase.md +50 -3
  161. package/gsd-core/workflows/new-milestone.md +54 -12
  162. package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
  163. package/gsd-core/workflows/new-project.md +32 -202
  164. package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
  165. package/gsd-core/workflows/plan-phase.md +22 -181
  166. package/gsd-core/workflows/pr-branch.md +19 -7
  167. package/gsd-core/workflows/quick.md +8 -1
  168. package/gsd-core/workflows/reapply-patches.md +77 -3
  169. package/gsd-core/workflows/settings.md +18 -5
  170. package/gsd-core/workflows/update.md +7 -5
  171. package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
  172. package/gsd-core/workflows/verify-work.md +20 -180
  173. package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
  174. package/hooks/dist/gsd-context-monitor.js +88 -15
  175. package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
  176. package/hooks/dist/gsd-secret-read-guard.js +44 -18
  177. package/hooks/dist/gsd-statusline.js +11 -7
  178. package/hooks/dist/gsd-validate-commit.sh +34 -4
  179. package/hooks/dist/gsd-worktree-path-guard.js +25 -14
  180. package/hooks/dist/gsd-write-guard.js +46 -1
  181. package/hooks/dist/lib/dispatch-identity.js +187 -0
  182. package/hooks/dist/lib/filename-classification.js +64 -0
  183. package/hooks/dist/lib/isolation-deny-reason.js +53 -1
  184. package/hooks/dist/lib/isolation-sentinel.js +58 -19
  185. package/hooks/gsd-agent-isolation-guard.js +42 -16
  186. package/hooks/gsd-context-monitor.js +88 -15
  187. package/hooks/gsd-cursor-subagent-start.js +34 -14
  188. package/hooks/gsd-secret-read-guard.js +44 -18
  189. package/hooks/gsd-statusline.js +11 -7
  190. package/hooks/gsd-validate-commit.sh +34 -4
  191. package/hooks/gsd-worktree-path-guard.js +25 -14
  192. package/hooks/gsd-write-guard.js +46 -1
  193. package/hooks/lib/dispatch-identity.js +187 -0
  194. package/hooks/lib/filename-classification.js +64 -0
  195. package/hooks/lib/isolation-deny-reason.js +53 -1
  196. package/hooks/lib/isolation-sentinel.js +58 -19
  197. package/package.json +10 -6
  198. package/scripts/benchmark-compact-content-variants.cjs +298 -0
  199. package/scripts/benchmark-compact-content.cjs +368 -0
  200. package/scripts/check-contract-drift.cjs +4 -1
  201. package/scripts/check-env.cjs +36 -8
  202. package/scripts/check-glossary-refs.cjs +25 -21
  203. package/scripts/ci-next-health.cjs +271 -0
  204. package/scripts/ci-prepare-test-scope.cjs +7 -7
  205. package/scripts/ci-test-scope.cjs +126 -20
  206. package/scripts/ci-timeout-report.cjs +1 -1
  207. package/scripts/diff-touches-shipped-paths.cjs +1 -1
  208. package/scripts/docs-guard-registry.cjs +7 -2
  209. package/scripts/gen-adr-index.cjs +8 -2
  210. package/scripts/gen-inventory-manifest.cjs +12 -0
  211. package/scripts/gen-platform-conformance-tier.cjs +557 -0
  212. package/scripts/lib/drift-scan.cjs +1 -1
  213. package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
  214. package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
  215. package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
  216. package/scripts/lib/suite-detection.cjs +32 -0
  217. package/scripts/lint-allowed-tools-parity.cjs +221 -0
  218. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +19 -2
  219. package/scripts/lint-phase-id-drift.cjs +338 -13
  220. package/scripts/lint-response-language-coverage.cjs +9 -3
  221. package/scripts/lint-source-test-name-collision.cjs +1 -1
  222. package/scripts/lint-test-file-count.allowlist.json +1 -0
  223. package/scripts/lint-vendored-deps.cjs +128 -17
  224. package/scripts/lint-workflow-shellcheck-baseline.json +85 -0
  225. package/scripts/prompt-injection-scan.sh +14 -0
  226. package/scripts/workflow-size.cjs +139 -0
  227. package/skills/gsd-cleanup/SKILL.md +1 -0
  228. package/skills/gsd-code-review/SKILL.md +2 -1
  229. package/skills/gsd-complete-milestone/SKILL.md +1 -0
  230. package/skills/gsd-config/SKILL.md +1 -0
  231. package/skills/gsd-debug/SKILL.md +1 -0
  232. package/skills/gsd-graphify/SKILL.md +1 -0
  233. package/skills/gsd-health/SKILL.md +1 -0
  234. package/skills/gsd-mempalace-capture/SKILL.md +1 -0
  235. package/skills/gsd-mempalace-recall/SKILL.md +1 -0
  236. package/skills/gsd-new-milestone/SKILL.md +1 -0
  237. package/skills/gsd-new-project/SKILL.md +1 -0
  238. package/skills/gsd-next/SKILL.md +1 -0
  239. package/skills/gsd-pause-work/SKILL.md +1 -0
  240. package/skills/gsd-phase/SKILL.md +1 -0
  241. package/skills/gsd-pr-branch/SKILL.md +1 -0
  242. package/skills/gsd-resume-work/SKILL.md +1 -0
  243. package/skills/gsd-review-backlog/SKILL.md +1 -0
  244. package/skills/gsd-settings/SKILL.md +2 -1
  245. package/skills/gsd-stats/SKILL.md +1 -0
  246. package/skills/gsd-thread/SKILL.md +1 -0
  247. package/skills/gsd-workspace/SKILL.md +1 -0
  248. package/skills/gsd-workstreams/SKILL.md +1 -0
  249. package/vscode/package.json +1 -1
  250. package/gsd-core/templates/claude-md.md +0 -145
  251. package/gsd-core/templates/codebase/concerns.md +0 -310
  252. package/gsd-core/templates/codebase/conventions.md +0 -307
  253. package/gsd-core/templates/codebase/integrations.md +0 -280
  254. package/gsd-core/templates/codebase/structure.md +0 -285
  255. package/gsd-core/templates/codebase/testing.md +0 -480
  256. package/gsd-core/templates/debug-subagent-prompt.md +0 -91
  257. package/gsd-core/templates/discovery.md +0 -146
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengsd/gsd-core",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
5
5
  "main": ".opencode/plugins/gsd-core.js",
6
6
  "bin": {
@@ -75,7 +75,8 @@
75
75
  "espree": "^10.4.0",
76
76
  "fast-check": "^4.8.0",
77
77
  "globals": "^16.5.0",
78
- "js-yaml": "^4.3.1",
78
+ "gpt-tokenizer": "4.0.0",
79
+ "js-yaml": "^4.3.2",
79
80
  "mutation-testing-metrics": "^3.7.3",
80
81
  "re2js": "^2.8.6",
81
82
  "typescript": "^6.0.3",
@@ -84,7 +85,8 @@
84
85
  "overrides": {
85
86
  "qs": ">=6.15.2",
86
87
  "body-parser": ">=2.3.0",
87
- "@hono/node-server": ">=2.0.5"
88
+ "@hono/node-server": ">=2.0.5",
89
+ "hono": ">=4.13.5"
88
90
  },
89
91
  "optionalDependencies": {
90
92
  "fallow": "^2.70.0"
@@ -108,7 +110,7 @@
108
110
  "gen:registry": "node scripts/gen-registry.cjs --write",
109
111
  "gen:install-tree": "node scripts/gen-install-tree-fixtures.cjs",
110
112
  "gen:section-manifest": "node scripts/gen-section-manifest.cjs --write",
111
- "regen:derived": "npm run build && npm run gen:registry && node scripts/gen-adr-index.cjs --write && node scripts/gen-features.cjs --write && node scripts/gen-capability-matrix.cjs --write && node scripts/gen-inventory-manifest.cjs --write && node scripts/gen-context-index.cjs --write && node scripts/gen-state-md-docs.cjs --write && npm run gen:section-manifest && node scripts/sync-manifest-versions.cjs && npm run gen:install-tree && node scripts/gen-scripts-cli-exit.cjs --write && node scripts/gen-hooks-cli-exit.cjs --write && node scripts/gen-exit-code-registry.cjs --write && node scripts/gen-exit-code-docs.cjs --write",
113
+ "regen:derived": "npm run build && npm run gen:registry && node scripts/gen-adr-index.cjs --write && node scripts/gen-features.cjs --write && node scripts/gen-capability-matrix.cjs --write && node scripts/gen-inventory-manifest.cjs --write && node scripts/gen-context-index.cjs --write && node scripts/gen-state-md-docs.cjs --write && npm run gen:section-manifest && node scripts/sync-manifest-versions.cjs && npm run gen:install-tree && node scripts/gen-scripts-cli-exit.cjs --write && node scripts/gen-hooks-cli-exit.cjs --write && node scripts/gen-exit-code-registry.cjs --write && node scripts/gen-exit-code-docs.cjs --write && node scripts/gen-platform-conformance-tier.cjs --write && node scripts/gen-platform-conformance-tier.cjs --target macos --write",
112
114
  "validate:registry": "node scripts/validate-registry.cjs",
113
115
  "prepack": "npm run build:lib",
114
116
  "prepare": "npm run build:lib",
@@ -122,7 +124,7 @@
122
124
  "lint:frontmatter-scalar-broad-grep": "node scripts/lint-frontmatter-scalar-broad-grep.cjs",
123
125
  "lint:removed-but-needed": "node scripts/lint-removed-but-needed.cjs",
124
126
  "lint:response-language": "node scripts/lint-response-language-coverage.cjs",
125
- "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-portable-timeout.cjs && node scripts/lint-portable-grep.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-fix-has-regression-tests.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-unreachable-guard-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-slug-derivation-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs && node scripts/lint-vendored-deps.cjs && node scripts/lint-docs-guard-registration.cjs && node scripts/lint-source-test-name-collision.cjs && npm run lint:hooks-runtime-build-seam && node scripts/check-contract-drift.cjs && node scripts/lint-mutation-test-derivation-drift.cjs && node scripts/lint-seam-enforcement.cjs && node scripts/lint-workflow-shellcheck.cjs && npm run lint:response-language",
127
+ "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-portable-timeout.cjs && node scripts/lint-portable-grep.cjs && node scripts/lint-allowed-tools-parity.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-fix-has-regression-tests.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-unreachable-guard-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-slug-derivation-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs && node scripts/lint-vendored-deps.cjs && node scripts/lint-docs-guard-registration.cjs && node scripts/lint-source-test-name-collision.cjs && npm run lint:hooks-runtime-build-seam && node scripts/check-contract-drift.cjs && node scripts/lint-mutation-test-derivation-drift.cjs && node scripts/lint-seam-enforcement.cjs && node scripts/lint-workflow-shellcheck.cjs && npm run lint:response-language",
126
128
  "lint:allow-test-rule-refs": "node scripts/lint-allow-test-rule-refs.cjs",
127
129
  "lint:regression-names": "node scripts/lint-regression-test-names.cjs",
128
130
  "lint:descriptions": "node scripts/lint-descriptions.cjs",
@@ -130,7 +132,7 @@
130
132
  "lint:test-file-count": "node scripts/lint-test-file-count.cjs",
131
133
  "lint:pr-checks": "node scripts/lint-pr-check-project-dir.cjs",
132
134
  "lint:changeset": "node scripts/changeset/lint.cjs",
133
- "lint:generated-sync": "node scripts/gen-capability-registry.cjs --check && node scripts/gen-loop-host-contract.cjs --check && node scripts/gen-capability-matrix.cjs --check && node scripts/sync-manifest-versions.cjs --check && node scripts/gen-inventory-manifest.cjs --check && node scripts/generate-package-identity.cjs --check && node scripts/gen-plugin-skills.cjs --check && node scripts/gen-registry.cjs --check && node scripts/gen-adr-index.cjs --check && node scripts/gen-features.cjs --check && node scripts/check-glossary-refs.cjs --check && node scripts/lint-compiled-artifact-sync.cjs --check && node scripts/gen-context-index.cjs --check && node scripts/gen-section-manifest.cjs --check && node scripts/gen-health-docs.cjs --check && node scripts/gen-state-md-docs.cjs --check && node scripts/gen-scripts-cli-exit.cjs --check && node scripts/gen-hooks-cli-exit.cjs --check && node scripts/gen-exit-code-registry.cjs --check && node scripts/gen-exit-code-docs.cjs --check",
135
+ "lint:generated-sync": "node scripts/gen-capability-registry.cjs --check && node scripts/gen-loop-host-contract.cjs --check && node scripts/gen-capability-matrix.cjs --check && node scripts/sync-manifest-versions.cjs --check && node scripts/gen-inventory-manifest.cjs --check && node scripts/generate-package-identity.cjs --check && node scripts/gen-plugin-skills.cjs --check && node scripts/gen-registry.cjs --check && node scripts/gen-adr-index.cjs --check && node scripts/gen-features.cjs --check && node scripts/check-glossary-refs.cjs --check && node scripts/lint-compiled-artifact-sync.cjs --check && node scripts/gen-context-index.cjs --check && node scripts/gen-section-manifest.cjs --check && node scripts/gen-health-docs.cjs --check && node scripts/gen-state-md-docs.cjs --check && node scripts/gen-scripts-cli-exit.cjs --check && node scripts/gen-hooks-cli-exit.cjs --check && node scripts/gen-exit-code-registry.cjs --check && node scripts/gen-exit-code-docs.cjs --check && node scripts/gen-platform-conformance-tier.cjs --check && node scripts/gen-platform-conformance-tier.cjs --target macos --check",
134
136
  "lint:docs": "node scripts/lint-docs-required.cjs",
135
137
  "lint:qa-smells": "node scripts/qa-smell-ratchet.cjs",
136
138
  "lint:legacy-name": "node scripts/lint-legacy-dir-name.cjs",
@@ -138,6 +140,8 @@
138
140
  "lint:docs-command-form": "node scripts/lint-docs-command-form.cjs",
139
141
  "lint:hooks-runtime-build-seam": "node scripts/lint-hooks-runtime-build-seam.cjs",
140
142
  "ci:test-scope": "node scripts/ci-test-scope.cjs",
143
+ "benchmark:compact-content": "node scripts/benchmark-compact-content.cjs --check",
144
+ "benchmark:compact-content-variants": "node scripts/benchmark-compact-content-variants.cjs --check",
141
145
  "changeset": "node scripts/changeset/new.cjs",
142
146
  "changelog:render": "node scripts/changeset/cli.cjs render",
143
147
  "test": "node scripts/run-tests.cjs",
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Benchmarks the token-count effect of every registered variant-swap pair
6
+ * (ADR-4139, Phase 6 #4406 — `gsd-core/workflows/<name>/{modes,steps,templates}/*.compact.md`
7
+ * and `gsd-core/templates/**\/*.compact.md`). Sibling to
8
+ * `scripts/benchmark-compact-content.cjs` (Phase 3's spine/detail benchmark) rather than an
9
+ * extension of it — the two are different data shapes (a variant pair is two independent,
10
+ * deliberately-overlapping complete files; a spine/detail split is one document partitioned in
11
+ * two disjoint halves), and mixing them into one report would conflate an "off" total that means
12
+ * something different in each case.
13
+ *
14
+ * For every registered pair it reports the "off" token count (the canonical file — what
15
+ * `workflow.compact_content=false`, the default, pays at that call site) against the "on" token
16
+ * count (the `.compact.md` sibling — what `workflow.compact_content=true` pays once the gate
17
+ * resolves to it). See `gsd-core/references/compact-content-gate.md` §"Streams 1b and 4" for the
18
+ * resolution rule this measures.
19
+ *
20
+ * PROXY-TOKENIZER CAVEAT: same as the sibling script — `gpt-tokenizer` is a stand-in tokenizer;
21
+ * Anthropic publishes none for Claude 3+. The on/off comparison is exact under this one pinned
22
+ * tokenizer applied identically to both sides; the absolute counts are not Claude's real counts.
23
+ *
24
+ * Discovery is REIMPLEMENTED here rather than imported from
25
+ * `tests/helpers/compact-content-variant.cjs`, for the same reason
26
+ * `benchmark-compact-content.cjs` reimplements spine/detail discovery instead of importing it: a
27
+ * `scripts/` reporting tool depending on a test-only helper module inverts this repo's normal
28
+ * layering, and a test-only module changing shape should never be able to break a benchmark.
29
+ *
30
+ * Usage:
31
+ * node scripts/benchmark-compact-content-variants.cjs # print JSON to stdout
32
+ * node scripts/benchmark-compact-content-variants.cjs --write # write the committed baseline
33
+ * node scripts/benchmark-compact-content-variants.cjs --check # recompute, diff vs committed baseline
34
+ * node scripts/benchmark-compact-content-variants.cjs --check --baseline-path=<path>
35
+ *
36
+ * Same CRITICAL contract as the sibling script: this — and `--check` especially — MUST NEVER
37
+ * exit non-zero because a baseline is drifted, stale, or missing. Only a genuine I/O error
38
+ * reading a SOURCE file the benchmark measures may throw. This is a reporting instrument, never
39
+ * a gate.
40
+ */
41
+
42
+ const fs = require('node:fs');
43
+ const path = require('node:path');
44
+
45
+ const { countTokens } = require('gpt-tokenizer');
46
+ const { runMain } = require('./lib/cli-exit.cjs');
47
+
48
+ const ROOT = path.resolve(__dirname, '..');
49
+ // #4407: agents/ joins the scan. Reachability there is a code seam
50
+ // (cmdAgentSkills), not a markdown literal reference, but token accounting
51
+ // doesn't care how a pair is reached — only that it's registered.
52
+ const VARIANT_ROOTS = [
53
+ path.join(ROOT, 'gsd-core', 'workflows'),
54
+ path.join(ROOT, 'gsd-core', 'templates'),
55
+ path.join(ROOT, 'agents'),
56
+ ];
57
+ const BASELINE_PATH = path.join(ROOT, 'tests', 'fixtures', 'compact-content-variant-benchmark-baseline.json');
58
+ const COMPACT_SUFFIX = '.compact.md';
59
+
60
+ function getTokenizerVersion() {
61
+ const pkgPath = require.resolve('gpt-tokenizer/package.json');
62
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
63
+ return pkg.version;
64
+ }
65
+
66
+ /**
67
+ * Discover every registered variant pair under `roots`: a `.compact.md` file
68
+ * with a same-directory, same-stem canonical `.md` sibling. Mirrors
69
+ * `tests/helpers/compact-content-variant.cjs`'s `discoverRegisteredVariants`
70
+ * in shape but is a from-scratch, self-contained implementation (see module
71
+ * header for why this is not a shared import). Skips an orphaned compact
72
+ * file with no canonical sibling — that is the guard's problem, not this
73
+ * benchmark's; a pair with no canonical baseline has no "off" number to
74
+ * report against.
75
+ *
76
+ * @param {string[]} [roots]
77
+ * @returns {Array<{name: string, canonicalPath: string, compactPath: string}>}
78
+ */
79
+ function discoverRegisteredVariants(roots = VARIANT_ROOTS) {
80
+ const pairs = [];
81
+
82
+ function walk(dir) {
83
+ let entries;
84
+ try {
85
+ entries = fs.readdirSync(dir, { withFileTypes: true });
86
+ } catch {
87
+ return;
88
+ }
89
+ for (const entry of entries) {
90
+ const full = path.join(dir, entry.name);
91
+ if (entry.isDirectory()) {
92
+ walk(full);
93
+ } else if (entry.isFile() && entry.name.endsWith(COMPACT_SUFFIX)) {
94
+ const stem = entry.name.slice(0, -COMPACT_SUFFIX.length);
95
+ const canonicalPath = path.join(dir, `${stem}.md`);
96
+ if (fs.existsSync(canonicalPath)) {
97
+ const name = path.relative(ROOT, canonicalPath).split(path.sep).join('/');
98
+ pairs.push({ name, canonicalPath, compactPath: full });
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ for (const root of roots) walk(root);
105
+ return pairs.sort((a, b) => a.name.localeCompare(b.name));
106
+ }
107
+
108
+ /**
109
+ * Compute the off/on/reduction numbers for ONE registered variant pair.
110
+ * Throws on a genuine read failure of a source file (the one thing allowed
111
+ * to throw, per the module-header CRITICAL note).
112
+ *
113
+ * @param {{canonicalPath: string, compactPath: string}} pair
114
+ * @returns {{offTokens: number, onTokens: number, reductionPct: number}}
115
+ */
116
+ function computePairTokens(pair) {
117
+ const offTokens = countTokens(fs.readFileSync(pair.canonicalPath, 'utf8'));
118
+ const onTokens = countTokens(fs.readFileSync(pair.compactPath, 'utf8'));
119
+ const reductionPct = offTokens === 0 ? 0 : round2(((offTokens - onTokens) / offTokens) * 100);
120
+ return { offTokens, onTokens, reductionPct };
121
+ }
122
+
123
+ function round2(n) {
124
+ return Math.round(n * 100) / 100;
125
+ }
126
+
127
+ /**
128
+ * Aggregate per-pair numbers from SUMMED off/on totals, never averaged
129
+ * per-pair percentages. Reports `0` (not `NaN`) for zero registered pairs.
130
+ * @param {Record<string, {offTokens: number, onTokens: number}>} pairResults
131
+ */
132
+ function computeAggregate(pairResults) {
133
+ let offTokens = 0;
134
+ let onTokens = 0;
135
+ for (const key of Object.keys(pairResults)) {
136
+ offTokens += pairResults[key].offTokens;
137
+ onTokens += pairResults[key].onTokens;
138
+ }
139
+ const reductionPct = offTokens === 0 ? 0 : round2(((offTokens - onTokens) / offTokens) * 100);
140
+ return { offTokens, onTokens, reductionPct };
141
+ }
142
+
143
+ const LABEL =
144
+ "PROXY-TOKENIZER DELTA — gpt-tokenizer is a stand-in; Anthropic publishes no tokenizer for Claude 3+. " +
145
+ "The on/off COMPARISON is exact under this pinned tokenizer; absolute counts are not Claude's real token counts.";
146
+
147
+ /**
148
+ * @param {string[]} [roots]
149
+ * @returns {object}
150
+ */
151
+ function buildReport(roots = VARIANT_ROOTS) {
152
+ const pairs = discoverRegisteredVariants(roots);
153
+ const pairReports = {};
154
+ for (const pair of pairs) {
155
+ pairReports[pair.name] = computePairTokens(pair);
156
+ }
157
+ return {
158
+ schema_version: 1,
159
+ generated_by: 'scripts/benchmark-compact-content-variants.cjs',
160
+ tokenizer: { name: 'gpt-tokenizer', version: getTokenizerVersion() },
161
+ label: LABEL,
162
+ pairs: pairReports,
163
+ aggregate: computeAggregate(pairReports),
164
+ };
165
+ }
166
+
167
+ /**
168
+ * Format a human-readable drift summary between a (possibly missing/invalid)
169
+ * committed baseline and a freshly-computed live report. Never throws.
170
+ * @param {string} baselinePath
171
+ * @param {object} live
172
+ * @returns {string}
173
+ */
174
+ function formatDriftReport(baselinePath, live) {
175
+ const lines = [];
176
+ let baseline = null;
177
+ let baselineReadError = null;
178
+ try {
179
+ const raw = fs.readFileSync(baselinePath, 'utf8');
180
+ try {
181
+ baseline = JSON.parse(raw);
182
+ } catch (parseErr) {
183
+ baselineReadError = `baseline at ${baselinePath} could not be parsed as JSON: ${parseErr.message}`;
184
+ }
185
+ } catch {
186
+ baselineReadError = `no baseline found at ${baselinePath}`;
187
+ }
188
+
189
+ if (baselineReadError) {
190
+ lines.push(`DRIFT: ${baselineReadError} — treating as fully drifted (this is reported, not an error).`);
191
+ lines.push('Live pairs:');
192
+ for (const name of Object.keys(live.pairs).sort()) {
193
+ const p = live.pairs[name];
194
+ lines.push(` + ${name}: off=${p.offTokens} on=${p.onTokens} reduction=${p.reductionPct}%`);
195
+ }
196
+ lines.push(
197
+ `Live aggregate: off=${live.aggregate.offTokens} on=${live.aggregate.onTokens} ` +
198
+ `reduction=${live.aggregate.reductionPct}%`,
199
+ );
200
+ return lines.join('\n');
201
+ }
202
+
203
+ const baselinePairs = (baseline && typeof baseline === 'object' && baseline.pairs) || {};
204
+ const livePairs = live.pairs;
205
+ const allNames = new Set([...Object.keys(baselinePairs), ...Object.keys(livePairs)]);
206
+ let anyDrift = false;
207
+
208
+ if (!baseline || typeof baseline.label !== 'string' || !baseline.label.includes('PROXY-TOKENIZER')) {
209
+ anyDrift = true;
210
+ lines.push('DRIFT: committed baseline is missing the required "PROXY-TOKENIZER" label.');
211
+ }
212
+
213
+ for (const name of [...allNames].sort()) {
214
+ const b = baselinePairs[name];
215
+ const l = livePairs[name];
216
+ if (!b) {
217
+ anyDrift = true;
218
+ lines.push(`DRIFT: pair "${name}" is new (not in committed baseline) — live off=${l.offTokens} on=${l.onTokens} reduction=${l.reductionPct}%`);
219
+ } else if (!l) {
220
+ anyDrift = true;
221
+ lines.push(`DRIFT: pair "${name}" was removed (present in committed baseline, not found live) — baseline off=${b.offTokens} on=${b.onTokens} reduction=${b.reductionPct}%`);
222
+ } else if (b.offTokens !== l.offTokens || b.onTokens !== l.onTokens || b.reductionPct !== l.reductionPct) {
223
+ anyDrift = true;
224
+ lines.push(
225
+ `DRIFT: pair "${name}": off ${b.offTokens} -> ${l.offTokens} (${l.offTokens - b.offTokens >= 0 ? '+' : ''}${l.offTokens - b.offTokens}), ` +
226
+ `on ${b.onTokens} -> ${l.onTokens} (${l.onTokens - b.onTokens >= 0 ? '+' : ''}${l.onTokens - b.onTokens}), ` +
227
+ `reduction ${b.reductionPct}% -> ${l.reductionPct}% (${round2(l.reductionPct - b.reductionPct) >= 0 ? '+' : ''}${round2(l.reductionPct - b.reductionPct)}pp)`,
228
+ );
229
+ }
230
+ }
231
+
232
+ const ba = (baseline && baseline.aggregate) || {};
233
+ const la = live.aggregate;
234
+ if (ba.offTokens !== la.offTokens || ba.onTokens !== la.onTokens || ba.reductionPct !== la.reductionPct) {
235
+ anyDrift = true;
236
+ lines.push(
237
+ `DRIFT: aggregate: off ${ba.offTokens} -> ${la.offTokens}, on ${ba.onTokens} -> ${la.onTokens}, ` +
238
+ `reduction ${ba.reductionPct}% -> ${la.reductionPct}%`,
239
+ );
240
+ }
241
+
242
+ if (!anyDrift) {
243
+ lines.push(`Baseline at ${baselinePath} is up to date with the live recompute.`);
244
+ } else {
245
+ lines.push('');
246
+ lines.push('Run `node scripts/benchmark-compact-content-variants.cjs --write` to refresh the committed baseline.');
247
+ lines.push('(This is a REPORT, not a gate — exiting 0 regardless of drift, per this script\'s own contract.)');
248
+ }
249
+ return lines.join('\n');
250
+ }
251
+
252
+ function parseArgs(argv) {
253
+ const opts = { write: false, check: false, baselinePath: BASELINE_PATH };
254
+ for (const arg of argv) {
255
+ if (arg === '--write') opts.write = true;
256
+ else if (arg === '--check') opts.check = true;
257
+ else if (arg.startsWith('--baseline-path=')) opts.baselinePath = arg.slice('--baseline-path='.length);
258
+ }
259
+ return opts;
260
+ }
261
+
262
+ function main() {
263
+ const opts = parseArgs(process.argv.slice(2));
264
+
265
+ if (opts.write) {
266
+ const report = buildReport();
267
+ fs.mkdirSync(path.dirname(BASELINE_PATH), { recursive: true });
268
+ fs.writeFileSync(BASELINE_PATH, JSON.stringify(report, null, 2) + '\n');
269
+ process.stdout.write(`Wrote ${BASELINE_PATH}\n`);
270
+ return;
271
+ }
272
+
273
+ if (opts.check) {
274
+ const live = buildReport();
275
+ process.stdout.write(formatDriftReport(opts.baselinePath, live) + '\n');
276
+ return;
277
+ }
278
+
279
+ process.stdout.write(JSON.stringify(buildReport(), null, 2) + '\n');
280
+ }
281
+
282
+ /* c8 ignore next 3 -- CLI entry guard; this repo measures coverage with c8, which does not honor istanbul pragmas */
283
+ if (require.main === module) {
284
+ runMain(main);
285
+ }
286
+
287
+ module.exports = {
288
+ discoverRegisteredVariants,
289
+ computePairTokens,
290
+ computeAggregate,
291
+ buildReport,
292
+ formatDriftReport,
293
+ getTokenizerVersion,
294
+ parseArgs,
295
+ LABEL,
296
+ BASELINE_PATH,
297
+ VARIANT_ROOTS,
298
+ };