@bongos/core 1.17.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 (1142) hide show
  1. package/.bongos-core.json +5727 -0
  2. package/.claude/skills/blocker-review/SKILL.md +91 -0
  3. package/.claude/skills/blocker-solve/SKILL.md +96 -0
  4. package/.claude/skills/builder-backup/SKILL.md +54 -0
  5. package/.claude/skills/builder-box/SKILL.md +45 -0
  6. package/.claude/skills/builder-claim/SKILL.md +76 -0
  7. package/.claude/skills/builder-connect/SKILL.md +59 -0
  8. package/.claude/skills/builder-cost/SKILL.md +47 -0
  9. package/.claude/skills/builder-end/SKILL.md +81 -0
  10. package/.claude/skills/builder-exit/SKILL.md +88 -0
  11. package/.claude/skills/builder-key/SKILL.md +40 -0
  12. package/.claude/skills/builder-reauth/SKILL.md +50 -0
  13. package/.claude/skills/builder-redteam/SKILL.md +109 -0
  14. package/.claude/skills/builder-release/SKILL.md +41 -0
  15. package/.claude/skills/builder-setup/SKILL.md +62 -0
  16. package/.claude/skills/builder-ship/SKILL.md +79 -0
  17. package/.claude/skills/builder-stage/SKILL.md +51 -0
  18. package/.claude/skills/builder-start/SKILL.md +46 -0
  19. package/.claude/skills/close-box/SKILL.md +43 -0
  20. package/.claude/skills/design-sync/SKILL.md +69 -0
  21. package/.claude/skills/dev/SKILL.md +49 -0
  22. package/.claude/skills/feedback/SKILL.md +32 -0
  23. package/.claude/skills/figma-design-sync/SKILL.md +74 -0
  24. package/.claude/skills/goal-review/SKILL.md +89 -0
  25. package/.claude/skills/idea-triage/SKILL.md +144 -0
  26. package/.claude/skills/ideate/SKILL.md +82 -0
  27. package/.claude/skills/merge-mode/SKILL.md +169 -0
  28. package/.claude/skills/new-project/SKILL.md +108 -0
  29. package/.claude/skills/otb-character-review/SKILL.md +71 -0
  30. package/.claude/skills/otb-design-review/SKILL.md +47 -0
  31. package/.claude/skills/otb-feedback-capture/SKILL.md +45 -0
  32. package/.claude/skills/otb-figma-sync/SKILL.md +54 -0
  33. package/.claude/skills/otb-tile-generate/SKILL.md +45 -0
  34. package/.claude/skills/paint/SKILL.md +70 -0
  35. package/.claude/skills/planning-session/SKILL.md +299 -0
  36. package/.claude/skills/priority-session/SKILL.md +134 -0
  37. package/.claude/skills/read-session-export/SKILL.md +58 -0
  38. package/.claude/skills/recall/SKILL.md +52 -0
  39. package/.claude/skills/session-handoff/SKILL.md +46 -0
  40. package/.claude/skills/status/SKILL.md +33 -0
  41. package/.devcontainer/Dockerfile +69 -0
  42. package/.devcontainer/README.md +73 -0
  43. package/.devcontainer/devcontainer-lock.json +19 -0
  44. package/.devcontainer/devcontainer.json +93 -0
  45. package/.devcontainer/healthcheck.sh +84 -0
  46. package/.devcontainer/init-firewall.sh +198 -0
  47. package/.devcontainer/post-create.sh +153 -0
  48. package/.dockerignore +36 -0
  49. package/.env.local.example +32 -0
  50. package/.gitattributes +83 -0
  51. package/.gitignore +91 -0
  52. package/CLAUDE.md +242 -0
  53. package/CODE_OF_CONDUCT.md +46 -0
  54. package/CONTRIBUTING.md +51 -0
  55. package/Dockerfile +46 -0
  56. package/GOVERNANCE.md +44 -0
  57. package/LICENSE +661 -0
  58. package/README.md +88 -0
  59. package/bin/bongos.js +222 -0
  60. package/clients/bongos-client/README.md +48 -0
  61. package/clients/bongos-client/bongos-client.global.js +617 -0
  62. package/clients/bongos-client/examples/hello-world.mjs +62 -0
  63. package/clients/bongos-client/index.cjs +619 -0
  64. package/clients/bongos-client/index.d.ts +793 -0
  65. package/clients/bongos-client/index.mjs +614 -0
  66. package/clients/bongos-client/package.json +32 -0
  67. package/config/branding.neutral.json +60 -0
  68. package/config/design-tokens.neutral.json +51 -0
  69. package/config/design-tokens.schema.json +96 -0
  70. package/config/modules.neutral.json +9 -0
  71. package/docker-compose.yml +52 -0
  72. package/docker-entrypoint.sh +30 -0
  73. package/docs/adr/0001-stack-choice-single-vps.md +45 -0
  74. package/docs/adr/0002-digitalocean-over-hetzner.md +27 -0
  75. package/docs/adr/0003-cloudflare-origin-cert-over-letsencrypt.md +35 -0
  76. package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +45 -0
  77. package/docs/adr/0005-invisible-other-players-in-v1.md +37 -0
  78. package/docs/adr/0006-singleton-worldroom-plus-queueroom.md +30 -0
  79. package/docs/adr/0007-persistent-identity-via-localstorage-uuid.md +31 -0
  80. package/docs/adr/0008-family-based-tile-generation.md +86 -0
  81. package/docs/adr/0008-google-chat-oauth-user-auth.md +37 -0
  82. package/docs/adr/0009-autotile-architecture.md +181 -0
  83. package/docs/adr/0010-pms-architecture.md +70 -0
  84. package/docs/adr/0011-terrain-id-rendering-model.md +174 -0
  85. package/docs/adr/0012-ground-object-cell-split.md +208 -0
  86. package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +158 -0
  87. package/docs/adr/0014-families-canonical-over-vocabulary.md +103 -0
  88. package/docs/adr/0015-task-dependencies-and-auto-promotion.md +125 -0
  89. package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +132 -0
  90. package/docs/adr/0017-code-simplification-and-repo-cleanliness.md +74 -0
  91. package/docs/adr/0018-three-rank-model-goes-live.md +97 -0
  92. package/docs/adr/0019-red-team-bounty-payout-table.md +52 -0
  93. package/docs/adr/0020-security-model-public-stub.md +70 -0
  94. package/docs/adr/0021-per-builder-skill-model-preferences.md +99 -0
  95. package/docs/adr/0022-secrets-policy.md +124 -0
  96. package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +118 -0
  97. package/docs/adr/0024-cloneable-repo-local-first-memory.md +277 -0
  98. package/docs/adr/0024-multi-agent-system-architecture.md +125 -0
  99. package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +59 -0
  100. package/docs/adr/0025-structured-criterion-task-link.md +44 -0
  101. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +117 -0
  102. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +170 -0
  103. package/docs/adr/0028-honest-llm-spend-accounting.md +109 -0
  104. package/docs/adr/0029-status-page-outage-resilience.md +59 -0
  105. package/docs/adr/0030-builders-hall-live-updates.md +59 -0
  106. package/docs/adr/0031-cloud-dev-environments-for-builders.md +222 -0
  107. package/docs/adr/0032-discord-mirror-of-gds.md +50 -0
  108. package/docs/adr/0033-discord-bot-service-principal.md +82 -0
  109. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +53 -0
  110. package/docs/adr/0035-builder-onboarding-three-paths.md +128 -0
  111. package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +40 -0
  112. package/docs/adr/0037-discord-channel-management.md +51 -0
  113. package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +111 -0
  114. package/docs/adr/0039-setup-first-onboarding-ux.md +49 -0
  115. package/docs/adr/0040-remote-control-default-browser-onramp.md +47 -0
  116. package/docs/adr/0041-temporary-grader-bypass-killswitch.md +40 -0
  117. package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +82 -0
  118. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +74 -0
  119. package/docs/adr/0044-mediterranean-palette-replacement.md +73 -0
  120. package/docs/adr/0044-per-box-live-game-preview.md +57 -0
  121. package/docs/adr/0045-devbox-desktop-app.md +133 -0
  122. package/docs/adr/0046-sandbox-first-review-gate.md +72 -0
  123. package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +36 -0
  124. package/docs/adr/0048-works-in-progress-ia-split.md +32 -0
  125. package/docs/adr/0049-split-parallel-safety-contract.md +46 -0
  126. package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +43 -0
  127. package/docs/adr/0051-full-session-transcript-corpus.md +57 -0
  128. package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +70 -0
  129. package/docs/adr/0053-scoped-dev-box-session.md +55 -0
  130. package/docs/adr/0054-session-token-reward-cost-plus.md +63 -0
  131. package/docs/adr/0055-server-mediated-branch-publish.md +47 -0
  132. package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +62 -0
  133. package/docs/adr/0057-container-cost-ledger.md +28 -0
  134. package/docs/adr/0057-discord-archon-approval-channels.md +64 -0
  135. package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +64 -0
  136. package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +37 -0
  137. package/docs/adr/0060-gds-retrieval-layer.md +97 -0
  138. package/docs/adr/0061-context-layer-decomposition.md +61 -0
  139. package/docs/adr/0062-medusa-instance-model-decoupling.md +107 -0
  140. package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +44 -0
  141. package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +49 -0
  142. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +69 -0
  143. package/docs/adr/0066-generated-file-map-sections.md +51 -0
  144. package/docs/adr/0067-session-start-fast-forward-pull.md +43 -0
  145. package/docs/adr/0068-shared-art-key-for-newcomers.md +38 -0
  146. package/docs/adr/0069-hall-live-channel-sse-decouple.md +35 -0
  147. package/docs/adr/0070-deterministic-session-card-delivery.md +44 -0
  148. package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +59 -0
  149. package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +35 -0
  150. package/docs/adr/0072-dev-box-code-staleness-visibility.md +39 -0
  151. package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +64 -0
  152. package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +37 -0
  153. package/docs/adr/0074-memory-map-stays-light.md +32 -0
  154. package/docs/adr/0075-per-system-cost-attribution.md +37 -0
  155. package/docs/adr/0076-in-session-deterministic-compaction.md +58 -0
  156. package/docs/adr/0077-content-addressed-llm-cache.md +32 -0
  157. package/docs/adr/0078-deterministic-first-cron.md +48 -0
  158. package/docs/adr/0079-value-gated-autonomous-engagement.md +39 -0
  159. package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +46 -0
  160. package/docs/adr/0081-tool-agnostic-design-layer.md +62 -0
  161. package/docs/adr/0082-productized-key-storage-provisioning.md +32 -0
  162. package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +55 -0
  163. package/docs/adr/0083-modular-architecture-strangler-build-plan.md +143 -0
  164. package/docs/adr/0084-auto-derived-per-task-rank-floor.md +86 -0
  165. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +39 -0
  166. package/docs/adr/0086-goal-scoped-work-hierarchy.md +123 -0
  167. package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +49 -0
  168. package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +65 -0
  169. package/docs/adr/0088-consistent-character-generative-animation.md +169 -0
  170. package/docs/adr/0089-modular-multi-model-adversarial-grader.md +179 -0
  171. package/docs/adr/0090-metic-task-authoring.md +73 -0
  172. package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +106 -0
  173. package/docs/adr/0092-hosting-module-and-on-prem-target.md +117 -0
  174. package/docs/adr/0093-tranche-2-core-carve-sequence.md +72 -0
  175. package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +49 -0
  176. package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +65 -0
  177. package/docs/adr/0095-cross-agent-context-management.md +58 -0
  178. package/docs/adr/0096-require-reward-before-workable.md +209 -0
  179. package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +46 -0
  180. package/docs/adr/0097-retroactive-reward-backfill.md +124 -0
  181. package/docs/adr/0098-public-mirror-publish-manifest.md +92 -0
  182. package/docs/adr/0099-delayed-redacted-mirror-export.md +55 -0
  183. package/docs/adr/0100-two-repo-instance-model.md +60 -0
  184. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +90 -0
  185. package/docs/adr/0102-finish-bounding-the-kernel.md +36 -0
  186. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +70 -0
  187. package/docs/adr/0103-gdsv4-bongos-consolidation.md +28 -0
  188. package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +42 -0
  189. package/docs/adr/0105-instance-seed-migrations-out-of-core.md +47 -0
  190. package/docs/adr/0106-goal-owner-manager-authority.md +84 -0
  191. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +83 -0
  192. package/docs/adr/0108-instance-composition-configurable-root.md +87 -0
  193. package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +44 -0
  194. package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +49 -0
  195. package/docs/adr/0111-instance-hosting-provisioning-module.md +135 -0
  196. package/docs/adr/0112-private-goals-invitations-join-requests.md +72 -0
  197. package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +41 -0
  198. package/docs/adr/0114-instance-docs-discovery-contract.md +32 -0
  199. package/docs/adr/0115-scheduled-routines-portability.md +31 -0
  200. package/docs/adr/0116-api-error-envelope-and-code-taxonomy.md +62 -0
  201. package/docs/adr/0117-api-versioning-and-deprecation-policy.md +50 -0
  202. package/docs/adr/0118-api-contract-source-of-truth-code-first.md +47 -0
  203. package/docs/adr/0119-api-pagination-contract.md +34 -0
  204. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +108 -0
  205. package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +97 -0
  206. package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +43 -0
  207. package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +36 -0
  208. package/docs/adr/0124-typed-api-response-bodies.md +37 -0
  209. package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +36 -0
  210. package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +30 -0
  211. package/docs/adr/0127-cross-box-fleet-observability.md +64 -0
  212. package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +46 -0
  213. package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +34 -0
  214. package/docs/adr/0130-provision-remote-exec-over-ssh.md +61 -0
  215. package/docs/adr/0131-rank-scoped-skill-visibility.md +47 -0
  216. package/docs/adr/0132-cohost-fleet-control-plane-migration.md +54 -0
  217. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +42 -0
  218. package/docs/adr/0134-private-first-npm-distribution.md +38 -0
  219. package/docs/adr/README.md +208 -0
  220. package/docs/api/openapi.json +13720 -0
  221. package/docs/api-reference.md +437 -0
  222. package/docs/architecture.md +244 -0
  223. package/docs/branding-contract.md +66 -0
  224. package/docs/branding-fill-prompt.md +25 -0
  225. package/docs/canonical-permissions.md +241 -0
  226. package/docs/design/container-exterior-tiles.md +49 -0
  227. package/docs/design/container-exterior.md +81 -0
  228. package/docs/design/container-interior.md +77 -0
  229. package/docs/design/modular-architecture/00-research-report.md +107 -0
  230. package/docs/design/modular-architecture/01-architecture.md +125 -0
  231. package/docs/design/modular-architecture/02-module-map.md +116 -0
  232. package/docs/design/modular-architecture/03-builder-flows.md +104 -0
  233. package/docs/design/modular-architecture/04-module-lifecycle.md +62 -0
  234. package/docs/design/modular-architecture/README.md +27 -0
  235. package/docs/design/vanilla-hall-ui-redesign-scope.md +284 -0
  236. package/docs/design/world-map-scroll.md +41 -0
  237. package/docs/design-contract.md +320 -0
  238. package/docs/file-map.md +418 -0
  239. package/docs/handoff-template.md +92 -0
  240. package/docs/modules-contract.md +293 -0
  241. package/docs/onboarding/browser-terminal-guide.md +73 -0
  242. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +28 -0
  243. package/docs/onboarding/diagrams/02-rank-ladder.mmd +54 -0
  244. package/docs/onboarding/diagrams/03-drachmae-karma.mmd +37 -0
  245. package/docs/onboarding/diagrams/04-architecture.mmd +51 -0
  246. package/docs/onboarding/diagrams/05-versioning.mmd +29 -0
  247. package/docs/onboarding/diagrams/README.md +94 -0
  248. package/docs/onboarding/diagrams/assertions.json +170 -0
  249. package/docs/onboarding/diagrams/puppeteer-config.json +1 -0
  250. package/docs/onboarding/diagrams/render-config.json +9 -0
  251. package/docs/onboarding/drachmae-and-karma.md +178 -0
  252. package/docs/onboarding/primer.md +202 -0
  253. package/docs/project-context.template.md +67 -0
  254. package/docs/recipes/README.md +35 -0
  255. package/docs/recipes/audit-replay.md +100 -0
  256. package/docs/recipes/bongos-cli-release.md +66 -0
  257. package/docs/recipes/builder-box-lifecycle.md +152 -0
  258. package/docs/recipes/claude-design-loop.md +143 -0
  259. package/docs/recipes/claude-gemini-bongos-doors.md +155 -0
  260. package/docs/recipes/cloudbongos-staging-gate.md +139 -0
  261. package/docs/recipes/cloudbongos-standup.md +111 -0
  262. package/docs/recipes/devbox-app-release.md +93 -0
  263. package/docs/recipes/devbox-app-windows-test.md +128 -0
  264. package/docs/recipes/failed-ship-recovery-proof.md +102 -0
  265. package/docs/recipes/figma-design-loop.md +129 -0
  266. package/docs/recipes/gds-db-backup.md +255 -0
  267. package/docs/recipes/instance-deploy.md +37 -0
  268. package/docs/recipes/managed-settings-remote-control.md +416 -0
  269. package/docs/recipes/memory-backup.md +71 -0
  270. package/docs/recipes/memory-sync-conflicts.md +98 -0
  271. package/docs/recipes/multi-builder-merge.md +78 -0
  272. package/docs/recipes/ops-gotchas.md +212 -0
  273. package/docs/recipes/overnight-builder.md +53 -0
  274. package/docs/recipes/packaging-the-core.md +49 -0
  275. package/docs/recipes/parallel-load-results-2026-05-30.md +63 -0
  276. package/docs/recipes/parallel-load-test.md +32 -0
  277. package/docs/recipes/pixel-art-pipeline.md +411 -0
  278. package/docs/recipes/private-npm-distribution.md +79 -0
  279. package/docs/recipes/search-index-ingestion.md +67 -0
  280. package/docs/recipes/self-host.md +60 -0
  281. package/docs/recipes/ship-and-regrade.md +72 -0
  282. package/docs/recipes/staging-data.md +50 -0
  283. package/docs/recipes/staging-environment.md +215 -0
  284. package/docs/recipes/upgrading-the-core.md +58 -0
  285. package/docs/research/cursor-competitive-analysis.md +255 -0
  286. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +227 -0
  287. package/migrations/003_pms.sql +284 -0
  288. package/migrations/004_pms_cost_idempotency.sql +13 -0
  289. package/migrations/005_pms_smoke_filter.sql +44 -0
  290. package/migrations/006_pms_v2.sql +378 -0
  291. package/migrations/007_pms_v2_hierarchy_trigger.sql +59 -0
  292. package/migrations/008_pms_v2_migration_allocator.sql +27 -0
  293. package/migrations/009_pms_v2_blockers_table.sql +68 -0
  294. package/migrations/010_uptime.sql +29 -0
  295. package/migrations/011_backfill_v2_v3_done_when.sql +20 -0
  296. package/migrations/012_pms_v2_task_lifecycle_states.sql +82 -0
  297. package/migrations/013_claim_locking.sql +22 -0
  298. package/migrations/014_pms_task_discipline.sql +86 -0
  299. package/migrations/015_pms_estimation_calibration.sql +140 -0
  300. package/migrations/016_pms_task_grades.sql +60 -0
  301. package/migrations/017_version_progress_exclude_abandoned.sql +49 -0
  302. package/migrations/019_rename_pms_v3_to_gds_v3.sql +65 -0
  303. package/migrations/020_task_dependencies.sql +99 -0
  304. package/migrations/021_v3_renumber_and_deps.sql +224 -0
  305. package/migrations/022_v3_reconcile_dep_gated_status.sql +41 -0
  306. package/migrations/023_pms_rank.sql +30 -0
  307. package/migrations/025_builder_exit_status.sql +15 -0
  308. package/migrations/027_builders_preferred_disciplines.sql +46 -0
  309. package/migrations/028_tasks_parallel_safe.sql +71 -0
  310. package/migrations/029_rank_three_live.sql +32 -0
  311. package/migrations/030_vulnerability_reports.sql +61 -0
  312. package/migrations/031_reward_simplification.sql +57 -0
  313. package/migrations/032_repo_health_snapshots.sql +56 -0
  314. package/migrations/033_audit_log.sql +64 -0
  315. package/migrations/034_lifecycle_breakdown_leaf_only.sql +62 -0
  316. package/migrations/035_auth_token_hygiene.sql +66 -0
  317. package/migrations/037_builder_skill_model_prefs.sql +44 -0
  318. package/migrations/038_task_override_requests.sql +51 -0
  319. package/migrations/039_newcomer_friendly_column.sql +124 -0
  320. package/migrations/040_builder_karma.sql +58 -0
  321. package/migrations/041_kind_multipliers_and_idea_bonus.sql +42 -0
  322. package/migrations/042_criterion_proposal_workflow.sql +78 -0
  323. package/migrations/043_builder_onboarding_observability.sql +66 -0
  324. package/migrations/044_tasks_requires_rank.sql +97 -0
  325. package/migrations/045_builder_onboarding_state.sql +147 -0
  326. package/migrations/046_cost_log_builder_attribution.sql +42 -0
  327. package/migrations/054_builder_memory.sql +108 -0
  328. package/migrations/055_task_criteria.sql +90 -0
  329. package/migrations/058_peer_votes.sql +41 -0
  330. package/migrations/059_onboarding_pick_disciplines.sql +59 -0
  331. package/migrations/060_access_requests.sql +46 -0
  332. package/migrations/061_backfill_override_ship_attribution.sql +29 -0
  333. package/migrations/062_session_records.sql +34 -0
  334. package/migrations/063_session_records_tool_errors_idx.sql +11 -0
  335. package/migrations/064_grade_attempts.sql +18 -0
  336. package/migrations/065_bfg_principal.sql +58 -0
  337. package/migrations/066_live_notify_triggers.sql +69 -0
  338. package/migrations/067_builder_boxes.sql +117 -0
  339. package/migrations/067_discord_bot_foundation.sql +69 -0
  340. package/migrations/068_thetes_requires_rank.sql +27 -0
  341. package/migrations/069_builder_ssh_keys.sql +66 -0
  342. package/migrations/070_box_intents.sql +101 -0
  343. package/migrations/071_box_first_provision_approval.sql +46 -0
  344. package/migrations/072_onboarding_ship_three_remove_primer.sql +80 -0
  345. package/migrations/073_box_terminal_access.sql +46 -0
  346. package/migrations/075_builder_sound_prefs.sql +28 -0
  347. package/migrations/078_box_claude_active.sql +22 -0
  348. package/migrations/079_box_first_connected_at.sql +20 -0
  349. package/migrations/081_tasks_needs_migration.sql +34 -0
  350. package/migrations/084_tasks_security_sensitive.sql +24 -0
  351. package/migrations/085_security_engagement_docs.sql +34 -0
  352. package/migrations/086_rename_disciplines.sql +125 -0
  353. package/migrations/088_builders_anthropic_link.sql +28 -0
  354. package/migrations/089_builders_monthly_budget.sql +19 -0
  355. package/migrations/090_box_scoped_sessions.sql +38 -0
  356. package/migrations/091_session_token_reward.sql +44 -0
  357. package/migrations/093_tasks_sandbox_review.sql +22 -0
  358. package/migrations/102_remove_box_first_provision_approval.sql +58 -0
  359. package/migrations/103_search_chunks.sql +90 -0
  360. package/migrations/104_builder_box_blocked.sql +34 -0
  361. package/migrations/105_box_host_keys.sql +35 -0
  362. package/migrations/111_medusa_customer0_framing.sql +35 -0
  363. package/migrations/112_drop_track_check.sql +25 -0
  364. package/migrations/113_merge_lease.sql +16 -0
  365. package/migrations/114_cloudbongos_rename_live_strings.sql +32 -0
  366. package/migrations/118_currency_label_decoupling.sql +23 -0
  367. package/migrations/119_builder_behavior_prefs.sql +31 -0
  368. package/migrations/121_box_code_version.sql +37 -0
  369. package/migrations/122_builder_gemini_key.sql +30 -0
  370. package/migrations/123_cost_log_tiers.sql +33 -0
  371. package/migrations/124_cost_baseline.sql +45 -0
  372. package/migrations/125_llm_cache.sql +54 -0
  373. package/migrations/126_autonomy_runs.sql +33 -0
  374. package/migrations/143_cost_calibration.sql +63 -0
  375. package/migrations/145_backfill_task_requires_rank.sql +84 -0
  376. package/migrations/160_goal_tier.sql +88 -0
  377. package/migrations/163_polymorphic_dependencies.sql +187 -0
  378. package/migrations/164_event_sound_prefs.sql +20 -0
  379. package/migrations/165_task_module_key.sql +15 -0
  380. package/migrations/171_claims_published_head_sha.sql +24 -0
  381. package/migrations/174_close_orphaned_claims_on_terminal_tasks.sql +30 -0
  382. package/migrations/175_task_grades_diff_hash.sql +9 -0
  383. package/migrations/176_promotion_respects_open_blockers.sql +58 -0
  384. package/migrations/176_ui_discipline.sql +40 -0
  385. package/migrations/177_require_reward_before_workable.sql +76 -0
  386. package/migrations/178_reward_gate_auto_assign_revert_autopromote.sql +61 -0
  387. package/migrations/179_search_chunk_usefulness.sql +33 -0
  388. package/migrations/180_one_active_claim_per_session.sql +54 -0
  389. package/migrations/181_cost_log_skill_name.sql +33 -0
  390. package/migrations/184_claim_release_requested.sql +25 -0
  391. package/migrations/185_module_upstreaming_goal_wiring.sql +59 -0
  392. package/migrations/187_goal_sort_order.sql +30 -0
  393. package/migrations/187_tasks_kind_check_add_verify.sql +21 -0
  394. package/migrations/README.md +88 -0
  395. package/migrations/core_188_goal_visibility_and_membership_requests.sql +71 -0
  396. package/migrations/core_189_core_upgrades_ledger.sql +37 -0
  397. package/migrations/core_189_credit_log_task_id_index.sql +19 -0
  398. package/migrations/core_190_rebuild_claimable_task_views.sql +71 -0
  399. package/migrations/instance/018_pms_v3_planning_seed.sql +131 -0
  400. package/migrations/instance/080_gds_v4_planning_seed.sql +75 -0
  401. package/migrations/instance/110_medusa_planning_seed.sql +70 -0
  402. package/migrations/instance/120_bongos_v1_planning_seed.sql +69 -0
  403. package/migrations/instance/144_bongos_v1_module_system_criterion.sql +26 -0
  404. package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +29 -0
  405. package/migrations/instance/172_v2_world_planning_seed.sql +163 -0
  406. package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +33 -0
  407. package/migrations/instance/182_gdsv4_bongos_consolidation.sql +132 -0
  408. package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +116 -0
  409. package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +69 -0
  410. package/modules/autonomy/CLAUDE.md +49 -0
  411. package/modules/autonomy/db.js +50 -0
  412. package/modules/autonomy/module.json +13 -0
  413. package/modules/autonomy/pollers/routine-timers.js +146 -0
  414. package/modules/autonomy/routes/autonomy.js +37 -0
  415. package/modules/builder-settings/CLAUDE.md +58 -0
  416. package/modules/builder-settings/builder-needs.js +115 -0
  417. package/modules/builder-settings/db.js +168 -0
  418. package/modules/builder-settings/display-name.js +84 -0
  419. package/modules/builder-settings/event-sound-prefs.js +169 -0
  420. package/modules/builder-settings/interaction-prefs.js +201 -0
  421. package/modules/builder-settings/model-alloc-prefs.js +147 -0
  422. package/modules/builder-settings/module.json +10 -0
  423. package/modules/builder-settings/render-prefs.js +103 -0
  424. package/modules/builder-settings/routes/settings.js +417 -0
  425. package/modules/builder-settings/settings.js +52 -0
  426. package/modules/builder-settings/skill-prefs.js +196 -0
  427. package/modules/builder-settings/sound-prefs.js +178 -0
  428. package/modules/builder-settings/wandering-prefs.js +282 -0
  429. package/modules/dev-box/CLAUDE.md +38 -0
  430. package/modules/dev-box/box-access.js +275 -0
  431. package/modules/dev-box/box-credential.js +94 -0
  432. package/modules/dev-box/box-onboard.js +486 -0
  433. package/modules/dev-box/boxes.js +842 -0
  434. package/modules/dev-box/db.js +39 -0
  435. package/modules/dev-box/module.json +14 -0
  436. package/modules/dev-box/routes/box.js +977 -0
  437. package/modules/discord/CLAUDE.md +50 -0
  438. package/modules/discord/auth.js +54 -0
  439. package/modules/discord/bug-attachments.js +210 -0
  440. package/modules/discord/db.js +96 -0
  441. package/modules/discord/discord-approvals.js +145 -0
  442. package/modules/discord/discord-audit.js +29 -0
  443. package/modules/discord/discord-bot.js +545 -0
  444. package/modules/discord/discord-bugs.js +257 -0
  445. package/modules/discord/discord-channels.js +412 -0
  446. package/modules/discord/discord-inbound.js +147 -0
  447. package/modules/discord/discord-message-buffer.js +54 -0
  448. package/modules/discord/discord-reaction.js +186 -0
  449. package/modules/discord/discord-role-sync.js +318 -0
  450. package/modules/discord/module.json +14 -0
  451. package/modules/discord/pollers/discord-bot.js +19 -0
  452. package/modules/discord/routes/bug-attachments.js +44 -0
  453. package/modules/discord/routes/discord.js +102 -0
  454. package/modules/discord/ship-broadcast.js +78 -0
  455. package/modules/discord/webhook.js +74 -0
  456. package/modules/economy/CLAUDE.md +48 -0
  457. package/modules/economy/achievements.js +594 -0
  458. package/modules/economy/cost-classify.js +331 -0
  459. package/modules/economy/cost-summary-shape.js +90 -0
  460. package/modules/economy/cost.js +438 -0
  461. package/modules/economy/credits.js +430 -0
  462. package/modules/economy/migrations/economy_001_fix_gds_shipper_description.sql +91 -0
  463. package/modules/economy/module.json +10 -0
  464. package/modules/economy/reward.js +61 -0
  465. package/modules/economy/routes/cost.js +97 -0
  466. package/modules/economy/routes/leaderboard.js +26 -0
  467. package/modules/economy/routes/reward.js +42 -0
  468. package/modules/economy/streak.js +285 -0
  469. package/modules/grading/CLAUDE.md +42 -0
  470. package/modules/grading/db.js +39 -0
  471. package/modules/grading/grader-rubric.json +151 -0
  472. package/modules/grading/grader-workers/efficiency.js +213 -0
  473. package/modules/grading/grader-workers/extract-json.js +80 -0
  474. package/modules/grading/grader-workers/fence.js +100 -0
  475. package/modules/grading/grader-workers/hacker.js +239 -0
  476. package/modules/grading/grader-workers/index.js +183 -0
  477. package/modules/grading/grader-workers/narc.js +207 -0
  478. package/modules/grading/grader-workers/quality.js +198 -0
  479. package/modules/grading/grader-workers/rubric.js +20 -0
  480. package/modules/grading/grader-workers/worker-prompt.js +89 -0
  481. package/modules/grading/grader-workers/worker-verdict.js +50 -0
  482. package/modules/grading/grader.js +1790 -0
  483. package/modules/grading/module.json +12 -0
  484. package/modules/grading/routes/grade.js +51 -0
  485. package/modules/hall-ui/CLAUDE.md +61 -0
  486. package/modules/hall-ui/module.json +14 -0
  487. package/modules/hall-ui/public/atlas.html +180 -0
  488. package/modules/hall-ui/public/atlas.js +336 -0
  489. package/modules/hall-ui/public/box-panel.js +423 -0
  490. package/modules/hall-ui/public/brand-apply.js +117 -0
  491. package/modules/hall-ui/public/builders.js +3028 -0
  492. package/modules/hall-ui/public/chime.wav +0 -0
  493. package/modules/hall-ui/public/copy-on-click.js +174 -0
  494. package/modules/hall-ui/public/cursors/arrow.png +0 -0
  495. package/modules/hall-ui/public/cursors/hand-pointer.png +0 -0
  496. package/modules/hall-ui/public/cursors/magnifier-out.png +0 -0
  497. package/modules/hall-ui/public/cursors/magnifier.png +0 -0
  498. package/modules/hall-ui/public/diagram-viewer.css +125 -0
  499. package/modules/hall-ui/public/diagram-viewer.js +330 -0
  500. package/modules/hall-ui/public/diagrams.html +113 -0
  501. package/modules/hall-ui/public/dom-utils.js +212 -0
  502. package/modules/hall-ui/public/drachmae.html +68 -0
  503. package/modules/hall-ui/public/drachmae.js +136 -0
  504. package/modules/hall-ui/public/gate.html +143 -0
  505. package/modules/hall-ui/public/gate.js +228 -0
  506. package/modules/hall-ui/public/goals-page.js +1297 -0
  507. package/modules/hall-ui/public/goals.html +376 -0
  508. package/modules/hall-ui/public/goals.js +119 -0
  509. package/modules/hall-ui/public/hall-widgets.js +189 -0
  510. package/modules/hall-ui/public/harbor.html +167 -0
  511. package/modules/hall-ui/public/harbor.js +335 -0
  512. package/modules/hall-ui/public/index.html +116 -0
  513. package/modules/hall-ui/public/instances-panel.js +172 -0
  514. package/modules/hall-ui/public/md-reader.js +234 -0
  515. package/modules/hall-ui/public/modules.html +119 -0
  516. package/modules/hall-ui/public/modules.js +154 -0
  517. package/modules/hall-ui/public/new-project-wizard.js +186 -0
  518. package/modules/hall-ui/public/not-ready.html +48 -0
  519. package/modules/hall-ui/public/pair.html +119 -0
  520. package/modules/hall-ui/public/pair.js +132 -0
  521. package/modules/hall-ui/public/primer.css +404 -0
  522. package/modules/hall-ui/public/primer.html +85 -0
  523. package/modules/hall-ui/public/primer.js +641 -0
  524. package/modules/hall-ui/public/profile.html +85 -0
  525. package/modules/hall-ui/public/profile.js +236 -0
  526. package/modules/hall-ui/public/ranks.html +69 -0
  527. package/modules/hall-ui/public/ranks.js +206 -0
  528. package/modules/hall-ui/public/roadmap.html +209 -0
  529. package/modules/hall-ui/public/roadmap.js +375 -0
  530. package/modules/hall-ui/public/sessions.html +92 -0
  531. package/modules/hall-ui/public/sessions.js +222 -0
  532. package/modules/hall-ui/public/settings.html +640 -0
  533. package/modules/hall-ui/public/settings.js +1219 -0
  534. package/modules/hall-ui/public/shell.js +495 -0
  535. package/modules/hall-ui/public/style.css +1748 -0
  536. package/modules/hall-ui/public/task-detail.js +253 -0
  537. package/modules/hall-ui/public/task.html +83 -0
  538. package/modules/hall-ui/public/task.js +326 -0
  539. package/modules/hall-ui/public/watch.html +333 -0
  540. package/modules/hall-ui/public/watch.js +859 -0
  541. package/modules/hall-ui/public/work.html +469 -0
  542. package/modules/hall-ui/public/work.js +1292 -0
  543. package/modules/ideas/CLAUDE.md +37 -0
  544. package/modules/ideas/blockers.js +100 -0
  545. package/modules/ideas/inbox.js +459 -0
  546. package/modules/ideas/module.json +10 -0
  547. package/modules/ideas/routes/blockers.js +120 -0
  548. package/modules/ideas/routes/inbox.js +230 -0
  549. package/modules/lifecycle/CLAUDE.md +63 -0
  550. package/modules/lifecycle/conflict-resolve.js +436 -0
  551. package/modules/lifecycle/db.js +4970 -0
  552. package/modules/lifecycle/done-when.js +355 -0
  553. package/modules/lifecycle/github-push.js +969 -0
  554. package/modules/lifecycle/goal-conflicts.js +133 -0
  555. package/modules/lifecycle/hierarchy.js +104 -0
  556. package/modules/lifecycle/lifecycle.js +58 -0
  557. package/modules/lifecycle/merge-lock.js +261 -0
  558. package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +28 -0
  559. package/modules/lifecycle/migrations/lifecycle_002_backfill_orphan_task_goals.sql +36 -0
  560. package/modules/lifecycle/module.json +13 -0
  561. package/modules/lifecycle/pollers/publish-reconciler.js +20 -0
  562. package/modules/lifecycle/publish-reconciler.js +418 -0
  563. package/modules/lifecycle/routes/analytics.js +63 -0
  564. package/modules/lifecycle/routes/claims.js +506 -0
  565. package/modules/lifecycle/routes/dependencies.js +82 -0
  566. package/modules/lifecycle/routes/done-when.js +136 -0
  567. package/modules/lifecycle/routes/gate-approvals.js +197 -0
  568. package/modules/lifecycle/routes/goals.js +1419 -0
  569. package/modules/lifecycle/routes/lifecycle.js +43 -0
  570. package/modules/lifecycle/routes/tasks.js +1703 -0
  571. package/modules/lifecycle/routes/versions.js +128 -0
  572. package/modules/lifecycle/ship-card.js +505 -0
  573. package/modules/lifecycle/ship-preflight.js +225 -0
  574. package/modules/lifecycle/task-classifier.js +512 -0
  575. package/modules/lifecycle/version-close.js +106 -0
  576. package/modules/memory/CLAUDE.md +40 -0
  577. package/modules/memory/learnings.js +275 -0
  578. package/modules/memory/memory.js +572 -0
  579. package/modules/memory/module.json +12 -0
  580. package/modules/memory/routes/learnings.js +90 -0
  581. package/modules/memory/routes/memory.js +916 -0
  582. package/modules/memory/routes/search.js +188 -0
  583. package/modules/memory/search-chunker.js +252 -0
  584. package/modules/memory/search.js +320 -0
  585. package/modules/onboarding/CLAUDE.md +55 -0
  586. package/modules/onboarding/approval-broadcast.js +59 -0
  587. package/modules/onboarding/db.js +54 -0
  588. package/modules/onboarding/module.json +10 -0
  589. package/modules/onboarding/newcomer-restock.js +212 -0
  590. package/modules/onboarding/onboarding-state.js +532 -0
  591. package/modules/onboarding/onboarding.js +508 -0
  592. package/modules/onboarding/port.js +63 -0
  593. package/modules/onboarding/routes/access-requests.js +208 -0
  594. package/modules/onboarding/routes/onboarding.js +54 -0
  595. package/modules/security/CLAUDE.md +44 -0
  596. package/modules/security/module.json +10 -0
  597. package/modules/security/routes/reports.js +251 -0
  598. package/modules/security/secret-scrub.js +201 -0
  599. package/modules/security/security.js +294 -0
  600. package/modules/sessions/CLAUDE.md +39 -0
  601. package/modules/sessions/bfg.js +357 -0
  602. package/modules/sessions/db.js +326 -0
  603. package/modules/sessions/module.json +10 -0
  604. package/modules/sessions/pulse.js +149 -0
  605. package/modules/sessions/routes/sessions.js +331 -0
  606. package/modules/status-ui/CLAUDE.md +47 -0
  607. package/modules/status-ui/module.json +14 -0
  608. package/modules/status-ui/public/README.md +29 -0
  609. package/modules/status-ui/public/cursors/arrow.png +0 -0
  610. package/modules/status-ui/public/cursors/hand-pointer.png +0 -0
  611. package/modules/status-ui/public/index.html +363 -0
  612. package/modules/status-ui/public/snapshots/.gitkeep +0 -0
  613. package/modules/status-ui/public/status.js +1375 -0
  614. package/modules/status-ui/public/style.css +1342 -0
  615. package/package-lock.json +975 -0
  616. package/package.json +28 -0
  617. package/public-docs/index.html +35 -0
  618. package/public-docs/redoc.standalone.js +1838 -0
  619. package/public-docs/redoc.standalone.js.LICENSE.txt +102 -0
  620. package/public-landing/buddha.png +0 -0
  621. package/public-landing/create.html +868 -0
  622. package/public-landing/index.html +168 -0
  623. package/scripts/bongos-mcp.js +28 -0
  624. package/scripts/cron/style_guide_audit.sh +134 -0
  625. package/scripts/discord/README.md +52 -0
  626. package/scripts/discord/lib.js +80 -0
  627. package/scripts/discord/post.js +51 -0
  628. package/scripts/gds/CLAUDE.md +144 -0
  629. package/scripts/gds/abandon.js +58 -0
  630. package/scripts/gds/adopt-detect.js +123 -0
  631. package/scripts/gds/adopt-import.js +111 -0
  632. package/scripts/gds/adopt-preflight.js +121 -0
  633. package/scripts/gds/api.js +78 -0
  634. package/scripts/gds/architect-audit.js +288 -0
  635. package/scripts/gds/art-stage.js +152 -0
  636. package/scripts/gds/audit-authorship.sh +243 -0
  637. package/scripts/gds/audit-deps.js +121 -0
  638. package/scripts/gds/audit-exports.js +128 -0
  639. package/scripts/gds/audit-replay.sh +161 -0
  640. package/scripts/gds/audit-ships.js +251 -0
  641. package/scripts/gds/autonomy-gate.js +135 -0
  642. package/scripts/gds/backfill-cost.js +165 -0
  643. package/scripts/gds/backfill-sessions.js +351 -0
  644. package/scripts/gds/backfill-task-rewards.js +408 -0
  645. package/scripts/gds/backup.js +94 -0
  646. package/scripts/gds/bfg-principal-token.js +65 -0
  647. package/scripts/gds/box-auth-check.js +160 -0
  648. package/scripts/gds/box-connect-lib.js +453 -0
  649. package/scripts/gds/box.js +1258 -0
  650. package/scripts/gds/build-bongos.js +94 -0
  651. package/scripts/gds/capture.js +123 -0
  652. package/scripts/gds/cf-tunnel.js +419 -0
  653. package/scripts/gds/ci-grade.js +82 -0
  654. package/scripts/gds/claim-recall.js +112 -0
  655. package/scripts/gds/claim.js +333 -0
  656. package/scripts/gds/classify-discipline.js +153 -0
  657. package/scripts/gds/claude-materialize.js +176 -0
  658. package/scripts/gds/cleanup-orphans.js +74 -0
  659. package/scripts/gds/cli-lib.js +518 -0
  660. package/scripts/gds/code.js +101 -0
  661. package/scripts/gds/codemod-rename-src-gds.js +145 -0
  662. package/scripts/gds/conductor-dispatch.js +426 -0
  663. package/scripts/gds/context-pack.js +228 -0
  664. package/scripts/gds/coreB-carve-proof.sh +716 -0
  665. package/scripts/gds/cost.js +47 -0
  666. package/scripts/gds/db-backup-nightly.sh +160 -0
  667. package/scripts/gds/db-backup-offsite.sh +152 -0
  668. package/scripts/gds/demote.js +40 -0
  669. package/scripts/gds/design-sync.js +62 -0
  670. package/scripts/gds/diagnose-achievements.js +118 -0
  671. package/scripts/gds/diagram-facts.js +319 -0
  672. package/scripts/gds/discipline-modes.json +25 -0
  673. package/scripts/gds/discord-channels.js +66 -0
  674. package/scripts/gds/do-api.js +176 -0
  675. package/scripts/gds/docs-entropy.js +403 -0
  676. package/scripts/gds/doctor.js +389 -0
  677. package/scripts/gds/feedback-latest.js +94 -0
  678. package/scripts/gds/fetch-art-key.js +106 -0
  679. package/scripts/gds/fetch-sound-prefs.js +88 -0
  680. package/scripts/gds/figma-design-sync.js +61 -0
  681. package/scripts/gds/fitness.js +1446 -0
  682. package/scripts/gds/gate-review.js +322 -0
  683. package/scripts/gds/gating.js +325 -0
  684. package/scripts/gds/gds-db-backup.service +24 -0
  685. package/scripts/gds/gds-db-backup.timer +20 -0
  686. package/scripts/gds/gds-db-offsite.service +29 -0
  687. package/scripts/gds/gds-db-offsite.timer +18 -0
  688. package/scripts/gds/gen-api-client.js +508 -0
  689. package/scripts/gds/gen-api-docs.js +1037 -0
  690. package/scripts/gds/gen-atlas.js +366 -0
  691. package/scripts/gds/gen-diagrams.js +608 -0
  692. package/scripts/gds/gen-file-map.js +196 -0
  693. package/scripts/gds/gen-repo-map.js +519 -0
  694. package/scripts/gds/gen-session-index.js +210 -0
  695. package/scripts/gds/git-merge-regen.js +147 -0
  696. package/scripts/gds/grade-smoke.js +205 -0
  697. package/scripts/gds/hooks/pre-push.js +169 -0
  698. package/scripts/gds/http-api-client.js +88 -0
  699. package/scripts/gds/init.js +903 -0
  700. package/scripts/gds/install-git-hooks.js +114 -0
  701. package/scripts/gds/key-card.js +213 -0
  702. package/scripts/gds/key-status.js +126 -0
  703. package/scripts/gds/land-watch.js +168 -0
  704. package/scripts/gds/learning-capture.js +145 -0
  705. package/scripts/gds/lib.sh +44 -0
  706. package/scripts/gds/linkify-refs.js +473 -0
  707. package/scripts/gds/local-preview-lib.js +159 -0
  708. package/scripts/gds/local-preview.js +95 -0
  709. package/scripts/gds/login.js +225 -0
  710. package/scripts/gds/main-audit.js +279 -0
  711. package/scripts/gds/me.js +88 -0
  712. package/scripts/gds/memory-map.js +174 -0
  713. package/scripts/gds/merge-lock.js +71 -0
  714. package/scripts/gds/migration-namespace.js +200 -0
  715. package/scripts/gds/mirror-lineage-check.js +319 -0
  716. package/scripts/gds/mirror-redact.js +305 -0
  717. package/scripts/gds/module.js +482 -0
  718. package/scripts/gds/newcomer-floor.config.json +13 -0
  719. package/scripts/gds/newcomer-floor.js +415 -0
  720. package/scripts/gds/newcomer-floor.templates.json +86 -0
  721. package/scripts/gds/newcomer-floor.test.js +235 -0
  722. package/scripts/gds/next-number.js +163 -0
  723. package/scripts/gds/oauth-secret.js +197 -0
  724. package/scripts/gds/onboard.js +217 -0
  725. package/scripts/gds/onboarding-analyze.js +226 -0
  726. package/scripts/gds/onboarding-config.js +178 -0
  727. package/scripts/gds/onboarding-ship.js +92 -0
  728. package/scripts/gds/package-core.js +684 -0
  729. package/scripts/gds/paste-token.js +131 -0
  730. package/scripts/gds/portability-smoke-art.sh +85 -0
  731. package/scripts/gds/portability-smoke-memory.sh +147 -0
  732. package/scripts/gds/provision.js +1324 -0
  733. package/scripts/gds/publish-manifest.js +307 -0
  734. package/scripts/gds/pulse.js +309 -0
  735. package/scripts/gds/push-main.js +45 -0
  736. package/scripts/gds/read-session-export.js +359 -0
  737. package/scripts/gds/reauth.js +149 -0
  738. package/scripts/gds/recall.js +135 -0
  739. package/scripts/gds/release.js +71 -0
  740. package/scripts/gds/render-ideas.js +173 -0
  741. package/scripts/gds/repo-metrics.js +363 -0
  742. package/scripts/gds/resolve-skill-model.js +57 -0
  743. package/scripts/gds/run-routine.js +197 -0
  744. package/scripts/gds/run-unit-tests.js +178 -0
  745. package/scripts/gds/sandbox-stage.js +653 -0
  746. package/scripts/gds/search-ingest-db.js +209 -0
  747. package/scripts/gds/search-ingest-md.js +225 -0
  748. package/scripts/gds/security-baseline-check.sh +180 -0
  749. package/scripts/gds/seed-adopt-tasks.js +75 -0
  750. package/scripts/gds/seed-api-contract-goal-tasks.js +141 -0
  751. package/scripts/gds/seed-bongos-app-tasks.js +294 -0
  752. package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +81 -0
  753. package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +100 -0
  754. package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +307 -0
  755. package/scripts/gds/seed-bongos-module-system-tasks.js +346 -0
  756. package/scripts/gds/seed-bongos-tasks.js +214 -0
  757. package/scripts/gds/seed-gds-v4-tasks.js +624 -0
  758. package/scripts/gds/seed-governance-tasks.js +340 -0
  759. package/scripts/gds/seed-medusa-tasks.js +440 -0
  760. package/scripts/gds/seed-module-upstreaming.js +219 -0
  761. package/scripts/gds/seed-productization-tasks.js +235 -0
  762. package/scripts/gds/seed-provisioning-tasks.js +83 -0
  763. package/scripts/gds/seed-staging.sh +104 -0
  764. package/scripts/gds/seed-v2-world-tasks.js +283 -0
  765. package/scripts/gds/seed-v3-tasks.js +1981 -0
  766. package/scripts/gds/session-cost.js +393 -0
  767. package/scripts/gds/session-digest.js +179 -0
  768. package/scripts/gds/session-eval.js +576 -0
  769. package/scripts/gds/session.js +404 -0
  770. package/scripts/gds/setup.js +504 -0
  771. package/scripts/gds/shell.js +100 -0
  772. package/scripts/gds/ship.js +3618 -0
  773. package/scripts/gds/smoke-batch-claim.sh +213 -0
  774. package/scripts/gds/smoke-batch-release.sh +296 -0
  775. package/scripts/gds/smoke-box.sh +150 -0
  776. package/scripts/gds/smoke-claim-consistency.sh +212 -0
  777. package/scripts/gds/smoke-dependencies.sh +84 -0
  778. package/scripts/gds/smoke-gds.js +125 -0
  779. package/scripts/gds/smoke-write.sh +168 -0
  780. package/scripts/gds/start.js +782 -0
  781. package/scripts/gds/status.js +365 -0
  782. package/scripts/gds/triage.js +366 -0
  783. package/scripts/gds/upgrade.js +421 -0
  784. package/scripts/gds/validate-design.js +254 -0
  785. package/scripts/gds/version-close.js +549 -0
  786. package/scripts/migrate.sh +183 -0
  787. package/scripts/migrate_cost_ledger.sh +178 -0
  788. package/scripts/public-mirror-export.js +291 -0
  789. package/scripts/render-diagrams.sh +88 -0
  790. package/scripts/security/dep-audit.js +276 -0
  791. package/scripts/setup-builder.js +274 -0
  792. package/scripts/setup-builder.sh +28 -0
  793. package/scripts/snapshots/backfill-history.mjs +292 -0
  794. package/scripts/snapshots/maybe-render-on-deploy.sh +66 -0
  795. package/scripts/snapshots/render-pre-art-eras.mjs +329 -0
  796. package/scripts/snapshots/render-world.mjs +322 -0
  797. package/scripts/status-mirror-sync.js +192 -0
  798. package/scripts/status-probe.mjs +185 -0
  799. package/server.js +212 -0
  800. package/src/asset-version.js +71 -0
  801. package/src/bongos/CLAUDE.md +64 -0
  802. package/src/bongos/api-errors.js +281 -0
  803. package/src/bongos/api-path-404.js +67 -0
  804. package/src/bongos/api-prefix.js +38 -0
  805. package/src/bongos/app-pair.js +172 -0
  806. package/src/bongos/auth.js +979 -0
  807. package/src/bongos/cascade-dispatch.js +123 -0
  808. package/src/bongos/db-kernel.js +252 -0
  809. package/src/bongos/db.js +911 -0
  810. package/src/bongos/goal-scope-check.js +120 -0
  811. package/src/bongos/hierarchy-config.js +189 -0
  812. package/src/bongos/live-channel.js +145 -0
  813. package/src/bongos/llm-cache.js +260 -0
  814. package/src/bongos/llm-pricing.js +220 -0
  815. package/src/bongos/metrics.js +286 -0
  816. package/src/bongos/middleware/audit.js +274 -0
  817. package/src/bongos/middleware/error-envelope.js +93 -0
  818. package/src/bongos/middleware/rate-limit.js +195 -0
  819. package/src/bongos/module-scope-map.js +271 -0
  820. package/src/bongos/optimizer.js +166 -0
  821. package/src/bongos/path-match.js +47 -0
  822. package/src/bongos/permission-path-check.js +248 -0
  823. package/src/bongos/pool.js +55 -0
  824. package/src/bongos/repo-info.js +91 -0
  825. package/src/bongos/route-rank-check.js +420 -0
  826. package/src/bongos/routes/CLAUDE.md +43 -0
  827. package/src/bongos/routes/_helpers.js +369 -0
  828. package/src/bongos/routes/audit-log.js +59 -0
  829. package/src/bongos/routes/auth.js +755 -0
  830. package/src/bongos/routes/backup.js +104 -0
  831. package/src/bongos/routes/builders.js +526 -0
  832. package/src/bongos/routes/healthz.js +16 -0
  833. package/src/bongos/routes/instance.js +129 -0
  834. package/src/bongos/routes/live.js +125 -0
  835. package/src/bongos/routes/llm-cache.js +119 -0
  836. package/src/bongos/routes/me.js +380 -0
  837. package/src/bongos/routes/modules.js +131 -0
  838. package/src/bongos/routes/optimizer.js +54 -0
  839. package/src/bongos/routes/public.js +476 -0
  840. package/src/bongos/routes/security.js +207 -0
  841. package/src/bongos/routes.js +166 -0
  842. package/src/bongos/secret-box.js +177 -0
  843. package/src/bongos/sensitive-surfaces.js +30 -0
  844. package/src/bongos/serve-internal.js +889 -0
  845. package/src/bongos/uptime-poller.js +90 -0
  846. package/src/bongos-downloads.js +70 -0
  847. package/src/branding.js +182 -0
  848. package/src/build-info.js +77 -0
  849. package/src/devbox-downloads.js +67 -0
  850. package/src/instance-config.js +200 -0
  851. package/src/module-api.js +366 -0
  852. package/src/module-loader/loader.js +268 -0
  853. package/src/module-loader/manifest-schema.js +140 -0
  854. package/src/module-loader/provenance.js +28 -0
  855. package/src/module-loader/semver.js +100 -0
  856. package/src/module-seams.js +291 -0
  857. package/src/modules.js +254 -0
  858. package/src/platform-server.js +135 -0
  859. package/src/ui/adapters/claude-design/index.js +333 -0
  860. package/src/ui/adapters/figma/index.js +328 -0
  861. package/tests/abandon_task.mjs +138 -0
  862. package/tests/achievements_reconcile.mjs +167 -0
  863. package/tests/adopt_detect.mjs +141 -0
  864. package/tests/adopt_import.mjs +132 -0
  865. package/tests/adopt_preflight.mjs +105 -0
  866. package/tests/anti_grid.mjs +89 -0
  867. package/tests/api_client.mjs +231 -0
  868. package/tests/api_docs.mjs +283 -0
  869. package/tests/api_error_envelope_buildcheck.mjs +56 -0
  870. package/tests/api_error_envelope_middleware.mjs +99 -0
  871. package/tests/api_error_registry.mjs +108 -0
  872. package/tests/api_pagination_contract.mjs +51 -0
  873. package/tests/api_path_404.mjs +96 -0
  874. package/tests/app_pair.mjs +168 -0
  875. package/tests/architect.mjs +140 -0
  876. package/tests/art_key.mjs +121 -0
  877. package/tests/art_stage.mjs +121 -0
  878. package/tests/asset_version.mjs +94 -0
  879. package/tests/atlas.mjs +69 -0
  880. package/tests/audit_deps.mjs +74 -0
  881. package/tests/audit_middleware.mjs +385 -0
  882. package/tests/auth_admission_gate.mjs +164 -0
  883. package/tests/auth_anthropic_identity.mjs +95 -0
  884. package/tests/auth_duplicate_cookie.mjs +56 -0
  885. package/tests/auth_error_brand.mjs +77 -0
  886. package/tests/auth_flow_slice1.mjs +223 -0
  887. package/tests/auth_inactive_builder.mjs +94 -0
  888. package/tests/auth_page_gate.mjs +92 -0
  889. package/tests/auth_session_ttl.mjs +155 -0
  890. package/tests/autonomous_fit_protected_paths.mjs +115 -0
  891. package/tests/autonomy_gate.mjs +85 -0
  892. package/tests/autotile_variants.mjs +256 -0
  893. package/tests/autotile_xverify.mjs +133 -0
  894. package/tests/backfill_sessions.mjs +101 -0
  895. package/tests/backfill_task_rewards.mjs +152 -0
  896. package/tests/backup_cli.mjs +104 -0
  897. package/tests/bfg.mjs +327 -0
  898. package/tests/bfg_write.mjs +423 -0
  899. package/tests/bongos_box_connect.mjs +89 -0
  900. package/tests/bongos_dispatch.mjs +74 -0
  901. package/tests/bongos_downloads.mjs +118 -0
  902. package/tests/bongos_login.mjs +34 -0
  903. package/tests/box_access.mjs +186 -0
  904. package/tests/box_auth_check.mjs +114 -0
  905. package/tests/box_code_staleness.mjs +116 -0
  906. package/tests/box_connect_e2e.mjs +196 -0
  907. package/tests/box_cost.mjs +70 -0
  908. package/tests/box_dns_repoint.mjs +151 -0
  909. package/tests/box_host_keys.mjs +102 -0
  910. package/tests/box_onboard.mjs +317 -0
  911. package/tests/box_scope_session.mjs +115 -0
  912. package/tests/box_ship_permissions.mjs +71 -0
  913. package/tests/box_terminal_ssrf.mjs +90 -0
  914. package/tests/boxes.mjs +1255 -0
  915. package/tests/branding.mjs +88 -0
  916. package/tests/bug_attachments.mjs +189 -0
  917. package/tests/build_info.mjs +88 -0
  918. package/tests/builder_needs.mjs +99 -0
  919. package/tests/cap_newcomer_tasks.mjs +114 -0
  920. package/tests/cascade-dispatch.mjs +95 -0
  921. package/tests/cf_tunnel.mjs +459 -0
  922. package/tests/claim_recall.mjs +150 -0
  923. package/tests/claims_bind_session.mjs +47 -0
  924. package/tests/claude_materialize.mjs +148 -0
  925. package/tests/cli_box_scope.mjs +52 -0
  926. package/tests/cli_lib_formatters.mjs +87 -0
  927. package/tests/cli_token_reissue.mjs +147 -0
  928. package/tests/client_bundle_route.mjs +47 -0
  929. package/tests/cloud_bongos_pack.mjs +85 -0
  930. package/tests/conductor.mjs +496 -0
  931. package/tests/confirm_attribution.mjs +156 -0
  932. package/tests/conflict_resolve.mjs +462 -0
  933. package/tests/context_pack.mjs +240 -0
  934. package/tests/cost.mjs +94 -0
  935. package/tests/cost_audit.mjs +122 -0
  936. package/tests/cost_budget_status.mjs +78 -0
  937. package/tests/cost_reconcile.mjs +102 -0
  938. package/tests/cost_summary_shape.mjs +94 -0
  939. package/tests/credit_grant.mjs +376 -0
  940. package/tests/criterion_progress.mjs +220 -0
  941. package/tests/criterion_proposal.mjs +109 -0
  942. package/tests/criterion_ref_parse.mjs +53 -0
  943. package/tests/currency_label.mjs +119 -0
  944. package/tests/day_cycle.mjs +197 -0
  945. package/tests/dependencies_migration.mjs +77 -0
  946. package/tests/dependencies_polymorphic.mjs +212 -0
  947. package/tests/deploy_instance.mjs +78 -0
  948. package/tests/devbox_downloads.mjs +122 -0
  949. package/tests/diff_derived_signals.mjs +115 -0
  950. package/tests/discord_approvals.mjs +174 -0
  951. package/tests/discord_autojoin.mjs +158 -0
  952. package/tests/discord_bot.mjs +94 -0
  953. package/tests/discord_bugs.mjs +297 -0
  954. package/tests/discord_channels.mjs +337 -0
  955. package/tests/discord_foundation.mjs +76 -0
  956. package/tests/discord_inbound.mjs +190 -0
  957. package/tests/discord_message_buffer.mjs +87 -0
  958. package/tests/discord_oauth.mjs +55 -0
  959. package/tests/discord_reaction.mjs +170 -0
  960. package/tests/discord_role_sync.mjs +151 -0
  961. package/tests/display_name.mjs +123 -0
  962. package/tests/dns_preflight.mjs +74 -0
  963. package/tests/event_sound_prefs.mjs +194 -0
  964. package/tests/eviction_policy.mjs +279 -0
  965. package/tests/figma_adapter.mjs +199 -0
  966. package/tests/file_map.mjs +91 -0
  967. package/tests/first_admin_bootstrap.mjs +40 -0
  968. package/tests/fitness.mjs +435 -0
  969. package/tests/fitness_two_root.mjs +104 -0
  970. package/tests/fixtures/session-506-digest.json +34 -0
  971. package/tests/gate_approvals.mjs +451 -0
  972. package/tests/gate_review.mjs +152 -0
  973. package/tests/git_merge_regen.mjs +151 -0
  974. package/tests/github_push_ancestry.mjs +119 -0
  975. package/tests/github_push_land_proof.mjs +127 -0
  976. package/tests/github_push_merge_driver.mjs +175 -0
  977. package/tests/goal_abuse_matrix.mjs +187 -0
  978. package/tests/goal_achievement.mjs +146 -0
  979. package/tests/goal_conflicts.mjs +305 -0
  980. package/tests/goal_dependencies_read.mjs +73 -0
  981. package/tests/goal_member_roles.mjs +420 -0
  982. package/tests/goal_membership_requests.mjs +332 -0
  983. package/tests/goal_routes.mjs +203 -0
  984. package/tests/goal_scope_check.mjs +167 -0
  985. package/tests/goal_tier.mjs +312 -0
  986. package/tests/goal_wall_proof.mjs +112 -0
  987. package/tests/grade_attempts.mjs +73 -0
  988. package/tests/grade_attribution.mjs +117 -0
  989. package/tests/grade_diff_hash.mjs +191 -0
  990. package/tests/grade_server_authoritative.mjs +305 -0
  991. package/tests/grade_unavailable_display.mjs +66 -0
  992. package/tests/grader.mjs +1960 -0
  993. package/tests/grader_bypass.mjs +57 -0
  994. package/tests/grader_panel.mjs +252 -0
  995. package/tests/grader_prompt_fencing.mjs +192 -0
  996. package/tests/helpers.mjs +52 -0
  997. package/tests/hierarchy-config.mjs +81 -0
  998. package/tests/init.mjs +463 -0
  999. package/tests/instance_config.mjs +76 -0
  1000. package/tests/instance_manifest.mjs +110 -0
  1001. package/tests/interaction_prefs.mjs +72 -0
  1002. package/tests/key-card.mjs +110 -0
  1003. package/tests/key-status.mjs +45 -0
  1004. package/tests/land_watch.mjs +115 -0
  1005. package/tests/lifecycle-card.mjs +145 -0
  1006. package/tests/lifecycle_breakdown_view.mjs +236 -0
  1007. package/tests/linkify_refs.mjs +264 -0
  1008. package/tests/live_channel.mjs +128 -0
  1009. package/tests/live_sse.mjs +122 -0
  1010. package/tests/llm-cache-route.mjs +110 -0
  1011. package/tests/llm-cache.mjs +140 -0
  1012. package/tests/llm-pricing.mjs +152 -0
  1013. package/tests/local_preview.mjs +136 -0
  1014. package/tests/main_audit.mjs +137 -0
  1015. package/tests/memory_isolation.mjs +431 -0
  1016. package/tests/memory_map.mjs +107 -0
  1017. package/tests/memory_pull.mjs +429 -0
  1018. package/tests/merge_lease.mjs +100 -0
  1019. package/tests/merge_lock.mjs +312 -0
  1020. package/tests/metrics.mjs +147 -0
  1021. package/tests/migration_allocation.mjs +146 -0
  1022. package/tests/migration_namespace.mjs +185 -0
  1023. package/tests/migrations_fresh_replay_guard.mjs +38 -0
  1024. package/tests/mirror_lineage_fulltree.mjs +45 -0
  1025. package/tests/mirror_lineage_proof.mjs +72 -0
  1026. package/tests/mirror_redact.mjs +193 -0
  1027. package/tests/model_alloc_prefs.mjs +63 -0
  1028. package/tests/module-scope-map.mjs +88 -0
  1029. package/tests/module_api.mjs +110 -0
  1030. package/tests/module_cli.mjs +292 -0
  1031. package/tests/module_contributions.mjs +148 -0
  1032. package/tests/module_loader.mjs +286 -0
  1033. package/tests/module_manifest.mjs +194 -0
  1034. package/tests/module_owned_tests.mjs +105 -0
  1035. package/tests/module_provenance.mjs +34 -0
  1036. package/tests/module_route_rank.mjs +147 -0
  1037. package/tests/module_seams.mjs +68 -0
  1038. package/tests/modules.mjs +156 -0
  1039. package/tests/modules_route.mjs +38 -0
  1040. package/tests/newcomer_floor.mjs +17 -0
  1041. package/tests/newcomer_restock.mjs +80 -0
  1042. package/tests/next_number.mjs +154 -0
  1043. package/tests/oauth_secret.mjs +60 -0
  1044. package/tests/onboard.mjs +70 -0
  1045. package/tests/onboarding_gate.mjs +267 -0
  1046. package/tests/onboarding_observability.mjs +344 -0
  1047. package/tests/onboarding_route_signals.mjs +175 -0
  1048. package/tests/onboarding_state.mjs +346 -0
  1049. package/tests/one_active_claim_per_session.mjs +111 -0
  1050. package/tests/orphaned_claim_ship.mjs +135 -0
  1051. package/tests/override_attribution.mjs +142 -0
  1052. package/tests/override_request_ownership.mjs +235 -0
  1053. package/tests/package_core.mjs +435 -0
  1054. package/tests/parallel-load.mjs +402 -0
  1055. package/tests/path_match.mjs +62 -0
  1056. package/tests/permission_path.mjs +151 -0
  1057. package/tests/platform_boot.mjs +167 -0
  1058. package/tests/pre_compact.mjs +60 -0
  1059. package/tests/preview_server.mjs +127 -0
  1060. package/tests/profile_route.mjs +197 -0
  1061. package/tests/provision.mjs +805 -0
  1062. package/tests/provisioning_github_app_routes.mjs +98 -0
  1063. package/tests/public_mirror_export.mjs +124 -0
  1064. package/tests/public_task_resolver.mjs +112 -0
  1065. package/tests/publish_branch_route.mjs +422 -0
  1066. package/tests/publish_manifest.mjs +184 -0
  1067. package/tests/publish_reconciler.mjs +728 -0
  1068. package/tests/rank_gate.mjs +463 -0
  1069. package/tests/rate_limit_public.mjs +217 -0
  1070. package/tests/read_session_export.mjs +89 -0
  1071. package/tests/recall_cli.mjs +82 -0
  1072. package/tests/recall_hints.mjs +119 -0
  1073. package/tests/recent_shipped_window.mjs +80 -0
  1074. package/tests/regrade_eligibility.mjs +202 -0
  1075. package/tests/render_prefs.mjs +122 -0
  1076. package/tests/repo_map.mjs +92 -0
  1077. package/tests/reward_gate.mjs +284 -0
  1078. package/tests/route_rank_check.mjs +243 -0
  1079. package/tests/route_validation.mjs +490 -0
  1080. package/tests/routine_timers.mjs +72 -0
  1081. package/tests/rubric_fixtures.mjs +140 -0
  1082. package/tests/run_routine.mjs +69 -0
  1083. package/tests/sandbox_stage.mjs +582 -0
  1084. package/tests/scrubber_corpus.mjs +257 -0
  1085. package/tests/search_chunker.mjs +209 -0
  1086. package/tests/search_ingest.mjs +96 -0
  1087. package/tests/search_ingest_db.mjs +88 -0
  1088. package/tests/search_isolation.mjs +204 -0
  1089. package/tests/search_route.mjs +95 -0
  1090. package/tests/secret_box.mjs +189 -0
  1091. package/tests/security_docs.mjs +186 -0
  1092. package/tests/security_route.mjs +108 -0
  1093. package/tests/served_paths_shipped.mjs +118 -0
  1094. package/tests/session-cards.mjs +142 -0
  1095. package/tests/session_506_regression.mjs +69 -0
  1096. package/tests/session_caps.mjs +238 -0
  1097. package/tests/session_cost.mjs +133 -0
  1098. package/tests/session_digest.mjs +73 -0
  1099. package/tests/session_eval.mjs +386 -0
  1100. package/tests/session_index.mjs +105 -0
  1101. package/tests/session_records.mjs +369 -0
  1102. package/tests/session_search.mjs +78 -0
  1103. package/tests/session_start_freshness.mjs +123 -0
  1104. package/tests/setup_smoke.mjs +154 -0
  1105. package/tests/ship_autolinkify.mjs +60 -0
  1106. package/tests/ship_automerge_injection.mjs +174 -0
  1107. package/tests/ship_broadcast_routing.mjs +125 -0
  1108. package/tests/ship_broadcast_server.mjs +175 -0
  1109. package/tests/ship_card.mjs +280 -0
  1110. package/tests/ship_ci_deploy.mjs +374 -0
  1111. package/tests/ship_preflight.mjs +368 -0
  1112. package/tests/ship_premerge.mjs +75 -0
  1113. package/tests/ship_resume.mjs +129 -0
  1114. package/tests/ship_upload_skip.mjs +89 -0
  1115. package/tests/ship_zero_commit.mjs +60 -0
  1116. package/tests/single_host_hall.mjs +98 -0
  1117. package/tests/skill_prefs.mjs +150 -0
  1118. package/tests/smoke_preflight.mjs +66 -0
  1119. package/tests/snapshot_picker.mjs +233 -0
  1120. package/tests/sound_prefs.mjs +198 -0
  1121. package/tests/sprite_facing.mjs +35 -0
  1122. package/tests/status-probe.mjs +89 -0
  1123. package/tests/status_backfill.mjs +94 -0
  1124. package/tests/stop_refcheck.mjs +127 -0
  1125. package/tests/streak.mjs +175 -0
  1126. package/tests/subdomain_auth.mjs +58 -0
  1127. package/tests/task_classifier_vocab.mjs +41 -0
  1128. package/tests/task_create_idempotency.mjs +96 -0
  1129. package/tests/task_goal_link.mjs +93 -0
  1130. package/tests/task_ownership_gate.mjs +125 -0
  1131. package/tests/terrain_picker.mjs +298 -0
  1132. package/tests/touches_overlap.mjs +75 -0
  1133. package/tests/triage_development.mjs +140 -0
  1134. package/tests/turn_in_place.mjs +280 -0
  1135. package/tests/upgrade.mjs +357 -0
  1136. package/tests/verify_task_guard.mjs +64 -0
  1137. package/tests/version_close.mjs +76 -0
  1138. package/tests/versions_route.mjs +93 -0
  1139. package/tests/walkability.mjs +188 -0
  1140. package/tests/wandering_prefs.mjs +176 -0
  1141. package/tests/worktree_disambiguation.mjs +105 -0
  1142. package/tests/worktree_guard.mjs +90 -0
