@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,1219 @@
1
+ // public-builders/settings.js — the personal Settings page (/builders/settings).
2
+ //
3
+ // Open to ANY authenticated builder (no rank gate — a builder owns their own
4
+ // preferences; the server enforces the same posture on /me/skill-prefs and
5
+ // /me/disciplines). Two sections:
6
+ //
7
+ // 1. VOICES — per-skill model picker (GET/PATCH /api/bongos/me/skill-prefs).
8
+ // 2. CRAFT — preferred disciplines (GET /api/bongos/me, PATCH /api/bongos/me/disciplines).
9
+ // 3. DEV BOX — full box management via the shared OTBBoxPanel (#807, ADR 0040):
10
+ // request/open the online terminal, SSH details, and close.
11
+ // 5. SOUNDS — per-sound on/off toggles (GET/PATCH /api/bongos/me/sound-prefs, #786).
12
+ //
13
+ // On a 401 it redirects into the web sign-in flow, like the other hall pages.
14
+ // Each section loads behind its own try/catch so one failing endpoint doesn't
15
+ // take the whole page down.
16
+
17
+ (() => {
18
+ 'use strict';
19
+
20
+ const API = '/api/bongos';
21
+ // Generated typed client (task 1996 / R09), served as window.BongosClient by
22
+ // /lib/bongos-client.js. baseUrl:'' passes the full same-origin paths this file
23
+ // builds through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
24
+ const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
25
+
26
+ // escapeHtml / $ / toast now come from the shared window.OTB toolkit
27
+ // (dom-utils.js, loaded first). The toast wrapper keeps this page's variant
28
+ // (the 3200ms non-clearing toast).
29
+ const { escapeHtml, $ } = window.OTB;
30
+ const toast = (msg) => window.OTB.toast(msg);
31
+ const brandWorld = () => (window.__BRANDING__ && window.__BRANDING__.identity && window.__BRANDING__.identity.worldName) || 'the project';
32
+
33
+ function toAuth() {
34
+ window.location.assign(`${API}/auth/web/start`);
35
+ }
36
+
37
+ // ---- model labels (match the hall's wording in builders.js) --------------
38
+
39
+ const MODEL_LABELS = {
40
+ opus: 'Opus',
41
+ sonnet: 'Sonnet',
42
+ haiku: 'Haiku',
43
+ script: 'no LLM',
44
+ };
45
+ const modelLabel = (m) => MODEL_LABELS[m] || m || '—';
46
+
47
+ // ---- section 1: VOICES (per-skill model picker) --------------------------
48
+
49
+ // Render one row per skill in `effective`. Each row: skill name, the system
50
+ // default (muted), and a <select> whose value is the builder's override (or
51
+ // "" for "fall back to default"). The allowed model set is echoed back by the
52
+ // endpoint so we don't mirror it from a second source.
53
+ function renderVoices(data) {
54
+ const body = $('#voices-body');
55
+ if (!body) return;
56
+ const rows = Array.isArray(data?.effective) ? data.effective : [];
57
+ const allowed = Array.isArray(data?.allowed_models)
58
+ ? data.allowed_models
59
+ : ['opus', 'sonnet', 'haiku', 'script'];
60
+ if (rows.length === 0) {
61
+ body.innerHTML = '<tr><td colspan="3" class="voices-empty">No skills registered.</td></tr>';
62
+ return;
63
+ }
64
+ body.innerHTML = rows.map((row) => {
65
+ const opts = [`<option value="">Default (${escapeHtml(modelLabel(row.default_model))})</option>`]
66
+ .concat(allowed.map((m) => {
67
+ const sel = row.override_model === m ? ' selected' : '';
68
+ return `<option value="${escapeHtml(m)}"${sel}>${escapeHtml(modelLabel(m))}</option>`;
69
+ }))
70
+ .join('');
71
+ return (
72
+ `<tr data-skill="${escapeHtml(row.skill)}">` +
73
+ `<td class="voices__skill"><code>${escapeHtml(row.skill)}</code></td>` +
74
+ `<td><span class="voices__default" title="System default for this skill">${escapeHtml(modelLabel(row.default_model))}</span></td>` +
75
+ '<td>' +
76
+ `<select class="voices__select" data-skill="${escapeHtml(row.skill)}" aria-label="Voice for ${escapeHtml(row.skill)}">${opts}</select>` +
77
+ '</td>' +
78
+ '</tr>'
79
+ );
80
+ }).join('');
81
+ }
82
+
83
+ // PATCH a single skill's override. Body shape (see me.js + skill-prefs.js):
84
+ // { "<skill-name>": "<model>" | null }
85
+ // where null clears the override (fall back to the system default).
86
+ async function saveVoice(skill, modelOrNull) {
87
+ const status = $('#voices-status');
88
+ if (status) status.textContent = 'Saving…';
89
+ try {
90
+ const body = { [skill]: modelOrNull };
91
+ const res = await api.request('PATCH', `${API}/me/skill-prefs`, { body });
92
+ if (res.status === 401) { toAuth(); return; }
93
+ if (!res.ok) throw new Error(`skill-prefs PATCH → ${res.status}`);
94
+ const data = res.data;
95
+ renderVoices(data);
96
+ const msg = modelOrNull
97
+ ? `${skill} → ${modelLabel(modelOrNull)}.`
98
+ : `Cleared ${skill} — falls back to default.`;
99
+ if (status) status.textContent = msg;
100
+ toast(msg);
101
+ } catch (err) {
102
+ console.error('[settings] save voice failed', err);
103
+ if (status) status.textContent = 'Could not save. Try again.';
104
+ toast('Could not save your choice. Try again.');
105
+ }
106
+ }
107
+
108
+ async function loadVoices() {
109
+ try {
110
+ const res = await api.request('GET', `${API}/me/skill-prefs`);
111
+ if (res.status === 401) { toAuth(); return; }
112
+ if (!res.ok) throw new Error(`skill-prefs GET → ${res.status}`);
113
+ const data = res.data;
114
+ renderVoices(data);
115
+ $('#voices-scroll').hidden = false;
116
+ // Delegate change events so post-render selects stay wired.
117
+ const table = $('#voices-table');
118
+ table.addEventListener('change', (e) => {
119
+ const sel = e.target.closest('.voices__select');
120
+ if (!sel) return;
121
+ const skill = sel.dataset.skill;
122
+ const next = sel.value === '' ? null : sel.value;
123
+ saveVoice(skill, next);
124
+ });
125
+ } catch (err) {
126
+ console.error('[settings] failed to load voices', err);
127
+ $('#voices-scroll').hidden = false;
128
+ $('#voices-body').innerHTML =
129
+ '<tr><td colspan="3" class="voices-empty">The skill list could not be loaded. Reload the page to try again.</td></tr>';
130
+ }
131
+ }
132
+
133
+ // ---- section 5: SOUNDS (per-sound on/off toggles, #786) ------------------
134
+
135
+ // Render one row per sound from `effective`. Each row: label + description and
136
+ // an On/Off toggle button whose data-on reflects the effective enabled-state.
137
+ function renderSounds(data) {
138
+ const wrap = $('#sound-rows');
139
+ if (!wrap) return;
140
+ const rows = Array.isArray(data?.effective) ? data.effective : [];
141
+ if (rows.length === 0) {
142
+ wrap.innerHTML = '<p class="voices-empty">No sounds registered.</p>';
143
+ return;
144
+ }
145
+ wrap.innerHTML = rows.map((row) => {
146
+ const on = row.enabled !== false;
147
+ return (
148
+ `<div class="sound-row" data-sound="${escapeHtml(row.sound)}">` +
149
+ '<div class="sound-row__text">' +
150
+ `<div class="sound-row__label">${escapeHtml(row.label || row.sound)}</div>` +
151
+ `<div class="sound-row__desc">${escapeHtml(row.desc || '')}</div>` +
152
+ '</div>' +
153
+ `<button type="button" class="sound-toggle" data-sound="${escapeHtml(row.sound)}" ` +
154
+ `data-on="${on ? '1' : '0'}" aria-pressed="${on ? 'true' : 'false'}">` +
155
+ `${on ? 'On' : 'Off'}</button>` +
156
+ '</div>'
157
+ );
158
+ }).join('');
159
+ }
160
+
161
+ // Live chime preview while dragging the volume slider (#792). Uses the HTML5
162
+ // Audio element's native .volume (linear gain, matching the local player's
163
+ // WAV scaling). PLAY-WHEN-IDLE so a continuous drag repeats the chime without
164
+ // overlapping copies (which would pile into noise); the in-flight chime's
165
+ // volume is updated live so the level you hear tracks the slider immediately.
166
+ // Silent at 0. Best-effort: any audio error is swallowed.
167
+ let chimeAudio = null;
168
+ let chimePlaying = false;
169
+ function previewChime(volPct) {
170
+ try {
171
+ if (!chimeAudio) {
172
+ chimeAudio = new Audio('/builders/chime.wav');
173
+ chimeAudio.addEventListener('ended', () => { chimePlaying = false; });
174
+ chimeAudio.addEventListener('error', () => { chimePlaying = false; });
175
+ }
176
+ const gain = Math.max(0, Math.min(1, volPct / 100));
177
+ chimeAudio.volume = gain; // live — affects an in-flight chime too
178
+ if (gain <= 0) return; // muted — don't start a new one
179
+ if (chimePlaying) return; // let the current one finish (no overlap)
180
+ chimePlaying = true;
181
+ chimeAudio.currentTime = 0;
182
+ const p = chimeAudio.play();
183
+ if (p && typeof p.catch === 'function') p.catch(() => { chimePlaying = false; });
184
+ } catch (_) { /* best-effort preview */ }
185
+ }
186
+
187
+ // Reflect the master volume (#788) into the slider + percentage label.
188
+ function renderVolume(data) {
189
+ const wrap = $('#sound-volume');
190
+ const range = $('#sound-volume-range');
191
+ const pct = $('#sound-volume-pct');
192
+ if (!wrap || !range || !pct) return;
193
+ const v = (typeof data?.volume === 'number') ? data.volume : 100;
194
+ range.value = String(v);
195
+ pct.textContent = `${v}%`;
196
+ wrap.hidden = false;
197
+ }
198
+
199
+ // PATCH the master volume. Body shape: { volume: <0-100> } (see me.js +
200
+ // sound-prefs.js). Saved on the slider's `change` event (release), not on every
201
+ // `input` tick — the pct label updates live on input, the PATCH fires once.
202
+ // Feedback for volume goes to the toast popup ONLY — no in-card status line
203
+ // (it duplicated the percentage at the bottom of the card; #794).
204
+ async function saveVolume(value) {
205
+ try {
206
+ const res = await api.request('PATCH', `${API}/me/sound-prefs`, { body: { volume: value } });
207
+ if (res.status === 401) { toAuth(); return; }
208
+ if (!res.ok) throw new Error(`sound-prefs PATCH → ${res.status}`);
209
+ const data = res.data;
210
+ renderVolume(data);
211
+ toast(`Volume ${value}%.`);
212
+ } catch (err) {
213
+ console.error('[settings] save volume failed', err);
214
+ toast('Could not save the volume. Try again.');
215
+ }
216
+ }
217
+
218
+ // PATCH a single sound's enabled-state. Body shape (see me.js + sound-prefs.js):
219
+ // { "<sound>": <bool> } — we always send an explicit boolean (not the null
220
+ // clear sentinel), so the toggle is a plain on/off.
221
+ // Feedback goes to the toast popup ONLY — no in-card status line (it duplicated
222
+ // the toast at the bottom of the card; #798, matching the volume cleanup #794).
223
+ async function saveSound(sound, enabled, btn) {
224
+ if (btn) btn.disabled = true;
225
+ try {
226
+ const res = await api.request('PATCH', `${API}/me/sound-prefs`, { body: { [sound]: enabled } });
227
+ if (res.status === 401) { toAuth(); return; }
228
+ if (!res.ok) throw new Error(`sound-prefs PATCH → ${res.status}`);
229
+ const data = res.data;
230
+ renderSounds(data);
231
+ toast(enabled ? `${sound} — on.` : `${sound} — off.`);
232
+ } catch (err) {
233
+ console.error('[settings] save sound failed', err);
234
+ toast('Could not save your sound preference. Try again.');
235
+ if (btn) btn.disabled = false;
236
+ }
237
+ }
238
+
239
+ async function loadSounds() {
240
+ try {
241
+ const res = await api.request('GET', `${API}/me/sound-prefs`);
242
+ if (res.status === 401) { toAuth(); return; }
243
+ if (!res.ok) throw new Error(`sound-prefs GET → ${res.status}`);
244
+ const data = res.data;
245
+ renderSounds(data);
246
+ renderVolume(data);
247
+ $('#sounds-scroll').hidden = false;
248
+ // Delegate clicks so post-render toggles stay wired.
249
+ const wrap = $('#sound-rows');
250
+ wrap.addEventListener('click', (e) => {
251
+ const btn = e.target.closest('.sound-toggle');
252
+ if (!btn) return;
253
+ const sound = btn.dataset.sound;
254
+ const next = btn.dataset.on !== '1'; // flip current state
255
+ saveSound(sound, next, btn);
256
+ });
257
+ // Volume slider: live % label on input (drag), persist once on change (release).
258
+ const range = $('#sound-volume-range');
259
+ const pct = $('#sound-volume-pct');
260
+ if (range) {
261
+ range.addEventListener('input', () => {
262
+ if (pct) pct.textContent = `${range.value}%`;
263
+ previewChime(Number(range.value)); // hear the level as you drag (#792)
264
+ });
265
+ range.addEventListener('change', () => { saveVolume(Number(range.value)); });
266
+ }
267
+ } catch (err) {
268
+ console.error('[settings] failed to load sounds', err);
269
+ $('#sounds-scroll').hidden = false;
270
+ $('#sound-rows').innerHTML =
271
+ '<p class="voices-empty">The sounds could not be loaded. Reload the page to try again.</p>';
272
+ }
273
+ }
274
+
275
+ // ---- section 5b: EVENT SOUNDS (per-event variant picker, task 582) ---------
276
+ //
277
+ // GET/PATCH /api/bongos/me/event-sound-prefs. The payload carries each of the 5
278
+ // build events with their available world-themed variants and the builder's
279
+ // current selection. A <select> dropdown per event + a preview button that
280
+ // plays the current variant inline via Web Audio.
281
+
282
+ function renderEventSounds(data) {
283
+ const wrap = $('#event-sound-rows');
284
+ if (!wrap) return;
285
+ const rows = Array.isArray(data?.effective) ? data.effective : [];
286
+ if (rows.length === 0) {
287
+ wrap.innerHTML = '<p class="voices-empty">No event sounds registered.</p>';
288
+ return;
289
+ }
290
+ wrap.innerHTML = rows.map((row) => {
291
+ const opts = row.variants.map((v) =>
292
+ `<option value="${escapeHtml(v.id)}" data-wav="${escapeHtml(v.wav)}"` +
293
+ `${row.selected === v.id ? ' selected' : ''}>` +
294
+ `${escapeHtml(v.label)}${v.is_default ? ' (default)' : ''}</option>`
295
+ ).join('');
296
+ return (
297
+ `<div class="event-sound-row" data-event="${escapeHtml(row.event)}">` +
298
+ '<div class="event-sound-row__text">' +
299
+ `<div class="event-sound-row__label">${escapeHtml(row.label || row.event)}</div>` +
300
+ `<div class="event-sound-row__desc">${escapeHtml(row.desc || '')}</div>` +
301
+ '</div>' +
302
+ '<div class="event-sound-row__controls">' +
303
+ `<select class="event-sound-select" data-event="${escapeHtml(row.event)}" ` +
304
+ `data-wav="${escapeHtml(row.wav)}" aria-label="Sound for ${escapeHtml(row.label)}">${opts}</select>` +
305
+ `<button type="button" class="event-sound-preview" data-wav="${escapeHtml(row.wav)}" ` +
306
+ `aria-label="Preview ${escapeHtml(row.label)} sound">Play</button>` +
307
+ '</div>' +
308
+ '</div>'
309
+ );
310
+ }).join('');
311
+ }
312
+
313
+ async function saveEventSound(eventId, variantId, sel) {
314
+ if (sel) sel.disabled = true;
315
+ try {
316
+ const res = await api.request('PATCH', `${API}/me/event-sound-prefs`, { body: { [eventId]: variantId } });
317
+ if (res.status === 401) { toAuth(); return; }
318
+ if (!res.ok) throw new Error(`event-sound-prefs PATCH → ${res.status}`);
319
+ const data = res.data;
320
+ renderEventSounds(data);
321
+ toast('Sound preference saved.');
322
+ } catch (err) {
323
+ console.error('[settings] save event sound failed', err);
324
+ toast('Could not save your sound preference. Try again.');
325
+ if (sel) sel.disabled = false;
326
+ }
327
+ }
328
+
329
+ function previewEventSound(wavName) {
330
+ try {
331
+ const audio = new Audio(`/builders/${wavName}.wav`);
332
+ audio.volume = (() => {
333
+ try {
334
+ const range = $('#sound-volume-range');
335
+ return range ? Math.max(0, Math.min(1, Number(range.value) / 100)) : 1;
336
+ } catch (_) { return 1; }
337
+ })();
338
+ const p = audio.play();
339
+ if (p && typeof p.catch === 'function') p.catch(() => {});
340
+ } catch (_) { /* best-effort preview */ }
341
+ }
342
+
343
+ async function loadEventSounds() {
344
+ try {
345
+ const res = await api.request('GET', `${API}/me/event-sound-prefs`);
346
+ if (res.status === 401) { toAuth(); return; }
347
+ if (!res.ok) throw new Error(`event-sound-prefs GET → ${res.status}`);
348
+ const data = res.data;
349
+ renderEventSounds(data);
350
+ $('#event-sounds-scroll').hidden = false;
351
+
352
+ const wrap = $('#event-sound-rows');
353
+ // Delegate select changes → save new variant.
354
+ wrap.addEventListener('change', (e) => {
355
+ const sel = e.target.closest('.event-sound-select');
356
+ if (!sel) return;
357
+ const eventId = sel.dataset.event;
358
+ saveEventSound(eventId, sel.value, sel);
359
+ });
360
+ // Update preview button's data-wav live when the select changes so the
361
+ // Play button always previews the currently selected option.
362
+ wrap.addEventListener('input', (e) => {
363
+ const sel = e.target.closest('.event-sound-select');
364
+ if (!sel) return;
365
+ const row = sel.closest('.event-sound-row');
366
+ if (!row) return;
367
+ const opt = sel.options[sel.selectedIndex];
368
+ const wav = opt && opt.dataset.wav;
369
+ if (!wav) return;
370
+ const previewBtn = row.querySelector('.event-sound-preview');
371
+ if (previewBtn) previewBtn.dataset.wav = wav;
372
+ });
373
+ // Delegate preview button clicks.
374
+ wrap.addEventListener('click', (e) => {
375
+ const btn = e.target.closest('.event-sound-preview');
376
+ if (!btn) return;
377
+ // btn.dataset.wav is the wav name from the last render (or updated on input).
378
+ // After a change event saves and re-renders, it's the server-confirmed wav.
379
+ // Before a save, it's the variant id (which is also a valid wav fallback).
380
+ previewEventSound(btn.dataset.wav);
381
+ });
382
+ } catch (err) {
383
+ console.error('[settings] failed to load event sounds', err);
384
+ $('#event-sounds-scroll').hidden = false;
385
+ $('#event-sound-rows').innerHTML =
386
+ '<p class="voices-empty">The event sounds could not be loaded. Reload the page to try again.</p>';
387
+ }
388
+ }
389
+
390
+ // ---- section 2b: WANDERING (off-task thread suppression, task 1268) -------
391
+ //
392
+ // GET/PATCH /api/bongos/me/wandering. The payload (wandering-prefs.describeForApi)
393
+ // carries the level catalog with per-rank `allowed`, the effective level, the
394
+ // rank default, and the ceiling. Single-select: clicking an allowed level
395
+ // PATCHes { wandering: n }. Levels above the rank cap render disabled — the
396
+ // server enforces the same clamp, this is just the friendly UI half. Reuses
397
+ // the .sound-row / .sound-toggle styling.
398
+ function renderWandering(data) {
399
+ const wrap = $('#wander-rows');
400
+ if (!wrap) return;
401
+ const levels = Array.isArray(data?.levels) ? data.levels : [];
402
+ const eff = typeof data?.effective_level === 'number' ? data.effective_level : null;
403
+ const def = typeof data?.default_level === 'number' ? data.default_level : null;
404
+ if (levels.length === 0) {
405
+ wrap.innerHTML = '<p class="voices-empty">No levels registered.</p>';
406
+ return;
407
+ }
408
+ wrap.innerHTML = levels.map((lv) => {
409
+ const chosen = lv.n === eff;
410
+ const tag = lv.n === def ? ' <span class="voices__default">· default for your rank</span>' : '';
411
+ let btn;
412
+ if (!lv.allowed) {
413
+ btn = `<button type="button" class="sound-toggle" disabled data-level="${lv.n}" data-on="0">Rank-locked</button>`;
414
+ } else if (chosen) {
415
+ btn = `<button type="button" class="sound-toggle" data-level="${lv.n}" data-on="1" aria-pressed="true">Chosen</button>`;
416
+ } else {
417
+ btn = `<button type="button" class="sound-toggle" data-level="${lv.n}" data-on="0" aria-pressed="false">Choose</button>`;
418
+ }
419
+ return (
420
+ `<div class="sound-row" data-level="${lv.n}">` +
421
+ '<div class="sound-row__text">' +
422
+ `<div class="sound-row__label">${escapeHtml(lv.label)}${tag}</div>` +
423
+ `<div class="sound-row__desc">${escapeHtml(lv.blurb || '')}</div>` +
424
+ '</div>' +
425
+ btn +
426
+ '</div>'
427
+ );
428
+ }).join('');
429
+ const note = $('#wandering-note');
430
+ if (note) {
431
+ note.textContent = (data && data.effective_label && data.max_key)
432
+ ? `Now: ${data.effective_label}. Your rank allows up to ${data.max_key}.`
433
+ : '';
434
+ }
435
+ }
436
+
437
+ // PATCH the wandering level. Body: { wandering: <0..3> } (see me.js +
438
+ // wandering-prefs.js). A level above the rank ceiling is rejected server-side
439
+ // (422) — but those buttons render disabled, so it shouldn't be reachable.
440
+ async function saveWandering(level, btn) {
441
+ const status = $('#wandering-status');
442
+ if (status) status.textContent = 'Saving…';
443
+ if (btn) btn.disabled = true;
444
+ try {
445
+ const res = await api.request('PATCH', `${API}/me/wandering`, { body: { wandering: level } });
446
+ if (res.status === 401) { toAuth(); return; }
447
+ if (!res.ok) throw new Error(`wandering PATCH → ${res.status}`);
448
+ const data = res.data;
449
+ renderWandering(data);
450
+ const msg = `Wandering → ${data.effective_label || level}.`;
451
+ if (status) status.textContent = msg;
452
+ toast(msg);
453
+ } catch (err) {
454
+ console.error('[settings] save wandering failed', err);
455
+ if (status) status.textContent = 'Could not save. Try again.';
456
+ toast('Could not save your wandering level. Try again.');
457
+ if (btn) btn.disabled = false;
458
+ }
459
+ }
460
+
461
+ async function loadWandering() {
462
+ try {
463
+ const res = await api.request('GET', `${API}/me/wandering`);
464
+ if (res.status === 401) { toAuth(); return; }
465
+ if (!res.ok) throw new Error(`wandering GET → ${res.status}`);
466
+ const data = res.data;
467
+ renderWandering(data);
468
+ $('#wandering-scroll').hidden = false;
469
+ // Delegate clicks so post-render buttons stay wired (single-select).
470
+ const wrap = $('#wander-rows');
471
+ wrap.addEventListener('click', (e) => {
472
+ const b = e.target.closest('.sound-toggle');
473
+ if (!b || b.disabled || b.dataset.on === '1') return; // ignore locked + already-chosen
474
+ const level = Number(b.dataset.level);
475
+ if (Number.isInteger(level)) saveWandering(level, b);
476
+ });
477
+ } catch (err) {
478
+ console.error('[settings] failed to load wandering', err);
479
+ $('#wandering-scroll').hidden = false;
480
+ $('#wander-rows').innerHTML =
481
+ '<p class="voices-empty">Your wandering level could not be loaded. Reload the page to try again.</p>';
482
+ }
483
+ }
484
+
485
+ // ---- section 2c: RENDERING (rich in-feed cards on/off, task 1279) ---------
486
+ //
487
+ // GET/PATCH /api/bongos/me/render. A single boolean (widgets_enabled). The session
488
+ // cards render as an HTML widget Claude must relay verbatim every time, which
489
+ // costs tokens; off uses the cheap text form. No rank gate (cost/taste). Reuses
490
+ // the .sound-row / .sound-toggle styling — one row, one on/off toggle.
491
+ function renderRender(data) {
492
+ const wrap = $('#render-rows');
493
+ if (!wrap) return;
494
+ const on = data ? data.widgets_enabled !== false : true;
495
+ const isDefault = !data || data.override == null;
496
+ wrap.innerHTML =
497
+ '<div class="sound-row" data-render="widgets">' +
498
+ '<div class="sound-row__text">' +
499
+ '<div class="sound-row__label">Rich in-feed cards' +
500
+ (isDefault ? ' <span class="voices__default">· default (on)</span>' : '') +
501
+ '</div>' +
502
+ '<div class="sound-row__desc">Render session cards as widgets (prettier, costs tokens) instead of plain text (cheaper, identical info).</div>' +
503
+ '</div>' +
504
+ `<button type="button" class="sound-toggle" data-on="${on ? '1' : '0'}" aria-pressed="${on ? 'true' : 'false'}">${on ? 'On' : 'Off'}</button>` +
505
+ '</div>';
506
+ }
507
+
508
+ // PATCH the render knob. Body: { widgets: <bool> } (see me.js + render-prefs.js).
509
+ async function saveRender(enabled, btn) {
510
+ const status = $('#render-status');
511
+ if (status) status.textContent = 'Saving…';
512
+ if (btn) btn.disabled = true;
513
+ try {
514
+ const res = await api.request('PATCH', `${API}/me/render`, { body: { widgets: enabled } });
515
+ if (res.status === 401) { toAuth(); return; }
516
+ if (!res.ok) throw new Error(`render PATCH → ${res.status}`);
517
+ const data = res.data;
518
+ renderRender(data);
519
+ const msg = enabled ? 'Rich in-feed cards — on.' : 'Rich in-feed cards — off (plain text).';
520
+ if (status) status.textContent = msg;
521
+ toast(msg);
522
+ } catch (err) {
523
+ console.error('[settings] save render failed', err);
524
+ if (status) status.textContent = 'Could not save. Try again.';
525
+ toast('Could not save your rendering preference. Try again.');
526
+ if (btn) btn.disabled = false;
527
+ }
528
+ }
529
+
530
+ async function loadRender() {
531
+ try {
532
+ const res = await api.request('GET', `${API}/me/render`);
533
+ if (res.status === 401) { toAuth(); return; }
534
+ if (!res.ok) throw new Error(`render GET → ${res.status}`);
535
+ const data = res.data;
536
+ renderRender(data);
537
+ $('#render-scroll').hidden = false;
538
+ // Delegate clicks so the post-render button stays wired (single toggle).
539
+ const wrap = $('#render-rows');
540
+ wrap.addEventListener('click', (e) => {
541
+ const b = e.target.closest('.sound-toggle');
542
+ if (!b || b.disabled) return;
543
+ saveRender(b.dataset.on !== '1', b); // flip current state
544
+ });
545
+ } catch (err) {
546
+ console.error('[settings] failed to load render', err);
547
+ $('#render-scroll').hidden = false;
548
+ $('#render-rows').innerHTML =
549
+ '<p class="voices-empty">Your rendering preference could not be loaded. Reload the page to try again.</p>';
550
+ }
551
+ }
552
+
553
+ // ---- section 2: CRAFT (discipline preferences) ---------------------------
554
+
555
+ // Valid disciplines are limited (see me.js + db.updateBuilderDisciplines):
556
+ // engineer | ideator (core) + artist when the art-pipeline module is on
557
+ // (R59 / task 1198). moduleOn defaults true if the flag is missing, so OTB
558
+ // (art on) keeps all three; a vanilla instance offers only the two it accepts.
559
+ const DISCIPLINES = ['engineer', 'artist', 'ideator']
560
+ .filter((d) => (d === 'artist' ? window.OTB.moduleOn('art-pipeline') : true));
561
+
562
+ // Current selection mutated in place as the builder toggles chips; saved on
563
+ // the "Save" button (the hall's profile card behaviour) via PATCH.
564
+ const craft = { selected: new Set() };
565
+
566
+ function renderCraft() {
567
+ const wrap = $('#craft-chips');
568
+ if (!wrap) return;
569
+ wrap.innerHTML = DISCIPLINES.map((d) => {
570
+ const on = craft.selected.has(d);
571
+ return (
572
+ `<label class="craft-chip" data-disc="${escapeHtml(d)}" data-on="${on ? '1' : '0'}">` +
573
+ `<input type="checkbox" class="craft-pref" value="${escapeHtml(d)}"${on ? ' checked' : ''}>` +
574
+ `${escapeHtml(d)}</label>`
575
+ );
576
+ }).join('');
577
+ }
578
+
579
+ async function saveCraft() {
580
+ const selected = DISCIPLINES.filter((d) => craft.selected.has(d));
581
+ const btn = $('#craft-save');
582
+ const status = $('#craft-status');
583
+ if (btn) { btn.disabled = true; btn.textContent = 'Saving…'; }
584
+ if (status) status.textContent = '';
585
+ try {
586
+ const res = await api.request('PATCH', `${API}/me/disciplines`, { body: { preferred_disciplines: selected } });
587
+ if (res.status === 401) { toAuth(); return; }
588
+ if (!res.ok) throw new Error(`disciplines PATCH → ${res.status}`);
589
+ const data = res.data;
590
+ // Re-seed local state from the server's echo so it stays authoritative.
591
+ const prefs = Array.isArray(data?.builder?.preferred_disciplines)
592
+ ? data.builder.preferred_disciplines
593
+ : selected;
594
+ craft.selected = new Set(prefs.filter((d) => DISCIPLINES.includes(d)));
595
+ renderCraft();
596
+ const msg = selected.length ? `Preferred work: ${selected.join(', ')}.` : 'Preference cleared.';
597
+ if (status) status.textContent = msg;
598
+ toast(msg);
599
+ } catch (err) {
600
+ console.error('[settings] save craft failed', err);
601
+ if (status) status.textContent = 'Could not save. Try again.';
602
+ toast('Could not save your preference. Try again.');
603
+ } finally {
604
+ if (btn) { btn.disabled = false; btn.textContent = 'Save'; }
605
+ }
606
+ }
607
+
608
+ function wireCraft() {
609
+ const wrap = $('#craft-chips');
610
+ if (wrap) {
611
+ wrap.addEventListener('change', (e) => {
612
+ const box = e.target.closest('.craft-pref');
613
+ if (!box) return;
614
+ if (box.checked) craft.selected.add(box.value);
615
+ else craft.selected.delete(box.value);
616
+ const chip = box.closest('.craft-chip');
617
+ if (chip) chip.dataset.on = box.checked ? '1' : '0';
618
+ });
619
+ }
620
+ const btn = $('#craft-save');
621
+ if (btn) btn.addEventListener('click', saveCraft);
622
+ }
623
+
624
+ // ---- section 2a: DISPLAY NAME (the label beside your work, task 1669) -----
625
+ //
626
+ // GET/PATCH /api/bongos/me/display-name. A single editable field, pre-filled from
627
+ // the /me payload (so the page already has it without a second fetch). Save
628
+ // PATCHes { display_name }; the server trims + validates (empty, control chars,
629
+ // length) and echoes the saved value back, which we re-seed into the field.
630
+ // Identity stays github_login/id — this only renames the rendered label.
631
+
632
+ // The longest name we let a builder type — mirrors display-name.js MAX_LENGTH
633
+ // (the input's maxlength attr already caps it; the server is the real gate).
634
+ const DISPLAY_NAME_MAX = 60;
635
+
636
+ function renderDisplayName(value) {
637
+ const input = $('#display-name-input');
638
+ if (input) input.value = value || '';
639
+ }
640
+
641
+ async function saveDisplayName() {
642
+ const input = $('#display-name-input');
643
+ const btn = $('#display-name-save');
644
+ const status = $('#display-name-status');
645
+ if (!input) return;
646
+ const next = input.value.trim();
647
+ if (status) status.textContent = '';
648
+ // Friendly client-side pre-check; the server is the authority.
649
+ if (!next) {
650
+ if (status) status.textContent = 'Name cannot be blank.';
651
+ toast('Your name cannot be blank.');
652
+ return;
653
+ }
654
+ if (Array.from(next).length > DISPLAY_NAME_MAX) {
655
+ if (status) status.textContent = `Name must be ${DISPLAY_NAME_MAX} characters or fewer.`;
656
+ toast(`Your name must be ${DISPLAY_NAME_MAX} characters or fewer.`);
657
+ return;
658
+ }
659
+ if (btn) { btn.disabled = true; btn.textContent = 'Saving…'; }
660
+ try {
661
+ const res = await api.request('PATCH', `${API}/me/display-name`, { body: { display_name: next } });
662
+ if (res.status === 401) { toAuth(); return; }
663
+ const data = (res.data || {});
664
+ if (res.status === 422) {
665
+ const first = Array.isArray(data.errors) && data.errors[0];
666
+ const msg = (first && first.message) || 'That name is not allowed.';
667
+ if (status) status.textContent = msg;
668
+ toast(msg);
669
+ return;
670
+ }
671
+ if (!res.ok) throw new Error(`display-name PATCH → ${res.status}`);
672
+ renderDisplayName(data.display_name);
673
+ // Reflect the new name in the page sub-header too, for instant feedback.
674
+ const sub = $('#settings-sub');
675
+ if (sub && data.display_name) sub.textContent = `Your preferences, ${data.display_name}.`;
676
+ const msg = `Saved — you're now “${data.display_name}”.`;
677
+ if (status) status.textContent = msg;
678
+ toast(msg);
679
+ } catch (err) {
680
+ console.error('[settings] save display name failed', err);
681
+ if (status) status.textContent = 'Could not save. Try again.';
682
+ toast('Could not save your name. Try again.');
683
+ } finally {
684
+ if (btn) { btn.disabled = false; btn.textContent = 'Save'; }
685
+ }
686
+ }
687
+
688
+ // Seed the field from the /me payload (the page already has the builder row),
689
+ // wire Save + Enter, then reveal the section. No separate GET needed for the
690
+ // initial value; the dedicated GET /me/display-name endpoint stays available
691
+ // for callers that want a fresh read.
692
+ function setupDisplayName(builder) {
693
+ renderDisplayName(builder && builder.display_name);
694
+ const btn = $('#display-name-save');
695
+ if (btn) btn.addEventListener('click', saveDisplayName);
696
+ const input = $('#display-name-input');
697
+ if (input) input.addEventListener('keydown', (e) => {
698
+ if (e.key === 'Enter') { e.preventDefault(); saveDisplayName(); }
699
+ });
700
+ $('#display-name-scroll').hidden = false;
701
+ }
702
+
703
+ // ---- section 1b: DEV BOX (full management, #807 ADR 0040) ----------------
704
+ //
705
+ // Mounts the shared OTBBoxPanel (box-panel.js) with showSsh:true so a builder
706
+ // sees BOTH the online terminal and the SSH IP + command, and can request,
707
+ // open, or close their box. The module owns its own polling + state painting;
708
+ // we just hand it the mount element. Hidden if the module failed to load.
709
+ function setupBox() {
710
+ // R60 (task 1199): dev boxes are a feature module. On a vanilla instance the
711
+ // module is off — there is no box to manage, so the whole section stays
712
+ // hidden (its #box-scroll defaults to hidden in the markup).
713
+ if (!window.OTB.moduleOn('dev-box')) return;
714
+ const mount = $('#box-mount');
715
+ if (!mount || !window.OTBBoxPanel) return;
716
+ const panel = window.OTBBoxPanel.create({ api: API, showSsh: true, toast });
717
+ $('#box-scroll').hidden = false;
718
+ panel.mount(mount);
719
+ }
720
+
721
+ // ---- section 3: CONNECTIONS (Discord link/unlink, F7 ADR 0033) -----------
722
+ //
723
+ // The /me payload carries `discord: { configured, linked }`. The section is
724
+ // hidden entirely when the server has no Discord OAuth creds (configured=false)
725
+ // — there's nothing to connect to. Linking is a plain <a> into the server's
726
+ // OAuth start; the callback now returns to /settings (see routes/auth.js), and
727
+ // handleDiscordReturn() below surfaces the one-shot confirmation.
728
+
729
+ let discordState = null;
730
+
731
+ function renderDiscord() {
732
+ const card = $('#discord-card');
733
+ if (!card) return;
734
+ const d = discordState;
735
+ if (!d || !d.configured) { $('#connections-scroll').hidden = true; return; }
736
+ $('#connections-scroll').hidden = false;
737
+ if (d.linked) {
738
+ card.innerHTML =
739
+ '<span class="settings-card__ok">Discord connected ✓</span>' +
740
+ `<span class="settings-card__hint">You're in the ${brandWorld()} server with your rank role.</span>` +
741
+ '<button type="button" class="settings-btn" id="discord-unlink-btn">Unlink</button>';
742
+ const btn = $('#discord-unlink-btn');
743
+ if (btn) btn.addEventListener('click', unlinkDiscord);
744
+ } else {
745
+ card.innerHTML =
746
+ `<a class="settings-btn settings-btn--discord" href="${API}/auth/discord/start">Connect Discord &amp; join the server</a>` +
747
+ '<span class="settings-card__hint">Link first — your rank role is set the moment you join.</span>';
748
+ }
749
+ }
750
+
751
+ async function unlinkDiscord() {
752
+ const btn = $('#discord-unlink-btn');
753
+ if (btn) { btn.disabled = true; btn.textContent = 'Unlinking…'; }
754
+ try {
755
+ const res = await api.request('POST', `${API}/auth/discord/unlink`);
756
+ if (res.status === 401) { toAuth(); return; }
757
+ if (!res.ok) throw new Error(`discord unlink → ${res.status}`);
758
+ toast('Discord unlinked.');
759
+ if (discordState) discordState.linked = false;
760
+ renderDiscord();
761
+ } catch (err) {
762
+ console.error('[settings] discord unlink failed', err);
763
+ toast(`Unlink failed: ${err.message}`);
764
+ if (btn) { btn.disabled = false; btn.textContent = 'Unlink'; }
765
+ }
766
+ }
767
+
768
+ function setupConnections(discord) {
769
+ discordState = discord || null;
770
+ renderDiscord();
771
+ }
772
+
773
+ // One-shot toast when the Discord OAuth callback returns here with
774
+ // ?discord=joined|linked, then strip the param so a refresh doesn't re-toast.
775
+ function handleDiscordReturn() {
776
+ try {
777
+ const dq = new URLSearchParams(location.search).get('discord');
778
+ if (dq === 'joined' || dq === 'linked') {
779
+ toast(dq === 'joined'
780
+ ? "Discord connected — you're in the server with your rank role."
781
+ : "Discord connected. (You'll get your role when the server bot is live.)");
782
+ const url = new URL(location.href);
783
+ url.searchParams.delete('discord');
784
+ history.replaceState(null, '', url.pathname + url.search + url.hash);
785
+ }
786
+ } catch (_) { /* non-fatal */ }
787
+ }
788
+
789
+ // ---- section 4: CLI ACCESS (re-issue a CLI bearer, V3.R91 #401) ----------
790
+
791
+ async function issueCliToken() {
792
+ const btn = $('#cli-reissue-btn');
793
+ const result = $('#cli-reissue-result');
794
+ if (!btn) return;
795
+ btn.disabled = true;
796
+ const original = btn.textContent;
797
+ btn.textContent = 'Minting…';
798
+ try {
799
+ const res = await api.request('POST', `${API}/auth/cli-token/issue`);
800
+ if (res.status === 401) { toAuth(); return; }
801
+ const body = (res.data || {});
802
+ if (!res.ok) throw new Error(body.message || `HTTP ${res.status}`);
803
+ const expires = body.expires_at ? new Date(body.expires_at).toLocaleString() : 'unknown';
804
+ result.innerHTML =
805
+ '<p>Fresh CLI token issued. Paste it in your terminal:</p>' +
806
+ `<pre>${escapeHtml(body.paste_command || '')}</pre>` +
807
+ '<p><button type="button" class="settings-btn" id="cli-reissue-copy">Copy</button>' +
808
+ `<span class="settings-card__hint" style="margin-left:0.6rem;">Expires ${escapeHtml(expires)} · refreshes on use.</span></p>`;
809
+ result.hidden = false;
810
+ const copyBtn = $('#cli-reissue-copy');
811
+ if (copyBtn) copyBtn.addEventListener('click', async () => {
812
+ try { await navigator.clipboard.writeText(body.paste_command); toast('Paste command copied.'); }
813
+ catch (err) { toast(`Could not copy automatically — select and copy manually. (${err.message})`); }
814
+ });
815
+ } catch (err) {
816
+ console.error('[settings] cli token reissue failed', err);
817
+ toast(`Re-issue failed: ${err.message}`);
818
+ } finally {
819
+ btn.disabled = false;
820
+ btn.textContent = original;
821
+ }
822
+ }
823
+
824
+ function setupCliAccess() {
825
+ $('#cli-scroll').hidden = false;
826
+ const btn = $('#cli-reissue-btn');
827
+ if (btn) btn.addEventListener('click', issueCliToken);
828
+ }
829
+
830
+ // ---- section 2d: IMAGE KEY (per-builder Gemini key, task 1013 / ADR 0073) --
831
+ //
832
+ // GET/PUT/DELETE /api/bongos/me/art-key/own. Write-only: set / replace / remove,
833
+ // masked (last-4) read. The banner + tone come from the /me `needs` art_key
834
+ // item — covered for newcomers, action-needed once coverage ends, satisfied
835
+ // once a key is on file. The full key is never shown again after saving.
836
+ const AISTUDIO = 'https://aistudio.google.com/app/apikey';
837
+ const getOneLink = `<a href="${AISTUDIO}" target="_blank" rel="noopener">Get a free key ↗</a>`;
838
+
839
+ // Viewer's live rank (set in loadArtKey). Decides whether the "storage not yet
840
+ // configured" state shows an Archon "set it up" button (task 1398) or the
841
+ // wait-for-an-Archon message. The button POST is server-gated regardless — this
842
+ // only controls what we render. Divine tiers sit above archon and also pass.
843
+ let viewerRank = null;
844
+ const ARCHON_OR_ABOVE = new Set(['archon', 'olympian', 'hermes', 'oracle', 'demigod']);
845
+ function isArchonRank(r) { return ARCHON_OR_ABOVE.has(r); }
846
+
847
+ function artKeyNeedItem(me) {
848
+ const items = me && me.needs && Array.isArray(me.needs.items) ? me.needs.items : [];
849
+ return items.find((n) => n && n.id === 'art_key') || null;
850
+ }
851
+
852
+ // The "we cover it" / "action needed" banner above the card. Satisfied (or no
853
+ // need data) → hidden; the card itself shows the on-file state.
854
+ function renderArtKeyBanner(need) {
855
+ const el = $('#artkey-banner');
856
+ if (!el) return;
857
+ if (!need || need.state === 'satisfied') { el.hidden = true; return; }
858
+ el.hidden = false;
859
+ el.className = `artkey-banner artkey-banner--${need.state === 'action_needed' ? 'action' : 'covered'}`;
860
+ el.innerHTML = `<strong>${escapeHtml(need.title)}</strong> — ${escapeHtml(need.message)}`;
861
+ }
862
+
863
+ function artKeyEntryHtml() {
864
+ return (
865
+ '<div class="artkey-entry-row">' +
866
+ '<input type="password" class="artkey-input" id="artkey-input" autocomplete="off" ' +
867
+ 'spellcheck="false" placeholder="Paste your Google AI Studio key">' +
868
+ '<button type="button" class="settings-btn" id="artkey-save">Save key</button>' +
869
+ '</div>' +
870
+ `<p class="settings-card__hint">${getOneLink} · Stored encrypted; never shown again after saving.</p>`
871
+ );
872
+ }
873
+
874
+ function renderArtKeyCard(meta) {
875
+ const card = $('#artkey-card');
876
+ if (!card) return;
877
+ if (meta && meta.storage_configured === false) {
878
+ // Storage isn't provisioned yet. An Archon can turn it on right here — the
879
+ // server generates + stores the master key, no terminal (task 1398). Everyone
880
+ // else waits for an Archon to do it.
881
+ if (isArchonRank(viewerRank)) {
882
+ card.innerHTML =
883
+ '<p class="settings-card__hint">Key storage isn\'t set up yet. As an Archon you can turn it on now — the server generates and safely stores the master key (no terminal needed).</p>' +
884
+ '<div class="artkey-actions"><button type="button" class="settings-btn" id="artkey-setup-storage">Set up key storage</button></div>' +
885
+ '<p class="settings-card__hint" id="artkey-status"></p>';
886
+ } else {
887
+ card.innerHTML = `<p class="settings-card__hint">Key storage is being set up by an Archon — check back shortly. ${getOneLink}</p>`;
888
+ }
889
+ return;
890
+ }
891
+ if (meta && meta.set) {
892
+ const added = meta.set_at ? new Date(meta.set_at).toLocaleDateString() : '';
893
+ card.innerHTML =
894
+ `<p class="settings-card__ok">Key on file ending …${escapeHtml(meta.last4 || '????')}${added ? ` · added ${escapeHtml(added)}` : ''}.</p>` +
895
+ '<div class="artkey-actions">' +
896
+ '<button type="button" class="settings-btn" id="artkey-replace">Replace</button>' +
897
+ '<button type="button" class="settings-btn settings-btn--quiet" id="artkey-remove">Remove</button>' +
898
+ '</div>' +
899
+ '<div id="artkey-entry" hidden></div>';
900
+ return;
901
+ }
902
+ card.innerHTML = artKeyEntryHtml();
903
+ }
904
+
905
+ // (Re)wire the card's controls after every render — re-rendering replaces the
906
+ // nodes, so handlers are attached fresh each time.
907
+ function wireArtKeyCard() {
908
+ const setup = $('#artkey-setup-storage');
909
+ if (setup) setup.onclick = () => setupKeyStorage(setup);
910
+ const save = $('#artkey-save');
911
+ if (save) save.onclick = () => { const i = $('#artkey-input'); saveArtKey(i ? i.value : '', save); };
912
+ const input = $('#artkey-input');
913
+ if (input) input.addEventListener('keydown', (e) => {
914
+ if (e.key === 'Enter') { e.preventDefault(); saveArtKey(input.value, $('#artkey-save')); }
915
+ });
916
+ const replace = $('#artkey-replace');
917
+ if (replace) replace.onclick = () => {
918
+ const entry = $('#artkey-entry');
919
+ if (!entry) return;
920
+ entry.innerHTML = artKeyEntryHtml();
921
+ entry.hidden = false;
922
+ wireArtKeyCard();
923
+ const i = $('#artkey-input'); if (i) i.focus();
924
+ };
925
+ const remove = $('#artkey-remove');
926
+ if (remove) remove.onclick = removeArtKey;
927
+ }
928
+
929
+ async function saveArtKey(value, btn) {
930
+ const status = $('#artkey-status');
931
+ const key = (value || '').trim();
932
+ if (!key) { toast('Paste your key first.'); return; }
933
+ if (status) status.textContent = 'Checking your key…';
934
+ if (btn) btn.disabled = true;
935
+ try {
936
+ const res = await api.request('PUT', `${API}/me/art-key/own`, { body: { key } });
937
+ if (res.status === 401) { toAuth(); return; }
938
+ const body = (res.data || {});
939
+ if (!res.ok) {
940
+ const msg = body.message || `Could not save (HTTP ${res.status}).`;
941
+ if (status) status.textContent = msg;
942
+ toast(msg);
943
+ if (btn) btn.disabled = false;
944
+ return;
945
+ }
946
+ renderArtKeyCard({ set: true, last4: body.last4, set_at: body.set_at, storage_configured: true });
947
+ wireArtKeyCard();
948
+ renderArtKeyBanner({ state: 'satisfied' }); // the need is now satisfied
949
+ const msg = body.validated === false
950
+ ? 'Key saved — but we couldn\'t reach Google to verify it just now.'
951
+ : 'Key saved and verified.';
952
+ if (status) status.textContent = msg;
953
+ toast(msg);
954
+ } catch (err) {
955
+ console.error('[settings] save art key failed', err);
956
+ if (status) status.textContent = 'Could not save. Try again.';
957
+ toast('Could not save your key. Try again.');
958
+ if (btn) btn.disabled = false;
959
+ }
960
+ }
961
+
962
+ // Archon-only: turn on key storage (provision the server master key) without a
963
+ // terminal — task 1398. On success the card re-renders to the key-entry state so
964
+ // the Archon can add their own key immediately. The server enforces the rank.
965
+ async function setupKeyStorage(btn) {
966
+ const status = $('#artkey-status');
967
+ if (btn) btn.disabled = true;
968
+ if (status) status.textContent = 'Setting up key storage…';
969
+ try {
970
+ const res = await api.request('POST', `${API}/me/art-key/storage-init`);
971
+ if (res.status === 401) { toAuth(); return; }
972
+ const body = (res.data || {});
973
+ if (!res.ok) {
974
+ const msg = body.message || `Could not set up storage (HTTP ${res.status}).`;
975
+ if (status) status.textContent = msg;
976
+ toast(msg);
977
+ if (btn) btn.disabled = false;
978
+ return;
979
+ }
980
+ // Storage is on now — drop straight into the key-entry state.
981
+ renderArtKeyCard({ set: false, storage_configured: true });
982
+ wireArtKeyCard();
983
+ const msg = body.created
984
+ ? 'Key storage is set up — paste your key below.'
985
+ : 'Key storage was already set up — paste your key below.';
986
+ const s2 = $('#artkey-status');
987
+ if (s2) s2.textContent = msg;
988
+ toast(msg);
989
+ const i = $('#artkey-input'); if (i) i.focus();
990
+ } catch (err) {
991
+ console.error('[settings] set up key storage failed', err);
992
+ if (status) status.textContent = 'Could not set up storage. Try again.';
993
+ toast('Could not set up key storage. Try again.');
994
+ if (btn) btn.disabled = false;
995
+ }
996
+ }
997
+
998
+ async function removeArtKey() {
999
+ const status = $('#artkey-status');
1000
+ if (!window.confirm('Remove your image-generation key? Art generation will stop until you add one again.')) return;
1001
+ if (status) status.textContent = 'Removing…';
1002
+ try {
1003
+ const res = await api.request('DELETE', `${API}/me/art-key/own`);
1004
+ if (res.status === 401) { toAuth(); return; }
1005
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1006
+ renderArtKeyCard({ set: false, storage_configured: true });
1007
+ wireArtKeyCard();
1008
+ if (status) status.textContent = 'Key removed.';
1009
+ toast('Image key removed.');
1010
+ } catch (err) {
1011
+ console.error('[settings] remove art key failed', err);
1012
+ if (status) status.textContent = 'Could not remove. Try again.';
1013
+ toast('Could not remove your key. Try again.');
1014
+ }
1015
+ }
1016
+
1017
+ async function loadArtKey(me) {
1018
+ try {
1019
+ // Remember the viewer's rank so the not-yet-configured state can offer the
1020
+ // Archon "set it up" button (task 1398).
1021
+ viewerRank = (me && me.builder && me.builder.rank) || (me && me.rank) || null;
1022
+ renderArtKeyBanner(artKeyNeedItem(me));
1023
+ const res = await api.request('GET', `${API}/me/art-key/own`);
1024
+ if (res.status === 401) { toAuth(); return; }
1025
+ if (!res.ok) throw new Error(`art-key GET → ${res.status}`);
1026
+ renderArtKeyCard(res.data);
1027
+ wireArtKeyCard();
1028
+ $('#art-key').hidden = false;
1029
+ // Landed here from the hall's "Add your key" action / the CLI nudge link.
1030
+ if (location.hash === '#art-key') $('#art-key').scrollIntoView({ behavior: 'smooth' });
1031
+ } catch (err) {
1032
+ console.error('[settings] failed to load art key', err);
1033
+ $('#art-key').hidden = false;
1034
+ $('#artkey-card').innerHTML =
1035
+ '<p class="voices-empty">Your image-generation key settings could not be loaded. Reload the page to try again.</p>';
1036
+ }
1037
+ }
1038
+
1039
+ // ---- module-contributed panels (BV1.R42, ADR 0083) -----------------------
1040
+ //
1041
+ // Render every Settings panel a module registered via
1042
+ // window.OTB.contributeSettingsPanel (its browser-side client script runs
1043
+ // before this one). Each panel gets its own <section class="scroll"> with a
1044
+ // heading + a mount div, matching the hardcoded sections' shape. Empty until a
1045
+ // module registers (none today) → no behavior change. One failing panel is
1046
+ // isolated so it never takes the page down.
1047
+ function mountContributedPanels() {
1048
+ const host = $('#contributed-panels');
1049
+ if (!host || typeof window.OTB.settingsPanels !== 'function') return;
1050
+ for (const panel of window.OTB.settingsPanels()) {
1051
+ try {
1052
+ const section = document.createElement('section');
1053
+ section.className = 'scroll';
1054
+ section.id = `panel-${panel.id}`;
1055
+ if (panel.title) {
1056
+ const h = document.createElement('h2');
1057
+ h.className = 'scroll__h';
1058
+ h.textContent = panel.title;
1059
+ section.appendChild(h);
1060
+ }
1061
+ const mount = document.createElement('div');
1062
+ section.appendChild(mount);
1063
+ host.appendChild(section);
1064
+ panel.mount(mount);
1065
+ } catch (err) {
1066
+ console.error(`[settings] contributed panel "${panel && panel.id}" failed`, err);
1067
+ }
1068
+ }
1069
+ }
1070
+
1071
+ // ---- section rail (grouped sub-nav, task 1723) -----------------------------
1072
+ //
1073
+ // The sections sit in six groups behind a left rail (#settings-rail). CSS on
1074
+ // the page shows only the active group (.settings-groups[data-active]); a
1075
+ // section's own [hidden] attribute still wins, so the per-section reveal
1076
+ // logic above is unchanged. Deep links like #art-key activate their group
1077
+ // first, so the existing scrollIntoView lands on a visible section.
1078
+
1079
+ const RAIL_GROUPS = ['account', 'devbox', 'access', 'preferences', 'sound', 'extensions'];
1080
+
1081
+ function activateGroup(group, opts) {
1082
+ if (!RAIL_GROUPS.includes(group)) return;
1083
+ const groups = document.getElementById('settings-groups');
1084
+ if (!groups) return;
1085
+ groups.dataset.active = group;
1086
+ document.querySelectorAll('#settings-rail .rail-link').forEach((a) => {
1087
+ a.classList.toggle('rail-link--active', a.dataset.group === group);
1088
+ });
1089
+ if (!opts || opts.scroll !== false) window.scrollTo({ top: 0 });
1090
+ }
1091
+
1092
+ // Map a #hash to its group: either a group name itself (#access), or the id
1093
+ // of an element inside a grouped section (#art-key → access).
1094
+ function groupForHash(hash) {
1095
+ const id = String(hash || '').replace(/^#/, '');
1096
+ if (!id) return null;
1097
+ if (RAIL_GROUPS.includes(id)) return id;
1098
+ const el = document.getElementById(id);
1099
+ const grouped = el && el.closest ? el.closest('[data-group]') : null;
1100
+ return grouped ? grouped.getAttribute('data-group') : null;
1101
+ }
1102
+
1103
+ function wireRail() {
1104
+ const rail = document.getElementById('settings-rail');
1105
+ if (!rail) return;
1106
+ rail.addEventListener('click', (e) => {
1107
+ const a = e.target.closest ? e.target.closest('.rail-link') : null;
1108
+ if (!a || !a.dataset.group) return;
1109
+ e.preventDefault();
1110
+ activateGroup(a.dataset.group);
1111
+ });
1112
+ const fromHash = groupForHash(window.location.hash);
1113
+ if (fromHash) activateGroup(fromHash, { scroll: false });
1114
+ }
1115
+
1116
+ // Hide the rail + groups entirely while the not-signed-in gate is showing —
1117
+ // an empty content column next to a floating rail reads as broken.
1118
+ function hideLayout(hide) {
1119
+ const layout = document.getElementById('settings-layout');
1120
+ if (layout) layout.hidden = !!hide;
1121
+ }
1122
+
1123
+ // ---- bootstrap -----------------------------------------------------------
1124
+
1125
+ async function load() {
1126
+ // The OAuth callback (Discord) lands here with ?discord=…; surface it once.
1127
+ handleDiscordReturn();
1128
+ // Who is viewing? /me first — on 401 redirect into the web sign-in flow.
1129
+ let builder, me;
1130
+ try {
1131
+ const res = await api.request('GET', `${API}/me`);
1132
+ if (res.status === 401) { toAuth(); return; }
1133
+ if (!res.ok) throw new Error(`me → ${res.status}`);
1134
+ me = res.data;
1135
+ builder = (me && me.builder) ? me.builder : me;
1136
+ } catch (err) {
1137
+ console.error('[settings] failed to read viewer', err);
1138
+ $('#settings-sub').textContent = 'Could not load your account. Reload the page to try again.';
1139
+ $('#gate-scroll').hidden = false;
1140
+ hideLayout(true);
1141
+ toast('Could not load your settings.');
1142
+ return;
1143
+ }
1144
+
1145
+ if (!builder) {
1146
+ $('#settings-sub').textContent = 'No builder account found for this sign-in.';
1147
+ $('#gate-scroll').hidden = false;
1148
+ hideLayout(true);
1149
+ return;
1150
+ }
1151
+
1152
+ $('#gate-scroll').hidden = true;
1153
+ hideLayout(false);
1154
+ const who = builder.display_name || builder.github_login || 'builder';
1155
+ $('#settings-sub').textContent = `Your preferences, ${who}.`;
1156
+
1157
+ // Seed craft state from the /me payload, then render + reveal that section.
1158
+ const prefs = Array.isArray(builder.preferred_disciplines) ? builder.preferred_disciplines : [];
1159
+ craft.selected = new Set(prefs.filter((d) => DISCIPLINES.includes(d)));
1160
+ renderCraft();
1161
+ wireCraft();
1162
+ $('#craft-scroll').hidden = false;
1163
+
1164
+ // DISPLAY NAME — the label beside your work (task 1669); seeded from /me.
1165
+ setupDisplayName(builder);
1166
+
1167
+ // DEV BOX — full box management (online terminal + SSH + close), #807.
1168
+ setupBox();
1169
+ // DEV BOX APP — static download links (#904); revealed once we know the
1170
+ // viewer is a real builder (matches the page's hidden-until-loaded pattern).
1171
+ $('#boxapp-scroll').hidden = false;
1172
+ loadDevboxLatestVersion(); // #924: best-effort "latest version: X" courtesy line
1173
+ // CONNECTIONS — Discord (hidden unless the server has Discord configured).
1174
+ setupConnections(me && me.discord ? me.discord : null);
1175
+ // CLI ACCESS — re-issue a CLI bearer from this browser session.
1176
+ setupCliAccess();
1177
+ // MODULE PANELS — render any Settings panels modules contributed via the
1178
+ // seam (BV1.R42). None today; ready for dev-box/discord/art once they leave core.
1179
+ mountContributedPanels();
1180
+ // Reveal the Extensions rail entry only when a module actually contributed.
1181
+ const contributed = $('#contributed-panels');
1182
+ const railExt = document.getElementById('rail-extensions');
1183
+ if (railExt) railExt.hidden = !(contributed && contributed.children.length > 0);
1184
+
1185
+ // Voices loads independently — its own endpoint + its own try/catch.
1186
+ await loadVoices();
1187
+ // Sounds loads independently too (per-sound on/off, #786).
1188
+ await loadSounds();
1189
+ // Event sounds loads independently (per-event variant picker, task 582).
1190
+ await loadEventSounds();
1191
+ // Wandering loads independently too (off-task thread suppression, task 1268).
1192
+ await loadWandering();
1193
+ // Rendering loads independently too (rich in-feed cards on/off, task 1279).
1194
+ await loadRender();
1195
+ // Image key (per-builder Gemini key, task 1013) — own try/catch; `me` carries
1196
+ // the `needs` art_key item that drives the covered/action-needed banner.
1197
+ await loadArtKey(me);
1198
+ }
1199
+
1200
+ // #924: show the current published Dev Box app version next to the download
1201
+ // buttons, read from the same public endpoint the app polls (/downloads/devbox/
1202
+ // version → config/devbox-app.json). Best-effort: a fetch failure just leaves the
1203
+ // courtesy line hidden — it must never break the settings page.
1204
+ async function loadDevboxLatestVersion() {
1205
+ try {
1206
+ const r = await fetch('/downloads/devbox/version', { headers: { Accept: 'application/json' } });
1207
+ if (!r.ok) return;
1208
+ const v = await r.json();
1209
+ if (v && v.version) {
1210
+ const el = $('#boxapp-latest');
1211
+ el.textContent = `Latest version: v${v.version}.`;
1212
+ el.hidden = false;
1213
+ }
1214
+ } catch (_) { /* best-effort courtesy line — never break the page */ }
1215
+ }
1216
+
1217
+ wireRail();
1218
+ load();
1219
+ })();