@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,1292 @@
1
+ // public-builders/work.js — the Work Board (/builders/work, served at
2
+ // builders.amazonprimea.com/work).
3
+ //
4
+ // Signed-in builders only. The page is the builder-facing SOURCE OF TRUTH for
5
+ // "works in progress"; it gathers two views the hall + status dashboard used to
6
+ // scatter:
7
+ //
8
+ // 1. WORKS IN PROGRESS — the live in-flight list, every open claim across all
9
+ // builders (active_claims from GET /api/bongos/tasks/claimable). Version-level
10
+ // progress + goals moved to the Roadmap page (task 1757).
11
+ // 2. WORKS WAITING TO BE CLAIMED — the claimable queue, filterable, with the
12
+ // copy-prompt action (same shape as the hall's Claim section).
13
+ //
14
+ // Read-only across the wire EXCEPT the per-task copy-prompt button, which is
15
+ // purely client-side (writes to the clipboard, no server call).
16
+ //
17
+ // Auth: the page itself is gated server-side (server.js requireBuilderPage), so
18
+ // an unauthenticated visitor never reaches this script — but on a 401 from any
19
+ // fetch (expired session) we still bounce into the web sign-in flow. The
20
+ // claimable feed is additionally primer-gated server-side; a signed-in builder
21
+ // who hasn't acknowledged the primer gets a friendly gate notice instead of the
22
+ // queue.
23
+
24
+ (() => {
25
+ 'use strict';
26
+
27
+ const API = '/api/bongos';
28
+ // Generated typed client (task 1996 / R09), served as window.BongosClient by
29
+ // /lib/bongos-client.js. baseUrl:'' passes the full same-origin paths this file
30
+ // builds through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
31
+ const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
32
+ const AUTH_NEEDED = 'auth-needed';
33
+
34
+ // escapeHtml / $ / fmtNum / fmtDate / toast now come from the shared
35
+ // window.OTB toolkit (dom-utils.js, loaded first). Thin wrappers keep this
36
+ // file's exact variants (date-only fmtDate; a 2200ms clearing toast).
37
+ // fmtAgo / toAuth / getJSON / postJSON stay local below.
38
+ const { escapeHtml, $, fmtNum } = window.OTB;
39
+ const fmtDate = (iso) => window.OTB.fmtDate(iso);
40
+ const toast = (msg) => window.OTB.toast(msg, { duration: 2200, clear: true });
41
+ const curLabel = (caps) => { const l = (window.__BRANDING__ && window.__BRANDING__.currency && window.__BRANDING__.currency.label) || 'credits'; return caps ? l.charAt(0).toUpperCase() + l.slice(1) : l; };
42
+
43
+ // Coarse relative time ("just now", "3h", "2d") for how long a claim's been
44
+ // borne. Kept compact so the in-flight table stays narrow.
45
+ function fmtAgo(iso) {
46
+ if (!iso) return '—';
47
+ const then = new Date(iso).getTime();
48
+ if (Number.isNaN(then)) return '—';
49
+ const secs = Math.max(0, Math.floor((Date.now() - then) / 1000));
50
+ if (secs < 60) return 'just now';
51
+ const mins = Math.floor(secs / 60);
52
+ if (mins < 60) return `${mins}m`;
53
+ const hrs = Math.floor(mins / 60);
54
+ if (hrs < 24) return `${hrs}h`;
55
+ const days = Math.floor(hrs / 24);
56
+ return `${days}d`;
57
+ }
58
+
59
+ function toAuth() {
60
+ window.location.assign(`${API}/auth/web/start`);
61
+ }
62
+
63
+ // getJSON with the hall's auth posture: softAuth surfaces a typed error so the
64
+ // caller can decide (here, /me uses it to render nothing fatal); everything
65
+ // else redirects into sign-in on a 401. A 403 is surfaced (not redirected) so
66
+ // the primer gate can be handled gracefully.
67
+ async function getJSON(path, opts = {}) {
68
+ const res = await api.request('GET', path);
69
+ if (res.status === 401) {
70
+ if (opts.softAuth) {
71
+ const err = new Error(AUTH_NEEDED);
72
+ err.code = AUTH_NEEDED;
73
+ throw err;
74
+ }
75
+ toAuth();
76
+ throw new Error('redirecting to auth');
77
+ }
78
+ if (!res.ok) {
79
+ const err = new Error(`${path} → ${res.status}`);
80
+ err.status = res.status;
81
+ throw err;
82
+ }
83
+ return res.data;
84
+ }
85
+
86
+ // Mutating call (claim / release). Cookie-authed, same posture as the settings
87
+ // page. Resolves to the parsed body on 2xx; on a non-2xx it throws an Error
88
+ // carrying { status, data } so callers can map the server's typed error code
89
+ // (ALREADY_CLAIMED, TOUCHES_CONFLICT, …) to a friendly message. A 401 still
90
+ // bounces to sign-in — a mutation with a dead session is unrecoverable here.
91
+ async function postJSON(path, body) {
92
+ const res = await api.request('POST', path, { body: body || {} });
93
+ if (res.status === 401) { toAuth(); throw new Error('redirecting to auth'); }
94
+ let data = null;
95
+ try { data = res.data; } catch (_) { /* empty/non-JSON body */ }
96
+ if (!res.ok) {
97
+ const err = new Error(`${path} → ${res.status}`);
98
+ err.status = res.status;
99
+ err.data = data || {};
100
+ throw err;
101
+ }
102
+ return data;
103
+ }
104
+
105
+ // ---- paginated lists: per-page dropdown + Prev/Next (task 1931/1955) -------
106
+ // All four Work-page lists (In Progress, Ready, Completed, Backlog) paginate
107
+ // the same way, through one shared helper. Each list owns a `pager` (page +
108
+ // per-page, persisted to its own localStorage key) and a DOM id `prefix`;
109
+ // renderPagerBar paints `#<prefix>-{pager,range,page,prev,next,per-page}`.
110
+ // Page/per-page live in module scope so a background poll / action refresh
111
+ // re-renders on the SAME page — only an explicit filter/per-page change resets.
112
+ const PER_PAGE_OPTIONS = [10, 25, 50, 100];
113
+ const MIN_PER_PAGE = PER_PAGE_OPTIONS[0]; // ≤ this fits one page — hide the bar
114
+ const DEFAULT_PER_PAGE = 25;
115
+ function loadPerPage(key, fallback) {
116
+ try {
117
+ const v = Number(window.localStorage.getItem(key));
118
+ return PER_PAGE_OPTIONS.includes(v) ? v : fallback;
119
+ } catch (_) { return fallback; }
120
+ }
121
+ function makePager(prefix, key) {
122
+ return { prefix, key, page: 1, perPage: loadPerPage(key, DEFAULT_PER_PAGE) };
123
+ }
124
+ const pagers = {
125
+ wip: makePager('wip', 'otb.work.wipPerPage'),
126
+ ready: makePager('ready', 'otb.work.readyPerPage'),
127
+ done: makePager('done', 'otb.work.donePerPage'),
128
+ backlog: makePager('backlog', 'otb.work.backlogPerPage'),
129
+ };
130
+
131
+ // A section-header count pill: reveal it and set its number once data lands.
132
+ function setCountPill(sel, n) {
133
+ const el = $(sel);
134
+ if (!el) return;
135
+ el.hidden = false;
136
+ el.textContent = String(n);
137
+ }
138
+
139
+ // Slice `list` to the pager's current page (clamped — the filtered list may
140
+ // have shrunk under a poll), paint the bar, and return the slice to render.
141
+ // The bar hides when the whole list fits one minimum page.
142
+ function paginate(list, p) {
143
+ const total = list.length;
144
+ const totalPages = Math.max(1, Math.ceil(total / p.perPage));
145
+ if (p.page > totalPages) p.page = totalPages;
146
+ if (p.page < 1) p.page = 1;
147
+ const start = (p.page - 1) * p.perPage;
148
+ const slice = list.slice(start, start + p.perPage);
149
+ renderPagerBar(p, total, totalPages, start, slice.length);
150
+ return slice;
151
+ }
152
+
153
+ function renderPagerBar(p, total, totalPages, start, shownCount) {
154
+ const pager = $(`#${p.prefix}-pager`);
155
+ if (!pager) return;
156
+ if (total <= MIN_PER_PAGE) { pager.hidden = true; return; }
157
+ pager.hidden = false;
158
+ const range = $(`#${p.prefix}-range`);
159
+ if (range) range.textContent = total
160
+ ? `Showing ${start + 1}–${start + shownCount} of ${total}`
161
+ : 'Showing 0 of 0';
162
+ const ind = $(`#${p.prefix}-page`);
163
+ if (ind) ind.textContent = `Page ${p.page} of ${totalPages}`;
164
+ const prev = $(`#${p.prefix}-prev`);
165
+ if (prev) prev.disabled = p.page <= 1;
166
+ const next = $(`#${p.prefix}-next`);
167
+ if (next) next.disabled = p.page >= totalPages;
168
+ const sel = $(`#${p.prefix}-per-page`);
169
+ if (sel && sel.value !== String(p.perPage)) sel.value = String(p.perPage);
170
+ }
171
+
172
+ // Wire a pager's controls once (per-page dropdown + Prev/Next). `rerender`
173
+ // re-renders that list; a per-page change resets to page 1 and persists.
174
+ function wirePager(p, rerender) {
175
+ $(`#${p.prefix}-per-page`)?.addEventListener('change', (e) => {
176
+ const v = Number(e.target.value);
177
+ if (!PER_PAGE_OPTIONS.includes(v)) return;
178
+ p.perPage = v; p.page = 1;
179
+ try { window.localStorage.setItem(p.key, String(v)); } catch (_) { /* private mode → in-memory only */ }
180
+ rerender();
181
+ });
182
+ $(`#${p.prefix}-prev`)?.addEventListener('click', () => { if (p.page > 1) { p.page -= 1; rerender(); } });
183
+ $(`#${p.prefix}-next`)?.addEventListener('click', () => { p.page += 1; rerender(); });
184
+ }
185
+
186
+ // ---- 1a. live in-flight list (active claims) -----------------------------
187
+
188
+ function renderInFlight(active) {
189
+ if (Array.isArray(active)) liveState.active = active;
190
+ const tbody = $('#wip-table tbody');
191
+ if (!tbody) return;
192
+ // task 1899: sort the viewer's own claims first (mine-first), preserving
193
+ // server order within each group (JS sort is stable). The count pill always
194
+ // reflects the TOTAL active-claim count, even on the empty path below.
195
+ const all = liveState.active;
196
+ const isMine = (c) => viewer.id != null && Number(c.builder_id) === Number(viewer.id);
197
+ const rows = all.slice().sort((a, b) => (isMine(b) ? 1 : 0) - (isMine(a) ? 1 : 0));
198
+ setCountPill('#wip-count', all.length);
199
+ if (!rows.length) {
200
+ tbody.innerHTML = `<tr><td colspan="6" class="ledger__empty">No tasks are in progress right now.</td></tr>`;
201
+ pagers.wip.page = 1;
202
+ const wp = $('#wip-pager');
203
+ if (wp) wp.hidden = true;
204
+ return;
205
+ }
206
+ const shown = paginate(rows, pagers.wip);
207
+ tbody.innerHTML = shown.map((c) => {
208
+ const who = escapeHtml(c.display_name || c.github_login || 'a builder');
209
+ const handle = c.github_login && c.display_name && c.display_name !== c.github_login
210
+ ? ` <small>@${escapeHtml(c.github_login)}</small>`
211
+ : '';
212
+ const ver = c.version_id ? `<span class="task__tag">${escapeHtml(c.version_id)}</span>` : '';
213
+ const mine = viewer.id != null && Number(c.builder_id) === Number(viewer.id);
214
+ const ageTitle = c.claimed_at ? ` title="claimed ${escapeHtml(fmtDate(c.claimed_at))}"` : '';
215
+ // task 1759: bulk-release checkbox — offered ONLY on the viewer's own rows,
216
+ // same scoping rule as the single Release button below.
217
+ const claimIdStr = escapeHtml(String(c.claim_id));
218
+ const selectCb = mine
219
+ ? `<input type="checkbox" class="wip__select" data-claim-id="${claimIdStr}" data-task-id="${escapeHtml(String(c.task_id))}" aria-label="Select task ${escapeHtml(String(c.task_id))} for bulk release"${selection.release.has(claimIdStr) ? ' checked' : ''}>`
220
+ : '';
221
+ // Release is offered ONLY on the viewer's own claims (the server scopes it
222
+ // too via NOT_YOUR_CLAIM, but we don't dangle a button others can't use).
223
+ const action = mine
224
+ ? `<button class="wip__release" type="button" data-claim-id="${claimIdStr}" data-task-id="${escapeHtml(String(c.task_id))}">Release</button>`
225
+ : '<span class="wip__mine-none" aria-hidden="true">—</span>';
226
+ return `
227
+ <tr${mine ? ' class="wip__row--mine"' : ''}>
228
+ <td class="wip__select-cell">${selectCb}</td>
229
+ <td>#${escapeHtml(String(c.task_id))}</td>
230
+ <td>${escapeHtml(c.title || '(untitled)')} ${ver}${mine ? ' <span class="task__tag task__tag--mine">yours</span>' : ''}</td>
231
+ <td class="wip-builder">${who}${handle}</td>
232
+ <td${ageTitle}>${escapeHtml(fmtAgo(c.claimed_at))}</td>
233
+ <td class="wip__action">${action}</td>
234
+ </tr>
235
+ `;
236
+ }).join('');
237
+ }
238
+
239
+ // ---- 2. claimable queue (filter + sort + copy prompt) --------------------
240
+
241
+ const taskState = { all: [], versions: [], backlog: [] };
242
+ // Live view context. viewerId lets renderInFlight decide which active claims
243
+ // are the viewer's own (→ a Release action). active holds the last in-flight
244
+ // payload so a poll can re-render claim ages without another round-trip.
245
+ const viewer = { id: null };
246
+ const liveState = { active: [] };
247
+ // Guards against double-firing an action while its request is in flight.
248
+ const pending = new Set();
249
+
250
+ // task 1759: multi-select + the bottom bulk toolbar. `claim` holds
251
+ // String(task_id) from the claimable queue; `release` maps
252
+ // String(claim_id) -> task_id for the viewer's OWN in-flight claims (release
253
+ // is only ever offered on those rows to begin with — see renderInFlight).
254
+ const selection = { claim: new Set(), release: new Map() };
255
+ // Last server dry-run (POST /claims/batch/validate) result, keyed by the
256
+ // sorted selected-id set so an unrelated re-render doesn't refire it.
257
+ const validateCache = { key: '', parallelSafe: null, byId: new Map() };
258
+ let validateDebounce = null;
259
+ // Per-task dependency edges for the "can these be sequenced?" cycle check —
260
+ // fetched lazily via GET /tasks/:id (task-detail.js's call) since the
261
+ // claimable feed doesn't carry dependencies[] per row. Stable for a ready
262
+ // task within one page session, so caching by id is safe.
263
+ const depsCache = new Map(); // task_id -> [dependency task_id, ...]
264
+ const sequentialCache = { key: '', order: null };
265
+ let sequentialDebounce = null;
266
+
267
+ function creditsPerMinute(t) {
268
+ const c = Number(t.credits_reward);
269
+ const m = Number(t.est_minutes);
270
+ if (!c || !m || m <= 0) return -Infinity;
271
+ return c / m;
272
+ }
273
+
274
+ function priorityRank(p) {
275
+ if (!p) return 99;
276
+ const m = /^P(\d+)/i.exec(String(p));
277
+ return m ? Number(m[1]) : 99;
278
+ }
279
+
280
+ // task 1756: two tasks can't run in parallel if their touches[] overlap — exact
281
+ // path match or a directory-prefix either way (mirrors the server's touchesOverlap
282
+ // / the goals-page conflict estimate). Empty touches never conflict.
283
+ function touchesConflict(aList, bList) {
284
+ const norm = (p) => String(p).replace(/\/+$/, '');
285
+ for (const a of (Array.isArray(aList) ? aList : [])) {
286
+ const na = norm(a);
287
+ for (const b of (Array.isArray(bList) ? bList : [])) {
288
+ const nb = norm(b);
289
+ if (na === nb || na.startsWith(nb + '/') || nb.startsWith(na + '/')) return true;
290
+ }
291
+ }
292
+ return false;
293
+ }
294
+
295
+ // task 1752 (+ 1498 / ADR 0084): the minimum rank to claim a task, as a tag shown
296
+ // on EVERY task row. 'xenos' is the open default → a muted "Any rank" tag (open to
297
+ // all, not a warning); 'archon' shows bare; other ranks show 'Rank+'. Kept here for
298
+ // now; the shared task-detail view (task 1753) will lift this into the common
299
+ // toolkit so every task surface renders rank identically.
300
+ function rankTagHtml(requiresRank) {
301
+ const rr = (requiresRank || 'xenos').toLowerCase();
302
+ if (rr === 'xenos') {
303
+ return '<span class="task__tag task__tag--rank-open" title="No rank floor — any builder can claim this task">Any rank</span>';
304
+ }
305
+ const label = rr === 'archon' ? 'Archon' : rr.charAt(0).toUpperCase() + rr.slice(1) + '+';
306
+ return `<span class="task__tag task__tag--rank" title="Minimum rank required to claim this task">${escapeHtml(label)}</span>`;
307
+ }
308
+
309
+ // task 1829: free-text search over the claimable/backlog lists. Matches the
310
+ // query (case-insensitive substring) against the task id (with or without a
311
+ // leading '#'), the title, and the goal title (taskState.goalTitles — the
312
+ // same map that labels the Goal filter, task 1755). Empty query matches all,
313
+ // so clearing the box restores the full (still dropdown-filtered) list.
314
+ function searchQuery() {
315
+ const el = $('#f-search');
316
+ return el ? el.value.trim().toLowerCase() : '';
317
+ }
318
+ function matchesSearch(t, q) {
319
+ if (!q) return true;
320
+ const needle = q.replace(/^#/, '');
321
+ if (!needle) return true;
322
+ if (String(t.id).includes(needle)) return true;
323
+ if ((t.title || '').toLowerCase().includes(needle)) return true;
324
+ const goalTitle = (taskState.goalTitles || {})[String(t.goal_id)] || '';
325
+ if (goalTitle.toLowerCase().includes(needle)) return true;
326
+ return false;
327
+ }
328
+
329
+ function renderClaimable() {
330
+ const root = $('#task-list');
331
+ if (!root) return;
332
+ const vFilter = $('#f-version').value;
333
+ const gFilter = $('#f-goal') ? $('#f-goal').value : '';
334
+ const kFilter = $('#f-kind').value;
335
+ const dFilter = $('#f-discipline').value;
336
+ const nFilter = $('#f-newcomer') ? $('#f-newcomer').value : '';
337
+ const sortBy = $('#f-sort').value;
338
+ const sQuery = searchQuery();
339
+
340
+ let list = taskState.all.filter((t) => {
341
+ if (!matchesSearch(t, sQuery)) return false;
342
+ if (vFilter && t.version_id !== vFilter) return false;
343
+ // task 1755: goal filter. '__none__' isolates tasks with no goal.
344
+ if (gFilter === '__none__' && t.goal_id != null) return false;
345
+ if (gFilter && gFilter !== '__none__' && String(t.goal_id) !== gFilter) return false;
346
+ if (kFilter && t.kind !== kFilter) return false;
347
+ if (dFilter && (t.discipline || 'unclassified') !== dFilter) return false;
348
+ if (nFilter === 'yes' && !t.newcomer_friendly) return false;
349
+ return true;
350
+ });
351
+
352
+ // task 1756: run-mode filter — a task is "parallel-safe" when its touches[]
353
+ // don't overlap any OTHER task in the currently-visible set; "conflict" is the
354
+ // complement (would collide, so run them sequentially). Computed over the
355
+ // already-filtered list so it answers "of what I'm looking at, what can run
356
+ // together." O(n²) over touches — fine at board scale.
357
+ const fitFilter = $('#f-fit') ? $('#f-fit').value : '';
358
+ if (fitFilter === 'parallel' || fitFilter === 'conflict') {
359
+ const base = list;
360
+ list = base.filter((t) => {
361
+ const conflicts = base.some((o) => o !== t && touchesConflict(t.touches, o.touches));
362
+ return fitFilter === 'parallel' ? !conflicts : conflicts;
363
+ });
364
+ }
365
+
366
+ if (sortBy === 'cpm') {
367
+ list = list.slice().sort((a, b) => creditsPerMinute(b) - creditsPerMinute(a));
368
+ } else if (sortBy === 'credits') {
369
+ list = list.slice().sort((a, b) => Number(b.credits_reward || 0) - Number(a.credits_reward || 0));
370
+ } else if (sortBy === 'minutes') {
371
+ list = list.slice().sort((a, b) => (Number(a.est_minutes) || Infinity) - (Number(b.est_minutes) || Infinity));
372
+ } else if (sortBy === 'priority') {
373
+ list = list.slice().sort((a, b) => priorityRank(a.priority) - priorityRank(b.priority));
374
+ } else if (sortBy === 'id') {
375
+ // task 1830: sort by task number, ascending (lowest/oldest first).
376
+ list = list.slice().sort((a, b) => Number(a.id) - Number(b.id));
377
+ }
378
+
379
+ const count = $('#claim-count');
380
+ if (count) count.textContent = `${list.length} ${list.length === 1 ? 'task' : 'tasks'} waiting`;
381
+ // task 1899: header count pill reflects the filtered-list length.
382
+ setCountPill('#ready-count', list.length);
383
+
384
+ if (!list.length) {
385
+ root.innerHTML = `<div class="profile__placeholder">No tasks match these filters. Try widening them.</div>`;
386
+ pagers.ready.page = 1;
387
+ const pager = $('#ready-pager');
388
+ if (pager) pager.hidden = true;
389
+ return;
390
+ }
391
+
392
+ // task 1931/1955: paginate via the shared helper. renderClaimable never
393
+ // RESETS the page (only an explicit filter/sort/per-page change does), so a
394
+ // background poll keeps the viewer on their current page.
395
+ const shown = paginate(list, pagers.ready);
396
+ root.innerHTML = shown.map((t) => {
397
+ const cpm = creditsPerMinute(t);
398
+ const cpmText = Number.isFinite(cpm) ? `${cpm.toFixed(2)} d/min` : '— d/min';
399
+ const minutesText = t.est_minutes ? `${t.est_minutes}m` : '—';
400
+ const discipline = t.discipline || 'unclassified';
401
+ const disciplineTag = discipline !== 'unclassified'
402
+ ? `<span class="task__tag">${escapeHtml(discipline)}</span>`
403
+ : '';
404
+ const newcomerTag = t.newcomer_friendly
405
+ ? '<span class="task__tag task__tag--newcomer">newcomer</span>'
406
+ : '';
407
+ const rankTag = rankTagHtml(t.requires_rank);
408
+ const touches = Array.isArray(t.touches) ? t.touches : [];
409
+ const touchesLine = touches.length
410
+ ? `<div class="task__touches" title="Files this task is responsible for">✎ ${touches.map(escapeHtml).join(', ')}</div>`
411
+ : '';
412
+ const selected = selection.claim.has(String(t.id));
413
+ return `
414
+ <div class="task${selected ? ' task--selected' : ''}" data-task-id="${t.id}">
415
+ <div class="task__id">
416
+ <input type="checkbox" class="task__select" data-task-id="${t.id}" aria-label="Select task ${t.id} for bulk actions"${selected ? ' checked' : ''}>
417
+ <span>#${t.id}</span>
418
+ <button class="task__open" type="button" data-task-id="${t.id}" title="Open task details" aria-label="Open details for task ${t.id}">⤢</button>
419
+ </div>
420
+ <div class="task__body">
421
+ <p class="task__title">${escapeHtml(t.title)}</p>
422
+ <div class="task__meta">
423
+ <span class="task__tag">${escapeHtml(t.version_id || '?')}</span>
424
+ <span class="task__tag">${escapeHtml(t.kind || 'unclassified')}</span>
425
+ ${disciplineTag}
426
+ ${newcomerTag}
427
+ ${rankTag}
428
+ ${t.priority ? `<span class="task__tag">${escapeHtml(t.priority)}</span>` : ''}
429
+ <span>${minutesText}</span>
430
+ <span>${cpmText}</span>
431
+ </div>
432
+ ${touchesLine}
433
+ </div>
434
+ <div class="task__credits">
435
+ <span class="task__credits-num">${fmtNum(window.OTBBranding?.taskCreditValue?.(t) ?? t.credits_reward)}</span>
436
+ <span class="task__credits-label">${window.OTBBranding?.taskCreditLabel?.(t) ?? ((window.OTBBranding?.currencyLabel?.() ?? 'credits') + ' Est.')}</span>
437
+ </div>
438
+ <div class="task__actions">
439
+ <button class="task__claim" type="button" data-task-id="${t.id}" aria-label="Claim task ${t.id}">Claim</button>
440
+ <button class="task__copy" type="button" data-task-id="${t.id}" title="Copy the task prompt to your clipboard" aria-label="Copy prompt for task ${t.id}"><span class="task__copy-icon" aria-hidden="true">📋</span><span class="task__copy-label">Copy prompt</span></button>
441
+ </div>
442
+ </div>
443
+ `;
444
+ }).join('');
445
+ }
446
+
447
+ function populateVersionFilter(versions) {
448
+ const sel = $('#f-version');
449
+ if (!sel) return;
450
+ for (const v of versions) {
451
+ const opt = document.createElement('option');
452
+ opt.value = v.id;
453
+ opt.textContent = v.id;
454
+ if (v.name && v.name !== v.id) opt.title = v.name;
455
+ sel.appendChild(opt);
456
+ }
457
+ }
458
+
459
+ // task 1755: the Goal filter — options are the goals present on the claimable
460
+ // feed, labeled by title (falls back to 'Goal #id' before titles load).
461
+ // '__none__' isolates ungoaled tasks. Populated once after the feed loads.
462
+ function populateGoalFilter() {
463
+ const sel = $('#f-goal');
464
+ if (!sel) return;
465
+ const cur = sel.value;
466
+ const titles = taskState.goalTitles || {};
467
+ const ids = [...new Set(taskState.all.map((t) => t.goal_id).filter((x) => x != null).map(String))];
468
+ ids.sort((a, b) => String(titles[a] || `Goal #${a}`).localeCompare(String(titles[b] || `Goal #${b}`)));
469
+ sel.length = 1; // keep the leading "— all goals —" option
470
+ for (const id of ids) {
471
+ const opt = document.createElement('option');
472
+ opt.value = id;
473
+ opt.textContent = titles[id] || `Goal #${id}`;
474
+ sel.appendChild(opt);
475
+ }
476
+ if (taskState.all.some((t) => t.goal_id == null)) {
477
+ const opt = document.createElement('option');
478
+ opt.value = '__none__';
479
+ opt.textContent = 'No goal';
480
+ sel.appendChild(opt);
481
+ }
482
+ if (cur) sel.value = cur;
483
+ }
484
+
485
+ // Editors and the Claude desktop/CLI app autolink any bare "#NNN" in a session
486
+ // transcript to the dev box's GitHub remote (…/issues/NNN), which 404s because
487
+ // our records number tasks separately from GitHub. So the scroll we hand the
488
+ // builder must never carry a bare "#NNN": write "task NNN" + the clickable hall
489
+ // URL, and strip "#NNN" out of the (free-text) task description too.
490
+ function deHashRefs(text) {
491
+ return String(text == null ? '' : text).replace(/#(\d{1,5})\b/g, 'task $1');
492
+ }
493
+
494
+ function buildPromptForTask(t) {
495
+ const desc = deHashRefs((t.description || '').trim());
496
+ const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
497
+ const hallUrl = `${hallBase}#/task/${t.id}`;
498
+ return [
499
+ `I am working on GDS task ${t.id} (${t.title}) — ${hallUrl}`,
500
+ `Read the task description, follow its acceptance criteria, stay strictly within touches[], and ship via /builder-ship when done.`,
501
+ ``,
502
+ `Task description:`,
503
+ desc || '(no description)',
504
+ ].join('\n');
505
+ }
506
+
507
+ async function copyPromptForTask(taskId, btn) {
508
+ const t = taskState.all.find((x) => String(x.id) === String(taskId));
509
+ if (!t) return;
510
+ const prompt = buildPromptForTask(t);
511
+ const icon = btn.querySelector('.task__copy-icon');
512
+ const label = btn.querySelector('.task__copy-label');
513
+ try {
514
+ await navigator.clipboard.writeText(prompt);
515
+ btn.dataset.copied = '1';
516
+ if (icon) icon.textContent = '✓';
517
+ if (label) label.textContent = 'Copied';
518
+ setTimeout(() => {
519
+ btn.dataset.copied = '0';
520
+ if (icon) icon.textContent = '📋';
521
+ if (label) label.textContent = 'Copy prompt';
522
+ }, 1600);
523
+ toast(`Prompt for #${taskId} copied to your clipboard.`);
524
+ } catch (err) {
525
+ try {
526
+ const ta = document.createElement('textarea');
527
+ ta.value = prompt;
528
+ ta.style.position = 'fixed';
529
+ ta.style.opacity = '0';
530
+ document.body.appendChild(ta);
531
+ ta.select();
532
+ document.execCommand('copy');
533
+ ta.remove();
534
+ toast(`Prompt for #${taskId} copied.`);
535
+ } catch (_) {
536
+ toast('Could not copy to the clipboard. Try again.');
537
+ }
538
+ }
539
+ }
540
+
541
+ // ---- claim / release actions ---------------------------------------------
542
+
543
+ // Map the server's typed error code to a builder-readable line. The codes are
544
+ // exactly those POST /claims and POST /claims/:id/resolve can return.
545
+ function friendlyActionError(status, data) {
546
+ const code = data && data.error;
547
+ switch (code) {
548
+ case 'ALREADY_CLAIMED':
549
+ return 'Someone else claimed that task first. The board will refresh.';
550
+ case 'TASK_NOT_READY':
551
+ return 'That task is no longer ready to be claimed.';
552
+ case 'TOUCHES_CONFLICT': {
553
+ const titles = Array.isArray(data.conflicts) ? data.conflicts.map((c) => c.title).filter(Boolean) : [];
554
+ return titles.length
555
+ ? `That task overlaps an active claim: ${titles.join('; ')}. Wait for it to ship.`
556
+ : 'That task overlaps another active claim. Wait for it to ship.';
557
+ }
558
+ case 'DEPS_NOT_SHIPPED':
559
+ return 'That task waits on unshipped dependencies. Pick another for now.';
560
+ case 'rank_too_low_for_task':
561
+ case 'XENOS_RESTRICTED':
562
+ case 'INSUFFICIENT_RANK':
563
+ return 'Your rank is not yet high enough for that task.';
564
+ case 'newcomer_must_ship_first':
565
+ return data.what_to_do || 'Ship your current claim before opening another.';
566
+ case 'NOT_YOUR_CLAIM':
567
+ return 'That claim is not yours to release.';
568
+ case 'ALREADY_RESOLVED':
569
+ return 'That claim was already resolved. The board will refresh.';
570
+ case 'BUILDER_INACTIVE':
571
+ return 'Your account is inactive. Contact an Archon.';
572
+ default:
573
+ return `The board could not complete that (${status}). Try again.`;
574
+ }
575
+ }
576
+
577
+ // Maps a single batch-item failure reason (POST /claims/batch,
578
+ // /claims/batch/validate) to a builder-readable fragment. Distinct from
579
+ // friendlyActionError above because a batch item's shape differs (reason,
580
+ // not error; extra fields like conflictsWithTaskId/module_key).
581
+ function friendlyBatchReason(item) {
582
+ switch (item && item.reason) {
583
+ case 'TASK_NOT_FOUND': return 'no longer exists';
584
+ case 'TASK_NOT_READY': return `no longer ready${item.currentStatus ? ` (${item.currentStatus})` : ''}`;
585
+ case 'ALREADY_CLAIMED': return 'already claimed';
586
+ case 'TOUCHES_CONFLICT': return `overlaps ${item.conflictTaskTitle || 'an active claim'}`;
587
+ case 'BATCH_TOUCHES_CONFLICT': return `overlaps task #${item.conflictsWithTaskId} in this selection`;
588
+ case 'MODULE_CONFLICT': return item.conflictsWithTaskId
589
+ ? `shares module "${item.module_key}" with task #${item.conflictsWithTaskId} in this selection`
590
+ : `shares module "${item.module_key}" with an active claim`;
591
+ case 'DEPS_NOT_SHIPPED': return 'waits on unshipped dependencies';
592
+ case 'rank_too_low_for_task':
593
+ case 'XENOS_RESTRICTED':
594
+ case 'INSUFFICIENT_RANK': return 'rank too low';
595
+ case 'BUILDER_INACTIVE': return 'account inactive';
596
+ default: return (item && item.reason) || 'not claimable';
597
+ }
598
+ }
599
+
600
+ async function copyText(text) {
601
+ try { await navigator.clipboard.writeText(text); return true; }
602
+ catch (_) {
603
+ try {
604
+ const ta = document.createElement('textarea');
605
+ ta.value = text;
606
+ ta.style.position = 'fixed';
607
+ ta.style.opacity = '0';
608
+ document.body.appendChild(ta);
609
+ ta.select();
610
+ const ok = document.execCommand('copy');
611
+ ta.remove();
612
+ return ok;
613
+ } catch (_e) {
614
+ return false;
615
+ }
616
+ }
617
+ }
618
+
619
+ // ---- bulk selection (task 1759) -------------------------------------------
620
+
621
+ function selectionTotal() { return selection.claim.size + selection.release.size; }
622
+
623
+ // task 1869: the single place that flips a card's selection — used by both
624
+ // the checkbox itself (forceChecked = its post-click .checked) and a click
625
+ // anywhere else on the card body (forceChecked omitted = toggle). Keeping
626
+ // one path means the checkbox's visual state, the card's highlight class,
627
+ // and the selection Set can never drift out of sync with each other.
628
+ function toggleTaskSelection(card, forceChecked) {
629
+ const cb = card.querySelector('.task__select');
630
+ if (!cb) return;
631
+ const next = forceChecked != null ? forceChecked : !cb.checked;
632
+ cb.checked = next;
633
+ const id = String(cb.dataset.taskId);
634
+ if (next) selection.claim.add(id); else selection.claim.delete(id);
635
+ card.classList.toggle('task--selected', next);
636
+ renderBulkToolbar();
637
+ }
638
+
639
+ // Drops any selected id that's fallen out from under the selection (claimed
640
+ // by someone else, shipped, released elsewhere, …) so a stale checkbox never
641
+ // outlives the row it pointed at. Called after every live refresh.
642
+ function pruneSelection() {
643
+ const claimableIds = new Set(taskState.all.map((t) => String(t.id)));
644
+ for (const id of Array.from(selection.claim)) {
645
+ if (!claimableIds.has(id)) selection.claim.delete(id);
646
+ }
647
+ const mineClaimIds = new Set(
648
+ liveState.active
649
+ .filter((c) => viewer.id != null && Number(c.builder_id) === Number(viewer.id))
650
+ .map((c) => String(c.claim_id))
651
+ );
652
+ for (const claimId of Array.from(selection.release.keys())) {
653
+ if (!mineClaimIds.has(claimId)) selection.release.delete(claimId);
654
+ }
655
+ }
656
+
657
+ function selectedClaimIds() { return [...selection.claim].map(Number); }
658
+ function selectionKey(ids) { return ids.slice().sort((a, b) => a - b).join(','); }
659
+
660
+ async function scheduleValidate(key, ids) {
661
+ if (validateDebounce) clearTimeout(validateDebounce);
662
+ validateDebounce = setTimeout(async () => {
663
+ try {
664
+ const result = await postJSON(`${API}/claims/batch/validate`, { task_ids: ids });
665
+ validateCache.key = key;
666
+ validateCache.parallelSafe = !!result.parallel_safe;
667
+ validateCache.byId = new Map((result.results || []).map((r) => [String(r.task_id), r]));
668
+ } catch (_) {
669
+ // Best-effort — leave Parallel disabled-with-a-generic-title on failure
670
+ // rather than risk copying a prompt for tasks we couldn't verify.
671
+ validateCache.key = key;
672
+ validateCache.parallelSafe = false;
673
+ validateCache.byId = new Map();
674
+ }
675
+ renderBulkToolbar();
676
+ }, 350);
677
+ }
678
+
679
+ async function fetchDeps(taskId) {
680
+ if (depsCache.has(taskId)) return depsCache.get(taskId);
681
+ let deps = [];
682
+ try {
683
+ const d = await getJSON(`${API}/tasks/${encodeURIComponent(taskId)}`, { softAuth: true });
684
+ if (d && d.task && Array.isArray(d.task.dependencies)) {
685
+ deps = d.task.dependencies.map((x) => Number(x.id)).filter((x) => Number.isFinite(x));
686
+ }
687
+ } catch (_) { /* treat as no known dependencies */ }
688
+ depsCache.set(taskId, deps);
689
+ return deps;
690
+ }
691
+
692
+ // Kahn's algorithm restricted to edges BETWEEN the selected ids (an
693
+ // out-of-selection dependency doesn't block sequencing the selection
694
+ // itself). Returns the topological order, or null if the restricted graph
695
+ // has a cycle (can't be sequenced).
696
+ function topoOrder(ids, edgesByTask) {
697
+ const remaining = new Set(ids);
698
+ const order = [];
699
+ let progressed = true;
700
+ while (remaining.size && progressed) {
701
+ progressed = false;
702
+ for (const id of Array.from(remaining)) {
703
+ const deps = (edgesByTask.get(id) || []).filter((d) => remaining.has(d));
704
+ if (deps.length === 0) {
705
+ order.push(id);
706
+ remaining.delete(id);
707
+ progressed = true;
708
+ }
709
+ }
710
+ }
711
+ return remaining.size ? null : order;
712
+ }
713
+
714
+ function scheduleSequentialCheck(key, ids) {
715
+ if (sequentialDebounce) clearTimeout(sequentialDebounce);
716
+ sequentialDebounce = setTimeout(async () => {
717
+ const edgesByTask = new Map();
718
+ await Promise.all(ids.map(async (id) => { edgesByTask.set(id, await fetchDeps(id)); }));
719
+ sequentialCache.key = key;
720
+ sequentialCache.order = topoOrder(ids, edgesByTask);
721
+ renderBulkToolbar();
722
+ }, 350);
723
+ }
724
+
725
+ function renderBulkToolbar() {
726
+ const bar = $('#bulk-toolbar');
727
+ if (!bar) return;
728
+ if (selectionTotal() === 0) { bar.hidden = true; return; }
729
+ bar.hidden = false;
730
+
731
+ const countEl = $('#bulk-count');
732
+ if (countEl) {
733
+ const parts = [];
734
+ if (selection.claim.size) parts.push(`${selection.claim.size} to claim`);
735
+ if (selection.release.size) parts.push(`${selection.release.size} to release`);
736
+ countEl.textContent = parts.join(' · ');
737
+ }
738
+
739
+ const claimBtn = $('#bulk-claim-btn');
740
+ if (claimBtn) {
741
+ claimBtn.disabled = selection.claim.size === 0;
742
+ claimBtn.textContent = selection.claim.size > 1 ? `Bulk claim (${selection.claim.size})` : 'Bulk claim';
743
+ }
744
+ const releaseBtn = $('#bulk-release-btn');
745
+ if (releaseBtn) {
746
+ releaseBtn.disabled = selection.release.size === 0;
747
+ releaseBtn.textContent = selection.release.size > 1 ? `Bulk release (${selection.release.size})` : 'Bulk release';
748
+ }
749
+
750
+ const parallelBtn = $('#bulk-parallel-btn');
751
+ if (parallelBtn) {
752
+ if (selection.claim.size < 2) {
753
+ parallelBtn.disabled = true;
754
+ parallelBtn.title = 'Select at least 2 waiting tasks to copy a parallel-session prompt.';
755
+ } else {
756
+ const ids = selectedClaimIds();
757
+ const key = selectionKey(ids);
758
+ if (validateCache.key === key && validateCache.parallelSafe != null) {
759
+ parallelBtn.disabled = !validateCache.parallelSafe;
760
+ if (!validateCache.parallelSafe) {
761
+ const bad = [...validateCache.byId.values()].filter((r) => !r.valid);
762
+ const detail = bad.map((r) => `#${r.task_id}: ${(r.reasons || []).map(friendlyBatchReason).join(', ')}`).join(' · ');
763
+ parallelBtn.title = detail || 'Selected tasks conflict and cannot run in parallel.';
764
+ } else {
765
+ parallelBtn.title = '';
766
+ }
767
+ } else {
768
+ parallelBtn.disabled = true;
769
+ parallelBtn.title = 'Checking for conflicts…';
770
+ scheduleValidate(key, ids);
771
+ }
772
+ }
773
+ }
774
+
775
+ const sequentialBtn = $('#bulk-sequential-btn');
776
+ if (sequentialBtn) {
777
+ if (selection.claim.size < 2) {
778
+ sequentialBtn.disabled = true;
779
+ sequentialBtn.title = 'Select at least 2 waiting tasks to copy a sequential prompt.';
780
+ } else {
781
+ const ids = selectedClaimIds();
782
+ const key = selectionKey(ids);
783
+ if (sequentialCache.key === key) {
784
+ sequentialBtn.disabled = sequentialCache.order == null;
785
+ sequentialBtn.title = sequentialCache.order == null
786
+ ? 'These tasks depend on each other in a cycle and cannot be ordered.'
787
+ : '';
788
+ } else {
789
+ sequentialBtn.disabled = true;
790
+ sequentialBtn.title = 'Checking dependency order…';
791
+ scheduleSequentialCheck(key, ids);
792
+ }
793
+ }
794
+ }
795
+ }
796
+
797
+ function clearSelection() {
798
+ selection.claim.clear();
799
+ selection.release.clear();
800
+ renderClaimable();
801
+ renderInFlight();
802
+ renderBulkToolbar();
803
+ }
804
+
805
+ function buildBulkParallelPrompt(tasks) {
806
+ const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
807
+ const lines = [
808
+ `I want to work on ${tasks.length} GDS tasks IN PARALLEL — open ${tasks.length} separate Claude Code sessions, each in its own worktree, one per task below, so they can run at the same time without touching each other's files.`,
809
+ '',
810
+ ];
811
+ tasks.forEach((t, i) => {
812
+ lines.push(`Session ${i + 1} — task ${t.id} (${t.title}) — ${hallBase}#/task/${t.id}`);
813
+ lines.push(`Claim it with /builder-claim ${t.id}, follow its acceptance criteria, stay within touches[], ship via /builder-ship when done.`);
814
+ lines.push('');
815
+ });
816
+ return lines.join('\n').trim();
817
+ }
818
+
819
+ function buildBulkSequentialPrompt(tasks) {
820
+ const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
821
+ const lines = [
822
+ `I want to work on ${tasks.length} GDS tasks IN ORDER, one after another in this same session — claim, build, and ship each before moving to the next.`,
823
+ '',
824
+ ];
825
+ tasks.forEach((t, i) => {
826
+ lines.push(`${i + 1}. Task ${t.id} (${t.title}) — ${hallBase}#/task/${t.id}`);
827
+ });
828
+ lines.push('');
829
+ lines.push('For each: /builder-claim <id>, follow its acceptance criteria, stay within touches[], /builder-ship <id> before starting the next.');
830
+ return lines.join('\n');
831
+ }
832
+
833
+ async function doBulkClaim() {
834
+ const ids = selectedClaimIds();
835
+ if (!ids.length) return;
836
+ const btn = $('#bulk-claim-btn');
837
+ if (btn) { btn.disabled = true; btn.textContent = 'Claiming…'; }
838
+ try {
839
+ if (ids.length === 1) {
840
+ await postJSON(`${API}/claims`, { task_id: ids[0], bind_session: false });
841
+ toast(`You claimed #${ids[0]}. Open a terminal to start work.`);
842
+ } else {
843
+ await postJSON(`${API}/claims/batch`, { task_ids: ids, bind_session: false });
844
+ toast(`Claimed ${ids.length} tasks. Open a terminal to start work.`);
845
+ }
846
+ selection.claim.clear();
847
+ await refreshLive();
848
+ } catch (err) {
849
+ if (err && err.message === 'redirecting to auth') return;
850
+ if (err.status === 409 && err.data && err.data.error === 'batch_failed') {
851
+ const failures = Array.isArray(err.data.failures) ? err.data.failures : [];
852
+ const lines = failures.map((f) => `#${f.task_id} (${friendlyBatchReason(f)})`);
853
+ toast(lines.length
854
+ ? `The batch could not be claimed — ${lines.join('; ')}.`
855
+ : 'The batch could not be claimed. It was left untouched (all-or-nothing).');
856
+ await refreshLive().catch(() => {});
857
+ } else {
858
+ toast(friendlyActionError(err.status, err.data));
859
+ if (err.status === 409) await refreshLive().catch(() => {});
860
+ }
861
+ } finally {
862
+ renderBulkToolbar();
863
+ }
864
+ }
865
+
866
+ async function doBulkRelease() {
867
+ const entries = [...selection.release.entries()]; // [claimId, taskId]
868
+ if (!entries.length) return;
869
+ const label = entries.length === 1 ? `#${entries[0][1]}` : `${entries.length} tasks`;
870
+ const pronoun = entries.length === 1 ? 'It returns' : 'They return';
871
+ if (!window.confirm(`Release ${label}? ${pronoun} to the queue and no ${curLabel()} are earned.`)) return;
872
+ const btn = $('#bulk-release-btn');
873
+ if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
874
+ try {
875
+ if (entries.length === 1) {
876
+ await postJSON(`${API}/claims/${encodeURIComponent(entries[0][0])}/resolve`, { outcome: 'abandoned' });
877
+ toast(`Released #${entries[0][1]}. It returns to the queue.`);
878
+ } else {
879
+ const result = await postJSON(`${API}/claims/batch/release`, { claim_ids: entries.map(([cid]) => Number(cid)) });
880
+ const released = Array.isArray(result.released) ? result.released.length : 0;
881
+ const failed = Array.isArray(result.failures) ? result.failures.length : 0;
882
+ toast(failed
883
+ ? `Released ${released} of ${entries.length} — ${failed} could not be released.`
884
+ : `Released ${released} tasks. They return to the queue.`);
885
+ }
886
+ selection.release.clear();
887
+ await refreshLive();
888
+ } catch (err) {
889
+ if (err && err.message === 'redirecting to auth') return;
890
+ toast(friendlyActionError(err.status, err.data));
891
+ } finally {
892
+ renderBulkToolbar();
893
+ }
894
+ }
895
+
896
+ async function doClaim(taskId, btn) {
897
+ const key = `claim:${taskId}`;
898
+ if (pending.has(key)) return;
899
+ pending.add(key);
900
+ if (btn) { btn.disabled = true; btn.textContent = 'Claiming…'; }
901
+ try {
902
+ // bind_session:false — a web claim has no working session, so leave it
903
+ // unbound (see claims.js resolveCreatorSessionId) and the builder can ship
904
+ // it later from any of their own terminals.
905
+ await postJSON(`${API}/claims`, { task_id: Number(taskId), bind_session: false });
906
+ toast(`You claimed #${taskId}. Open a terminal to start work.`);
907
+ await refreshLive();
908
+ } catch (err) {
909
+ if (err && err.message === 'redirecting to auth') return;
910
+ toast(friendlyActionError(err.status, err.data));
911
+ // A conflict/already-claimed means our view is stale — pull fresh truth.
912
+ if (err.status === 409) await refreshLive().catch(() => {});
913
+ if (btn) { btn.disabled = false; btn.textContent = 'Claim'; }
914
+ } finally {
915
+ pending.delete(key);
916
+ }
917
+ }
918
+
919
+ async function doRelease(claimId, taskId, btn) {
920
+ const key = `release:${claimId}`;
921
+ if (pending.has(key)) return;
922
+ if (!window.confirm(`Release your claim on #${taskId}? It returns to the queue and no ${curLabel()} are earned.`)) return;
923
+ pending.add(key);
924
+ if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
925
+ try {
926
+ // No DELETE /claims/:id exists — release is resolve(outcome=abandoned),
927
+ // the same path /builder-release uses. Scoped to the caller's own claim
928
+ // server-side (NOT_YOUR_CLAIM).
929
+ await postJSON(`${API}/claims/${encodeURIComponent(claimId)}/resolve`, { outcome: 'abandoned' });
930
+ toast(`Released #${taskId}. It returns to the queue.`);
931
+ await refreshLive();
932
+ } catch (err) {
933
+ if (err && err.message === 'redirecting to auth') return;
934
+ toast(friendlyActionError(err.status, err.data));
935
+ if (err.status === 409) await refreshLive().catch(() => {});
936
+ if (btn) { btn.disabled = false; btn.textContent = 'Release'; }
937
+ } finally {
938
+ pending.delete(key);
939
+ }
940
+ }
941
+
942
+ // ---- recent history (completed / shipped) --------------------------------
943
+
944
+ // task 1899: module-level store so the Completed pager can re-render without a
945
+ // param (mirrors renderInFlight's liveState.active pattern).
946
+ let historyRows = [];
947
+
948
+ // ---- task 1469: project-wide "works completed" momentum summary ----------
949
+ //
950
+ // A compact card above the Completed list summarizing the WHOLE project's
951
+ // shipped output (NOT the viewer's own — that lives on the profile page): total
952
+ // works shipped (all-time, summed from /public/progress lifecycle_shipped_count),
953
+ // total <currency> awarded (/public/cost-summary total_value_credits), and the
954
+ // most recent ship (the head of the recent-shipped feed already loaded for the
955
+ // history list). All three are public reads; no per-builder data.
956
+ let worksSummary = { progress: null, cost: null };
957
+
958
+ function summaryTile(label, num, sub, subTitle) {
959
+ const subHtml = sub
960
+ ? `<span class="stat-tile__sub" ${subTitle ? `title="${escapeHtml(subTitle)}" ` : ''}style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(sub)}</span>`
961
+ : '';
962
+ return `<div class="stat-tile"><span class="stat-tile__label">${escapeHtml(label)}</span>` +
963
+ `<span class="stat-tile__num">${escapeHtml(String(num))}</span>${subHtml}</div>`;
964
+ }
965
+
966
+ function renderWorksSummary() {
967
+ const root = $('#works-summary');
968
+ if (!root) return;
969
+ const versions = worksSummary.progress && Array.isArray(worksSummary.progress.progress)
970
+ ? worksSummary.progress.progress : null;
971
+ // Wait for the shipped-count feed; the card stays hidden until then (and on a
972
+ // project that has shipped nothing).
973
+ if (!versions) { root.hidden = true; return; }
974
+ const shipped = versions.reduce((n, v) => n + (Number(v.lifecycle_shipped_count) || 0), 0);
975
+ if (!shipped) { root.hidden = true; return; }
976
+ const awarded = worksSummary.cost ? Number(worksSummary.cost.total_value_credits) || 0 : null;
977
+ const latest = Array.isArray(historyRows) && historyRows.length ? historyRows[0] : null;
978
+
979
+ const tiles = [summaryTile('Works shipped', fmtNum(shipped))];
980
+ if (awarded != null) tiles.push(summaryTile(`${curLabel(true)} awarded`, fmtNum(awarded)));
981
+ if (latest) {
982
+ tiles.push(summaryTile('Latest ship', latest.shipped_at ? fmtAgo(latest.shipped_at) : '—',
983
+ latest.title || `#${latest.id}`, latest.title || ''));
984
+ }
985
+ root.innerHTML = tiles.join('');
986
+ root.hidden = false;
987
+ }
988
+
989
+ function renderHistory(tasks) {
990
+ if (Array.isArray(tasks)) historyRows = tasks;
991
+ const root = $('#history-list');
992
+ if (!root) return;
993
+ // task 1754: reveal the Completed group only when there are shipped tasks.
994
+ const scroll = $('#history-scroll');
995
+ if (scroll) scroll.hidden = !historyRows.length;
996
+ const rows = historyRows;
997
+ setCountPill('#done-count', rows.length);
998
+ if (!rows.length) {
999
+ root.innerHTML = `<div class="profile__placeholder">Nothing shipped yet.</div>`;
1000
+ pagers.done.page = 1;
1001
+ const dp = $('#done-pager');
1002
+ if (dp) dp.hidden = true;
1003
+ return;
1004
+ }
1005
+ const shown = paginate(rows, pagers.done);
1006
+ root.innerHTML = shown.map((t) => {
1007
+ const ver = t.version_id ? `<span class="task__tag">${escapeHtml(t.version_id)}</span>` : '';
1008
+ const when = t.shipped_at ? fmtAgo(t.shipped_at) : '';
1009
+ return `
1010
+ <div class="history__row">
1011
+ <span class="history__id">#${escapeHtml(String(t.id))}</span>
1012
+ <span class="history__title">${escapeHtml(t.title || '(untitled)')} ${ver}</span>
1013
+ <span class="history__when" title="${escapeHtml(fmtDate(t.shipped_at))}">${escapeHtml(when)}</span>
1014
+ </div>
1015
+ `;
1016
+ }).join('');
1017
+ // task 1469: now that historyRows is populated, (re)render the momentum card so
1018
+ // its "Latest ship" tile reflects the newest shipped task.
1019
+ renderWorksSummary();
1020
+ }
1021
+
1022
+ // ---- backlog (not-yet-ready tasks) ---------------------------------------
1023
+
1024
+ // task 1754: the collapsed "Backlog" group — status=backlog tasks, read-only
1025
+ // (they can't be claimed until their blockers ship). Reuses rankTagHtml (1752)
1026
+ // and opens the shared task-detail overlay (1753) on click.
1027
+ // task 1955: module-level store so the Backlog pager re-renders without a param.
1028
+ let backlogRows = [];
1029
+
1030
+ function renderBacklog(tasks) {
1031
+ if (Array.isArray(tasks)) backlogRows = tasks;
1032
+ const scroll = $('#backlog-scroll');
1033
+ const root = $('#backlog-list');
1034
+ if (!root) return;
1035
+ // Hide the whole section only when there's genuinely no backlog; when a
1036
+ // search is active but filters everything out, keep the section visible so
1037
+ // the "0 tasks" count explains the empty list (task 1829).
1038
+ if (!backlogRows.length) { if (scroll) scroll.hidden = true; return; }
1039
+ const q = searchQuery();
1040
+ const list = q ? backlogRows.filter((t) => matchesSearch(t, q)) : backlogRows;
1041
+ if (scroll) scroll.hidden = false;
1042
+ const countEl = $('#backlog-count');
1043
+ if (countEl) countEl.textContent = `${list.length} ${list.length === 1 ? 'task' : 'tasks'}`;
1044
+ const shown = paginate(list, pagers.backlog);
1045
+ root.innerHTML = shown.map((t) => {
1046
+ const ver = t.version_id ? `<span class="task__tag">${escapeHtml(t.version_id)}</span>` : '';
1047
+ return `
1048
+ <div class="history__row" data-task-id="${escapeHtml(String(t.id))}" title="Open task #${escapeHtml(String(t.id))}">
1049
+ <span class="history__id">#${escapeHtml(String(t.id))}</span>
1050
+ <span class="history__title">${escapeHtml(t.title || '(untitled)')} ${ver} ${rankTagHtml(t.requires_rank)}</span>
1051
+ <span class="history__when">${escapeHtml(t.kind || '')}</span>
1052
+ </div>`;
1053
+ }).join('');
1054
+ }
1055
+
1056
+ // ---- live refresh + polling ----------------------------------------------
1057
+
1058
+ // Pull the claimable feed + active claims and re-render both. Shared by the
1059
+ // post-action refresh and the poll timer. Swallows the gate/transport errors
1060
+ // the same way loadAll does so a transient failure doesn't blank the board.
1061
+ async function refreshLive() {
1062
+ try {
1063
+ const claimResp = await getJSON(`${API}/tasks/claimable`);
1064
+ renderInFlight(claimResp.active_claims || []);
1065
+ taskState.all = claimResp.claimable || [];
1066
+ renderClaimable();
1067
+ pruneSelection();
1068
+ renderBulkToolbar();
1069
+ } catch (err) {
1070
+ if (err && err.status === 403) { showClaimGate(); renderInFlight([]); }
1071
+ // Other failures: keep the last good render rather than blanking.
1072
+ }
1073
+ }
1074
+
1075
+ function startPolling() {
1076
+ // Re-render the in-flight ages every tick even without new data, and pull
1077
+ // fresh truth from the server. Paused while the tab is hidden to avoid
1078
+ // pointless background traffic.
1079
+ const POLL_MS = 20000;
1080
+ setInterval(() => {
1081
+ if (document.hidden) { return; }
1082
+ refreshLive();
1083
+ }, POLL_MS);
1084
+ // Cheap local re-render of claim ages between polls.
1085
+ setInterval(() => {
1086
+ if (!document.hidden && liveState.active.length) renderInFlight();
1087
+ }, 60000);
1088
+ // Snap to fresh data the moment the tab regains focus.
1089
+ document.addEventListener('visibilitychange', () => {
1090
+ if (!document.hidden) refreshLive();
1091
+ });
1092
+ }
1093
+
1094
+ // ---- gate notice (signed in, primer not yet read) ------------------------
1095
+
1096
+ function showClaimGate() {
1097
+ const gate = $('#gate-scroll');
1098
+ const claim = $('#claim-scroll');
1099
+ if (gate) gate.hidden = false;
1100
+ if (claim) claim.hidden = true;
1101
+ }
1102
+
1103
+ // ---- bootstrap -----------------------------------------------------------
1104
+
1105
+ async function loadAll() {
1106
+ // /me confirms the viewer + personalises the header. softAuth so a stale
1107
+ // session renders the page chrome rather than a hard bounce — though the
1108
+ // server gate means an unauth visitor never gets here in the first place.
1109
+ let me = null;
1110
+ try {
1111
+ me = await getJSON(`${API}/me`, { softAuth: true });
1112
+ } catch (err) {
1113
+ if (err && err.code === AUTH_NEEDED) { toAuth(); return; }
1114
+ throw err;
1115
+ }
1116
+ viewer.id = me?.builder?.id ?? null;
1117
+ const sub = $('#work-sub');
1118
+ if (sub) {
1119
+ const name = me?.builder?.display_name || me?.builder?.github_login || 'builder';
1120
+ sub.textContent = `Welcome, ${name}. Claim and release tasks and follow work in progress — no terminal needed.`;
1121
+ }
1122
+
1123
+ // Recent history — public read surface, never gated. Independent of the
1124
+ // claimable feed so it paints even if that's gated/late.
1125
+ getJSON(`${API}/public/recent-shipped`)
1126
+ .then((r) => renderHistory(r.tasks || []))
1127
+ .catch(() => {
1128
+ const root = $('#history-list');
1129
+ if (root) root.innerHTML = `<div class="profile__placeholder">Recent history could not be loaded right now.</div>`;
1130
+ });
1131
+
1132
+ // task 1469: the project-wide "works completed" momentum summary above the
1133
+ // Completed list. Public reads, independent of the claimable feed so it paints
1134
+ // regardless; the card stays hidden if either read fails.
1135
+ Promise.all([
1136
+ getJSON(`${API}/public/progress`).catch(() => null),
1137
+ getJSON(`${API}/public/cost-summary`).catch(() => null),
1138
+ ]).then(([progress, cost]) => {
1139
+ worksSummary = { progress, cost };
1140
+ renderWorksSummary();
1141
+ });
1142
+
1143
+ // task 1754: the Backlog group — not-yet-ready tasks. Best-effort + independent
1144
+ // of the claimable feed; leaves the section hidden on failure.
1145
+ getJSON(`${API}/tasks?status=backlog`)
1146
+ .then((r) => { taskState.backlog = r.tasks || []; renderBacklog(taskState.backlog); })
1147
+ .catch(() => { /* backlog stays hidden */ });
1148
+
1149
+ // task 1755: goal id → title map for the Goal filter (best-effort).
1150
+ taskState.goalTitles = {};
1151
+ try {
1152
+ const gr = await getJSON(`${API}/goals`);
1153
+ (gr.goals || []).forEach((g) => { taskState.goalTitles[g.id] = g.title; });
1154
+ } catch (_) { /* filter labels fall back to 'Goal #id' */ }
1155
+
1156
+ // Claimable + active claims in one call. A 403 means the primer gate — show
1157
+ // the friendly notice and hide the claim queue, but keep the in-flight list
1158
+ // empty-but-honest (the gate also blocks active_claims, so say so).
1159
+ try {
1160
+ const [claimResp, versionsResp] = await Promise.all([
1161
+ getJSON(`${API}/tasks/claimable`),
1162
+ getJSON(`${API}/versions`).catch(() => ({ versions: [] })),
1163
+ ]);
1164
+ taskState.versions = versionsResp.versions || [];
1165
+ populateVersionFilter(taskState.versions);
1166
+ renderInFlight(claimResp.active_claims || []);
1167
+ taskState.all = claimResp.claimable || [];
1168
+ renderClaimable();
1169
+ populateGoalFilter(); // task 1755
1170
+ pruneSelection();
1171
+ renderBulkToolbar();
1172
+ } catch (err) {
1173
+ if (err && err.status === 403) {
1174
+ showClaimGate();
1175
+ renderInFlight([]);
1176
+ return;
1177
+ }
1178
+ const root = $('#task-list');
1179
+ if (root) root.innerHTML = `<div class="profile__placeholder">The task list could not be loaded right now.</div>`;
1180
+ renderInFlight([]);
1181
+ }
1182
+ }
1183
+
1184
+ function wireEvents() {
1185
+ // task 1931: any filter/sort change re-paginates the Ready list from page 1.
1186
+ const onReadyFilterChange = () => { pagers.ready.page = 1; renderClaimable(); };
1187
+ $('#f-version')?.addEventListener('change', onReadyFilterChange);
1188
+ $('#f-goal')?.addEventListener('change', onReadyFilterChange);
1189
+ $('#f-kind')?.addEventListener('change', onReadyFilterChange);
1190
+ $('#f-discipline')?.addEventListener('change', onReadyFilterChange);
1191
+ $('#f-newcomer')?.addEventListener('change', onReadyFilterChange);
1192
+ $('#f-fit')?.addEventListener('change', onReadyFilterChange);
1193
+ $('#f-sort')?.addEventListener('change', onReadyFilterChange);
1194
+ // task 1829: free-text search — 'input' fires on every keystroke and on the
1195
+ // type=search clear (✕). Re-render BOTH the claimable and backlog lists,
1196
+ // resetting each to page 1 so matches aren't hidden behind pagination.
1197
+ $('#f-search')?.addEventListener('input', () => {
1198
+ pagers.ready.page = 1;
1199
+ pagers.backlog.page = 1;
1200
+ renderClaimable();
1201
+ renderBacklog();
1202
+ });
1203
+
1204
+ // task 1931/1955: pagination controls for all four Work-page lists.
1205
+ wirePager(pagers.ready, renderClaimable);
1206
+ wirePager(pagers.wip, renderInFlight);
1207
+ wirePager(pagers.done, renderHistory);
1208
+ wirePager(pagers.backlog, renderBacklog);
1209
+
1210
+ $('#task-list')?.addEventListener('click', (e) => {
1211
+ // task 1759: the multi-select checkbox. Toggle the Set + the card's
1212
+ // highlight class directly (cheaper than a full renderClaimable) and
1213
+ // stop here — it must never fall through to the card-select handler below.
1214
+ const selectCb = e.target.closest('.task__select');
1215
+ if (selectCb) {
1216
+ const card = selectCb.closest('.task');
1217
+ if (card) toggleTaskSelection(card, selectCb.checked);
1218
+ return;
1219
+ }
1220
+ const claimBtn = e.target.closest('.task__claim');
1221
+ if (claimBtn) { doClaim(claimBtn.dataset.taskId, claimBtn); return; }
1222
+ const copyBtn = e.target.closest('.task__copy');
1223
+ if (copyBtn) { copyPromptForTask(copyBtn.dataset.taskId, copyBtn); return; }
1224
+ // task 1869: the explicit open-detail hitbox — now the ONLY way to open
1225
+ // the shared task-detail overlay from a claimable card (the card body
1226
+ // itself toggles selection below, since multi-select is the primary card
1227
+ // interaction now that 1759 shipped).
1228
+ const openBtn = e.target.closest('.task__open');
1229
+ if (openBtn && openBtn.dataset.taskId && window.OTBTaskDetail) {
1230
+ window.OTBTaskDetail.open(openBtn.dataset.taskId);
1231
+ return;
1232
+ }
1233
+ // task 1869: clicking the card anywhere else toggles its selection
1234
+ // checkbox (matches the affordance a checkbox already implies).
1235
+ const card = e.target.closest('.task');
1236
+ if (card && card.dataset.taskId) toggleTaskSelection(card);
1237
+ });
1238
+
1239
+ // task 1754: a Backlog row opens the shared task-detail overlay (read-only).
1240
+ $('#backlog-list')?.addEventListener('click', (e) => {
1241
+ const row = e.target.closest('.history__row');
1242
+ if (row && row.dataset.taskId && window.OTBTaskDetail) window.OTBTaskDetail.open(row.dataset.taskId);
1243
+ });
1244
+
1245
+ // Release lives in the in-flight table (the viewer's own claims only).
1246
+ $('#wip-table')?.addEventListener('click', (e) => {
1247
+ // task 1759: the bulk-release checkbox — mirrors the task-list one above.
1248
+ const selectCb = e.target.closest('.wip__select');
1249
+ if (selectCb) {
1250
+ const claimId = String(selectCb.dataset.claimId);
1251
+ if (selectCb.checked) selection.release.set(claimId, selectCb.dataset.taskId);
1252
+ else selection.release.delete(claimId);
1253
+ renderBulkToolbar();
1254
+ return;
1255
+ }
1256
+ const rel = e.target.closest('.wip__release');
1257
+ if (!rel) return;
1258
+ doRelease(rel.dataset.claimId, rel.dataset.taskId, rel);
1259
+ });
1260
+
1261
+ // task 1759: the bottom bulk-action toolbar.
1262
+ $('#bulk-claim-btn')?.addEventListener('click', doBulkClaim);
1263
+ $('#bulk-release-btn')?.addEventListener('click', doBulkRelease);
1264
+ $('#bulk-clear-btn')?.addEventListener('click', clearSelection);
1265
+ $('#bulk-parallel-btn')?.addEventListener('click', async () => {
1266
+ const ids = selectedClaimIds();
1267
+ const tasks = ids.map((id) => taskState.all.find((t) => Number(t.id) === id)).filter(Boolean);
1268
+ if (tasks.length < 2) return;
1269
+ const ok = await copyText(buildBulkParallelPrompt(tasks));
1270
+ toast(ok ? `Parallel-session prompt for ${tasks.length} tasks copied.` : 'Could not copy to the clipboard.');
1271
+ });
1272
+ $('#bulk-sequential-btn')?.addEventListener('click', async () => {
1273
+ const ids = selectedClaimIds();
1274
+ const key = selectionKey(ids);
1275
+ if (sequentialCache.key !== key || sequentialCache.order == null) return;
1276
+ const tasks = sequentialCache.order.map((id) => taskState.all.find((t) => Number(t.id) === id)).filter(Boolean);
1277
+ if (tasks.length < 2) return;
1278
+ const ok = await copyText(buildBulkSequentialPrompt(tasks));
1279
+ toast(ok ? `Sequential prompt for ${tasks.length} tasks copied.` : 'Could not copy to the clipboard.');
1280
+ });
1281
+ }
1282
+
1283
+ document.addEventListener('DOMContentLoaded', () => {
1284
+ wireEvents();
1285
+ loadAll()
1286
+ .then(() => startPolling())
1287
+ .catch((err) => {
1288
+ console.error('[work] load failed:', err);
1289
+ toast('The work board failed to load. Reload to try again.');
1290
+ });
1291
+ });
1292
+ })();