@@ -0,0 +1,1446 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // fitness.js — architecture fitness functions (Path C / ADR 0062 §9, task 1225).
4
+ //
5
+ // The ENFORCEMENT layer for the Cloud Bongos decoupling. A dependency-cruiser/ArchUnit-style gate that
6
+ // keeps the architecture from silently rotting back together. Each check is a pure function over
7
+ // the repo on disk returning { name, ok, hardFail, violations[], warnings[], note }. The CLI runs
8
+ // them all, prints a report, and exits non-zero on any hard failure. tests/fitness.mjs asserts
9
+ // each check passes, so the DB-free unit gate (run-unit-tests.js → the `unit` required check)
10
+ // enforces them on every PR — no new workflow needed.
11
+ //
12
+ // RATCHET, not big-bang. The structural epics that physically separate the core (R51 entrypoint
13
+ // #1190, R52 DB carve-out #1191, R54 module system #1193) are not built yet, so the codebase is
14
+ // still fused (every src/bongos/*.js shares the game's pg Pool via require('../db')). These checks
15
+ // therefore lock in the boundary DIRECTION now and fail only on NEW violations beyond a documented
16
+ // grandfather list; as the epics land they SHRINK the grandfather list. Module-level boundary
17
+ // enforcement (§4) tightens once R54 (#1193) gives us a module manifest to check against.
18
+ //
19
+ // Usage:
20
+ // node scripts/gds/fitness.js # run all checks, print report, exit 1 on any hard failure
21
+ // node scripts/gds/fitness.js --json # machine-readable report
22
+
23
+ const fs = require('node:fs');
24
+ const path = require('node:path');
25
+ const { execFileSync } = require('node:child_process');
26
+ const { checkRouteRanks } = require('../../src/bongos/route-rank-check');
27
+ const genApiDocs = require('./gen-api-docs'); // R12 (#1999): reuse the route model (readsBody/validated) that drives the OpenAPI spec
28
+ const { parseRoutineFrontmatter } = require('./run-routine'); // System 4 (ADR 0078): deterministic-routine boundary
29
+ const { filenameViolation, stemCollisions, CORE_NS, INSTANCE_NS } = require('./migration-namespace'); // ADR 0108 §5: cross-repo migration namespacing
30
+ const { resolveCoreRoot, resolveInstanceRoot } = require('../../src/instance-config'); // ADR 0108 §1: configurable-root
31
+
32
+ // ADR 0108 §Consequences #4 (configurable-root W6 / task 1886) — the guardrail walks TWO trees.
33
+ // CORE code + core-owned docs (src/, server.js, scripts/, the src/bongos + scripts/gds nested docs)
34
+ // live under the core PACKAGE; INSTANCE content (the root CLAUDE.md, the .claude/ methodology
35
+ // surface, instance modules + migrations) lives under the host REPO. Modules ship from BOTH.
36
+ // Today both resolve to the same checkout, so every walk below is byte-identical; once the core
37
+ // installs from node_modules they diverge and each check inspects the CORRECT tree. A NEW
38
+ // hardcoded `path.resolve(__dirname,'..','..')` here reintroduces the bug this check exists to stop.
39
+ const CORE_ROOT = resolveCoreRoot();
40
+ const INSTANCE_ROOT = resolveInstanceRoot();
41
+ // REPO_ROOT retained as an alias to the core root for the many purely-core walks below (src/bongos,
42
+ // KERNEL_FILES, scope map, …) — those keep their exact behavior. Instance-owned reads route
43
+ // through INSTANCE_ROOT explicitly, and module reads through MODULE_ROOTS.
44
+ const REPO_ROOT = CORE_ROOT;
45
+ // Deduped module search roots — a module can ship from the core package OR the instance repo (the
46
+ // loader errors on a cross-root key collision, so a key resolves to exactly one root). Deduped so
47
+ // today's single checkout scans each module ONCE (no double-counting).
48
+ const MODULE_ROOTS = [...new Set([CORE_ROOT, INSTANCE_ROOT])];
49
+
50
+ // ---- shared fs helpers -----------------------------------------------------
51
+
52
+ function walkJs(absRoot, out = []) {
53
+ let st;
54
+ try { st = fs.statSync(absRoot); } catch { return out; }
55
+ if (st.isFile()) { if (absRoot.endsWith('.js')) out.push(absRoot); return out; }
56
+ for (const ent of fs.readdirSync(absRoot, { withFileTypes: true })) {
57
+ if (ent.name === 'node_modules' || ent.name === '.git') continue;
58
+ walkJs(path.join(absRoot, ent.name), out);
59
+ }
60
+ return out;
61
+ }
62
+ // repo-relative POSIX path for an abs file under EITHER root (core or instance). The classifier
63
+ // regexes throughout this file key on these relative strings, which are identical whichever tree
64
+ // a file lives in — so only the WALK roots change under the split, not the classification.
65
+ const rel = (abs) => {
66
+ for (const base of [CORE_ROOT, INSTANCE_ROOT]) {
67
+ const r = path.relative(base, abs);
68
+ if (r && !r.startsWith('..') && !path.isAbsolute(r)) return r.split(path.sep).join('/');
69
+ }
70
+ return path.relative(CORE_ROOT, abs).split(path.sep).join('/');
71
+ };
72
+ // Walk .js under `subpath` (e.g. 'modules') across ALL module roots, deduped by repo-relative
73
+ // path so the same file is never scanned twice when the roots coincide (today).
74
+ function walkJsAllRoots(subpath, roots = MODULE_ROOTS) {
75
+ const seen = new Set();
76
+ const out = [];
77
+ for (const root of roots) {
78
+ for (const abs of walkJs(path.join(root, subpath))) {
79
+ const r = rel(abs);
80
+ if (seen.has(r)) continue;
81
+ seen.add(r);
82
+ out.push(abs);
83
+ }
84
+ }
85
+ return out;
86
+ }
87
+
88
+ // ============================================================================
89
+ // Check 1 — core ↔ host/game import boundary (ADR 0062 §1 cut-line)
90
+ // ============================================================================
91
+ //
92
+ // The portable core (src/bongos/) must not import the HOST GAME runtime. After BV1.R49 the game
93
+ // module lives in modules/game/ (rooms, world, db, seats, public, migrations). The only game-
94
+ // related file that stays in src/ is src/preview-server.js (the game-only sandbox entrypoint).
95
+ // Imports into scripts/ (build tooling / engine) are NOT host-game coupling.
96
+ const HOST_GAME = [
97
+ /^modules\/game\//, // everything in the game module (moved from src/ by BV1.R49)
98
+ /^src\/preview-server\.js$/, // game-only sandbox entrypoint (stays in src/)
99
+ ];
100
+ // Empty as of R52/#1191 — the core owns its pool (src/bongos/pool.js) and no longer imports the
101
+ // game's db helper. The boundary now enforces with ZERO grandfathered exceptions.
102
+ const GRANDFATHERED = new Set([]);
103
+ const REQUIRE_RE = /require\(\s*['"](\.\.?\/[^'"]+)['"]\s*\)/g;
104
+
105
+ function resolvesToHostGame(fromAbsFile, spec) {
106
+ const abs = path.resolve(path.dirname(fromAbsFile), spec);
107
+ const r = rel(abs);
108
+ for (const cand of [r, r + '.js', r.replace(/\/$/, '') + '/index.js']) {
109
+ if (GRANDFATHERED.has(cand)) return null; // allowed (and not a violation)
110
+ if (HOST_GAME.some((re) => re.test(cand))) return cand;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ function checkCoreHostBoundary() {
116
+ const coreDir = path.join(REPO_ROOT, 'src', 'bongos');
117
+ const violations = [];
118
+ for (const abs of walkJs(coreDir)) {
119
+ // scan line-by-line, skipping comment lines so a require(...) quoted in a doc comment
120
+ // (e.g. explaining the boundary) is never mistaken for a real import.
121
+ for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
122
+ const trimmed = raw.trim();
123
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
124
+ let m;
125
+ REQUIRE_RE.lastIndex = 0;
126
+ while ((m = REQUIRE_RE.exec(raw))) {
127
+ const target = resolvesToHostGame(abs, m[1]);
128
+ if (target) violations.push(`${rel(abs)} → require('${m[1]}') reaches host-game code (${target}) — the core must not import the game runtime (ADR 0062 §1).`);
129
+ }
130
+ }
131
+ }
132
+ return {
133
+ name: 'core↔host import boundary',
134
+ ok: violations.length === 0,
135
+ hardFail: violations.length > 0,
136
+ violations,
137
+ warnings: [],
138
+ note: `src/bongos/ must not import the host game runtime (grandfathered: ${[...GRANDFATHERED].join(', ') || 'none'} — severed by R52/#1191).`,
139
+ };
140
+ }
141
+
142
+ // ============================================================================
143
+ // Check 2 — root CLAUDE.md size budget + charter presence (ADR 0061)
144
+ // ============================================================================
145
+ const ROOT_HARD_MAX = 300; // ceiling: the always-loaded file must never balloon past this. Ratcheted
146
+ // 360→310 when task 1226 generated the §13 session-log index (was a ~58-line
147
+ // hand-maintained list), then 310→300 when task 1283 trimmed the quick-nav
148
+ // table + §2/§8/§9/§10/§12 verbose prose down to ~294 lines. Lower further as
149
+ // more detail relocates to nested docs / ADRs (ADR 0061).
150
+ const ROOT_TARGET = 200; // the charter's stated aim; keep ratcheting toward it.
151
+
152
+ // The line count is a structure/readability proxy; the cost paid EVERY session is
153
+ // characters/tokens. A 600-char quick-nav row is one line but ~150 tokens, so
154
+ // within-line bloat slips past ROOT_HARD_MAX entirely (exactly what task 1283
155
+ // trimmed). Guard the real cost too (task 1286). Ratcheted just above the
156
+ // post-1283 size (~39.5K chars / ~9.9K tok); lower alongside ROOT_HARD_MAX as
157
+ // detail relocates to nested docs / ADRs.
158
+ const ROOT_CHAR_HARD_MAX = 42000; // ~10.5K tokens — the always-loaded file must never balloon past this.
159
+ const ROOT_CHAR_TARGET = 32000; // ~8K tokens — aspirational, mirrors the ~200-line aim.
160
+ const estTokens = (n) => Math.round(n / 4); // ~4 chars/token for markdown prose.
161
+
162
+ function checkClaudeMdBudget(opts = {}) {
163
+ const p = path.join(INSTANCE_ROOT, 'CLAUDE.md'); // the root CLAUDE.md is INSTANCE content (materialized per instance; ADR 0108)
164
+ const text = opts.text != null ? opts.text : fs.readFileSync(p, 'utf8');
165
+ const lines = text.split('\n').length;
166
+ const violations = [];
167
+ const warnings = [];
168
+ if (lines > ROOT_HARD_MAX) {
169
+ violations.push(`CLAUDE.md is ${lines} lines — over the ${ROOT_HARD_MAX}-line hard ceiling. It loads in full every session; relocate detail to a nested CLAUDE.md / ADR / recipe (ADR 0061).`);
170
+ } else if (lines > ROOT_TARGET) {
171
+ warnings.push(`CLAUDE.md is ${lines} lines — over the ~${ROOT_TARGET}-line target (still under the ${ROOT_HARD_MAX} ceiling). Relocate more detail to nested CLAUDE.md / ADRs / recipes (ADR 0061), then lower ROOT_HARD_MAX as it shrinks.`);
172
+ }
173
+ const chars = text.length;
174
+ const tok = estTokens(chars);
175
+ if (chars > ROOT_CHAR_HARD_MAX) {
176
+ violations.push(`CLAUDE.md is ${chars} chars (~${tok} tokens) — over the ${ROOT_CHAR_HARD_MAX}-char (~${estTokens(ROOT_CHAR_HARD_MAX)}-token) hard ceiling. The line count may be fine, but the per-session token cost is not — trim verbose prose WITHIN lines (long quick-nav rows, restated rationale), not just whole lines (ADR 0061).`);
177
+ } else if (chars > ROOT_CHAR_TARGET) {
178
+ warnings.push(`CLAUDE.md is ${chars} chars (~${tok} tokens) — over the ~${ROOT_CHAR_TARGET}-char (~${estTokens(ROOT_CHAR_TARGET)}-token) target. This is the true per-session cost; within-line bloat (a 600-char table row is one line) escapes the line check. Trim prose, then lower ROOT_CHAR_HARD_MAX.`);
179
+ }
180
+ if (!text.includes('## Working with this file (content charter)')) {
181
+ violations.push('CLAUDE.md is missing the content-charter section (ADR 0061) — the per-line test + "what goes where" table must stay.');
182
+ }
183
+ return { name: 'root CLAUDE.md budget + charter', ok: violations.length === 0, hardFail: violations.length > 0, violations, warnings, note: `≤ ${ROOT_HARD_MAX} lines / ${ROOT_CHAR_HARD_MAX} chars (~${estTokens(ROOT_CHAR_HARD_MAX)} tok); targets ~${ROOT_TARGET} lines / ~${estTokens(ROOT_CHAR_TARGET)} tok; charter present.` };
184
+ }
185
+
186
+ // ============================================================================
187
+ // Check 3 — nested per-module CLAUDE.md presence for high-traffic dirs (ADR 0061)
188
+ // ============================================================================
189
+ const HIGH_TRAFFIC = ['src/bongos', 'src/bongos/routes', 'scripts/gds'];
190
+
191
+ function checkNestedDocsPresent() {
192
+ const violations = [];
193
+ for (const dir of HIGH_TRAFFIC) {
194
+ // HIGH_TRAFFIC dirs (src/bongos, src/bongos/routes, scripts/gds) are CORE-package dirs — resolve
195
+ // against the core root, not the instance repo (ADR 0108 §Consequences #4).
196
+ const doc = path.join(CORE_ROOT, dir, 'CLAUDE.md');
197
+ if (!fs.existsSync(doc)) violations.push(`${dir}/CLAUDE.md is missing — high-traffic dirs must carry a nested altitude map (ADR 0061).`);
198
+ }
199
+ return { name: 'nested CLAUDE.md presence', ok: violations.length === 0, hardFail: violations.length > 0, violations, warnings: [], note: `present for: ${HIGH_TRAFFIC.join(', ')}.` };
200
+ }
201
+
202
+ // ============================================================================
203
+ // Check 4 — no route bypasses server-side rank checks (ADR 0016) [reuses route-rank-check]
204
+ // ============================================================================
205
+ function checkRankGuards() {
206
+ const r = checkRouteRanks();
207
+ // hardFail = an ungated WRITE route or a downgraded privileged route. Unclassified read routes
208
+ // are 'concerns' (the Hacker adjudicates intent) — surfaced as warnings, not a hard failure.
209
+ const violations = r.findings.filter((f) => f.severity === 'blocker').map((f) => `${f.route} (${f.file}) — ${f.summary}`);
210
+ const warnings = r.findings.filter((f) => f.severity !== 'blocker').map((f) => `${f.route} (${f.file}) — ${f.summary}`);
211
+ return { name: 'route rank guards (ADR 0016)', ok: !r.hardFail, hardFail: r.hardFail, violations, warnings, note: `${r.routeCount} routes classify to a known rank; no ungated writes, no privileged downgrades.` };
212
+ }
213
+
214
+ // ============================================================================
215
+ // Check 5 — generated artifacts are fresh (Path C enforces Path B; ADR 0063 + ADR 0062 §8)
216
+ // ============================================================================
217
+ // Deterministic file-reads with a `--check` mode (regenerate in-memory, diff, exit non-zero on
218
+ // drift). This gate fails a PR that committed a stale artifact so the "generate, don't hand-maintain"
219
+ // contract can't rot.
220
+ // ADR 0110 (task 1926): docs/repo-map.md + docs/session-log-index.md are now GITIGNORED and
221
+ // regenerated at deploy/box-fetch (the gen-atlas.js model), so parallel branches never collide on
222
+ // them — and there is no committed copy to gate. Their generators are DUAL-DUTY, though: each also
223
+ // refreshes a TRACKED embedded block (the nested CLAUDE.md symbol blocks; the CLAUDE.md §13 snippet).
224
+ // Those blocks stay committed, so we gate the BLOCK bucket HARD via `--check-block`. A naive `--check`
225
+ // here would false-fail every PR (the gitignored whole file is absent from a clean checkout, so it
226
+ // always reads as "would change"); `--check-block` is the exact, non-noisy gate. (docs/file-map.md +
227
+ // the OpenAPI docs are still fully committed and unchanged by ADR 0110 — they keep the plain --check.)
228
+ function checkGeneratedArtifactsFresh() {
229
+ const violations = [];
230
+ try {
231
+ execFileSync(process.execPath, [path.join(__dirname, 'gen-repo-map.js'), '--check-block'], { cwd: REPO_ROOT, stdio: 'pipe' });
232
+ } catch {
233
+ violations.push('The tracked nested-doc symbol blocks (src/bongos/CLAUDE.md, src/bongos/routes/CLAUDE.md, scripts/gds/CLAUDE.md) are STALE — run `node scripts/gds/gen-repo-map.js` and commit (ADR 0063 / ADR 0110). NB docs/repo-map.md itself is gitignored + deploy-regenerated, not gated here.');
234
+ }
235
+ try {
236
+ execFileSync(process.execPath, [path.join(__dirname, 'gen-session-index.js'), '--check-block'], { cwd: REPO_ROOT, stdio: 'pipe' });
237
+ } catch {
238
+ violations.push('The tracked CLAUDE.md §13 recent-sessions snippet is STALE — run `node scripts/gds/gen-session-index.js` and commit (ADR 0062 §8 / ADR 0110). NB docs/session-log-index.md itself is gitignored + deploy-regenerated, not gated here.');
239
+ }
240
+ try {
241
+ execFileSync(process.execPath, [path.join(__dirname, 'gen-file-map.js'), '--check'], { cwd: REPO_ROOT, stdio: 'pipe' });
242
+ } catch {
243
+ violations.push('docs/file-map.md generated sections (.claude/skills/, .claude/scheduled-tasks/) are STALE or a skill/routine has no note in docs/file-map.notes.json — run `node scripts/gds/gen-file-map.js` and add any missing note, then commit (ADR 0066).');
244
+ }
245
+ try {
246
+ // Also structurally validates the spec (exit 1 on invalid OpenAPI 3.1) beyond the drift check (exit 2). Task 1918.
247
+ execFileSync(process.execPath, [path.join(__dirname, 'gen-api-docs.js'), '--check'], { cwd: REPO_ROOT, stdio: 'pipe' });
248
+ } catch {
249
+ violations.push('docs/api/openapi.json + docs/api-reference.md + docs/routes-permissions.md are STALE (or the spec is not valid OpenAPI 3.1) — run `node scripts/gds/gen-api-docs.js` and commit (task 1918).');
250
+ }
251
+ return { name: 'generated artifacts fresh', ok: violations.length === 0, hardFail: violations.length > 0, violations, warnings: [], note: 'Tracked generated content matches source: nested CLAUDE.md symbol blocks (code) + the CLAUDE.md §13 snippet (sessions) via --check-block, plus docs/file-map.md skills/scheduled-tasks sections and docs/api/openapi.json + api-reference.md (API). The gitignored docs/repo-map.md + docs/session-log-index.md are deploy-regenerated (ADR 0110), not gated here.' };
252
+ }
253
+
254
+ // ---- deterministic cron routine boundary (System 4 / ADR 0078) -------------
255
+ //
256
+ // A `mode: deterministic` routine runs as plain node via run-routine.js — NO
257
+ // model session reads its SKILL body. So that body must not rot back into a
258
+ // wrapped session: it may not instruct a model to mutate (Edit/Write/git
259
+ // push/commit/deploy) or synthesize free text, and its documented commands must
260
+ // not diverge from its declared steps. Keeps the markdown-graveyard pattern out.
261
+
262
+ const SCHEDULED_TASKS_DIR = path.join(INSTANCE_ROOT, '.claude', 'scheduled-tasks'); // .claude/ is materialized INSTANCE content (ADR 0108 §3)
263
+
264
+ // Judgment/mutation instructions illegal in a deterministic body. Negated
265
+ // mentions ("never pushes", "does NOT edit") are descriptive boundaries — allowed.
266
+ const JUDGMENT_PATTERNS = [
267
+ { re: /\bgit\s+(push|commit|add)\b/i, label: 'a git push/commit/add' },
268
+ { re: /deploy\.sh\b/i, label: 'a deploy.sh invocation' },
269
+ { re: /`(Edit|Write|MultiEdit|NotebookEdit)`/, label: 'an Edit/Write tool instruction' },
270
+ { re: /\bsynthesi[sz]e\b/i, label: 'free-text synthesis' },
271
+ { re: /\b(write|compose|draft)\s+(a|an|the)\s+(summary|report|analysis|narrative|note)\b/i, label: 'free-text synthesis' },
272
+ ];
273
+ const NEGATION_RE = /\b(never|not|no|without|cannot|can't|don'?t|doesn'?t|won'?t)\b/i;
274
+
275
+ // Pure: scan one routine's parsed (mode, steps, body). Returns violation strings.
276
+ // Only inspects deterministic routines; others return []. Exported for tests.
277
+ function scanDeterministicBody(name, { mode, steps, body }) {
278
+ if (mode !== 'deterministic') return [];
279
+ const violations = [];
280
+ if (!steps || steps.length === 0) {
281
+ violations.push(`${name}: mode: deterministic but no steps: — run-routine.js would launch nothing (ADR 0078).`);
282
+ }
283
+ const text = String(body == null ? '' : body);
284
+ for (const raw of text.split('\n')) {
285
+ const line = raw.trim();
286
+ if (!line || NEGATION_RE.test(line)) continue; // skip blank + negated (boundary) lines
287
+ for (const p of JUDGMENT_PATTERNS) {
288
+ if (p.re.test(line)) {
289
+ violations.push(`${name}: deterministic SKILL body contains ${p.label} ("${line.slice(0, 70)}") — it runs as plain node with no model; move remediation/judgment to a separate claimed task (ADR 0078).`);
290
+ }
291
+ }
292
+ }
293
+ // Divergence: every `node …` command in a fenced code block must be a declared step.
294
+ const stepCmds = (steps || []).map((s) => String(s.command).replace(/\s+/g, ' ').trim());
295
+ for (const fence of text.match(/```[\s\S]*?```/g) || []) {
296
+ for (const fl of fence.split('\n')) {
297
+ const cmd = fl.trim().replace(/\s+/g, ' ');
298
+ if (!cmd.startsWith('node ')) continue;
299
+ if (!stepCmds.some((s) => cmd === s || cmd.startsWith(s) || s.startsWith(cmd))) {
300
+ violations.push(`${name}: deterministic SKILL body documents \`${cmd.slice(0, 70)}\` which is not in its steps: — prose and steps must not diverge (ADR 0078).`);
301
+ }
302
+ }
303
+ }
304
+ return violations;
305
+ }
306
+
307
+ function checkDeterministicRoutines() {
308
+ const violations = [];
309
+ let detCount = 0;
310
+ let dirs = [];
311
+ try {
312
+ dirs = fs.readdirSync(SCHEDULED_TASKS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
313
+ } catch (_) { /* no scheduled-tasks dir → nothing to check */ }
314
+ for (const name of dirs) {
315
+ let md;
316
+ try { md = fs.readFileSync(path.join(SCHEDULED_TASKS_DIR, name, 'SKILL.md'), 'utf8'); } catch (_) { continue; }
317
+ const { mode, steps } = parseRoutineFrontmatter(md);
318
+ if (mode !== 'deterministic') continue;
319
+ detCount += 1;
320
+ const body = md.replace(/^---\n[\s\S]*?\n---\n?/, ''); // strip frontmatter
321
+ violations.push(...scanDeterministicBody(name, { mode, steps, body }));
322
+ }
323
+ return {
324
+ name: 'deterministic cron routines (ADR 0078)',
325
+ ok: violations.length === 0,
326
+ hardFail: violations.length > 0,
327
+ violations,
328
+ warnings: [],
329
+ note: `${detCount} mode:deterministic routine(s): steps declared, no judgment instructions, prose↔steps aligned.`,
330
+ };
331
+ }
332
+
333
+ // ============================================================================
334
+ // Check 7/8 — module boundary (the one-way rule, ADR 0083 / design 04§3)
335
+ // ============================================================================
336
+ //
337
+ // The module system's one-way rule: a module imports ONLY src/module-api.js (the
338
+ // published doorway); the core NEVER imports a module; a module NEVER imports a
339
+ // sibling module. These two checks enforce it.
340
+ //
341
+ // Both were GREEN through the strangler: dev-box + art-pipeline (the M07/M08
342
+ // moves) import only the doorway, and the core imports neither. The boundary
343
+ // shipped in WARN mode so a half-moved module mid-migration couldn't red the
344
+ // build; BV1.R45 (task 1414) flips it to ENFORCING now that the first move
345
+ // (dev-box, M07) proved clean — the monotonic ratchet (ADR 0062 §9 / design
346
+ // 04§3). A NEW cross-boundary import is now a red build; warn-mode is gone.
347
+ let MODULE_BOUNDARY_ENFORCING = true;
348
+ const MODULE_API_REL = 'src/module-api.js';
349
+
350
+ // If `spec` (a relative require) from `fromAbsFile` resolves into modules/<key>/,
351
+ // return <key>; else null. Pure path math — no fs.
352
+ function resolvesToModule(fromAbsFile, spec) {
353
+ const abs = path.resolve(path.dirname(fromAbsFile), spec);
354
+ const r = rel(abs);
355
+ const m = /^modules\/([^/]+)(\/|$)/.exec(r);
356
+ return m ? m[1] : null;
357
+ }
358
+
359
+ // The module a core/module file belongs to, or null if it isn't under modules/.
360
+ function moduleOwnerOf(absFile) {
361
+ const m = /^modules\/([^/]+)\//.exec(rel(absFile));
362
+ return m ? m[1] : null;
363
+ }
364
+
365
+ // For a require `spec` inside a MODULE file, return a violation string or null.
366
+ // Allowed: src/module-api.js (the doorway), intra-module relatives, and bare
367
+ // (npm/node) specs. Forbidden: deep core imports + sibling-module imports.
368
+ function moduleImportViolation(fromAbsFile, spec) {
369
+ const ownKey = moduleOwnerOf(fromAbsFile);
370
+ if (!ownKey) return null; // not a module file
371
+ const absTarget = path.resolve(path.dirname(fromAbsFile), spec);
372
+ const r = rel(absTarget);
373
+ // resolve possible file forms
374
+ const forms = [r, r + '.js', r.replace(/\/$/, '') + '/index.js'];
375
+ if (forms.includes(MODULE_API_REL)) return null; // the doorway — allowed
376
+ const otherKey = (() => { const m = /^modules\/([^/]+)(\/|$)/.exec(r); return m ? m[1] : null; })();
377
+ if (otherKey) {
378
+ if (otherKey === ownKey) return null; // intra-module — allowed
379
+ return `${rel(fromAbsFile)} → require('${spec}') reaches sibling module "${otherKey}" — modules must not import each other (use a kernel seam; ADR 0083).`;
380
+ }
381
+ if (/^src\//.test(r)) {
382
+ return `${rel(fromAbsFile)} → require('${spec}') reaches core internals (${r}) — a module may import only the published doorway src/module-api.js (ADR 0083).`;
383
+ }
384
+ return null; // outside the repo (node/npm) — not our concern
385
+ }
386
+
387
+ function checkCoreNeverImportsModule() {
388
+ const findings = [];
389
+ const roots = [path.join(REPO_ROOT, 'src'), path.join(REPO_ROOT, 'server.js')];
390
+ for (const root of roots) {
391
+ for (const abs of walkJs(root)) {
392
+ if (moduleOwnerOf(abs)) continue; // module files are scanned by the other check
393
+ if (HOST_GAME.some((re) => re.test(rel(abs)))) continue; // game-owned files that live in src/ (e.g. preview-server.js) may import game module
394
+ for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
395
+ const t = raw.trim();
396
+ if (t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) continue;
397
+ let m; REQUIRE_RE.lastIndex = 0;
398
+ while ((m = REQUIRE_RE.exec(raw))) {
399
+ const key = resolvesToModule(abs, m[1]);
400
+ if (key) findings.push(`${rel(abs)} → require('${m[1]}') reaches module "${key}" — the core must never import a module (ADR 0083).`);
401
+ }
402
+ }
403
+ }
404
+ }
405
+ const enforcing = MODULE_BOUNDARY_ENFORCING;
406
+ return {
407
+ name: 'core never imports a module',
408
+ ok: findings.length === 0,
409
+ hardFail: enforcing && findings.length > 0,
410
+ violations: enforcing ? findings : [],
411
+ warnings: enforcing ? [] : findings,
412
+ note: `core (src/, server.js) must not require modules/* — ${enforcing ? 'ENFORCING' : 'warn-mode until BV1.R45'} (ADR 0083).`,
413
+ };
414
+ }
415
+
416
+ function checkModulesImportOnlyApi() {
417
+ const findings = [];
418
+ for (const abs of walkJsAllRoots('modules')) { // modules ship from BOTH roots (ADR 0108 §Consequences #4)
419
+ for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
420
+ const t = raw.trim();
421
+ if (t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) continue;
422
+ let m; REQUIRE_RE.lastIndex = 0;
423
+ while ((m = REQUIRE_RE.exec(raw))) {
424
+ const v = moduleImportViolation(abs, m[1]);
425
+ if (v) findings.push(v);
426
+ }
427
+ }
428
+ }
429
+ const enforcing = MODULE_BOUNDARY_ENFORCING;
430
+ return {
431
+ name: 'modules import only the published API',
432
+ ok: findings.length === 0,
433
+ hardFail: enforcing && findings.length > 0,
434
+ violations: enforcing ? findings : [],
435
+ warnings: enforcing ? [] : findings,
436
+ note: `modules/** may require only src/module-api.js (no deep core, no sibling) — ${enforcing ? 'ENFORCING' : 'warn-mode until BV1.R45'} (ADR 0083).`,
437
+ };
438
+ }
439
+
440
+ // ============================================================================
441
+ // Check 9 — the goal scope map imports PROTECTED_GLOBS, never re-declares it
442
+ // (ADR 0086 §7; the SEC #863 "second list that drifts" failure mode)
443
+ // ============================================================================
444
+ //
445
+ // src/bongos/module-scope-map.js (BV1.R54) derives each module's `protected` flag
446
+ // from PROTECTED_GLOBS. That derivation is only trustworthy if it reads the ONE
447
+ // source (permission-path-check.js) — a hand-copied second list would silently
448
+ // drift from the real protected set and quietly de-protect a module. This check
449
+ // reds the build if the scope map stops importing PROTECTED_GLOBS, or copies it.
450
+ const SCOPE_MAP_REL = 'src/bongos/module-scope-map.js';
451
+
452
+ // Pure: scan the scope-map source for the import contract. Exported for the
453
+ // "actually catches a violation" test (mirrors scanDeterministicBody).
454
+ function scanScopeMapSource(src) {
455
+ const violations = [];
456
+ const text = String(src || '');
457
+ const importsFrom = /require\(\s*['"]\.\/permission-path-check['"]\s*\)/.test(text);
458
+ const namesIt = /\bPROTECTED_GLOBS\b/.test(text);
459
+ if (!importsFrom || !namesIt) {
460
+ violations.push(`${SCOPE_MAP_REL} must IMPORT PROTECTED_GLOBS from ./permission-path-check and derive \`protected\` from it — never hand-maintain a second protected list (ADR 0086 §7).`);
461
+ }
462
+ if (/\b(?:const|let|var)\s+PROTECTED_GLOBS\s*=/.test(text)) {
463
+ violations.push(`${SCOPE_MAP_REL} re-declares PROTECTED_GLOBS — it must import the single source from permission-path-check.js, not copy it (ADR 0086 §7; the SEC #863 second-list failure mode).`);
464
+ }
465
+ return violations;
466
+ }
467
+
468
+ function checkScopeMapImportsProtected() {
469
+ const abs = path.join(REPO_ROOT, SCOPE_MAP_REL);
470
+ let src = null;
471
+ try { src = fs.readFileSync(abs, 'utf8'); } catch (_) { /* absent → skip */ }
472
+ // Absent → clean pass: the scope map is a BV1.R54 deliverable; until it exists
473
+ // there is no import contract to enforce (its own unit test asserts existence).
474
+ const violations = src == null ? [] : scanScopeMapSource(src);
475
+ return {
476
+ name: 'scope map imports PROTECTED_GLOBS (ADR 0086)',
477
+ ok: violations.length === 0,
478
+ hardFail: violations.length > 0,
479
+ violations,
480
+ warnings: [],
481
+ note: src == null
482
+ ? `${SCOPE_MAP_REL} absent — skipped (created by BV1.R54).`
483
+ : `${SCOPE_MAP_REL} derives \`protected\` from the imported PROTECTED_GLOBS (single source; no copied list).`,
484
+ };
485
+ }
486
+
487
+ // ============================================================================
488
+ // Check 10 — core carries no module-specific code (ADR 0083 §Decision #4 / design 04§3)
489
+ // ============================================================================
490
+ //
491
+ // The module-system MACHINERY (the doorway + loader + seam registry) must stay
492
+ // module-AGNOSTIC: it discovers, validates, and mounts modules uniformly and may
493
+ // never special-case one by name. The import checks (Check 7/8) stop a module's
494
+ // CODE from being reached by `require`; this stops a module's IDENTITY from being
495
+ // hardcoded into the kernel (`if (key === 'dev-box') …`, a copied module helper,
496
+ // a registry entry that should have moved out). "No module-specific code" is
497
+ // operationalized as the one drift-free signal — the kernel names no discovered
498
+ // module KEY (the canonical id from modules/<key>/module.json). Domain terms are
499
+ // deliberately NOT pattern-matched: they're non-deterministic and the doorway
500
+ // legitimately re-exports capability accessors (getBuilderGeminiKeyRow, …); the
501
+ // KEY is the precise thing a generic kernel never needs to write.
502
+ //
503
+ // Scope (BV1.R70 / ADR 0091 §1): the real ~3.5k-LOC TRUST KERNEL — the module-
504
+ // system machinery PLUS the trust primitives + data substrate + config/identity
505
+ // the rest of core depends on. This replaces the original 6-file machinery-only
506
+ // roster (the kernel was unbounded before — the audit's finding). Deliberately
507
+ // EXCLUDED: the still-fused monolith remnants (src/bongos/db.js, routes/me.js,
508
+ // routes.js, server.js) still carry un-stranglered module seams by design
509
+ // (lifecycle + tranche-2 domains leave later) — scanning them would red the
510
+ // build on intentional state. Comment lines are skipped (a key named in a
511
+ // doc/example comment is documentation, not code), and the scope-map registry is
512
+ // exempt from the no-module-keys scan (see KERNEL_NAMES_MODULES_OK).
513
+ const KERNEL_FILES = [
514
+ // module-system machinery (the original roster)
515
+ 'src/module-api.js', // the published doorway
516
+ 'src/modules.js', // the registry/resolver facade
517
+ 'src/module-seams.js', // the seam registry (ports/events/contributions)
518
+ 'src/module-loader/loader.js', // the disk loader (discover → validate → mount)
519
+ 'src/module-loader/manifest-schema.js', // the manifest validator
520
+ 'src/module-loader/semver.js', // coreVersion compatibility
521
+ 'src/bongos/module-scope-map.js', // the module→path registry (it NAMES every module — exempt below)
522
+ // trust primitives (ADR 0091 §1) — the authz boundary + data substrate
523
+ 'src/bongos/auth.js', // authz gates (requireBuilder/requireRank, ADR 0016)
524
+ 'src/bongos/route-rank-check.js', // deterministic route→rank classification
525
+ 'src/bongos/permission-path-check.js', // protected-path matcher (PROTECTED_GLOBS, ADR 0043)
526
+ 'src/bongos/goal-scope-check.js', // goal create-gate scope wall (composes the 3 matchers above; ADR 0086 §3, BV1.R60)
527
+ 'src/bongos/secret-box.js', // AES-256-GCM per-builder secret box
528
+ 'src/bongos/pool.js', // the singleton Postgres pool accessor
529
+ 'src/bongos/db-kernel.js', // the kernel db slice — identity/audit/rank/withTx (BV1.R69)
530
+ 'src/bongos/routes/_helpers.js', // shared request validators the doorway re-exports
531
+ // config / identity (host context a module reads, never hardcodes)
532
+ 'src/instance-config.js',
533
+ 'src/branding.js',
534
+ 'src/build-info.js',
535
+ // BV1.R86: the git-remote/slug + publish-target identity the doorway exposes
536
+ // (api.repoInfo) for the carved lifecycle github-push/conflict-resolve. It is
537
+ // KERNEL (already in the module-scope-map kernel key) and imports only the
538
+ // kernel (instance-config) + node built-ins, so it joins the roster cleanly.
539
+ 'src/bongos/repo-info.js',
540
+ ];
541
+
542
+ // Kernel files exempt from the no-module-keys scan (Check 10) because naming a
543
+ // module is, for them, the JOB — they stay in KERNEL_FILES for the import check
544
+ // (Check 12), which guards dependency *direction* (a much stronger guarantee than
545
+ // "never write the string 'dev-box'"). Two legitimate reasons a kernel file names
546
+ // a module:
547
+ // • the REGISTRY — module-scope-map.js IS the module→path map (and
548
+ // checkScopeMapImportsProtected already guards how it derives `protected`).
549
+ // • the TRUST BOUNDARY — auth.js carries the Discord OAuth account-LINK flow
550
+ // (core by design — see modules/discord/CLAUDE.md "NOT in this module"); and
551
+ // permission-path-check.js / route-rank-check.js MUST name module path
552
+ // subtrees ('modules/dev-box/', …) to protect + rank-gate them. Forbidding
553
+ // these the string would force the trust layer to stop protecting module
554
+ // surfaces — exactly backwards. The generic machinery + data + config files
555
+ // (module-api, the loader, db-kernel, secret-box, pool, _helpers, branding,
556
+ // instance-config, build-info) are NOT exempt and must stay module-agnostic.
557
+ const KERNEL_NAMES_MODULES_OK = new Set([
558
+ 'src/bongos/module-scope-map.js', // the module→path registry
559
+ 'src/bongos/auth.js', // Discord OAuth account-link flow (core by design)
560
+ 'src/bongos/permission-path-check.js', // names module path subtrees to PROTECT them
561
+ 'src/bongos/route-rank-check.js', // names module route dirs to rank-gate them
562
+ ]);
563
+
564
+ // The discovered module keys = the directory names under modules/ (a manifest's
565
+ // `key` must equal its dir name — manifest-schema). Empty before any move. With no explicit
566
+ // `modulesDir` it UNIONS every module root (core package + instance repo, ADR 0108 §Consequences
567
+ // #4); an explicit dir keeps the old single-dir behavior (tests). Deduped — a key resolves to
568
+ // exactly one root (the loader errors on a cross-root collision), and today the roots coincide.
569
+ function _moduleKeysIn(modulesDir) {
570
+ try {
571
+ return fs.readdirSync(modulesDir, { withFileTypes: true })
572
+ .filter((d) => d.isDirectory())
573
+ .map((d) => d.name);
574
+ } catch { return []; }
575
+ }
576
+ function discoveredModuleKeys(modulesDir) {
577
+ if (modulesDir) return _moduleKeysIn(modulesDir);
578
+ const keys = new Set();
579
+ for (const root of MODULE_ROOTS) for (const k of _moduleKeysIn(path.join(root, 'modules'))) keys.add(k);
580
+ return [...keys];
581
+ }
582
+ // Absolute path to a discovered module's dir, resolving WHICH root holds it (core package or
583
+ // instance repo). Today both roots coincide → the single checkout.
584
+ function moduleDir(key) {
585
+ for (const root of MODULE_ROOTS) {
586
+ const d = path.join(root, 'modules', key);
587
+ if (fs.existsSync(d)) return d;
588
+ }
589
+ return path.join(CORE_ROOT, 'modules', key);
590
+ }
591
+
592
+ // Match a module key as a DELIMITED token so `dev-box` hits in `'dev-box'` /
593
+ // `mount('dev-box')` but never inside `dev-boxes` or `xdev-box`. Hyphens are
594
+ // non-word chars, so the boundary guards against word-chars AND hyphens on both
595
+ // sides (no lookbehind — portable on every node the unit gate runs).
596
+ function keyTokenRe(key) {
597
+ const esc = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
598
+ return new RegExp(`(^|[^\\w-])${esc}($|[^\\w-])`);
599
+ }
600
+
601
+ // Pure: scan kernel source text for any hardcoded module key, skipping comment
602
+ // lines. Returns violation strings (one per hit). Exported for the negative test
603
+ // (mirrors scanScopeMapSource / scanDeterministicBody).
604
+ function scanKernelForModuleKeys(text, keys) {
605
+ const violations = [];
606
+ if (!keys || keys.length === 0) return violations;
607
+ const probes = keys.map((k) => ({ key: k, re: keyTokenRe(k) }));
608
+ String(text == null ? '' : text).split('\n').forEach((raw, i) => {
609
+ const t = raw.trim();
610
+ if (!t || t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) return;
611
+ for (const { key, re } of probes) {
612
+ if (re.test(raw)) {
613
+ violations.push(`names module "${key}" at line ${i + 1} — the kernel/module-system machinery must stay module-agnostic; no module key may be hardcoded in core (ADR 0083 §Decision #4).`);
614
+ }
615
+ }
616
+ });
617
+ return violations;
618
+ }
619
+
620
+ function checkCoreCarriesNoModuleCode() {
621
+ const keys = discoveredModuleKeys();
622
+ const violations = [];
623
+ for (const relFile of KERNEL_FILES) {
624
+ if (KERNEL_NAMES_MODULES_OK.has(relFile)) continue; // the registry legitimately names every module
625
+ let text;
626
+ try { text = fs.readFileSync(path.join(REPO_ROOT, relFile), 'utf8'); } catch { continue; }
627
+ for (const v of scanKernelForModuleKeys(text, keys)) violations.push(`${relFile} ${v}`);
628
+ }
629
+ return {
630
+ name: 'core carries no module code',
631
+ ok: violations.length === 0,
632
+ hardFail: violations.length > 0,
633
+ violations,
634
+ warnings: [],
635
+ note: `kernel machinery (${KERNEL_FILES.length} files) names no discovered module key${keys.length ? ` (keys: ${keys.join(', ')})` : ' (none yet)'} (ADR 0083 §Decision #4).`,
636
+ };
637
+ }
638
+
639
+ // ============================================================================
640
+ // Check 12 — the kernel imports no domain (ADR 0091 §1 / BV1.R70)
641
+ // ============================================================================
642
+ //
643
+ // A bounded kernel is only real if it DEPENDS on nothing but itself. This check
644
+ // walks every KERNEL_FILE's relative requires and flags any that reach a
645
+ // non-kernel core file (a soon-to-be-module domain). Kernel→kernel is fine;
646
+ // kernel→grandfathered-infra (the raw base pg pool, the pure path-match util) is
647
+ // fine; a bare npm/node spec is fine. Everything else is a tracked leak.
648
+ //
649
+ // ENFORCING (ADR 0102): the tranche-2 ratchet reached zero, so this is now a hard
650
+ // gate — the kernel imports only itself + grandfathered infra, and any NEW
651
+ // kernel→domain import fails the build (violations). The last leaks cleared were
652
+ // auth.js→db (carved to db-kernel), the doorway's Gemini-key re-exports (→ the
653
+ // art-pipeline 'art.getBuilderGeminiKeyRow' port) and builderRollingAvgGrade (→ the
654
+ // grading 'grade.builderRollingAvgGrade' port), plus reclassifying llm-cache /
655
+ // llm-pricing as kernel-adjacent infra. Adding a leak back reds the build.
656
+
657
+ // Grandfathered kernel-adjacent targets a KERNEL_FILE may import without it
658
+ // counting as a domain leak. The still-fused src/bongos/db.js monolith is NOT here:
659
+ // a kernel file reaching into it IS a real (tracked) leak until the identity/
660
+ // session slices finish carving into db-kernel.js.
661
+ const KERNEL_IMPORT_ALLOWLIST = new Set([
662
+ 'src/db.js', // the raw shared pg Pool (base infra; pool.js wraps it)
663
+ 'src/bongos/path-match.js', // pure touches-overlap matcher (no domain logic)
664
+ // Cross-cutting LLM-cost infra (ADR 0102): a stateless token→USD pricing table
665
+ // (llm-pricing) + a best-effort content-addressed cache (llm-cache), consumed by
666
+ // core, the grading module, and the CLI cost tooling ALIKE — no domain logic and
667
+ // no domain state beyond a cache table. Same category as the pg pool / path-match,
668
+ // not a domain module core would have to depend on anyway. The doorway may import
669
+ // them; they never flip the kernel→domain leak.
670
+ 'src/bongos/llm-cache.js',
671
+ 'src/bongos/llm-pricing.js',
672
+ ]);
673
+
674
+ // Pure: for relative require `spec` from kernel file `fromRel` (repo-relative),
675
+ // return the offending non-kernel target relpath, or null if the import is clean
676
+ // (kernel→kernel, kernel→allowlisted-infra, or a bare npm/node spec). Exported
677
+ // for the "actually catches a leak" negative test (mirrors scanScopeMapSource).
678
+ function kernelDomainImport(fromRel, spec, kernelSet = new Set(KERNEL_FILES), allow = KERNEL_IMPORT_ALLOWLIST) {
679
+ if (!/^\.\.?\//.test(spec)) return null; // bare npm/node spec — not our concern
680
+ const absTarget = path.resolve(path.dirname(path.join(REPO_ROOT, fromRel)), spec);
681
+ const r = rel(absTarget);
682
+ const forms = [r, r + '.js', r.replace(/\/$/, '') + '/index.js'];
683
+ if (forms.some((f) => kernelSet.has(f))) return null; // kernel → kernel
684
+ if (forms.some((f) => allow.has(f))) return null; // grandfathered infra/util
685
+ if (!/^src\//.test(r)) return null; // only flag app-source targets
686
+ return r.endsWith('.js') || r.endsWith('.json') ? r : `${r}.js`; // name the real file
687
+ }
688
+
689
+ function checkKernelImportsNoDomain() {
690
+ const kernelSet = new Set(KERNEL_FILES);
691
+ const findings = [];
692
+ for (const relFile of KERNEL_FILES) {
693
+ let text;
694
+ try { text = fs.readFileSync(path.join(REPO_ROOT, relFile), 'utf8'); } catch { continue; }
695
+ for (const raw of text.split('\n')) {
696
+ const t = raw.trim();
697
+ if (t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) continue;
698
+ let m; REQUIRE_RE.lastIndex = 0;
699
+ while ((m = REQUIRE_RE.exec(raw))) {
700
+ const target = kernelDomainImport(relFile, m[1], kernelSet);
701
+ if (target) findings.push(`${relFile} → require('${m[1]}') reaches non-kernel core file ${target} — a kernel file must depend only on the kernel (ADR 0091 §1). Carve ${target}'s domain out (or promote it into the kernel) to clear this.`);
702
+ }
703
+ }
704
+ }
705
+ return {
706
+ name: 'kernel imports no domain',
707
+ ok: findings.length === 0,
708
+ hardFail: findings.length > 0, // ENFORCING (ADR 0102): the ratchet reached zero — any new kernel→domain import now reds the build
709
+ violations: findings,
710
+ warnings: [],
711
+ note: `every KERNEL_FILE (${KERNEL_FILES.length}) must import only the kernel + grandfathered infra — ENFORCING (ADR 0091 §1 / ADR 0102).${findings.length ? ` ${findings.length} leak(s).` : ' Boundary clean.'}`,
712
+ };
713
+ }
714
+
715
+ // ============================================================================
716
+ // Check 11 — module migrations are additive + namespaced (ADR 0083 §Decision #5 / design 04§3)
717
+ // ============================================================================
718
+ //
719
+ // A module owns its DB slice through migrations under modules/<key>/migrations/.
720
+ // Two invariants keep that slice from clobbering core or a sibling:
721
+ // • ADDITIVE — no destructive DDL. "Disable a module" means "stop applying its
722
+ // migrations," never "drop its tables" (ADR 0083 §Module-owned migrations);
723
+ // there is no down path, so a DROP/TRUNCATE can only be an accident.
724
+ // • NAMESPACED — every object it CREATEs lives under the <key>_ prefix
725
+ // (dev-box → dev_box_), and it may only ALTER its own namespaced tables;
726
+ // core/sibling state is reached through the doorway/seams, never raw DDL.
727
+ //
728
+ // LEGACY_NAMESPACED: tables that predate the <key>_-prefix convention (created before the module
729
+ // system existed). Grandfathered per-module to avoid requiring a rename migration for legacy prod
730
+ // data. The fitness rule applies in full to any NEW migration a module ships.
731
+ const LEGACY_NAMESPACED = {
732
+ // game module: world_events + players were created before the module system (V1 schema). The
733
+ // schema_migrations table is a core tracking table that every migration may re-create idempotently.
734
+ game: new Set(['world_events', 'players', 'schema_migrations']),
735
+ };
736
+ const DESTRUCTIVE_DDL = [
737
+ { re: /\bdrop\s+table\b/i, label: 'DROP TABLE' },
738
+ { re: /\balter\s+table\b[\s\S]*?\bdrop\s+(?:column|constraint)\b/i, label: 'ALTER TABLE … DROP COLUMN/CONSTRAINT' },
739
+ { re: /\bdrop\s+(?:column|constraint)\b/i, label: 'DROP COLUMN/CONSTRAINT' },
740
+ { re: /\bdrop\s+(?:index|type|view|sequence|schema|materialized\s+view)\b/i, label: 'DROP <object>' },
741
+ { re: /\btruncate\b/i, label: 'TRUNCATE' },
742
+ ];
743
+ const CREATE_OBJ_RE = /\bcreate\s+(?:or\s+replace\s+)?(?:unique\s+)?(?:materialized\s+)?(table|view|sequence|type)\s+(?:if\s+not\s+exists\s+)?("?[\w.]+"?)/gi;
744
+ const CREATE_INDEX_RE = /\bcreate\s+(?:unique\s+)?index\s+(?:concurrently\s+)?(?:if\s+not\s+exists\s+)?(?:"?[\w.]+"?)\s+on\s+(?:only\s+)?("?[\w.]+"?)/gi;
745
+ const ALTER_TABLE_RE = /\balter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?("?[\w.]+"?)/gi;
746
+
747
+ // SQL object-namespace prefix for a module key: dev-box → dev_box (SQL-safe).
748
+ function moduleNamespace(key) { return key.replace(/-/g, '_'); }
749
+
750
+ // strip SQL comments so a commented-out statement is never scanned
751
+ function stripSqlComments(sql) {
752
+ return String(sql == null ? '' : sql)
753
+ .replace(/\/\*[\s\S]*?\*\//g, ' ')
754
+ .replace(/--[^\n]*/g, ' ');
755
+ }
756
+
757
+ // is `rawName` (possibly quoted / schema-qualified) under the <ns>_ namespace?
758
+ function inNamespace(rawName, ns, legacySet) {
759
+ const name = String(rawName).replace(/"/g, '').replace(/^[A-Za-z_]\w*\./, '');
760
+ return name.startsWith(ns + '_') || (legacySet && legacySet.has(name));
761
+ }
762
+
763
+ // A CHECK-enum WIDENING is additive-in-spirit: `DROP CONSTRAINT IF EXISTS <c>` PAIRED
764
+ // with a re-`ADD CONSTRAINT <c>` (SAME name) in the SAME migration only PERMITS more
765
+ // values — no existing row changes, no data is lost (task 1978: provisioning gains a
766
+ // 'standalone' hosting_shape). Allow that exact pattern; a BARE `DROP CONSTRAINT` (no
767
+ // re-add), or any DROP TABLE/COLUMN/TRUNCATE, stays destructive. PURE.
768
+ function reAddedConstraintNames(sql) {
769
+ const names = new Set();
770
+ const re = /\badd\s+constraint\s+("?[\w.]+"?)/gi;
771
+ let m;
772
+ while ((m = re.exec(sql))) names.add(m[1].replace(/"/g, ''));
773
+ return names;
774
+ }
775
+ function isConstraintWidening(stmt, reAdded) {
776
+ const m = /\bdrop\s+constraint\s+(?:if\s+exists\s+)?("?[\w.]+"?)/i.exec(stmt);
777
+ if (!m) return false; // not a DROP CONSTRAINT at all
778
+ // never let a DROP CONSTRAINT statement smuggle a table/column drop past the gate
779
+ if (/\bdrop\s+(?:table|column|index|type|view|sequence|schema|materialized\s+view)\b/i.test(stmt)) return false;
780
+ if (/\btruncate\b/i.test(stmt)) return false;
781
+ return reAdded.has(m[1].replace(/"/g, '')); // re-added same-named constraint → widening
782
+ }
783
+
784
+ // Pure: violations for ONE module's migration SQL. Exported for the negative test.
785
+ function moduleMigrationViolations(key, sql) {
786
+ const ns = moduleNamespace(key);
787
+ const legacy = LEGACY_NAMESPACED[key] || null;
788
+ const clean = stripSqlComments(sql);
789
+ const violations = [];
790
+ // additive — scan per statement so an ALTER…DROP never spans a `;`
791
+ const reAdded = reAddedConstraintNames(clean);
792
+ for (const stmt of clean.split(';')) {
793
+ if (isConstraintWidening(stmt, reAdded)) continue; // additive CHECK-enum widening (task 1978)
794
+ for (const d of DESTRUCTIVE_DDL) {
795
+ if (d.re.test(stmt)) {
796
+ violations.push(`module "${key}" migration uses destructive DDL (${d.label}) — module migrations must be ADDITIVE; disabling a module stops applying, it never drops (ADR 0083 §Decision #5).`);
797
+ break; // one destructive flag per statement is enough
798
+ }
799
+ }
800
+ }
801
+ // namespaced — CREATEd objects + ALTER targets must live under <ns>_ (or be grandfathered)
802
+ let m;
803
+ CREATE_OBJ_RE.lastIndex = 0;
804
+ while ((m = CREATE_OBJ_RE.exec(clean))) {
805
+ if (!inNamespace(m[2], ns, legacy)) {
806
+ violations.push(`module "${key}" migration creates ${m[1].toUpperCase()} ${m[2]} outside its namespace — module objects must be ${ns}_-prefixed (ADR 0083 §Decision #5).`);
807
+ }
808
+ }
809
+ CREATE_INDEX_RE.lastIndex = 0;
810
+ while ((m = CREATE_INDEX_RE.exec(clean))) {
811
+ if (!inNamespace(m[1], ns, legacy)) {
812
+ violations.push(`module "${key}" migration indexes a non-namespaced table ${m[1]} — a module migration may only touch its own ${ns}_ tables (ADR 0083 §Decision #5).`);
813
+ }
814
+ }
815
+ ALTER_TABLE_RE.lastIndex = 0;
816
+ while ((m = ALTER_TABLE_RE.exec(clean))) {
817
+ if (!inNamespace(m[1], ns, legacy)) {
818
+ violations.push(`module "${key}" migration alters a non-namespaced table ${m[1]} — a module migration may only touch its own ${ns}_ tables; reach core state through the doorway/seams, not DDL (ADR 0083 §Decision #5).`);
819
+ }
820
+ }
821
+ return violations;
822
+ }
823
+
824
+ function checkModuleMigrationsAdditive() {
825
+ const violations = [];
826
+ let scanned = 0;
827
+ for (const key of discoveredModuleKeys()) { // union across module roots (ADR 0108 §Consequences #4)
828
+ const migDir = path.join(moduleDir(key), 'migrations');
829
+ let files = [];
830
+ try { files = fs.readdirSync(migDir).filter((f) => f.endsWith('.sql')); } catch { continue; }
831
+ for (const f of files.sort()) {
832
+ scanned += 1;
833
+ const sql = fs.readFileSync(path.join(migDir, f), 'utf8');
834
+ for (const v of moduleMigrationViolations(key, sql)) {
835
+ violations.push(`modules/${key}/migrations/${f}: ${v}`);
836
+ }
837
+ }
838
+ }
839
+ return {
840
+ name: 'module migrations additive + namespaced',
841
+ ok: violations.length === 0,
842
+ hardFail: violations.length > 0,
843
+ violations,
844
+ warnings: [],
845
+ note: `${scanned} module migration file(s): additive (no destructive DDL) + <key>_-namespaced objects (ADR 0083 §Decision #5).`,
846
+ };
847
+ }
848
+
849
+ // ============================================================================
850
+ // Check 11b — migration FILENAMES are namespaced + no cross-root stem collision
851
+ // (ADR 0108 §5; extends Check 11 to the cross-repo layer)
852
+ // ============================================================================
853
+ //
854
+ // Check 11 (above) guards a module migration's OBJECTS (additive + <key>_-namespaced
855
+ // tables). THIS check guards migration FILENAMES across all three roots — the
856
+ // discipline that lets the core repo and the instance repo version independently
857
+ // (ADR 0108) without their migrations colliding:
858
+ // • migrations/*.sql (CORE) → NEW files must be core_-prefixed
859
+ // • migrations/instance/*.sql (INSTANCE) → stay bare (or instance_-prefixed)
860
+ // • modules/<key>/migrations/ (MODULE) → NEW files must be <key>_-prefixed
861
+ // Existing bare files are grandfathered (migration-namespace.js LEGACY_BARE_*). The
862
+ // second invariant is the real bug guard: migrate.sh keys schema_migrations by
863
+ // filename STEM, so two migrations from different sources sharing a stem would apply
864
+ // ONCE and the other be SILENTLY SKIPPED (never applied). Single-root today; W6/task
865
+ // 1886 makes the root resolution two-root-aware once the core installs from node_modules.
866
+ function sqlFilesIn(absDir) {
867
+ try { return fs.readdirSync(absDir).filter((f) => f.endsWith('.sql')).sort(); }
868
+ catch { return []; } // dir absent (e.g. no migrations/instance in a vanilla instance) → nothing to scan
869
+ }
870
+
871
+ function checkMigrationNamespacing() {
872
+ const violations = [];
873
+ const files = []; // { source, moduleKey?, basename } — fed to the stem-collision scan
874
+
875
+ const coreFiles = sqlFilesIn(path.join(CORE_ROOT, 'migrations')); // core migrations ship in the core package
876
+ for (const f of coreFiles) {
877
+ const v = filenameViolation(CORE_NS, f);
878
+ if (v) violations.push(`migrations/${f}: ${v}`);
879
+ files.push({ source: CORE_NS, basename: f });
880
+ }
881
+
882
+ const instFiles = sqlFilesIn(path.join(INSTANCE_ROOT, 'migrations', 'instance')); // instance seeds live in the host repo
883
+ for (const f of instFiles) {
884
+ const v = filenameViolation(INSTANCE_NS, f);
885
+ if (v) violations.push(`migrations/instance/${f}: ${v}`);
886
+ files.push({ source: INSTANCE_NS, basename: f });
887
+ }
888
+
889
+ let moduleCount = 0;
890
+ for (const key of discoveredModuleKeys()) {
891
+ for (const f of sqlFilesIn(path.join(moduleDir(key), 'migrations'))) {
892
+ moduleCount += 1;
893
+ const v = filenameViolation('module', f, { moduleKey: key });
894
+ if (v) violations.push(`modules/${key}/migrations/${f}: ${v}`);
895
+ files.push({ source: 'module', moduleKey: key, basename: f });
896
+ }
897
+ }
898
+
899
+ for (const c of stemCollisions(files)) {
900
+ violations.push(`migration stem "${c.stem}" is produced by more than one source (${c.files.join(', ')}) — schema_migrations is stem-keyed, so one applies and the rest are SILENTLY SKIPPED; namespace them (ADR 0108 §5).`);
901
+ }
902
+
903
+ return {
904
+ name: 'migration filenames namespaced + no stem collision (ADR 0108 §5)',
905
+ ok: violations.length === 0,
906
+ hardFail: violations.length > 0,
907
+ violations,
908
+ warnings: [],
909
+ note: `${coreFiles.length} core + ${instFiles.length} instance + ${moduleCount} module migration file(s): new core→core_, new module→<key>_, instance bare; no cross-root stem collision.`,
910
+ };
911
+ }
912
+
913
+ // ============================================================================
914
+ // Check 13 — portable code carries no hardcoded host-brand identity (BV1.R76 + task 1705)
915
+ // ============================================================================
916
+ //
917
+ // The portable core (src/bongos/**) and every feature MODULE (modules/**) must read
918
+ // the instance's product/world name + domains from branding() / window.__BRANDING__,
919
+ // never a baked-in "Off the Boats" / "Amazonprimea" / apex domain (ADR 0062 §3/§5;
920
+ // the branding contract). R76 shipped this as an 8-FILE allowlist over the kernel
921
+ // LLM-prompt headers; task 1705 WIDENS it to a directory-wide scan, so ANY new brand
922
+ // leak in portable code reds the build — not just the nine files someone remembered
923
+ // to list. (The 2026-07-02 separation audit found five leaks the old allowlist missed:
924
+ // the rank/graduation broadcasts, the auth error-page title, the apex-origin default,
925
+ // and ~124 served-UI strings.) Comments are stripped first (block /* */, HTML
926
+ // <!-- -->, and JS // line comments via a `://`-safe strip so a URL is never
927
+ // truncated) because the de-brand rationale legitimately names the old literals in
928
+ // prose. Instance-injected placeholders ({{worldName}} etc.) carry no literal, so
929
+ // they pass untouched.
930
+ //
931
+ // EXCLUDED — the small instance-1 / ops allowlist (task 1705):
932
+ // - modules/game/** — the INSTANCE'S OWN PRODUCT. A product's identity is
933
+ // instance-specific by definition (a vanilla instance ships a different product,
934
+ // or none); it is not portable-platform code the branding pack can neutralize.
935
+ // A documented carve-out, not an oversight.
936
+ // - generated / build-output dirs (no authored source).
937
+ const PROMPT_IDENTITY_LITERALS = [
938
+ /off the boats/i,
939
+ /amazonprimea/i,
940
+ ];
941
+ // Scan roots (recursive) + kernel-prompt CLIs that live outside them.
942
+ const IDENTITY_SCAN_ROOTS = ['src/bongos', 'modules'];
943
+ const IDENTITY_SCAN_EXTRA_FILES = ['scripts/gds/architect-audit.js'];
944
+ const IDENTITY_SCAN_EXTS = new Set(['.js', '.html', '.css']);
945
+ // Path-prefix carve-outs (see the header note). Kept SMALL and documented.
946
+ const IDENTITY_SCAN_EXCLUDE = ['modules/game/'];
947
+ // Directories with no authored source to gate.
948
+ const IDENTITY_SCAN_SKIP_DIRS = new Set(['node_modules', '.git', 'generated', 'iterations', 'references']);
949
+
950
+ // Strip comments so a rationale comment naming the old brand never trips the scan.
951
+ // The JS // strip is `://`-safe (keeps the char before `//` unless it's `:`), so a
952
+ // hardcoded "https://amazonprimea.com" URL is NOT truncated and still gets caught.
953
+ function stripCommentsForIdentity(text, ext) {
954
+ let t = String(text == null ? '' : text);
955
+ if (ext === '.html') return t.replace(/<!--[\s\S]*?-->/g, '');
956
+ t = t.replace(/\/\*[\s\S]*?\*\//g, ''); // block comments (js + css)
957
+ if (ext === '.js') t = t.split('\n').map((l) => l.replace(/(^|[^:])\/\/.*$/, '$1')).join('\n');
958
+ return t;
959
+ }
960
+
961
+ // Pure: scan one file's comment-stripped source for a hardcoded host-brand literal.
962
+ // Returns violation strings. Exported for the "actually catches a re-hardcode" test.
963
+ function scanPromptIdentity(relFile, text, ext = path.extname(relFile)) {
964
+ const violations = [];
965
+ stripCommentsForIdentity(text, ext).split('\n').forEach((raw, i) => {
966
+ if (!raw.trim()) return;
967
+ for (const re of PROMPT_IDENTITY_LITERALS) {
968
+ const m = raw.match(re);
969
+ if (m) {
970
+ violations.push(`${relFile}:${i + 1} hardcodes the host-brand identity "${m[0]}" — read it from branding()/window.__BRANDING__ (identity/domains/currency), or use a {{worldName}}-style token, so a non-OTB instance renders ITS OWN brand (ADR 0062 §3/§5; BV1.R76 / task 1705).`);
971
+ break; // one hit per line is enough
972
+ }
973
+ }
974
+ });
975
+ return violations;
976
+ }
977
+
978
+ // Recursively collect scannable files under one root, honoring the skip-dir + carve-out lists.
979
+ function collectIdentityFiles(absRoot, relRoot) {
980
+ const out = [];
981
+ let entries;
982
+ try { entries = fs.readdirSync(absRoot, { withFileTypes: true }); }
983
+ catch { return out; }
984
+ for (const e of entries) {
985
+ const rel = relRoot ? `${relRoot}/${e.name}` : e.name;
986
+ if (e.isDirectory()) {
987
+ if (IDENTITY_SCAN_SKIP_DIRS.has(e.name)) continue;
988
+ if (IDENTITY_SCAN_EXCLUDE.some((p) => `${rel}/`.startsWith(p))) continue;
989
+ out.push(...collectIdentityFiles(path.join(absRoot, e.name), rel));
990
+ } else if (IDENTITY_SCAN_EXTS.has(path.extname(e.name))
991
+ && !IDENTITY_SCAN_EXCLUDE.some((p) => rel.startsWith(p))) {
992
+ out.push(rel);
993
+ }
994
+ }
995
+ return out;
996
+ }
997
+
998
+ function checkKernelPromptsNoHardcodedIdentity() {
999
+ const violations = [];
1000
+ let scanned = 0;
1001
+ // Collect {abs, rel}: src/bongos is CORE; modules ship from BOTH roots (ADR 0108 §Consequences #4).
1002
+ // Dedup by repo-relative path so a shared checkout never scans a file twice, and read each file
1003
+ // from the root it was actually found under (a module may live in the instance repo post-split).
1004
+ const seen = new Set();
1005
+ const entries = [];
1006
+ const addFrom = (rootAbs, sub) => {
1007
+ for (const relFromRoot of collectIdentityFiles(path.join(rootAbs, sub), sub)) {
1008
+ if (seen.has(relFromRoot)) continue;
1009
+ seen.add(relFromRoot);
1010
+ entries.push({ abs: path.join(rootAbs, relFromRoot), rel: relFromRoot });
1011
+ }
1012
+ };
1013
+ for (const root of IDENTITY_SCAN_ROOTS) {
1014
+ for (const rootAbs of (root === 'modules' ? MODULE_ROOTS : [CORE_ROOT])) addFrom(rootAbs, root);
1015
+ }
1016
+ for (const extra of IDENTITY_SCAN_EXTRA_FILES) {
1017
+ if (!seen.has(extra)) { seen.add(extra); entries.push({ abs: path.join(CORE_ROOT, extra), rel: extra }); }
1018
+ }
1019
+ for (const { abs, rel: relFile } of entries) {
1020
+ let text;
1021
+ try { text = fs.readFileSync(abs, 'utf8'); }
1022
+ catch { continue; } // a file moved/carved away is not a failure of this gate
1023
+ scanned += 1;
1024
+ violations.push(...scanPromptIdentity(relFile, text));
1025
+ }
1026
+ return {
1027
+ name: 'portable code carries no hardcoded host identity (BV1.R76 / task 1705)',
1028
+ ok: violations.length === 0,
1029
+ hardFail: violations.length > 0,
1030
+ violations,
1031
+ warnings: [],
1032
+ note: `${scanned} portable source file(s) scanned (src/bongos/** + modules/** + kernel-prompt CLIs, minus the instance-1 product + generated dirs) — none may hardcode the host brand; identity comes from branding()/window.__BRANDING__ (ADR 0062 §3/§5).`,
1033
+ };
1034
+ }
1035
+
1036
+ // ---- design contract (ADR 0081 / task 1377) --------------------------------
1037
+
1038
+ function checkDesignContract() {
1039
+ const { spawnSync } = require('node:child_process');
1040
+ const validatorPath = path.join(REPO_ROOT, 'scripts', 'gds', 'validate-design.js');
1041
+ const adaptersDir = path.join(REPO_ROOT, 'src', 'ui', 'adapters');
1042
+ const hasAdapters = (() => {
1043
+ try { return fs.readdirSync(adaptersDir).some((e) => fs.statSync(path.join(adaptersDir, e)).isDirectory()); }
1044
+ catch { return false; }
1045
+ })();
1046
+
1047
+ const result = spawnSync(process.execPath, [validatorPath, '--json'], { encoding: 'utf8' });
1048
+ let report = { ok: true, errors: [], warnings: [] };
1049
+ try { report = JSON.parse(result.stdout || '{}'); } catch { /* fall through with defaults */ }
1050
+ if (result.status !== 0 && !report.errors.length) {
1051
+ report.errors.push(result.stderr ? result.stderr.trim() : 'validate-design.js exited non-zero');
1052
+ report.ok = false;
1053
+ }
1054
+
1055
+ return {
1056
+ name: 'design contract valid (docs/design-contract.md / ADR 0081)',
1057
+ ok: report.ok,
1058
+ hardFail: !report.ok && hasAdapters,
1059
+ violations: report.errors || [],
1060
+ warnings: report.warnings || [],
1061
+ note: hasAdapters
1062
+ ? 'Hard-fail: adapters present — contract must be valid. Run: node scripts/gds/validate-design.js'
1063
+ : 'Warn-only until first adapter lands (task 1378 / task 1379). Run: node scripts/gds/validate-design.js',
1064
+ };
1065
+ }
1066
+
1067
+ // ============================================================================
1068
+ // Check — hall sections are registry-owned, not hardcoded (BV1.R75 / ADR 0062 §4)
1069
+ // ============================================================================
1070
+ // The builders'-hall widgets are now a REGISTRY (modules/hall-ui/public/dom-utils.js
1071
+ // contributeHallWidget; the core set in modules/hall-ui/public/hall-widgets.js, modules
1072
+ // via their own client script). The hall's <main> is filled by builders.js from that
1073
+ // registry. So no widget may be hardcoded as a literal <section class="scroll"> inside
1074
+ // <main> in index.html — that was the pre-R75 second copy this carve removed. This
1075
+ // gate fails if one creeps back, keeping the registry the single source of truth (so a
1076
+ // reskin/module-toggle reaches every widget). Self-contained: a pure scan of
1077
+ // index.html's <main>, no module/registry state needed. Exported for the unit test.
1078
+ // (The hall surface itself carved to modules/hall-ui/public/ in BV1.R85.)
1079
+ const HALL_INDEX = path.join(moduleDir('hall-ui'), 'public', 'index.html'); // hall-ui is a module — resolve its root (ADR 0108)
1080
+
1081
+ function hallMainSlice(html) {
1082
+ // Strip HTML comments first so a comment that merely MENTIONS <main> or a
1083
+ // <section class="scroll"> (e.g. the carve's own explanatory note) never trips
1084
+ // the scan — only real, rendered markup counts.
1085
+ const stripped = html.replace(/<!--[\s\S]*?-->/g, '');
1086
+ const open = stripped.indexOf('<main');
1087
+ if (open === -1) return '';
1088
+ const start = stripped.indexOf('>', open);
1089
+ const close = stripped.indexOf('</main>', start === -1 ? open : start);
1090
+ if (start === -1 || close === -1) return '';
1091
+ return stripped.slice(start + 1, close);
1092
+ }
1093
+
1094
+ function scanHallSectionRegistry(html) {
1095
+ const main = hallMainSlice(html);
1096
+ // A hall widget section carries class="scroll" (possibly with modifiers, e.g.
1097
+ // "scroll scroll--welcome"). Catch any <section ... class="...scroll..."> in <main>.
1098
+ const re = /<section\b[^>]*\bclass\s*=\s*"(?:[^"]*\b)?scroll\b[^"]*"/gi;
1099
+ const hits = main.match(re) || [];
1100
+ return hits.map((h) => h.replace(/\s+/g, ' ').trim());
1101
+ }
1102
+
1103
+ function checkHallSectionRegistry() {
1104
+ let html = '';
1105
+ try { html = fs.readFileSync(HALL_INDEX, 'utf8'); }
1106
+ catch { return { name: 'hall widgets are registry-owned (ADR 0062 §4)', ok: true, hardFail: false, violations: [], warnings: ['modules/hall-ui/public/index.html not found — skipped'], note: 'index.html absent.' }; }
1107
+ const hardcoded = scanHallSectionRegistry(html);
1108
+ const violations = hardcoded.map((s) =>
1109
+ `modules/hall-ui/public/index.html <main> hardcodes a hall section "${s}" — register it via window.OTB.contributeHallWidget (modules/hall-ui/public/hall-widgets.js for core, a module's client script otherwise), don't inline the markup (BV1.R75 / ADR 0062 §4).`);
1110
+ return {
1111
+ name: 'hall widgets are registry-owned (ADR 0062 §4)',
1112
+ ok: violations.length === 0,
1113
+ hardFail: violations.length > 0,
1114
+ violations,
1115
+ warnings: [],
1116
+ note: 'modules/hall-ui/public/index.html <main> contains no hardcoded <section class="scroll"> — the hall-widget registry is the single source of truth.',
1117
+ };
1118
+ }
1119
+
1120
+ // ============================================================================
1121
+ // Check 16 — core code must not HARD-require an instance-excluded config/*.json
1122
+ // ============================================================================
1123
+ //
1124
+ // A vanilla self-host boot ships only the PUBLISHABLE tree (publish-manifest.js). An
1125
+ // instance-only config (config/devbox-app.json, config/bongos-cli.json, config/branding.json,
1126
+ // config/modules.json, …) is boundary-EXCLUDED, so a bare `require('../../config/<excluded>.json')`
1127
+ // in core code throws MODULE_NOT_FOUND and CRASHES the boot on a fresh instance — the exact
1128
+ // class that shipped in R88 (two hard-requires in src/bongos/serve-internal.js; task 1824). The
1129
+ // cure is to guard the read (tryRequireConfig / fs.existsSync → fail-to-vanilla), which also
1130
+ // makes the require a non-literal so it no longer matches here. Check 1 (core↔host) scans
1131
+ // code→code IMPORTS and misses this code→config class. Only an EXCLUDED config is a violation;
1132
+ // a publishable neutral.*/schema config (which ships with the core) is fine.
1133
+ const CONFIG_REQUIRE_RE = /require\(\s*['"]([^'"]*config\/[^'"]*\.json)['"]\s*\)/g;
1134
+
1135
+ function checkCoreRequiresExcludedConfig() {
1136
+ const { isPublishable } = require('./publish-manifest');
1137
+ const violations = [];
1138
+ // src/ + scripts/ are CORE; modules ship from BOTH roots (ADR 0108 §Consequences #4). Dedup by
1139
+ // repo-relative path so a shared checkout scans each file once.
1140
+ const seen = new Set();
1141
+ const allAbs = [
1142
+ ...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
1143
+ ...walkJsAllRoots('modules'),
1144
+ ];
1145
+ for (const abs of allAbs) {
1146
+ const relFile = rel(abs);
1147
+ if (seen.has(relFile)) continue;
1148
+ seen.add(relFile);
1149
+ if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
1150
+ for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
1151
+ const trimmed = raw.trim();
1152
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
1153
+ let m;
1154
+ CONFIG_REQUIRE_RE.lastIndex = 0;
1155
+ while ((m = CONFIG_REQUIRE_RE.exec(raw))) {
1156
+ const relCfg = rel(path.resolve(path.dirname(abs), m[1]));
1157
+ if (!isPublishable(relCfg)) {
1158
+ violations.push(`${relFile} → require('${m[1]}') hard-requires an instance-excluded config (${relCfg}) — guard the read (tryRequireConfig / fs.existsSync, fail-to-vanilla) so a fresh core boots (task 1824).`);
1159
+ }
1160
+ }
1161
+ }
1162
+ }
1163
+ return {
1164
+ name: 'core never hard-requires an excluded config (task 1824)',
1165
+ ok: violations.length === 0,
1166
+ hardFail: violations.length > 0,
1167
+ violations,
1168
+ warnings: [],
1169
+ note: "a publishable src/·scripts/·modules/ .js must not require('…config/X.json') when X.json is boundary-excluded — guard the read so a vanilla core boots.",
1170
+ };
1171
+ }
1172
+
1173
+ // ============================================================================
1174
+ // Check 17 — no route hand-rolls a FLAT error (BV1 R03 / task 1990 / ADR 0116)
1175
+ // ============================================================================
1176
+ //
1177
+ // The error-envelope contract (ADR 0116) says every failure is the ONE envelope
1178
+ // `{error:{code,message,details}}`, emitted via res.fail() or a thrown ApiError
1179
+ // (the R02 middleware, task 1989). A hand-rolled flat error —
1180
+ // `res.status(n).json({ error: 'x' })` or `.json({ error })` — breaks the
1181
+ // contract: the caller gets a bare string where the envelope should be.
1182
+ //
1183
+ // RATCHET, not big-bang. The R04 sweep (task 1991) migrates the ~562 existing
1184
+ // sites; until it finishes they'd all "fail" a strict check, so this HOLDS THE
1185
+ // LINE: it fails only when the count rises ABOVE the committed baseline (a NEW
1186
+ // hand-rolled flat error), and nudges the baseline DOWN as the sweep shrinks it.
1187
+ // When the sweep reaches 0, FLAT_ERROR_BASELINE is 0 and any new flat error
1188
+ // hard-fails immediately. Detector is shared with the sweep + its test.
1189
+ const FLAT_ERROR_BASELINE = 0; // R04 sweep (task 1991) landed the surface at 0 — the gate is now hard-zero: any new hand-rolled flat error fails the build immediately.
1190
+ // A flat error is a `.json({ … })` whose FIRST key is `error` with a value that
1191
+ // is NOT an object literal (a string/var/shorthand). The envelope's nested
1192
+ // `error: { … }` and `.json(envelope(…))` deliberately do NOT match.
1193
+ // The `(?!\s*\{)` lookahead is whitespace-aware: without the inner `\s*` the
1194
+ // preceding `\s*` backtracks to zero and the lookahead is tested at the space in
1195
+ // `error: {`, so the compliant nested envelope was wrongly flagged (task 2029).
1196
+ const FLAT_ERROR_VALUE_RE = /\.json\(\s*\{\s*error\s*:\s*(?!\s*\{)/g; // error: 'x' | error: code
1197
+ const FLAT_ERROR_SHORTHAND_RE = /\.json\(\s*\{\s*error\s*[,}]/g; // { error } | { error, … }
1198
+
1199
+ // Strip // line comments + /* */ block comments AND blank out string/template
1200
+ // contents (keeping the delimiters) so neither a DOC example of the anti-pattern
1201
+ // — e.g. the res.fail() usage note in middleware/error-envelope.js — nor a
1202
+ // pattern quoted inside a string literal is counted as a real flat error once
1203
+ // the gate is hard-zero (task 1991). Real flat errors are unaffected: in
1204
+ // `res.status(404).json({ error: 'x' })` the `.json({ error:` the detector keys
1205
+ // on is CODE (outside the string), so blanking the `'x'` content still matches.
1206
+ function stripCommentsForFlat(src) {
1207
+ let out = '';
1208
+ let i = 0;
1209
+ let quote = null;
1210
+ while (i < src.length) {
1211
+ const c = src[i];
1212
+ const d = src[i + 1];
1213
+ if (quote) {
1214
+ // inside a string/template: drop content, keep only the closing delimiter
1215
+ if (c === '\\') { i += 2; continue; }
1216
+ if (c === quote) { out += c; quote = null; }
1217
+ i++; continue;
1218
+ }
1219
+ if (c === '"' || c === "'" || c === '`') { quote = c; out += c; i++; continue; }
1220
+ if (c === '/' && d === '/') { while (i < src.length && src[i] !== '\n') i++; continue; }
1221
+ if (c === '/' && d === '*') { i += 2; while (i < src.length && !(src[i] === '*' && src[i + 1] === '/')) i++; i += 2; continue; }
1222
+ out += c; i++;
1223
+ }
1224
+ return out;
1225
+ }
1226
+
1227
+ function countFlatErrors(text) {
1228
+ const code = stripCommentsForFlat(text);
1229
+ return (code.match(FLAT_ERROR_VALUE_RE) || []).length
1230
+ + (code.match(FLAT_ERROR_SHORTHAND_RE) || []).length;
1231
+ }
1232
+
1233
+ // scanFlatErrorSites() — per-file flat-error counts across the API surface
1234
+ // (core src/bongos + every module), skipping tests. Shared by the check below, the
1235
+ // R04 codemod, and tests/api_error_envelope_buildcheck.mjs.
1236
+ function scanFlatErrorSites() {
1237
+ const seen = new Set();
1238
+ const byFile = {};
1239
+ let total = 0;
1240
+ const files = [
1241
+ ...walkJs(path.join(CORE_ROOT, 'src', 'bongos')),
1242
+ ...walkJsAllRoots('modules'),
1243
+ ];
1244
+ for (const abs of files) {
1245
+ const relFile = rel(abs);
1246
+ if (seen.has(relFile)) continue;
1247
+ seen.add(relFile);
1248
+ if (relFile.includes('/tests/') || relFile.endsWith('.test.js')) continue;
1249
+ const n = countFlatErrors(fs.readFileSync(abs, 'utf8'));
1250
+ if (n) { byFile[relFile] = n; total += n; }
1251
+ }
1252
+ return { total, byFile };
1253
+ }
1254
+
1255
+ function checkErrorEnvelope() {
1256
+ const { total, byFile } = scanFlatErrorSites();
1257
+ const violations = [];
1258
+ const warnings = [];
1259
+ if (total > FLAT_ERROR_BASELINE) {
1260
+ const top = Object.entries(byFile).sort((a, b) => b[1] - a[1]).slice(0, 8)
1261
+ .map(([f, n]) => `${f} (${n})`).join(', ');
1262
+ violations.push(
1263
+ `${total} hand-rolled flat-error sites — ABOVE the ${FLAT_ERROR_BASELINE} baseline. A new route emitted a flat \`res.status(n).json({error:'x'})\` instead of \`res.fail('x', n)\` / \`throw new ApiError('x')\` (ADR 0116). Top files: ${top}. Use the envelope helper (src/bongos/middleware/error-envelope.js).`
1264
+ );
1265
+ } else if (total < FLAT_ERROR_BASELINE) {
1266
+ warnings.push(
1267
+ `${total} flat-error sites — BELOW the ${FLAT_ERROR_BASELINE} baseline. The sweep tightened; lower FLAT_ERROR_BASELINE to ${total} in fitness.js so the ratchet can't slip back (BV1 R04 / task 1991).`
1268
+ );
1269
+ }
1270
+ return {
1271
+ name: 'no route hand-rolls a flat error (ADR 0116 / R03)',
1272
+ ok: violations.length === 0,
1273
+ hardFail: violations.length > 0,
1274
+ violations,
1275
+ warnings,
1276
+ note: `≤ ${FLAT_ERROR_BASELINE} flat \`.json({error:'…'})\` sites (ratchet → 0 as the R04 sweep lands); currently ${total}. New failures must use res.fail()/ApiError (the R02 envelope).`,
1277
+ };
1278
+ }
1279
+
1280
+ // ============================================================================
1281
+ // Check 18 — every write route validates its body (BV1 R12 / task 1999 / ADR 0118)
1282
+ // ============================================================================
1283
+ //
1284
+ // Criterion api-validation: "the build fails on an unvalidated write route." R11
1285
+ // (#1998) gave every body-reading write route a strict validateOrRespond schema
1286
+ // (code-first, spec-driven — ADR 0118); this LOCKS that in. Reusing the SAME
1287
+ // route model that generates the OpenAPI spec (gen-api-docs.extractModel —
1288
+ // readsBody + validated), a POST/PUT/PATCH/DELETE that reads req.body must carry
1289
+ // a validateOrRespond()/validate(req.body) schema. Hard-zero (no ratchet — the
1290
+ // R11 sweep already reached 100%): a NEW write route that reads a body but
1291
+ // declares no schema fails the build immediately, so unknown/typo'd fields can
1292
+ // never again be silently dropped.
1293
+ //
1294
+ // EXEMPT — the builder-settings /me/*-prefs routes. Their bodies are DYNAMIC-KEY
1295
+ // maps ({ "<skill|sound|event-id>": <value> }, or a single-key toggle) that a
1296
+ // FIXED field schema cannot express; they validate via their module lib's
1297
+ // validatePatch()/validate() (which already rejects unknown keys with a 422). A
1298
+ // fixed strict schema would wrongly reject the valid dynamic keys — so these are
1299
+ // validated-by-lib, not by validateOrRespond. Documented per-route below; see
1300
+ // learning #133 / ADR 0118. Adding a route here is a deliberate, reviewable act.
1301
+ const WRITE_VALIDATION_EXEMPT = new Set([
1302
+ 'PATCH /me/skill-prefs', // { "<skill>": "<model>"|null } — dynamic keys; skillPrefs.validatePatch
1303
+ 'PATCH /me/sound-prefs', // { "<sound>": <bool>|null } — dynamic keys; soundPrefs.validatePatch
1304
+ 'PATCH /me/event-sound-prefs', // { "<event>": "<variant>"|null } — dynamic keys; eventSoundPrefs.validatePatch
1305
+ 'PATCH /me/wandering', // { wandering: 0..3|null } — rank-capped; wanderingPrefs.validatePatch
1306
+ 'PATCH /me/interaction', // interaction profile — interactionPrefs.validatePatch
1307
+ 'PATCH /me/model-allocation', // per-slot tiers — modelAllocPrefs.validatePatch
1308
+ 'PATCH /me/render', // { widgets: <bool>|null } — renderPrefs.validatePatch
1309
+ 'PATCH /me/display-name', // { display_name } — displayName.validate
1310
+ ]);
1311
+
1312
+ function scanUnvalidatedWriteRoutes(model = genApiDocs.extractModel()) {
1313
+ const unvalidated = [];
1314
+ for (const r of model) {
1315
+ if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(r.method)) continue;
1316
+ // Only routes that actually read a body have an input surface to validate;
1317
+ // a write that ignores req.body has nothing to declare.
1318
+ if (!r.body || !r.body.readsBody || r.body.validated) continue;
1319
+ if (WRITE_VALIDATION_EXEMPT.has(`${r.method} ${r.route}`)) continue;
1320
+ unvalidated.push(`${r.method} ${r.route} [${r.file}]`);
1321
+ }
1322
+ return unvalidated;
1323
+ }
1324
+
1325
+ function checkWriteRoutesValidated() {
1326
+ const unvalidated = scanUnvalidatedWriteRoutes();
1327
+ const violations = [];
1328
+ if (unvalidated.length) {
1329
+ violations.push(
1330
+ `${unvalidated.length} write route(s) read req.body but declare NO validateOrRespond/validate schema (ADR 0118 / R11 requires one so unknown fields are rejected via the ADR-0116 envelope): ${unvalidated.join('; ')}. Add a strict validateOrRespond(req, res, { … }) listing every field the handler reads (see learning #133). A dynamic-key body validated by a module lib belongs in WRITE_VALIDATION_EXEMPT with a one-line reason.`
1331
+ );
1332
+ }
1333
+ return {
1334
+ name: 'every write route validates its body (ADR 0118 / R12)',
1335
+ ok: violations.length === 0,
1336
+ hardFail: violations.length > 0,
1337
+ violations,
1338
+ warnings: [],
1339
+ note: `body-reading POST/PUT/PATCH/DELETE routes must call validateOrRespond/validate (${WRITE_VALIDATION_EXEMPT.size} dynamic-key prefs routes validate by lib); ${unvalidated.length} unvalidated.`,
1340
+ };
1341
+ }
1342
+
1343
+ // ---- runner ----------------------------------------------------------------
1344
+
1345
+ const CHECKS = [
1346
+ checkCoreHostBoundary,
1347
+ checkClaudeMdBudget,
1348
+ checkNestedDocsPresent,
1349
+ checkRankGuards,
1350
+ checkGeneratedArtifactsFresh,
1351
+ checkDeterministicRoutines,
1352
+ checkCoreNeverImportsModule,
1353
+ checkModulesImportOnlyApi,
1354
+ checkScopeMapImportsProtected,
1355
+ checkCoreCarriesNoModuleCode,
1356
+ checkKernelImportsNoDomain,
1357
+ checkModuleMigrationsAdditive,
1358
+ checkMigrationNamespacing, // Check 11b — ADR 0108 §5 cross-repo migration filenames + stem collision
1359
+ checkDesignContract,
1360
+ checkKernelPromptsNoHardcodedIdentity, // BV1.R76 — kernel prompts read identity from branding()
1361
+ checkHallSectionRegistry,
1362
+ checkCoreRequiresExcludedConfig, // task 1824 — core→instance-config require guard
1363
+ checkErrorEnvelope, // Check 17 — BV1 R03 / task 1990: no route hand-rolls a flat error (ADR 0116)
1364
+ checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999: every body-reading write route validates (ADR 0118)
1365
+ ];
1366
+
1367
+ function runAll() {
1368
+ return CHECKS.map((fn) => fn());
1369
+ }
1370
+
1371
+ function main() {
1372
+ const results = runAll();
1373
+ if (process.argv.includes('--json')) {
1374
+ process.stdout.write(JSON.stringify(results, null, 2) + '\n');
1375
+ } else {
1376
+ console.log('Architecture fitness functions (Path C / ADR 0062 §9)\n');
1377
+ for (const r of results) {
1378
+ const mark = r.hardFail ? '✗ FAIL' : (r.warnings.length ? '! WARN' : '✓ PASS');
1379
+ console.log(`${mark} ${r.name}`);
1380
+ console.log(` ${r.note}`);
1381
+ for (const v of r.violations) console.log(` ✗ ${v}`);
1382
+ for (const w of r.warnings) console.log(` ! ${w}`);
1383
+ }
1384
+ const failed = results.filter((r) => r.hardFail).length;
1385
+ console.log(`\n${failed ? `${failed} check(s) FAILED` : 'all checks passed'}.`);
1386
+ }
1387
+ process.exit(results.some((r) => r.hardFail) ? 1 : 0);
1388
+ }
1389
+
1390
+ if (require.main === module) main();
1391
+
1392
+ module.exports = {
1393
+ runAll,
1394
+ resolvesToHostGame,
1395
+ checkCoreHostBoundary,
1396
+ checkClaudeMdBudget,
1397
+ checkNestedDocsPresent,
1398
+ checkRankGuards,
1399
+ checkGeneratedArtifactsFresh,
1400
+ checkDeterministicRoutines,
1401
+ checkCoreNeverImportsModule,
1402
+ checkModulesImportOnlyApi,
1403
+ checkScopeMapImportsProtected,
1404
+ checkCoreCarriesNoModuleCode,
1405
+ checkKernelImportsNoDomain,
1406
+ checkModuleMigrationsAdditive,
1407
+ checkMigrationNamespacing, // Check 11b — ADR 0108 §5 cross-repo migration filenames
1408
+ checkDesignContract,
1409
+ checkKernelPromptsNoHardcodedIdentity, // BV1.R76 / task 1705
1410
+ scanPromptIdentity, // BV1.R76 — negative-test hook
1411
+ stripCommentsForIdentity, // task 1705 — comment-strip helper (tested)
1412
+ checkHallSectionRegistry,
1413
+ scanHallSectionRegistry,
1414
+ hallMainSlice,
1415
+ checkErrorEnvelope, // Check 17 — BV1 R03 / task 1990 (ADR 0116)
1416
+ scanFlatErrorSites, // shared with the R04 sweep + its test
1417
+ countFlatErrors, // per-text detector (tested)
1418
+ FLAT_ERROR_BASELINE,
1419
+ checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999 (ADR 0118)
1420
+ scanUnvalidatedWriteRoutes, // shared with its test
1421
+ WRITE_VALIDATION_EXEMPT,
1422
+ scanScopeMapSource,
1423
+ scanKernelForModuleKeys,
1424
+ kernelDomainImport,
1425
+ KERNEL_FILES,
1426
+ discoveredModuleKeys,
1427
+ moduleMigrationViolations,
1428
+ moduleNamespace,
1429
+ resolvesToModule,
1430
+ moduleImportViolation,
1431
+ moduleOwnerOf,
1432
+ // test/ratchet hook: toggle the module boundary enforce flag. Default is now
1433
+ // ENFORCING (BV1.R45 / task 1414); tests that need warn-mode restore the
1434
+ // default in a finally.
1435
+ _setModuleBoundaryEnforcing: (v) => { MODULE_BOUNDARY_ENFORCING = !!v; },
1436
+ scanDeterministicBody,
1437
+ HOST_GAME,
1438
+ GRANDFATHERED,
1439
+ LEGACY_NAMESPACED,
1440
+ KERNEL_FILES,
1441
+ ROOT_HARD_MAX,
1442
+ ROOT_TARGET,
1443
+ ROOT_CHAR_HARD_MAX,
1444
+ ROOT_CHAR_TARGET,
1445
+ HIGH_TRAFFIC,
1446
+ };