@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,969 @@
1
+ // GDS server-mediated branch publish (ADR 0055 / task 1025).
2
+ //
3
+ // WHY THIS EXISTS
4
+ // A builder on a cloud dev box cannot publish in `ci` deploy mode: the box has
5
+ // NO GitHub push credential by design (credential-less HTTPS origin, no gh, no
6
+ // GH_TOKEN — infra/builder-box-cloud-init.yaml scrubs the clone token), and its
7
+ // baked GDS session is box-scoped (deny-by-default, #919/ADR 0053). CI self-
8
+ // deploy (#859/ADR 0042) fixed only the LAST leg (Actions deploys main->prod);
9
+ // the box still can't get its branch onto GitHub to open the PR.
10
+ //
11
+ // THE FIX (owner-chosen: "the box holds nothing"). The box uploads its branch as
12
+ // a THIN git bundle to the GDS over a RE-AUTHED (non-box) cli session; the server
13
+ // pushes the branch to GitHub + opens the PR + enables auto-merge using a SERVER-
14
+ // side push credential. The box never holds a GitHub push credential, so the #919
15
+ // model is preserved. The credential is scoped to a single repo (Contents:write +
16
+ // Pull-requests:write + Actions:read) and CANNOT push to branch-protected `main`
17
+ // nor bypass required checks — a leaked server credential can only land code on a
18
+ // feature branch behind the same checks every PR faces.
19
+ //
20
+ // THE SERVER CREDENTIAL (task 1028 / ADR 0055 hardening). Preferred: a GitHub
21
+ // APP — the server holds ONLY the App private key and mints a SHORT-LIVED,
22
+ // repo-scoped INSTALLATION token (1h) per push window, so there's no standing
23
+ // push secret at rest. Set GITHUB_APP_ID + GITHUB_APP_INSTALLATION_ID +
24
+ // GITHUB_APP_PRIVATE_KEY. Fallback (v1, kept for cutover): a standing fine-grained
25
+ // PAT in GITHUB_PUSH_TOKEN. resolveToken() prefers the App, then the PAT; the App
26
+ // JWT is RS256-signed with node:crypto (no jsonwebtoken / Octokit dependency).
27
+ //
28
+ // This module is the server seam: push a bundle, open/find the PR, enable auto-
29
+ // merge, report status. All git runs through execFile arg-arrays (shell:false,
30
+ // the #681 injection discipline); the credentialed URL is built in-process and
31
+ // NEVER persisted to a remote nor echoed on an error path (the box-source-fetch.sh
32
+ // "one-shot URL, scrub the credential" pattern). The GitHub API calls are raw
33
+ // global fetch (no Octokit — fetch is used throughout auth.js), header shape
34
+ // mirrors auth.js fetchGithubUser.
35
+
36
+ const { execFile } = require('node:child_process');
37
+ const crypto = require('node:crypto');
38
+ const fs = require('node:fs');
39
+ const os = require('node:os');
40
+ const path = require('node:path');
41
+ const { promisify } = require('node:util');
42
+ const { repoInfo, userAgent } = require('../../src/module-api');
43
+ const { loadRepoInfo } = repoInfo;
44
+
45
+ const execFileP = promisify(execFile);
46
+
47
+ // The server's own checkout root — a fast LOCAL object source for the bundle's
48
+ // `main..HEAD` prerequisites (so we never clone the whole repo over the network).
49
+ // Reading it via `git clone --bare --local` does NOT touch its working tree, so
50
+ // it never races the deploy worktree that `~/deploy.sh` resets.
51
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
52
+
53
+ // A branch ref the server will interpolate into argv + the GitHub API. Keep it
54
+ // to the chars a real `claude/*` worktree branch uses; reject anything that could
55
+ // be an option (leading '-'), a traversal ('..'), or a fully-qualified ref.
56
+ const SAFE_BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,200}$/;
57
+ const SAFE_SHA_RE = /^[0-9a-f]{7,64}$/;
58
+
59
+ // Configured if EITHER credential is present: a GitHub App (preferred) or the
60
+ // v1 PAT fallback. The route 503s when neither is set (task 1028).
61
+ function appConfigured() {
62
+ return !!(
63
+ process.env.GITHUB_APP_ID &&
64
+ process.env.GITHUB_APP_INSTALLATION_ID &&
65
+ process.env.GITHUB_APP_PRIVATE_KEY
66
+ );
67
+ }
68
+
69
+ function isConfigured() {
70
+ return !!process.env.GITHUB_PUSH_TOKEN || appConfigured();
71
+ }
72
+
73
+ function isSafeBranch(branch) {
74
+ return (
75
+ typeof branch === 'string' &&
76
+ SAFE_BRANCH_RE.test(branch) &&
77
+ !branch.includes('..') &&
78
+ !branch.startsWith('refs/')
79
+ );
80
+ }
81
+
82
+ function err(code, extra = {}) {
83
+ const e = new Error(code);
84
+ e.code = code;
85
+ Object.assign(e, extra);
86
+ return e;
87
+ }
88
+
89
+ // Extract the usable `repository` from a GraphQL response, tolerating PARTIAL errors.
90
+ //
91
+ // GitHub GraphQL returns `data` (with the un-readable field nulled) ALONGSIDE an
92
+ // `errors` array on a partial failure — e.g. a FORBIDDEN on statusCheckRollup.contexts
93
+ // when the App installation lacks the `Checks: read` permission (operator blocker). A
94
+ // brittle `if (out.errors) throw` discards the whole usable response and surfaces to the
95
+ // caller as a 502; instead, use the data we DID get (the forbidden sub-fields come back
96
+ // null, which every reader here already null-guards) and throw GRAPHQL_FAILED ONLY when
97
+ // there is no repository data at all (a genuine top-level failure: bad credential, SAML,
98
+ // rate-limit). Returns { repository, partialErrors } — partialErrors is the list of
99
+ // GraphQL error types present on an otherwise-usable response, so a caller can signal
100
+ // reduced fidelity (e.g. "checks unreadable") instead of silently degrading.
101
+ function graphqlRepository(out) {
102
+ const repository = out && out.data && out.data.repository;
103
+ const errors = (out && out.errors) || [];
104
+ if (repository) {
105
+ return { repository, partialErrors: errors.map((e) => e && e.type).filter(Boolean) };
106
+ }
107
+ // No data → fatal. Surface the error detail (truncated) so the cause is loud in logs
108
+ // and in the structured route response.
109
+ throw err('GRAPHQL_FAILED', { detail: JSON.stringify(errors.length ? errors : 'no data').slice(0, 200) });
110
+ }
111
+
112
+ // Build the one-shot authenticated push URL. NEVER logged, NEVER written to a
113
+ // persisted remote — it lives only as an argv element to a single git push/fetch.
114
+ function buildAuthUrl(owner, repo, token) {
115
+ const u = new URL(`https://github.com/${owner}/${repo}.git`);
116
+ u.username = 'x-access-token';
117
+ u.password = token;
118
+ return u.toString();
119
+ }
120
+
121
+ // Strip any embedded push credential from captured git output before it is logged
122
+ // or returned. The auth URL is x-access-token:<token>@github.com/…; git usually
123
+ // redacts the password in its own messages, but belt-and-brace it here too (task
124
+ // 1547 — we now SURFACE git stderr for diagnosability, so it must be scrubbed).
125
+ function scrubCredential(s) {
126
+ return String(s || '')
127
+ .replace(/x-access-token:[^@\s/]+@/gi, 'x-access-token:***@')
128
+ .replace(/(ghs_|ghp_|github_pat_)[A-Za-z0-9_]+/g, '$1***');
129
+ }
130
+
131
+ // Default git seam: execFile arg-array, shell:false, stdout captured. On failure
132
+ // throws GIT_FAILED. We never echo the (possibly credentialed) argv, but DO attach
133
+ // the credential-scrubbed stderr + the caller's step label (task 1547) so a real
134
+ // failure is a one-line log read instead of a multi-hour mystery — the BV1.R04/R05
135
+ // outage was a ~2h opaque GIT_FAILED window with no clue WHICH step or WHY. Tests
136
+ // inject their own `exec`.
137
+ async function defaultExec(args, opts = {}) {
138
+ const { step, ...execOpts } = opts;
139
+ try {
140
+ const { stdout } = await execFileP('git', args, {
141
+ encoding: 'utf8',
142
+ timeout: 120000,
143
+ maxBuffer: 64 * 1024 * 1024,
144
+ ...execOpts,
145
+ shell: false,
146
+ });
147
+ return stdout;
148
+ } catch (e) {
149
+ // Identify the failing step by the caller's label, never by echoing argv.
150
+ // Surface the scrubbed stderr (truncated) so the cause is loud in the log.
151
+ const stderr = scrubCredential(e && (e.stderr || e.message)).trim().slice(0, 500);
152
+ throw err('GIT_FAILED', { git_step: step || (args && args[0]) || null, stderr });
153
+ }
154
+ }
155
+
156
+ // Default GitHub-API seam (raw fetch). Tests inject `fetchImpl`.
157
+ function ghHeaders(token) {
158
+ return {
159
+ Authorization: `Bearer ${token}`,
160
+ Accept: 'application/vnd.github+json',
161
+ 'X-GitHub-Api-Version': '2022-11-28',
162
+ 'User-Agent': userAgent(),
163
+ };
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // GitHub App credential (task 1028 / ADR 0055 hardening)
168
+ // Hold only the App private key at rest; mint a short-lived, repo-scoped
169
+ // installation token per push window instead of a standing PAT.
170
+ // ---------------------------------------------------------------------------
171
+
172
+ function b64url(input) {
173
+ return Buffer.from(input)
174
+ .toString('base64')
175
+ .replace(/\+/g, '-')
176
+ .replace(/\//g, '_')
177
+ .replace(/=+$/, '');
178
+ }
179
+
180
+ // A PEM may arrive as a single env-line with literal "\n"; restore real newlines
181
+ // (and tolerate a key already containing real newlines).
182
+ function normalizePem(pem) {
183
+ return pem.includes('\\n') && !pem.includes('\n') ? pem.replace(/\\n/g, '\n') : pem;
184
+ }
185
+
186
+ // Mint an App JWT (RS256, node:crypto — no jsonwebtoken dependency). iat is
187
+ // backdated 60s for clock skew; exp is +9min (GitHub caps App JWTs at 10).
188
+ function mintAppJwt({ appId, privateKeyPem, nowSec }) {
189
+ const iat = nowSec - 60;
190
+ const exp = nowSec + 9 * 60;
191
+ const header = b64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));
192
+ const payload = b64url(JSON.stringify({ iat, exp, iss: String(appId) }));
193
+ const signingInput = `${header}.${payload}`;
194
+ const signer = crypto.createSign('RSA-SHA256');
195
+ signer.update(signingInput);
196
+ signer.end();
197
+ const sig = b64url(signer.sign(normalizePem(privateKeyPem)));
198
+ return `${signingInput}.${sig}`;
199
+ }
200
+
201
+ // Module-level cache of the current installation token, reused until ~60s before
202
+ // expiry. Installation tokens are short-lived by construction (1h) and repo-
203
+ // scoped to the single repo this server serves.
204
+ let _instCache = { token: null, expEpoch: 0 };
205
+ function _resetAppTokenCacheForTests() {
206
+ _instCache = { token: null, expEpoch: 0 };
207
+ }
208
+
209
+ // Returns a fresh/cached App installation token, or null if the App isn't
210
+ // configured (so resolveToken can fall through to the PAT). Throws
211
+ // APP_TOKEN_FAILED if the App IS configured but the exchange fails.
212
+ async function getInstallationToken(deps = {}) {
213
+ if (!appConfigured()) return null;
214
+ const nowSec = Math.floor((deps.now ? deps.now() : Date.now()) / 1000);
215
+ if (_instCache.token && _instCache.expEpoch - nowSec > 60) return _instCache.token;
216
+
217
+ const jwt = mintAppJwt({
218
+ appId: process.env.GITHUB_APP_ID,
219
+ privateKeyPem: process.env.GITHUB_APP_PRIVATE_KEY,
220
+ nowSec,
221
+ });
222
+ const doFetch = deps.fetchImpl || fetch;
223
+ const res = await doFetch(
224
+ `https://api.github.com/app/installations/${process.env.GITHUB_APP_INSTALLATION_ID}/access_tokens`,
225
+ {
226
+ method: 'POST',
227
+ headers: {
228
+ Authorization: `Bearer ${jwt}`,
229
+ Accept: 'application/vnd.github+json',
230
+ 'X-GitHub-Api-Version': '2022-11-28',
231
+ 'User-Agent': userAgent(),
232
+ },
233
+ }
234
+ );
235
+ if (!res.ok) {
236
+ const detail = await res.text().catch(() => '');
237
+ throw err('APP_TOKEN_FAILED', { status: res.status, detail: detail.slice(0, 200) });
238
+ }
239
+ const data = await res.json();
240
+ _instCache = {
241
+ token: data.token,
242
+ expEpoch: Math.floor(new Date(data.expires_at).getTime() / 1000),
243
+ };
244
+ return data.token;
245
+ }
246
+
247
+ // Resolve the credential for git push + GitHub API: an explicitly-injected token
248
+ // (tests) first, then a GitHub App installation token, then the v1 PAT, else null.
249
+ async function resolveToken(deps = {}) {
250
+ if (deps.token) return deps.token;
251
+ const appTok = await getInstallationToken(deps);
252
+ if (appTok) return appTok;
253
+ return process.env.GITHUB_PUSH_TOKEN || null;
254
+ }
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // pushBundle — receive a thin branch bundle, push it to origin via the server's
258
+ // credential. Returns { head_sha } (the tip actually pushed). Throws:
259
+ // PUSH_UNCONFIGURED — no server push credential (neither GitHub App nor PAT)
260
+ // NO_REPO_INFO — couldn't resolve owner/repo
261
+ // BAD_BRANCH — branch ref failed the safe pattern
262
+ // TIP_MISMATCH — the bundle's tip != the claimed head_sha (with {tip})
263
+ // GIT_FAILED — any git step failed
264
+ // ---------------------------------------------------------------------------
265
+ async function pushBundle({ bundlePath, branch, headSha }, deps = {}) {
266
+ const token = await resolveToken(deps);
267
+ if (!token) throw err('PUSH_UNCONFIGURED');
268
+ if (!isSafeBranch(branch)) throw err('BAD_BRANCH');
269
+ if (headSha != null && !SAFE_SHA_RE.test(String(headSha))) throw err('BAD_SHA');
270
+
271
+ const repoInfo = deps.repoInfo || loadRepoInfo();
272
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
273
+ const { owner, repo } = repoInfo;
274
+
275
+ const exec = deps.exec || defaultExec;
276
+ const repoRoot = deps.repoRoot || REPO_ROOT;
277
+ const authUrl = buildAuthUrl(owner, repo, token);
278
+ const ref = `refs/heads/${branch}`;
279
+
280
+ const tmp = (deps.mkdtemp || (() => fs.mkdtempSync(path.join(os.tmpdir(), 'otb-publish-'))))();
281
+ const workGit = path.join(tmp, 'work.git');
282
+ try {
283
+ // 1. Fast LOCAL bare clone for main's objects (no network, no worktree touch).
284
+ // Fall back to a shallow network clone if the local checkout isn't a repo
285
+ // (stripped container, test env).
286
+ try {
287
+ await exec(['clone', '--bare', '--local', repoRoot, workGit], { step: 'clone-local' });
288
+ } catch (_) {
289
+ await exec(['clone', '--bare', '--depth=200', authUrl, workGit], { step: 'clone-network' });
290
+ }
291
+ // 2. Top up recent `main` objects so a freshly-branched base commit is present
292
+ // even if the droplet checkout trails origin/main (just-merged-not-deployed).
293
+ // Best-effort: the bundle is usually self-sufficient against the local main.
294
+ try {
295
+ await exec(['-C', workGit, 'fetch', '--no-tags', authUrl, 'main'], { step: 'fetch-main' });
296
+ } catch (_) {
297
+ /* tolerated — step 3 fails loudly if a prerequisite is genuinely missing */
298
+ }
299
+ // 3. Unbundle the thin branch bundle (prerequisites = main, present above).
300
+ await exec(['-C', workGit, 'fetch', bundlePath, `${ref}:${ref}`], { step: 'unbundle' });
301
+ // 4. Verify the unbundled tip equals the claimed head_sha (anti-spoof: the
302
+ // grade ran against THIS sha client-side; the pushed code must match).
303
+ const tip = (await exec(['-C', workGit, 'rev-parse', ref], { step: 'rev-parse' })).trim();
304
+ if (headSha && tip !== headSha) throw err('TIP_MISMATCH', { tip });
305
+ // 5. Push to GitHub via the explicit authenticated URL (never the inherited
306
+ // remote). Refuse non-fast-forward implicitly (no --force, ever).
307
+ await exec(['-C', workGit, 'push', authUrl, `${ref}:${ref}`], { step: 'push' });
308
+ return { head_sha: tip };
309
+ } finally {
310
+ try {
311
+ (deps.rmtmp || ((d) => fs.rmSync(d, { recursive: true, force: true })))(tmp);
312
+ } catch (_) {
313
+ /* temp cleanup is best-effort */
314
+ }
315
+ }
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // openOrFindPullRequest — open a PR branch->main, or reuse the existing open one
320
+ // (gh pr create is not idempotent; mirror ciLand's "check first" behaviour).
321
+ // Returns { number, url, node_id }.
322
+ // ---------------------------------------------------------------------------
323
+ async function openOrFindPullRequest({ branch, title, body }, deps = {}) {
324
+ const token = await resolveToken(deps);
325
+ if (!token) throw err('PUSH_UNCONFIGURED');
326
+ if (!isSafeBranch(branch)) throw err('BAD_BRANCH');
327
+ const repoInfo = deps.repoInfo || loadRepoInfo();
328
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
329
+ const { owner, repo } = repoInfo;
330
+ const doFetch = deps.fetchImpl || fetch;
331
+ const api = `https://api.github.com/repos/${owner}/${repo}`;
332
+
333
+ // Reuse an existing open PR for this head if one exists.
334
+ const listRes = await doFetch(
335
+ `${api}/pulls?head=${encodeURIComponent(`${owner}:${branch}`)}&state=open`,
336
+ { headers: ghHeaders(token) }
337
+ );
338
+ if (listRes.ok) {
339
+ const existing = await listRes.json();
340
+ if (Array.isArray(existing) && existing.length) {
341
+ const pr = existing[0];
342
+ return { number: pr.number, url: pr.html_url, node_id: pr.node_id };
343
+ }
344
+ }
345
+
346
+ const createRes = await doFetch(`${api}/pulls`, {
347
+ method: 'POST',
348
+ headers: { ...ghHeaders(token), 'Content-Type': 'application/json' },
349
+ body: JSON.stringify({ title, head: branch, base: 'main', body }),
350
+ });
351
+ if (!createRes.ok) {
352
+ const detail = await createRes.text().catch(() => '');
353
+ throw err('PR_CREATE_FAILED', { status: createRes.status, detail: detail.slice(0, 300) });
354
+ }
355
+ const pr = await createRes.json();
356
+ return { number: pr.number, url: pr.html_url, node_id: pr.node_id };
357
+ }
358
+
359
+ // enableAutoMerge — arm GitHub to merge once required checks + branch protection
360
+ // pass (the GraphQL analogue of `gh pr merge --auto --merge`). mergeMethod MERGE
361
+ // keeps the project's --no-ff merge-commit convention. Best-effort: a repo
362
+ // without auto-merge enabled, or a PR already mergeable, returns false and the
363
+ // caller surfaces the open PR (exactly like ciLand on a failed enable).
364
+ async function enableAutoMerge({ nodeId }, deps = {}) {
365
+ const token = await resolveToken(deps);
366
+ if (!token || !nodeId) return false;
367
+ const doFetch = deps.fetchImpl || fetch;
368
+ const query = `mutation($id:ID!){enablePullRequestAutoMerge(input:{pullRequestId:$id,mergeMethod:MERGE}){clientMutationId}}`;
369
+ try {
370
+ const res = await doFetch('https://api.github.com/graphql', {
371
+ method: 'POST',
372
+ headers: { ...ghHeaders(token), 'Content-Type': 'application/json' },
373
+ body: JSON.stringify({ query, variables: { id: nodeId } }),
374
+ });
375
+ if (!res.ok) return false;
376
+ const out = await res.json();
377
+ return !out.errors;
378
+ } catch (_) {
379
+ return false;
380
+ }
381
+ }
382
+
383
+ // getPublishStatus — report the PR + prod-deploy state so ship.js can poll
384
+ // WITHOUT gh on the box. Returns:
385
+ // { pr_state, merged, merge_sha, deploy_status } where
386
+ // pr_state ∈ open|closed|none, deploy_status ∈ pending|success|none.
387
+ // Mirrors ship.js pollCiDeploy's two-stage check (PR merged -> deploy run).
388
+ async function getPublishStatus({ branch }, deps = {}) {
389
+ const token = await resolveToken(deps);
390
+ if (!token) throw err('PUSH_UNCONFIGURED');
391
+ if (!isSafeBranch(branch)) throw err('BAD_BRANCH');
392
+ const repoInfo = deps.repoInfo || loadRepoInfo();
393
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
394
+ const { owner, repo } = repoInfo;
395
+ const doFetch = deps.fetchImpl || fetch;
396
+ const api = `https://api.github.com/repos/${owner}/${repo}`;
397
+
398
+ // Find the most-recent PR for this head (open OR closed/merged).
399
+ const prRes = await doFetch(
400
+ `${api}/pulls?head=${encodeURIComponent(`${owner}:${branch}`)}&state=all&sort=created&direction=desc&per_page=1`,
401
+ { headers: ghHeaders(token) }
402
+ );
403
+ if (!prRes.ok) throw err('STATUS_FETCH_FAILED', { status: prRes.status });
404
+ const prs = await prRes.json();
405
+ if (!Array.isArray(prs) || !prs.length) {
406
+ return {
407
+ pr_state: 'none', merged: false, merge_sha: null, deploy_status: 'none',
408
+ pr_url: null, pr_number: null, pr_title: null, pr_body: null,
409
+ };
410
+ }
411
+ const pr = prs[0];
412
+ const merged = !!pr.merged_at;
413
+ const mergeSha = pr.merge_commit_sha || null;
414
+ // task 1547: surface the matched PR's identity so the reconcile can verify the PR
415
+ // belongs to THIS task (anti branch-reuse) when no published tip was recorded.
416
+ const prMeta = { pr_number: pr.number || null, pr_title: pr.title || null, pr_body: pr.body || null };
417
+ if (!merged) {
418
+ return { pr_state: pr.state, merged: false, merge_sha: mergeSha, deploy_status: 'pending', pr_url: pr.html_url, ...prMeta };
419
+ }
420
+
421
+ // Merged → did the prod-deploy workflow succeed for the merge commit?
422
+ let deployStatus = 'pending';
423
+ try {
424
+ const runsRes = await doFetch(
425
+ `${api}/actions/workflows/deploy-prod.yml/runs?branch=main&per_page=15`,
426
+ { headers: ghHeaders(token) }
427
+ );
428
+ if (runsRes.ok) {
429
+ const data = await runsRes.json();
430
+ const runs = (data && data.workflow_runs) || [];
431
+ const hit = runs.some(
432
+ (r) => r && r.head_sha === mergeSha && r.status === 'completed' && r.conclusion === 'success'
433
+ );
434
+ if (hit) deployStatus = 'success';
435
+ }
436
+ } catch (_) {
437
+ /* leave pending on a transient runs-API miss */
438
+ }
439
+ return { pr_state: pr.state, merged: true, merge_sha: mergeSha, deploy_status: deployStatus, pr_url: pr.html_url, ...prMeta };
440
+ }
441
+
442
+ // task 1547 — does the PR matched by getPublishStatus actually belong to THIS task?
443
+ // The robust land proof is published-tip containment; this is the fallback used when
444
+ // no published tip was recorded (a legacy row, or a publish in flight across the
445
+ // deploy that introduced the tip column). The server-mediated publish writes
446
+ // "task <id>:" in the PR title and "for task <id>" in the body (routes/tasks.js),
447
+ // and ship.js's local path titles its PR "task <id>: …" too, so a word-boundary
448
+ // "task #?<id>" match positively binds the matched PR to this task — and a REUSED
449
+ // branch whose latest PR was a DIFFERENT task (the BV1.R04/R05 → PR #386/task 1297
450
+ // case) fails the match, so the reconcile refuses to ship. Pure + sync.
451
+ function prReferencesTask(status, taskId) {
452
+ if (!status || !Number.isInteger(Number(taskId))) return false;
453
+ const hay = `${status.pr_title || ''}\n${status.pr_body || ''}`;
454
+ return new RegExp(`\\btask\\s+#?${Number(taskId)}\\b`, 'i').test(hay);
455
+ }
456
+
457
+ // ---------------------------------------------------------------------------
458
+ // Active merge driver (task 1162) — land a green PR the moment it's mergeable
459
+ // instead of waiting on GitHub's auto-merge daemon.
460
+ //
461
+ // WHY. enableAutoMerge only ARMS GitHub; GitHub's auto-merge fires on check-
462
+ // completion EVENTS. When a publish is re-driven and auto-merge is re-armed on a
463
+ // PR whose checks are ALREADY green, no fresh event arrives and the daemon never
464
+ // re-evaluates — the PR sits open + CLEAN + mergeable indefinitely, getPublishStatus
465
+ // reports merged:false forever, and the task strands at 'confirmed' (observed live
466
+ // on PR #125 / task 1093: 7/7 checks green, CLEAN, ~30+ min, never merged, needed a
467
+ // human click). This makes the SERVER perform the merge.
468
+ //
469
+ // SAFETY. The merge uses the same server credential that pushed the branch + opened
470
+ // the PR — it merges exactly where it is already allowed and CANNOT bypass branch
471
+ // protection. We merge ONLY when GitHub's own gate is satisfied (mergeable AND
472
+ // mergeStateStatus===CLEAN, the same condition GitHub auto-merge waits for). A
473
+ // review-gated PR the App can't approve reports BLOCKED here and the merge API 405s
474
+ // — we surface that reason, we never force it.
475
+
476
+ // getMergeState — one GraphQL read of the most-recent PR for `branch`: its merged
477
+ // flag, mergeability, merge-state (the CLEAN/BLOCKED/… GitHub's auto-merge gates
478
+ // on), review decision, head sha, and per-check rollup. Pure w.r.t. injected
479
+ // fetchImpl/repoInfo/token so it's unit-tested with no network.
480
+ async function getMergeState({ branch }, deps = {}) {
481
+ const token = await resolveToken(deps);
482
+ if (!token) throw err('PUSH_UNCONFIGURED');
483
+ if (!isSafeBranch(branch)) throw err('BAD_BRANCH');
484
+ const repoInfo = deps.repoInfo || loadRepoInfo();
485
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
486
+ const { owner, repo } = repoInfo;
487
+ const doFetch = deps.fetchImpl || fetch;
488
+ const query = `query($owner:String!,$repo:String!,$head:String!){
489
+ repository(owner:$owner,name:$repo){
490
+ pullRequests(headRefName:$head, first:1, orderBy:{field:CREATED_AT,direction:DESC}){
491
+ nodes{
492
+ number id state merged mergeable reviewDecision mergeStateStatus
493
+ commits(last:1){ nodes{ commit{ oid
494
+ statusCheckRollup{ state contexts(first:50){ nodes{
495
+ __typename
496
+ ... on CheckRun{ name conclusion status }
497
+ ... on StatusContext{ context state }
498
+ }}}
499
+ }}}
500
+ }
501
+ }
502
+ }
503
+ }`;
504
+ const res = await doFetch('https://api.github.com/graphql', {
505
+ method: 'POST',
506
+ headers: {
507
+ ...ghHeaders(token),
508
+ 'Content-Type': 'application/json',
509
+ // mergeStateStatus historically required this preview media type; GA now,
510
+ // but sending it is harmless and belt-and-braces against older endpoints.
511
+ Accept: 'application/vnd.github.merge-info-preview+json',
512
+ },
513
+ body: JSON.stringify({ query, variables: { owner, repo, head: branch } }),
514
+ });
515
+ if (!res.ok) throw err('STATUS_FETCH_FAILED', { status: res.status });
516
+ const out = await res.json();
517
+ // Partial FORBIDDEN on the check rollup (App lacks Checks:read) must not sink the whole
518
+ // read: mergeIfGreen gates on mergeable/mergeStateStatus (top-level, readable), and the
519
+ // checks array only feeds the human-readable block reason.
520
+ const { repository } = graphqlRepository(out);
521
+ const node = repository.pullRequests
522
+ && repository.pullRequests.nodes && repository.pullRequests.nodes[0];
523
+ if (!node) {
524
+ return {
525
+ pr_state: 'none', number: null, node_id: null, merged: false, head_sha: null,
526
+ mergeable: null, merge_state: null, review_decision: null, rollup_state: null, checks: [],
527
+ };
528
+ }
529
+ const commit = (node.commits && node.commits.nodes && node.commits.nodes[0] && node.commits.nodes[0].commit) || {};
530
+ const rollup = commit.statusCheckRollup || null;
531
+ // .filter(Boolean): a partial FORBIDDEN (App lacks Checks:read) nulls the individual
532
+ // CheckRun ELEMENTS inside contexts.nodes; drop the holes before mapping.
533
+ const checks = ((rollup && rollup.contexts && rollup.contexts.nodes) || []).filter(Boolean).map((c) =>
534
+ c.__typename === 'CheckRun'
535
+ ? { name: c.name, conclusion: c.conclusion, status: c.status }
536
+ : { name: c.context, conclusion: c.state, status: 'COMPLETED' }
537
+ );
538
+ return {
539
+ pr_state: String(node.state || '').toLowerCase(), // open|closed|merged
540
+ number: node.number,
541
+ node_id: node.id,
542
+ merged: !!node.merged,
543
+ head_sha: commit.oid || null,
544
+ mergeable: node.mergeable || null, // MERGEABLE|CONFLICTING|UNKNOWN
545
+ merge_state: node.mergeStateStatus || null, // CLEAN|BLOCKED|BEHIND|DIRTY|DRAFT|UNSTABLE|HAS_HOOKS|UNKNOWN
546
+ review_decision: node.reviewDecision || null, // APPROVED|REVIEW_REQUIRED|CHANGES_REQUESTED|null
547
+ rollup_state: (rollup && rollup.state) || null, // SUCCESS|PENDING|FAILURE|ERROR|EXPECTED
548
+ checks,
549
+ };
550
+ }
551
+
552
+ // A non-passing check conclusion (everything that isn't a clean success/skip).
553
+ const _BAD_CHECK = (c) =>
554
+ c.conclusion && !['SUCCESS', 'NEUTRAL', 'SKIPPED'].includes(String(c.conclusion).toUpperCase());
555
+
556
+ // Human-readable reason a not-yet-merged PR isn't mergeable, surfaced to a
557
+ // credential-less session so it reports the truth instead of guessing.
558
+ function mergeBlockReason(s) {
559
+ if (!s) return 'unknown';
560
+ if (s.merged) return null;
561
+ if (s.pr_state !== 'open') return 'no_open_pr';
562
+ if (s.mergeable === 'CONFLICTING' || s.merge_state === 'DIRTY') return 'merge_conflict';
563
+ if (s.merge_state === 'BEHIND') return 'branch_behind_base';
564
+ if (s.merge_state === 'DRAFT') return 'draft_pr';
565
+ if (s.review_decision === 'CHANGES_REQUESTED') return 'changes_requested';
566
+ if (s.review_decision === 'REVIEW_REQUIRED') return 'review_required';
567
+ if (s.merge_state === 'BLOCKED') {
568
+ const bad = s.checks.filter(_BAD_CHECK);
569
+ if (bad.length) return `failing_check:${bad[0].name}`;
570
+ return 'blocked_required_review_or_pending_check';
571
+ }
572
+ if (s.merge_state === 'UNSTABLE') {
573
+ const bad = s.checks.filter(_BAD_CHECK);
574
+ return bad.length ? `non_required_check_failing:${bad[0].name}` : 'non_required_check_pending';
575
+ }
576
+ if (s.mergeable === 'UNKNOWN' || s.merge_state === 'UNKNOWN' || s.merge_state == null) {
577
+ return 'mergeability_computing';
578
+ }
579
+ return String(s.merge_state).toLowerCase();
580
+ }
581
+
582
+ // mergeIfGreen — read the PR's merge state and, IFF GitHub's own gate is satisfied
583
+ // (mergeable AND CLEAN), perform the merge the auto-merge daemon didn't. Returns a
584
+ // rich result that doubles as diagnostics for the publish-status route:
585
+ // { pr_state, number, merged, merged_now, mergeable, merge_state,
586
+ // review_decision, rollup_state, checks, reason }
587
+ // merged_now is true ONLY when this call performed the merge. reason is the blocker
588
+ // when merged stays false. Best-effort merge: a 405 (review-gated)/409 (head moved)
589
+ // leaves the PR for the next sweep with the status surfaced, never forced.
590
+ async function mergeIfGreen({ branch }, deps = {}) {
591
+ const s = await getMergeState({ branch }, deps);
592
+ const base = {
593
+ pr_state: s.pr_state, number: s.number, merged: s.merged, merged_now: false,
594
+ mergeable: s.mergeable, merge_state: s.merge_state, review_decision: s.review_decision,
595
+ rollup_state: s.rollup_state, checks: s.checks, reason: null,
596
+ };
597
+ if (s.merged) return base;
598
+ if (s.pr_state !== 'open') return { ...base, reason: 'no_open_pr' };
599
+ if (!(s.mergeable === 'MERGEABLE' && s.merge_state === 'CLEAN')) {
600
+ return { ...base, reason: mergeBlockReason(s) };
601
+ }
602
+ // Green + stalled → merge it. mergeMethod MERGE keeps the --no-ff convention
603
+ // (matches enableAutoMerge). Pin the expected head sha so a race that moved the
604
+ // tip 409s instead of merging stale code.
605
+ const token = await resolveToken(deps);
606
+ const repoInfo = deps.repoInfo || loadRepoInfo();
607
+ const { owner, repo } = repoInfo;
608
+ const doFetch = deps.fetchImpl || fetch;
609
+ const res = await doFetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${s.number}/merge`, {
610
+ method: 'PUT',
611
+ headers: { ...ghHeaders(token), 'Content-Type': 'application/json' },
612
+ body: JSON.stringify({ merge_method: 'merge', ...(s.head_sha ? { sha: s.head_sha } : {}) }),
613
+ });
614
+ if (res.ok) return { ...base, merged: true, merged_now: true };
615
+ const detail = await res.text().catch(() => '');
616
+ return { ...base, reason: `merge_api_${res.status}`, detail: detail.slice(0, 160) };
617
+ }
618
+
619
+ // task 1139 (ADR 0058): server-set commit status — the TRUSTLESS rank attestation the
620
+ // gate-review CI check reads. Only the server (holding the App/PAT credential AND the
621
+ // authenticated builder's server-resolved rank) can set it, so CI can trust it where it
622
+ // must NOT trust a PR-author-controlled signal (a PR title is spoofable). Best-effort:
623
+ // returns false on any failure, in which case the gate-review check sees no attestation
624
+ // and escalates — the SAFE direction (no rank bypass without a real server signal).
625
+ async function setCommitStatus({ sha, state, context, description, targetUrl }, deps = {}) {
626
+ const token = await resolveToken(deps);
627
+ if (!token) return false;
628
+ if (!SAFE_SHA_RE.test(String(sha || ''))) return false;
629
+ const repoInfo = deps.repoInfo || loadRepoInfo();
630
+ if (!repoInfo || repoInfo.error) return false;
631
+ const { owner, repo } = repoInfo;
632
+ const doFetch = deps.fetchImpl || fetch;
633
+ try {
634
+ const res = await doFetch(`https://api.github.com/repos/${owner}/${repo}/statuses/${sha}`, {
635
+ method: 'POST',
636
+ headers: { ...ghHeaders(token), 'Content-Type': 'application/json' },
637
+ body: JSON.stringify({
638
+ state,
639
+ context,
640
+ description: String(description || '').slice(0, 140),
641
+ ...(targetUrl ? { target_url: targetUrl } : {}),
642
+ }),
643
+ });
644
+ return res.ok;
645
+ } catch (_) {
646
+ return false;
647
+ }
648
+ }
649
+
650
+ // task 1049 (B — server-side reconcile-on-read): does a publish-status report a
651
+ // fully-landed change — PR MERGED and deploy-prod SUCCEEDED? When true, the
652
+ // publish-status route flips the task confirmed→shipped so a successful publish
653
+ // never strands at confirmed waiting on a human to re-poll (the #1027
654
+ // retrospective). Pure + exported so the route's reconcile trigger is unit-tested.
655
+ function landConfirmed(status) {
656
+ return !!status && status.merged === true && status.deploy_status === 'success';
657
+ }
658
+
659
+ // task 1547 — is a landConfirmed status PROVEN to be THIS task's land, and not a
660
+ // reused worktree branch matching an OLDER task's already-merged PR (the BV1.R04/R05
661
+ // false-ship: branch claude/jovial-rosalind-84d6c3 → PR #386 / task 1297, merged,
662
+ // matched again for tasks 1299/1300 whose own push had failed)?
663
+ //
664
+ // The robust proof is the SERVER-pushed tip (claims.published_head_sha, task 1547):
665
+ // it must be an ancestor of main. NOTE claims.head_sha is the claim-TIME base — for a
666
+ // task whose base is already on main (e.g. an R03 base) an ancestry check on it
667
+ // false-positives — so we deliberately key on the published tip, never the base.
668
+ // When no tip was recorded (a legacy row, or a publish in flight across the deploy
669
+ // that introduced the column), fall back to verifying the matched PR references this
670
+ // task id. Returns { proven, reason }. The compare call is the only network hop and
671
+ // is injected-deps-pure for tests.
672
+ async function landProvenForTask({ taskId, publishedSha, status }, deps = {}) {
673
+ if (publishedSha && SAFE_SHA_RE.test(String(publishedSha))) {
674
+ const inMain = await isAncestorOfDefaultBranch({ sha: publishedSha }, deps);
675
+ return inMain
676
+ ? { proven: true, reason: `published tip ${publishedSha.slice(0, 8)} in main` }
677
+ : { proven: false, reason: `published tip ${publishedSha.slice(0, 8)} not in main (branch-reuse/stale PR)` };
678
+ }
679
+ if (prReferencesTask(status, taskId)) {
680
+ return { proven: true, reason: `matched PR references task ${taskId} (no published tip recorded)` };
681
+ }
682
+ return { proven: false, reason: `no published tip recorded and matched PR does not reference task ${taskId}` };
683
+ }
684
+
685
+ // task 1313 — proof-of-land that does NOT depend on the PR/branch still existing.
686
+ //
687
+ // WHY. getPublishStatus keys a land on finding the head branch's PR (merged +
688
+ // deploy succeeded). But a merge-commit merge that DELETES the branch afterwards
689
+ // leaves no PR discoverable by `head=owner:branch`, so getPublishStatus returns
690
+ // pr_state:'none' forever and the task strands at 'confirmed' even though the work
691
+ // is live on main. Other strand shapes have no branch at all (a claim with no
692
+ // worktree_name) or a corrupt head_sha. The branch/PR is incidental; the durable
693
+ // truth is "is the work's commit in main's history?" — answer that directly.
694
+ //
695
+ // Uses GitHub's compare API: compare/{base}...{head}. The `status` field is one of
696
+ // ahead | behind | identical | diverged, describing HEAD relative to BASE. When
697
+ // the claim's head_sha is an ancestor of main it has NO commits main lacks, so it
698
+ // is `behind` (or `identical` when it IS main's tip) — both mean "contained in
699
+ // main" ⇒ merged. `ahead`/`diverged` mean head carries commits not on main (e.g. a
700
+ // squash-merge produced a different commit, or it never landed) ⇒ NOT landed here;
701
+ // those still heal via the normal PR path, which getPublishStatus handles.
702
+ //
703
+ // SAFETY. sha MUST be a real commit-sha (SAFE_SHA_RE). The compare endpoint
704
+ // resolves refs, so passing a ref like "HEAD" or a branch name would resolve
705
+ // against the repo and could FALSE-POSITIVE (e.g. "HEAD" → main's tip → identical).
706
+ // We reject anything non-sha-shaped up front. A 404 (unknown commit — never pushed)
707
+ // is a clean "not landed", not an error. Pure w.r.t. injected fetchImpl/repoInfo/
708
+ // token so it unit-tests with no network.
709
+ async function isAncestorOfDefaultBranch({ sha, base = 'main' } = {}, deps = {}) {
710
+ if (typeof sha !== 'string' || !SAFE_SHA_RE.test(sha)) return false;
711
+ const token = await resolveToken(deps);
712
+ if (!token) throw err('PUSH_UNCONFIGURED');
713
+ const repoInfo = deps.repoInfo || loadRepoInfo();
714
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
715
+ const { owner, repo } = repoInfo;
716
+ const doFetch = deps.fetchImpl || fetch;
717
+ const res = await doFetch(
718
+ `https://api.github.com/repos/${owner}/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(sha)}`,
719
+ { headers: ghHeaders(token) }
720
+ );
721
+ if (res.status === 404) return false; // unknown commit → not in main's history
722
+ if (!res.ok) throw err('STATUS_FETCH_FAILED', { status: res.status });
723
+ const data = await res.json();
724
+ return !!data && (data.status === 'behind' || data.status === 'identical');
725
+ }
726
+
727
+ // ---------------------------------------------------------------------------
728
+ // Gate-surface PR approval from the hall (task 1179, ADR 0058 amendment).
729
+ //
730
+ // The gate-review CI check clears an ESCALATE (hard floor) ONLY on an owner
731
+ // approval. Historically that was a GitHub APPROVED review by the owner on the
732
+ // head sha. This adds a SECOND, trust-equal owner-approval channel the hall can
733
+ // drive: a server-set `gate-owner-approved` commit status — set EXACTLY like the
734
+ // existing `gate-author-trust` status (setCommitStatus), so only the server (which
735
+ // re-checks the caller's Archon rank server-side, ADR 0016) can produce it; a PR
736
+ // author cannot forge it. The gate-review workflow's trust step reads it on the
737
+ // head sha (sha-pinned, so a later commit's head carries no approval — the same
738
+ // red-team #2b protection as the GitHub-review path).
739
+ // ---------------------------------------------------------------------------
740
+ const GATE_OWNER_APPROVED_CONTEXT = 'gate-owner-approved';
741
+
742
+ // Set the server-attested owner-approval status on `sha`. Thin wrapper over
743
+ // setCommitStatus that pins the context + a provenance description (who approved,
744
+ // via where). Best-effort (returns false on failure → the gate simply stays
745
+ // un-approved, the safe direction). `login` is the approving Archon's handle.
746
+ async function setGateOwnerApproved({ sha, login, targetUrl }, deps = {}) {
747
+ return setCommitStatus(
748
+ {
749
+ sha,
750
+ state: 'success',
751
+ context: GATE_OWNER_APPROVED_CONTEXT,
752
+ description: `owner approval (${login || 'archon'}) via GDS hall`,
753
+ targetUrl,
754
+ },
755
+ deps
756
+ );
757
+ }
758
+
759
+ // listOpenPullRequests — one GraphQL read of the open PRs (most-recently-updated
760
+ // first) with the metadata the hall's gate-approval list needs: number, title,
761
+ // url, head ref + head sha, author login, body (for "task NNNN" extraction), and
762
+ // the head commit's check rollup (so the route can read the live `gate-review`
763
+ // check conclusion — the authoritative "is this awaiting approval" signal, rather
764
+ // than re-deriving it). Pure w.r.t. injected fetchImpl/repoInfo/token.
765
+ async function listOpenPullRequests(deps = {}) {
766
+ const token = await resolveToken(deps);
767
+ if (!token) throw err('PUSH_UNCONFIGURED');
768
+ const repoInfo = deps.repoInfo || loadRepoInfo();
769
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
770
+ const { owner, repo } = repoInfo;
771
+ const doFetch = deps.fetchImpl || fetch;
772
+ const first = Math.min(Math.max(Number(deps.first) || 40, 1), 100);
773
+ const query = `query($owner:String!,$repo:String!,$first:Int!){
774
+ repository(owner:$owner,name:$repo){
775
+ pullRequests(states:OPEN, first:$first, orderBy:{field:UPDATED_AT,direction:DESC}){
776
+ nodes{
777
+ number title url isDraft headRefName bodyText
778
+ author{ login }
779
+ reviewDecision
780
+ commits(last:1){ nodes{ commit{ oid
781
+ statusCheckRollup{ state contexts(first:50){ nodes{
782
+ __typename
783
+ ... on CheckRun{ name conclusion status detailsUrl }
784
+ ... on StatusContext{ context state targetUrl }
785
+ }}}
786
+ }}}
787
+ }
788
+ }
789
+ }
790
+ }`;
791
+ const res = await doFetch('https://api.github.com/graphql', {
792
+ method: 'POST',
793
+ headers: { ...ghHeaders(token), 'Content-Type': 'application/json' },
794
+ body: JSON.stringify({ query, variables: { owner, repo, first } }),
795
+ });
796
+ if (!res.ok) throw err('STATUS_FETCH_FAILED', { status: res.status });
797
+ const out = await res.json();
798
+ // Tolerate a partial FORBIDDEN (App lacks Checks:read → statusCheckRollup.contexts
799
+ // nulled): the PR list itself is still usable, so the approve path keeps working; the
800
+ // per-PR `checks` array just comes back empty.
801
+ const { repository, partialErrors } = graphqlRepository(out);
802
+ const nodes = ((repository.pullRequests || {}).nodes || []);
803
+ const result = nodes.map((n) => {
804
+ const commit = (n.commits && n.commits.nodes && n.commits.nodes[0] && n.commits.nodes[0].commit) || {};
805
+ const rollup = commit.statusCheckRollup || null;
806
+ // .filter(Boolean): a partial FORBIDDEN (App lacks Checks:read) nulls the individual
807
+ // CheckRun ELEMENTS inside contexts.nodes (the array survives with null holes); the
808
+ // readable StatusContexts — gate-author-trust / gate-owner-approved — remain.
809
+ const checks = ((rollup && rollup.contexts && rollup.contexts.nodes) || []).filter(Boolean).map((c) =>
810
+ c.__typename === 'CheckRun'
811
+ ? { name: c.name, conclusion: c.conclusion, status: c.status, url: c.detailsUrl || null }
812
+ : { name: c.context, conclusion: c.state, status: 'COMPLETED', url: c.targetUrl || null }
813
+ );
814
+ return {
815
+ number: n.number,
816
+ title: n.title || '',
817
+ url: n.url,
818
+ is_draft: !!n.isDraft,
819
+ head_ref: n.headRefName || null,
820
+ head_sha: commit.oid || null,
821
+ author: (n.author && n.author.login) || null,
822
+ body: n.bodyText || '',
823
+ review_decision: n.reviewDecision || null,
824
+ rollup_state: (rollup && rollup.state) || null,
825
+ checks,
826
+ };
827
+ });
828
+ // Signal reduced fidelity (e.g. checks unreadable → App lacks Checks:read) so a caller
829
+ // can say so instead of silently presenting an empty check rollup as "all clear".
830
+ // Non-enumerable: the array is JSON-serialized directly by the GET route and this must
831
+ // not leak into that payload — the route reads it explicitly.
832
+ if (partialErrors.length) {
833
+ Object.defineProperty(result, 'partialErrors', { value: partialErrors, enumerable: false });
834
+ }
835
+ return result;
836
+ }
837
+
838
+ // getPullRequestFiles — the changed paths of a PR, mapped to the shape
839
+ // classifyChanges() consumes ({ path, mode, deleted }). The REST files API does
840
+ // not return the git file mode, so we default to a regular-file mode; the symlink
841
+ // pre-flight reject is a CI-authoritative edge that does not affect this read-only
842
+ // "why did it escalate" display. Caps at 300 files (3 pages) — far beyond any real
843
+ // gate PR; a larger PR is itself an escalate-by-size in CI.
844
+ async function getPullRequestFiles({ number }, deps = {}) {
845
+ const token = await resolveToken(deps);
846
+ if (!token) throw err('PUSH_UNCONFIGURED');
847
+ if (!Number.isInteger(number) || number < 1) throw err('BAD_PR');
848
+ const repoInfo = deps.repoInfo || loadRepoInfo();
849
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
850
+ const { owner, repo } = repoInfo;
851
+ const doFetch = deps.fetchImpl || fetch;
852
+ const api = `https://api.github.com/repos/${owner}/${repo}`;
853
+ const files = [];
854
+ for (let page = 1; page <= 3; page += 1) {
855
+ const res = await doFetch(`${api}/pulls/${number}/files?per_page=100&page=${page}`, {
856
+ headers: ghHeaders(token),
857
+ });
858
+ if (!res.ok) throw err('STATUS_FETCH_FAILED', { status: res.status });
859
+ const batch = await res.json();
860
+ if (!Array.isArray(batch) || !batch.length) break;
861
+ for (const f of batch) {
862
+ files.push({
863
+ path: f.filename,
864
+ mode: '100644',
865
+ deleted: f.status === 'removed',
866
+ status: f.status,
867
+ additions: f.additions || 0,
868
+ deletions: f.deletions || 0,
869
+ });
870
+ }
871
+ if (batch.length < 100) break;
872
+ }
873
+ return files;
874
+ }
875
+
876
+ // How long to wait between the close PATCH and the reopen PATCH (task 1241). See the
877
+ // recheckPullRequest header for WHY this delay exists; overridable via deps.reopenDelayMs
878
+ // so tests run instantly (and an operator can tune it without a code change).
879
+ const RECHECK_REOPEN_DELAY_MS = 4000;
880
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
881
+
882
+ // recheckPullRequest — re-trigger the gate-review check after an owner approval was
883
+ // recorded. WHY close+reopen: setting a commit status does NOT fire a `pull_request`
884
+ // event, so the (red) gate-review check would not re-evaluate the new approval and
885
+ // the required check would stay failing. Re-running the check via the Actions API
886
+ // would need an `actions:write` App permission the publish App is not granted;
887
+ // closing then reopening the PR fires `pull_request: reopened` (a default activity
888
+ // type for the gate-review trigger) using the `pull_requests:write` the App already
889
+ // holds — zero new permission, and the re-run carries the real PR context so the
890
+ // fresh `gate-review` check posts on the correct head sha. Re-arms auto-merge after
891
+ // reopen (closing clears it). Best-effort; the publish reconciler's mergeIfGreen
892
+ // sweep (task 1162) is the backstop if auto-merge does not re-fire.
893
+ //
894
+ // WHY THE DELAY (task 1241). When the close + reopen PATCHes land ~1s apart, GitHub
895
+ // COALESCES them into no net state change and dispatches NO `pull_request: reopened`
896
+ // event — so the gate-review run never re-fires and the required check stays red after
897
+ // an owner Approve (observed on PR #167: close 22:36:39Z, reopen 22:36:40Z by the App
898
+ // bot, zero gate-review runs followed; a later manual rerun cleared it green, proving
899
+ // only the auto-re-trigger was broken). Sleeping a few seconds between the two PATCHes
900
+ // makes GitHub register two DISTINCT state transitions, so the reopen fires a real
901
+ // `reopened` event carrying full PR context. Stays inside the App's existing
902
+ // `pull_requests:write` — no new permission (the deterministic Actions rerun API would
903
+ // need `actions:write`, deliberately not granted; see blocker 37).
904
+ async function recheckPullRequest({ number }, deps = {}) {
905
+ const token = await resolveToken(deps);
906
+ if (!token) throw err('PUSH_UNCONFIGURED');
907
+ if (!Number.isInteger(number) || number < 1) throw err('BAD_PR');
908
+ const repoInfo = deps.repoInfo || loadRepoInfo();
909
+ if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
910
+ const { owner, repo } = repoInfo;
911
+ const doFetch = deps.fetchImpl || fetch;
912
+ const reopenDelayMs = Number.isFinite(deps.reopenDelayMs) ? deps.reopenDelayMs : RECHECK_REOPEN_DELAY_MS;
913
+ const api = `https://api.github.com/repos/${owner}/${repo}`;
914
+ const patchState = async (state) => {
915
+ const res = await doFetch(`${api}/pulls/${number}`, {
916
+ method: 'PATCH',
917
+ headers: { ...ghHeaders(token), 'Content-Type': 'application/json' },
918
+ body: JSON.stringify({ state }),
919
+ });
920
+ if (!res.ok) {
921
+ const detail = await res.text().catch(() => '');
922
+ throw err('PR_PATCH_FAILED', { status: res.status, detail: detail.slice(0, 200) });
923
+ }
924
+ return res.json();
925
+ };
926
+ await patchState('closed');
927
+ // Let GitHub register the close as a distinct state transition before reopening, so
928
+ // the reopen actually dispatches a `pull_request: reopened` event (task 1241).
929
+ if (reopenDelayMs > 0) await sleep(reopenDelayMs);
930
+ const reopened = await patchState('open');
931
+ let autoMerge = false;
932
+ try {
933
+ if (reopened && reopened.node_id) autoMerge = await enableAutoMerge({ nodeId: reopened.node_id }, deps);
934
+ } catch (_) {
935
+ /* auto-merge re-arm is best-effort; mergeIfGreen sweep backstops it */
936
+ }
937
+ return { reopened: true, auto_merge: autoMerge, node_id: (reopened && reopened.node_id) || null };
938
+ }
939
+
940
+ module.exports = {
941
+ isConfigured,
942
+ appConfigured,
943
+ isSafeBranch,
944
+ pushBundle,
945
+ openOrFindPullRequest,
946
+ enableAutoMerge,
947
+ getPublishStatus,
948
+ getMergeState,
949
+ mergeIfGreen,
950
+ setCommitStatus,
951
+ setGateOwnerApproved,
952
+ listOpenPullRequests,
953
+ getPullRequestFiles,
954
+ recheckPullRequest,
955
+ GATE_OWNER_APPROVED_CONTEXT,
956
+ landConfirmed,
957
+ landProvenForTask,
958
+ prReferencesTask,
959
+ scrubCredential,
960
+ isAncestorOfDefaultBranch,
961
+ resolveToken,
962
+ getInstallationToken,
963
+ // exported for unit tests
964
+ buildAuthUrl,
965
+ mintAppJwt,
966
+ mergeBlockReason,
967
+ _resetAppTokenCacheForTests,
968
+ SAFE_BRANCH_RE,
969
+ };