@bongos/core 1.24.78 → 1.24.80

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 (369) hide show
  1. package/.bongos-core.json +498 -353
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  4. package/.claude/skills/design/SKILL.md +1 -1
  5. package/.claude/skills/goal-create/SKILL.md +1 -1
  6. package/.claude/skills/planning-session/SKILL.md +1 -1
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +8 -0
  9. package/clients/bongos-client/index.cjs +8 -0
  10. package/clients/bongos-client/index.d.ts +11 -0
  11. package/clients/bongos-client/index.mjs +8 -0
  12. package/docs/adr/0274-one-kernel-three-role-packs.md +2 -2
  13. package/docs/adr/0399-the-visor-is-glass-over-the-scene-and-owns-no-camera.md +91 -0
  14. package/docs/adr/0400-a-chain-reserves-work-and-each-sub-quest-is-still-claimed-alone.md +109 -0
  15. package/docs/adr/0401-one-options-menu-opens-every-settings-screen-in-a-frame.md +53 -0
  16. package/docs/adr/README.md +3 -0
  17. package/docs/api/openapi.json +195 -3
  18. package/docs/api-reference.md +8 -4
  19. package/docs/architecture.md +7 -3
  20. package/docs/copy-inventory.md +390 -370
  21. package/docs/copy-registry.json +719 -518
  22. package/docs/design/characters/character-sprites-source.webp +0 -0
  23. package/docs/file-map.md +7 -8
  24. package/docs/module-api-changelog.md +4 -0
  25. package/docs/modules-contract.md +5 -0
  26. package/docs/onboarding/slash-commands.md +1 -1
  27. package/docs/packs/{engineer.md → artisan.md} +5 -5
  28. package/docs/packs/artist.md +50 -4
  29. package/docs/page-inventory.json +15 -4
  30. package/docs/page-readings.json +3182 -3336
  31. package/docs/{packs/ideator.md → recipes/ideation-playbook.md} +5 -48
  32. package/migrations/core_296_crafts_artisan_artist.sql +72 -0
  33. package/migrations/core_297_builder_character.sql +30 -0
  34. package/modules/copy-desk/proposals.js +1 -1
  35. package/modules/economy/credits-by-craft.js +29 -15
  36. package/modules/hall-ui/public/ai-studio.html +4 -4
  37. package/modules/hall-ui/public/atlas.html +5 -5
  38. package/modules/hall-ui/public/atlas.js +3 -1
  39. package/modules/hall-ui/public/blockers.html +5 -5
  40. package/modules/hall-ui/public/board-room.html +4 -4
  41. package/modules/hall-ui/public/characters/artisan.png +0 -0
  42. package/modules/hall-ui/public/characters/artist.png +0 -0
  43. package/modules/hall-ui/public/characters/patron.png +0 -0
  44. package/modules/hall-ui/public/city.css +2 -1
  45. package/modules/hall-ui/public/city.html +9 -6
  46. package/modules/hall-ui/public/city.js +7 -1
  47. package/modules/hall-ui/public/collab.html +4 -4
  48. package/modules/hall-ui/public/commands.html +4 -4
  49. package/modules/hall-ui/public/copy-desk.html +4 -4
  50. package/modules/hall-ui/public/craft-mode.js +258 -64
  51. package/modules/hall-ui/public/deploy.html +4 -4
  52. package/modules/hall-ui/public/diagram-viewer.js +2 -1
  53. package/modules/hall-ui/public/dom-utils.js +1 -1
  54. package/modules/hall-ui/public/goal-map.html +9 -6
  55. package/modules/hall-ui/public/goals.html +5 -5
  56. package/modules/hall-ui/public/government.html +4 -4
  57. package/modules/hall-ui/public/hall-render.js +3 -1
  58. package/modules/hall-ui/public/home-city-draw.js +3 -3
  59. package/modules/hall-ui/public/home-city.css +4 -0
  60. package/modules/hall-ui/public/home-city.js +17 -9
  61. package/modules/hall-ui/public/home-gallery.js +8 -1
  62. package/modules/hall-ui/public/home-idea-box.css +2 -2
  63. package/modules/hall-ui/public/home-idea-box.js +3 -3
  64. package/modules/hall-ui/public/idea.html +5 -5
  65. package/modules/hall-ui/public/ideas.html +5 -5
  66. package/modules/hall-ui/public/index.html +11 -7
  67. package/modules/hall-ui/public/market-watch-item.html +4 -4
  68. package/modules/hall-ui/public/market-watch-rules.html +4 -4
  69. package/modules/hall-ui/public/market-watch.html +6 -9
  70. package/modules/hall-ui/public/market-watch.js +22 -45
  71. package/modules/hall-ui/public/modules.html +4 -4
  72. package/modules/hall-ui/public/money.html +4 -4
  73. package/modules/hall-ui/public/my-agents.html +4 -4
  74. package/modules/hall-ui/public/options-menu.js +409 -0
  75. package/modules/hall-ui/public/paintbrush.js +3 -3
  76. package/modules/hall-ui/public/palette.js +11 -13
  77. package/modules/hall-ui/public/primer.html +5 -5
  78. package/modules/hall-ui/public/profile-crafts.js +1 -1
  79. package/modules/hall-ui/public/profile-roles.js +23 -19
  80. package/modules/hall-ui/public/profile.css +1 -1
  81. package/modules/hall-ui/public/profile.html +19 -18
  82. package/modules/hall-ui/public/profile.js +19 -18
  83. package/modules/hall-ui/public/profile.states.json +4 -4
  84. package/modules/hall-ui/public/project-settings.html +4 -4
  85. package/modules/hall-ui/public/roster.html +4 -4
  86. package/modules/hall-ui/public/roster.js +3 -3
  87. package/modules/hall-ui/public/settings.css +24 -4
  88. package/modules/hall-ui/public/settings.html +21 -17
  89. package/modules/hall-ui/public/settings.js +44 -44
  90. package/modules/hall-ui/public/shell-game.css +65 -0
  91. package/modules/hall-ui/public/shell-game.js +283 -3
  92. package/modules/hall-ui/public/shell.js +7 -26
  93. package/modules/hall-ui/public/sky-interaction.js +7 -0
  94. package/modules/hall-ui/public/speciality-tools.js +1 -2
  95. package/modules/hall-ui/public/street-inside.js +3 -4
  96. package/modules/hall-ui/public/street.html +4 -4
  97. package/modules/hall-ui/public/studio.html +5 -5
  98. package/modules/hall-ui/public/style.css +73 -47
  99. package/modules/hall-ui/public/task-detail.js +5 -4
  100. package/modules/hall-ui/public/task.html +5 -5
  101. package/modules/hall-ui/public/thinking.css +10 -0
  102. package/modules/hall-ui/public/thinking.html +9 -6
  103. package/modules/hall-ui/public/tweak-editor.html +5 -5
  104. package/modules/hall-ui/public/tweak-editor.js +5 -1
  105. package/modules/hall-ui/public/update-pill.js +91 -2
  106. package/modules/hall-ui/public/visor.css +80 -0
  107. package/modules/hall-ui/public/visor.js +316 -0
  108. package/modules/hall-ui/public/watch-repo-invite.js +159 -0
  109. package/modules/hall-ui/public/watch.html +6 -5
  110. package/modules/hall-ui/public/watch.js +17 -2
  111. package/modules/hall-ui/public/welcome-boot.js +15 -6
  112. package/modules/hall-ui/public/work.html +5 -5
  113. package/modules/hall-ui/records/builders-roster.md +2 -2
  114. package/modules/hall-ui/records/craft-mode.md +13 -11
  115. package/modules/hall-ui/records/game-mode.md +2 -2
  116. package/modules/hall-ui/records/governor-street.md +1 -1
  117. package/modules/hall-ui/records/home-city.md +3 -1
  118. package/modules/hall-ui/records/market-watch-settings.md +2 -0
  119. package/modules/hall-ui/records/options-menu.md +15 -0
  120. package/modules/hall-ui/records/profile-roles.md +11 -11
  121. package/modules/hall-ui/records/sky.md +3 -3
  122. package/modules/hall-ui/records/start-screen.md +21 -0
  123. package/modules/hall-ui/records/tweak-editor.md +1 -1
  124. package/modules/hall-ui/records/update-pill.md +12 -0
  125. package/modules/hall-ui/records/visor.md +23 -0
  126. package/modules/hall-ui/records/welcome-tower.md +5 -5
  127. package/modules/ideas/ideator-stats.js +3 -1
  128. package/modules/ideas/inbox.js +2 -2
  129. package/modules/lifecycle/activity.js +1 -1
  130. package/modules/lifecycle/cascade.js +4 -4
  131. package/modules/lifecycle/goal-spec-task.js +1 -1
  132. package/modules/lifecycle/help-requests.js +19 -15
  133. package/modules/lifecycle/kickoff-checklist.js +3 -3
  134. package/modules/lifecycle/kickoff-seed.js +1 -1
  135. package/modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql +29 -0
  136. package/modules/lifecycle/mingle.js +4 -4
  137. package/modules/lifecycle/page-tweak-write.js +1 -1
  138. package/modules/lifecycle/routes/task-write-routes.js +3 -1
  139. package/modules/lifecycle/routes/tasks.js +3 -3
  140. package/modules/lifecycle/ship-card.js +2 -3
  141. package/modules/lifecycle/task-classifier.js +39 -29
  142. package/modules/lifecycle/tests/cascade.mjs +7 -7
  143. package/modules/onboarding/founding-mode.js +3 -0
  144. package/modules/onboarding/genesis-home.js +21 -1
  145. package/modules/onboarding/routes/onboarding.js +2 -0
  146. package/modules/page-notes/notes.js +3 -2
  147. package/modules/page-notes/split.js +1 -1
  148. package/modules/platform-identity/craft-rollup.js +35 -7
  149. package/modules/platform-identity/migrations/platform_identity_036_crafts_artisan_artist.sql +45 -0
  150. package/modules/platform-identity/platform-identity.js +2 -2
  151. package/modules/platform-identity/routes/community-leaderboard.js +2 -1
  152. package/modules/platform-identity/routes/community-search.js +2 -1
  153. package/modules/platform-identity/routes/sso.js +2 -1
  154. package/modules/platform-identity/tests/platform-identity.mjs +6 -6
  155. package/modules/public-landing/public/community.html +3 -5
  156. package/modules/public-landing/public/index.html +1 -1
  157. package/modules/public-landing/public/projects.html +1 -1
  158. package/modules/specialities/migrations/specialities_004_crafts_artisan_artist.sql +30 -0
  159. package/modules/specialities/routes/specialities.js +2 -2
  160. package/modules/status-ui/public/status.js +1 -1
  161. package/modules/ui-design/kit/fixtures/community-leaderboard.json +10 -10
  162. package/modules/ui-design/kit/fixtures/community-search.json +2 -2
  163. package/modules/ui-design/kit/fixtures/scouting-recruiter.json +1 -1
  164. package/package-lock.json +2 -2
  165. package/package.json +1 -1
  166. package/release-notes.json +76 -0
  167. package/scripts/bongos/autobongos-host.js +19 -4
  168. package/scripts/bongos/autobongos-loop.js +1 -1
  169. package/scripts/bongos/claude-materialize.js +20 -1
  170. package/scripts/bongos/discipline-modes.json +7 -12
  171. package/scripts/bongos/fitness.js +1 -1
  172. package/scripts/bongos/gen-hall-sitemap.js +7 -7
  173. package/scripts/bongos/grade-smoke.js +2 -2
  174. package/scripts/bongos/init.js +2 -2
  175. package/scripts/bongos/newcomer-floor.config.json +1 -2
  176. package/scripts/bongos/newcomer-floor.js +4 -3
  177. package/scripts/bongos/newcomer-floor.templates.json +10 -12
  178. package/scripts/bongos/newcomer-floor.test.js +47 -38
  179. package/scripts/bongos/role-pack-guard.js +5 -4
  180. package/scripts/bongos/seed-tasks.example.json +3 -3
  181. package/scripts/bongos/setup.js +20 -107
  182. package/scripts/bongos/start.js +2 -2
  183. package/scripts/bongos/task.js +5 -4
  184. package/scripts/bongos/triage.js +1 -1
  185. package/scripts/bongos/worktree.js +16 -4
  186. package/scripts/setup-builder.js +6 -46
  187. package/src/bongos/auth-admission.js +8 -8
  188. package/src/bongos/craft-mode.js +2 -0
  189. package/src/bongos/db.js +39 -1
  190. package/src/bongos/github-repos.js +78 -0
  191. package/src/bongos/module-scope-map.js +22 -0
  192. package/src/bongos/role-stats.js +27 -11
  193. package/src/bongos/routes/github.js +100 -2
  194. package/src/bongos/routes/me.js +33 -0
  195. package/src/bongos/serve-internal.js +26 -9
  196. package/src/crafts.js +66 -0
  197. package/src/module-api.js +6 -1
  198. package/src/module-loader/manifest-schema.js +16 -0
  199. package/src/modules.js +2 -2
  200. package/src/role-responsibilities.js +6 -5
  201. package/tests/acclaim_feed_evidence_db.mjs +1 -1
  202. package/tests/activity_rollup_as_of.mjs +1 -1
  203. package/tests/api_alias_caller_ratchet.mjs +3 -3
  204. package/tests/application_lifecycle.mjs +1 -1
  205. package/tests/artist_review.mjs +1 -1
  206. package/tests/artist_review_goal.mjs +1 -1
  207. package/tests/autobongos_host.mjs +19 -1
  208. package/tests/blocker_hall_live_proof.mjs +1 -1
  209. package/tests/blocker_tasks_named_db.mjs +2 -2
  210. package/tests/canonical_profile_url.mjs +1 -1
  211. package/tests/cap_newcomer_tasks.mjs +13 -13
  212. package/tests/character_creation.mjs +294 -0
  213. package/tests/claim_gate_open_blocker.mjs +2 -2
  214. package/tests/claim_gate_rebase.mjs +6 -6
  215. package/tests/claude_materialize.mjs +75 -1
  216. package/tests/clawback_confirmed_backlog.mjs +3 -3
  217. package/tests/collab_page.mjs +8 -9
  218. package/tests/collab_self_settling.mjs +2 -2
  219. package/tests/community_leaderboard_route.mjs +13 -5
  220. package/tests/community_proof_db.mjs +3 -0
  221. package/tests/community_search_route.mjs +7 -6
  222. package/tests/conductor.mjs +1 -1
  223. package/tests/conductor_main_e2e.mjs +8 -8
  224. package/tests/copy_desk_flags_db.mjs +1 -1
  225. package/tests/copy_desk_page_approve.mjs +2 -2
  226. package/tests/copy_desk_page_claim.mjs +2 -2
  227. package/tests/copy_desk_page_next.mjs +1 -1
  228. package/tests/copy_desk_page_release.mjs +1 -1
  229. package/tests/copy_desk_page_submit.mjs +2 -2
  230. package/tests/core_258_box_session_claims_db.mjs +1 -1
  231. package/tests/craft_mode.mjs +48 -34
  232. package/tests/craft_mode_home.mjs +20 -20
  233. package/tests/craft_mode_looks.mjs +16 -15
  234. package/tests/craft_mode_nav.mjs +31 -29
  235. package/tests/craft_review_mode.mjs +46 -41
  236. package/tests/crafts_vocabulary.mjs +70 -0
  237. package/tests/cross_discipline_priority_direction.mjs +5 -5
  238. package/tests/fitness.mjs +7 -11
  239. package/tests/funded_project_pays_proof_db.mjs +1 -1
  240. package/tests/genesis_home.mjs +21 -1
  241. package/tests/github_chip.mjs +3 -3
  242. package/tests/github_repo_invite.mjs +379 -0
  243. package/tests/goal_criterion_confirm_ui.mjs +1 -1
  244. package/tests/goal_invite_consent.mjs +1 -1
  245. package/tests/goal_map_page.mjs +2 -2
  246. package/tests/goal_sort_staleness.mjs +1 -1
  247. package/tests/goal_spec_task.mjs +1 -1
  248. package/tests/goal_spec_task_db.mjs +3 -3
  249. package/tests/grade_engine.mjs +2 -2
  250. package/tests/grader_no_artifact.mjs +1 -1
  251. package/tests/grader_prompt_fencing.mjs +1 -1
  252. package/tests/grader_prompt_window_bound.mjs +1 -1
  253. package/tests/grader_reader_lens.mjs +2 -2
  254. package/tests/grader_severity_gate.mjs +1 -1
  255. package/tests/grader_unbacked_score.mjs +1 -1
  256. package/tests/hall_approval_queue.mjs +1 -1
  257. package/tests/hall_board_world.mjs +2 -2
  258. package/tests/hall_builders_page_pass.mjs +1 -1
  259. package/tests/hall_city.mjs +1 -1
  260. package/tests/hall_fullscreen.mjs +27 -3
  261. package/tests/hall_kit_charts.mjs +1 -1
  262. package/tests/hall_landing_boot.mjs +1 -1
  263. package/tests/hall_my_agents.mjs +16 -16
  264. package/tests/hall_nav.mjs +22 -15
  265. package/tests/hall_options_menu.mjs +247 -0
  266. package/tests/hall_oversight_world.mjs +1 -1
  267. package/tests/hall_palette.mjs +8 -9
  268. package/tests/hall_profile_world.mjs +3 -3
  269. package/tests/hall_record_world.mjs +2 -2
  270. package/tests/hall_settings_world.mjs +2 -1
  271. package/tests/hall_sitemap.mjs +6 -5
  272. package/tests/hall_start_screen.mjs +244 -0
  273. package/tests/hall_street.mjs +2 -2
  274. package/tests/hall_street_inside.mjs +17 -9
  275. package/tests/hall_studio_world.mjs +3 -3
  276. package/tests/hall_visor.mjs +224 -0
  277. package/tests/help_requests.mjs +47 -15
  278. package/tests/helpers.mjs +4 -4
  279. package/tests/home_city.mjs +6 -4
  280. package/tests/home_city_widget.mjs +33 -14
  281. package/tests/home_gallery_widget.mjs +4 -4
  282. package/tests/home_idea_box.mjs +6 -6
  283. package/tests/hub_community_page.mjs +3 -3
  284. package/tests/hub_craft_rollup.mjs +75 -36
  285. package/tests/hub_recent_ships.mjs +3 -2
  286. package/tests/idea_cascade.mjs +2 -2
  287. package/tests/idea_credit_streams_db.mjs +1 -1
  288. package/tests/idea_detail_page.mjs +1 -1
  289. package/tests/idea_ideate_full.mjs +5 -2
  290. package/tests/idea_spark_hall.mjs +1 -1
  291. package/tests/init.mjs +3 -3
  292. package/tests/instance_manifest.mjs +2 -2
  293. package/tests/kickoff_checklist.mjs +1 -1
  294. package/tests/leaderboard_roles.mjs +34 -25
  295. package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
  296. package/tests/lightbulb.mjs +14 -10
  297. package/tests/main_discipline.mjs +8 -8
  298. package/tests/markdown_line_endings.mjs +1 -1
  299. package/tests/market_watch_pages.mjs +52 -74
  300. package/tests/me_streak_leaderboard_queries.mjs +3 -3
  301. package/tests/migration_allocation.mjs +1 -1
  302. package/tests/mingle.mjs +36 -21
  303. package/tests/module-scope-map.mjs +22 -2
  304. package/tests/module_api.mjs +1 -0
  305. package/tests/module_contributions.mjs +13 -13
  306. package/tests/module_manifest.mjs +29 -0
  307. package/tests/module_skills_hook.mjs +2 -2
  308. package/tests/modules.mjs +14 -7
  309. package/tests/nav_permission_atoms.mjs +2 -1
  310. package/tests/needs_rebase.mjs +1 -1
  311. package/tests/newcomer_restock.mjs +13 -13
  312. package/tests/page_notes_panel.mjs +22 -22
  313. package/tests/page_tweak_no_block.mjs +1 -1
  314. package/tests/paintbrush.mjs +7 -7
  315. package/tests/privacy_matrix.mjs +1 -0
  316. package/tests/profile_activity.mjs +20 -23
  317. package/tests/profile_earnings.mjs +1 -1
  318. package/tests/profile_nudge_prefs.mjs +4 -4
  319. package/tests/profile_role_headline.mjs +73 -46
  320. package/tests/profile_role_stats.mjs +55 -45
  321. package/tests/profile_rollup_consent.mjs +19 -6
  322. package/tests/profile_route.mjs +12 -12
  323. package/tests/profile_ui_cross_project.mjs +4 -4
  324. package/tests/project_invite_suggestions.mjs +2 -2
  325. package/tests/promote_goal_id.mjs +1 -1
  326. package/tests/publish_manifest.mjs +1 -1
  327. package/tests/rank_floor_wiring.mjs +1 -1
  328. package/tests/regrade_actual_minutes.mjs +1 -1
  329. package/tests/removal_proposal.mjs +2 -2
  330. package/tests/reward_gate.mjs +4 -4
  331. package/tests/reward_mode_cost_plus_only.mjs +1 -1
  332. package/tests/role_responsibilities.mjs +18 -12
  333. package/tests/role_stats_db.mjs +17 -16
  334. package/tests/scouting_rollup_fanout.mjs +5 -5
  335. package/tests/seed_tasks.mjs +5 -2
  336. package/tests/session_earnings_mirror_db.mjs +1 -1
  337. package/tests/session_reward_on_land_db.mjs +1 -1
  338. package/tests/session_reward_reconcile_db.mjs +2 -2
  339. package/tests/sessions_kit_pager.mjs +1 -1
  340. package/tests/settings_art_key_absent.mjs +2 -2
  341. package/tests/settings_main_role.mjs +21 -20
  342. package/tests/setup_flow.mjs +3 -3
  343. package/tests/setup_smoke.mjs +20 -0
  344. package/tests/ship_card.mjs +6 -6
  345. package/tests/specialities.mjs +16 -4
  346. package/tests/speciality_db_shape.mjs +6 -6
  347. package/tests/speciality_injection.mjs +17 -17
  348. package/tests/speciality_offer_ui.mjs +5 -5
  349. package/tests/speciality_offered.mjs +6 -5
  350. package/tests/speciality_session_skills.mjs +7 -7
  351. package/tests/speciality_skills.mjs +11 -11
  352. package/tests/speciality_walkthrough_ui.mjs +2 -2
  353. package/tests/sso_checkin_identity_spoof.mjs +2 -1
  354. package/tests/task_classifier_vocab.mjs +10 -0
  355. package/tests/task_cli.mjs +11 -3
  356. package/tests/task_criteria_unlink_db.mjs +2 -2
  357. package/tests/task_discipline_required.mjs +38 -16
  358. package/tests/task_goal_required.mjs +4 -4
  359. package/tests/task_needs_db.mjs +1 -1
  360. package/tests/tweak_replaces_artist_review.mjs +1 -1
  361. package/tests/update_pill.mjs +153 -2
  362. package/tests/upkeep_home.mjs +1 -1
  363. package/tests/v3_c1_options_proof.mjs +145 -0
  364. package/tests/visual_review.mjs +3 -3
  365. package/tests/watch_applications_queue.mjs +8 -5
  366. package/tests/watch_sealed_floor.mjs +3 -1
  367. package/tests/welcome_boot.mjs +12 -9
  368. package/tests/welcome_boot_browser.mjs +13 -13
  369. package/tests/worktree_helper.mjs +16 -1
@@ -4,10 +4,9 @@
4
4
  // Usage:
5
5
  // node scripts/bongos/setup.js run interactively, perform the auth
6
6
  // node scripts/bongos/setup.js --force re-auth even if a valid session exists
7
- // node scripts/bongos/setup.js --skip-keys skip the per-builder Gemini-key prompt
8
- // (for CI / batch onboarding / builders
9
- // who don't need the art pipeline)
10
- // node scripts/bongos/setup.js --dry-run print every step that *would* run, without
7
+ // node scripts/bongos/setup.js --skip-keys accepted and ignored: setup asks for no
8
+ // API key any more (task 1005189)
9
+ // node scripts/bongos/setup.js --dry-run print every step that *would* run, without
11
10
  // contacting the API, writing files, or
12
11
  // prompting. Useful for newcomers who want
13
12
  // a preview before running it for real.
@@ -21,24 +20,18 @@
21
20
  const readline = require('readline');
22
21
  const { cliClient, errCode, saveSession, SESSION_PATH, loadSession, printOnboardingNudge, API_BASE, hallBase, surfaceAction, isTerminalSurface } = require('./cli-lib');
23
22
 
24
- // The instance's status dashboard, from branding — never a hardcoded host, so a
25
- // non-OTB instance points its builders at ITS OWN dashboard (task 1002428).
26
- // Falls back to the resolved API base when branding is unreadable.
27
- function statusOrigin() {
28
- try {
29
- const d = require('../../src/branding').branding().domains || {};
30
- if (d.statusOrigin) return d.statusOrigin;
31
- } catch (_) { /* fall through */ }
32
- return API_BASE;
33
- }
34
23
  const onboardingConfig = require('./onboarding-config');
35
24
  const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
36
25
 
37
26
  const DRY_RUN = process.argv.includes('--dry-run');
38
- // --skip-keys mirrors the same flag on scripts/setup-builder.sh — when set,
39
- // the Gemini-key prompt is skipped entirely. Used by CI / batch onboarding /
40
- // builders who don't intend to run the art pipeline.
41
- const SKIP_KEYS = process.argv.includes('--skip-keys');
27
+ // Setup asks for NO API key (task 1005189). It used to stop every new builder,
28
+ // on every project, at a Google AI Studio key question — including projects
29
+ // with no art pipeline, and including agent-run setups where nobody can type
30
+ // an answer. An optional key is noise at sign-in: a builder adds it in hall
31
+ // Settings (#art-key) when art actually needs it, and the art pipeline's own
32
+ // "no key found" message is the one place that says so. The server-stored key
33
+ // still reaches this machine through fetch-art-key.js. --skip-keys is still
34
+ // accepted (setup-builder passes it) and does nothing.
42
35
 
43
36
  // Tiny helper so the dry-run output reads as a clear plan rather than a wall
44
37
  // of identical-looking log lines.
@@ -46,77 +39,6 @@ function dryRunStep(msg) {
46
39
  console.log(`[dry-run] ${msg}`);
47
40
  }
48
41
 
49
- // Ask the new builder for their own Google AI Studio key — used by the
50
- // pixel-art pipeline (gen_api.py). V3.R11 (#91): each builder pays their own
51
- // Gemini bill, so spend is attributable per-builder on the public dashboard.
52
- //
53
- // Stored as gemini_api_key in ~/.config/otb/bongos-session.json (mode 600, same
54
- // file as the GitHub session token). gen_api.py reads this location before
55
- // falling back to ~/.config/otb/env or .env.local.
56
- //
57
- // Behavior:
58
- // - Non-TTY shells: skip silently. Builder can edit the session file later.
59
- // - --skip-keys flag: skip silently. CI/batch onboarding.
60
- // - Existing key on file: show last-4, ask "overwrite? [y/N]". Default no.
61
- // - Empty input: leave whatever was there. Don't blank a working key by
62
- // accident.
63
- async function promptGeminiKey() {
64
- if (SKIP_KEYS) return;
65
- if (!process.stdin.isTTY) return;
66
-
67
- const fs = require('node:fs/promises');
68
- let session;
69
- try {
70
- session = JSON.parse(await fs.readFile(SESSION_PATH, 'utf8'));
71
- } catch (_) {
72
- // saveSession just wrote it; if it's not readable something's badly wrong.
73
- // Better to bail than to silently lose a key the builder is about to type.
74
- return;
75
- }
76
-
77
- const existing = session.gemini_api_key;
78
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
79
- const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
80
- try {
81
- console.log('');
82
- console.log('Google AI Studio key for the pixel-art pipeline (per-builder, optional).');
83
- console.log(' Get one at https://aistudio.google.com/app/apikey');
84
- console.log(' (Or skip this and add it later with browser clicks: hall Settings →');
85
- console.log(` ${hallBase()}/settings#art-key — stored encrypted, ADR 0073.)`);
86
- console.log(' Each builder pays their own Gemini bill so spend is attributed to them');
87
- console.log(` on ${statusOrigin()}.`);
88
- let prompt;
89
- if (existing && typeof existing === 'string' && existing.length >= 4) {
90
- const last4 = existing.slice(-4);
91
- const overwrite = (await ask(` Key on file ending …${last4}. Overwrite? [y/N] `)).trim().toLowerCase();
92
- if (!(overwrite === 'y' || overwrite === 'yes')) {
93
- console.log(' Kept the existing key.');
94
- return;
95
- }
96
- prompt = ' New GOOGLE_AI_STUDIO_KEY (press Enter to clear): ';
97
- } else {
98
- prompt = ' GOOGLE_AI_STUDIO_KEY (press Enter to skip): ';
99
- }
100
- const input = (await ask(prompt)).trim();
101
- if (!input) {
102
- if (existing) {
103
- // The user asked to overwrite, then entered nothing → clear it.
104
- delete session.gemini_api_key;
105
- await fs.writeFile(SESSION_PATH, JSON.stringify(session, null, 2), { mode: 0o600 });
106
- console.log(' Cleared the key.');
107
- } else {
108
- console.log(' Skipped — set it later by re-running setup.js or editing the session file.');
109
- }
110
- return;
111
- }
112
- session.gemini_api_key = input;
113
- await fs.writeFile(SESSION_PATH, JSON.stringify(session, null, 2), { mode: 0o600 });
114
- console.log(` Saved Gemini key to ${SESSION_PATH} (mode 600).`);
115
- } finally {
116
- rl.close();
117
- }
118
- }
119
-
120
42
  // V3.R10 (#235), criterion C3 — link the builder's Anthropic account.
121
43
  //
122
44
  // Claude Code is the trusted agent on the builder's machine: it writes its own
@@ -213,15 +135,15 @@ async function promptDisciplines() {
213
135
  try {
214
136
  console.log('');
215
137
  console.log('Which kind of work do you want surfaced first?');
216
- console.log(' 1) engineer — code, gameplay mechanics, infra, the GDS itself, perf, bugs');
217
- console.log(' 2) artist — pixel-art tiles, sprites, icons, avatars, the art pipeline');
218
- console.log(' 3) ideator — idea capture, scoping, research, docs/briefs, narrative, lore');
219
- console.log(' 4) all of them');
220
- console.log(' 5) skip for now (you can set this later on /builders)');
221
- const choice = (await ask('Choose [1-5]: ')).trim();
138
+ console.log(' 1) artisan — code, gameplay mechanics, infra, Bongos itself, perf, bugs');
139
+ console.log(' 2) artist — how the project looks and reads, and its ideas: art, copy, scoping, lore');
140
+ console.log(' 3) both of them');
141
+ console.log(' 4) skip for now (you can set this later on /builders)');
142
+ const choice = (await ask('Choose [1-4]: ')).trim();
143
+ // Two task crafts since task 1005141 (the ideator folded into the artist).
222
144
  const map = {
223
- 1: ['engineer'], 2: ['artist'], 3: ['ideator'],
224
- 4: ['engineer', 'artist', 'ideator'], 5: null,
145
+ 1: ['artisan'], 2: ['artist'],
146
+ 3: ['artisan', 'artist'], 4: null,
225
147
  };
226
148
  const disciplines = map[choice];
227
149
  if (disciplines === undefined) {
@@ -327,12 +249,9 @@ async function finishSetup(builder) {
327
249
  } catch (e) {
328
250
  console.log(`Note: could not activate git hooks (${e.message}); run: git config core.hooksPath .husky`);
329
251
  }
330
- await promptGeminiKey();
331
252
  // #1281: deliver the GDS shared art key to entitled newcomers right now, so
332
253
  // a brand-new Xenos's first art task works without waiting for the next
333
- // session start. Best-effort, bounded — never block setup. (Phase 2 will
334
- // make the promptGeminiKey copy above rank-aware — "we cover your first art
335
- // tasks; add your own key when you reach Thetes".)
254
+ // session start. Best-effort, bounded, silent — never block setup.
336
255
  try {
337
256
  const { spawnSync } = require('node:child_process');
338
257
  const path = require('node:path');
@@ -405,11 +324,6 @@ async function main() {
405
324
  dryRunStep('If GitHub refuses with device_flow_disabled (a one-click App whose checkbox is unticked, task 1003051): would print the web sign-in guidance (hall Settings → CLI Access) and prompt for a pasted CLI token instead of dead-ending.');
406
325
  dryRunStep('Would print the verification_uri and user_code, then poll /api/bongos/auth/device/poll every few seconds until the user approves or the code expires.');
407
326
  dryRunStep(`On approval, would write the bearer token + builder profile to ${SESSION_PATH} (chmod 600).`);
408
- if (SKIP_KEYS) {
409
- dryRunStep('Would skip the Gemini-key prompt (--skip-keys set).');
410
- } else {
411
- dryRunStep(`Would prompt for a per-builder GOOGLE_AI_STUDIO_KEY on TTY shells and store it as gemini_api_key in ${SESSION_PATH} (mode 600). Used by modules/art-pipeline/pipeline/gen_api.py for per-builder cost attribution.`);
412
- }
413
327
  dryRunStep('Would read ~/.claude.json oauthAccount and, on a TTY, prompt to confirm the Anthropic email; on a match would POST /api/bongos/me/anthropic-link and store anthropic_user_id in the session (kept as cosmetic attribution; the identity pin was removed — task 1039 / idea 258). On mismatch would REFUSE to link.');
414
328
  dryRunStep('Would prompt for preferred disciplines (creative / engineering / both / skip) and PATCH /api/bongos/me/disciplines on TTY shells.');
415
329
  dryRunStep(`Would prompt for onboarding-friction consent (Y/n) and record the decision to ${onboardingConfig.CONSENT_PATH} on TTY shells.`);
@@ -531,7 +445,6 @@ async function main() {
531
445
  const { activate } = require('./install-git-hooks');
532
446
  activate();
533
447
  } catch (_) { /* hook activation is best-effort */ }
534
- await promptGeminiKey();
535
448
  await promptAnthropicLink();
536
449
  await promptDisciplines();
537
450
  await promptOnboardingConsent();
@@ -220,9 +220,9 @@ function roleLabel(t) {
220
220
  // that read in both light and dark. Colours the Type column so the work lane
221
221
  // reads at a glance (task 1273 follow-up: "spice up the view").
222
222
  function disciplineColor(d) {
223
- if (d === 'engineer') return '#1D9E75'; // sea-teal
223
+ if (d === 'artisan') return '#1D9E75'; // sea-teal
224
224
  if (d === 'artist') return '#D85A30'; // terracotta
225
- if (d === 'ideator') return '#639922'; // olive
225
+ if (d === 'artist') return '#639922'; // olive
226
226
  return '#888780'; // unclassified — neutral gray
227
227
  }
228
228
  function whatItIs(t) {
@@ -7,7 +7,7 @@
7
7
  // the first-class front door.
8
8
  //
9
9
  // Usage:
10
- // bongos task new --title "…" --description "…" [--kind feature] [--discipline engineer]
10
+ // bongos task new --title "…" --description "…" [--kind feature] [--discipline artisan]
11
11
  // [--priority 3] [--version <id>] [--goal <id>] [--criteria C1,C2]
12
12
  // [--touches a/,b/] [--est-minutes N] [--credits N] [--ready] [--json]
13
13
  // bongos task show <id> [--json]
@@ -29,7 +29,7 @@ const { UPKEEP_KIND } = require('../../modules/lifecycle/upkeep-home');
29
29
  // Valid enums (mirrors the server's task-classifier vocab; a friendly client-side check
30
30
  // so an obvious typo fails before the round-trip — the server stays authoritative).
31
31
  const VALID_KINDS = ['feature', 'bug', 'infra', 'refactor', 'spike', 'learning-capture', 'cleanup', 'decision', 'blocker-resolution', 'verify'];
32
- const VALID_DISCIPLINES = ['engineer', 'artist', 'ideator', 'ui'];
32
+ const VALID_DISCIPLINES = ['artisan', 'artist', 'ui'];
33
33
 
34
34
  function csv(v) {
35
35
  return String(v || '').split(',').map((s) => s.trim()).filter(Boolean);
@@ -46,7 +46,8 @@ function buildTaskBody(args) {
46
46
 
47
47
  const kind = arg('--kind', args) || 'feature';
48
48
  if (!VALID_KINDS.includes(kind)) errors.push(`--kind must be one of: ${VALID_KINDS.join(', ')}`);
49
- const discipline = arg('--discipline', args) || 'engineer';
49
+ // Old spellings still work (task 1005141): engineer → artisan, ideator → artist.
50
+ const discipline = ({ engineer: 'artisan', ideator: 'artist' })[arg('--discipline', args)] || arg('--discipline', args) || 'artisan';
50
51
  if (!VALID_DISCIPLINES.includes(discipline)) errors.push(`--discipline must be one of: ${VALID_DISCIPLINES.join(', ')}`);
51
52
 
52
53
  const num = (flag) => { const v = arg(flag, args); return v == null ? undefined : Number(v); };
@@ -82,7 +83,7 @@ function buildTaskBody(args) {
82
83
 
83
84
  function usage() {
84
85
  console.error('usage:');
85
- console.error(' bongos task new --title "…" --description "…" [--kind feature] [--discipline engineer]');
86
+ console.error(' bongos task new --title "…" --description "…" [--kind feature] [--discipline artisan]');
86
87
  console.error(' [--priority 3] [--version <id>] [--goal <id>] [--criteria C1,C2]');
87
88
  console.error(' [--touches a/,b/] [--est-minutes N] [--credits N] [--ready] [--json]');
88
89
  console.error(' bongos task show <id> [--json]');
@@ -282,7 +282,7 @@ async function createTask({ versionId, title, description, priority, sourceRef,
282
282
  // promotion they just decided on. So resolve it here with the SAME function the
283
283
  // server uses, and name a fallback rather than letting the loop break — the
284
284
  // identical trade modules/ideas/inbox.js makes for capture.
285
- discipline: resolveWriteDiscipline({ title, description }).discipline || 'engineer',
285
+ discipline: resolveWriteDiscipline({ title, description }).discipline || 'artisan',
286
286
  };
287
287
  if (goalId == null) {
288
288
  throw new Error('createTask: goal_id is required — every task belongs to a goal (task 1003598 deleted the catch-all fallback).');
@@ -291,9 +291,21 @@ function cmdAdd(root, argv) {
291
291
  }
292
292
  }
293
293
  console.log('');
294
- console.log('Next:');
295
- console.log(` cd "${wtPath}"`);
296
- console.log(' node scripts/bongos/claim.js <task-id> (worktree auto-detected)');
294
+ for (const line of nextStepLines(wtPath)) console.log(line);
295
+ }
296
+
297
+ // task 1005185 - feedback bug scripts: the "Next:" hint names the claim command that
298
+ // exists in the new tree. A project that installs the core from npm has no scripts/ of
299
+ // its own — scripts is a git-ignored link into node_modules/@bongos/core, so a fresh
300
+ // worktree has none and `node scripts/...` dies MODULE_NOT_FOUND. There the CLI is
301
+ // `npx bongos <verb>` (npx walks up to the main checkout's node_modules). Same check
302
+ // claim.js's finishHintLines uses (task 1005113).
303
+ function nextStepLines(wtPath, { installed } = {}) {
304
+ if (installed === undefined) {
305
+ try { installed = require('./fitness-lib').isInstalledCore(); } catch { installed = false; }
306
+ }
307
+ const claim = installed ? 'npx bongos claim <task-id>' : 'node scripts/bongos/claim.js <task-id>';
308
+ return ['Next:', ` cd "${wtPath}"`, ` ${claim} (worktree auto-detected)`];
297
309
  }
298
310
 
299
311
  function cmdRemove(root, argv) {
@@ -445,5 +457,5 @@ if (require.main === module) main();
445
457
 
446
458
  module.exports = {
447
459
  worktreeNameFor, needsJunction, removalPlan, nodeModulesState, shadowingNodeModules, huskDisposition,
448
- unlinkNodeModulesLink,
460
+ unlinkNodeModulesLink, nextStepLines,
449
461
  };
@@ -7,7 +7,7 @@
7
7
  // bootstrap a new builder runs right after cloning:
8
8
  //
9
9
  // node scripts/setup-builder.js
10
- // node scripts/setup-builder.js --skip-keys # don't prompt for API keys
10
+ // node scripts/setup-builder.js --skip-keys # accepted and ignored (no key is asked for)
11
11
  // node scripts/setup-builder.js --no-auth # skip the GDS Device Flow step
12
12
  // node scripts/setup-builder.js --help
13
13
  //
@@ -15,8 +15,8 @@
15
15
  // "already done" rather than clobbering. What it does, in order:
16
16
  // 1. Verify Node >= 22 and Python >= 3.10 (the two required toolchains).
17
17
  // 2. `npm ci` to install JS dependencies.
18
- // 3. Copy .env.local.example -> .env.local (if absent) + optionally prompt
19
- // for the Google AI Studio key (--skip-keys / non-TTY takes placeholders).
18
+ // 3. Copy .env.local.example -> .env.local (if absent). No API key is asked
19
+ // for (task 1005189): a builder adds one in hall Settings when art needs it.
20
20
  // 4. `node scripts/bongos/setup.js` — GitHub Device Flow auth (--no-auth skips).
21
21
  // 5. Print the "ready" banner pointing at /builder-start.
22
22
  //
@@ -25,7 +25,6 @@
25
25
 
26
26
  const fs = require('node:fs');
27
27
  const path = require('node:path');
28
- const readline = require('node:readline');
29
28
  const { spawnSync } = require('node:child_process');
30
29
 
31
30
  const REPO_ROOT = path.resolve(__dirname, '..');
@@ -33,17 +32,15 @@ const IS_WIN = process.platform === 'win32';
33
32
 
34
33
  // ---- arg parsing ----
35
34
  const argv = process.argv.slice(2);
36
- let SKIP_KEYS = false;
37
35
  let NO_AUTH = false;
38
36
  for (const a of argv) {
39
- if (a === '--skip-keys') SKIP_KEYS = true;
37
+ if (a === '--skip-keys') { /* accepted for old callers; setup asks for no key (task 1005189) */ }
40
38
  else if (a === '--no-auth') NO_AUTH = true;
41
39
  else if (a === '-h' || a === '--help') {
42
40
  console.log(`setup-builder.js — one-command bootstrap for a new OTB builder.
43
41
 
44
42
  Usage:
45
43
  node scripts/setup-builder.js
46
- node scripts/setup-builder.js --skip-keys # don't prompt for API keys
47
44
  node scripts/setup-builder.js --no-auth # skip the GDS Device Flow step
48
45
  node scripts/setup-builder.js --help
49
46
 
@@ -162,13 +159,6 @@ function npmCi() {
162
159
  // ---------------------------------------------------------------------------
163
160
  // 3. .env.local
164
161
  // ---------------------------------------------------------------------------
165
- function ask(question) {
166
- return new Promise((resolve) => {
167
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
168
- rl.question(question, (answer) => { rl.close(); resolve(answer); });
169
- });
170
- }
171
-
172
162
  async function envLocal() {
173
163
  step('3/5 Local environment (.env.local)');
174
164
  const example = path.join(REPO_ROOT, '.env.local.example');
@@ -186,37 +176,8 @@ async function envLocal() {
186
176
  fs.copyFileSync(example, target);
187
177
  ok('Created .env.local from .env.local.example');
188
178
 
189
- // Prompt for the Google AI Studio key only with a TTY and without --skip-keys.
190
- // An empty value is not fatal — the pipeline also resolves the key from
191
- // ~/.config/otb/env, so it just means art-pipeline calls raise the standard
192
- // "GOOGLE_AI_STUDIO_KEY not found" error until set.
193
- if (SKIP_KEYS) {
194
- skip('--skip-keys set; leaving GOOGLE_AI_STUDIO_KEY blank in .env.local');
195
- skip('you can edit .env.local later, or set it in ~/.config/otb/env');
196
- return;
197
- }
198
- if (!process.stdin.isTTY) {
199
- skip('no TTY; leaving GOOGLE_AI_STUDIO_KEY blank in .env.local');
200
- return;
201
- }
202
- console.log('');
203
- console.log(` ${C.dim}Google AI Studio key for the pixel-art pipeline.`);
204
- console.log(' Get one at https://aistudio.google.com/app/apikey .');
205
- console.log(` Press Enter to skip — you can always edit .env.local later.${C.reset}`);
206
- // Strip CR/LF so a pasted value with a stray newline can't inject extra
207
- // lines into the builder's .env.local; trim handles surrounding whitespace.
208
- const input = (await ask(' GOOGLE_AI_STUDIO_KEY: ')).replace(/[\r\n]+/g, '').trim();
209
- if (input) {
210
- const contents = fs.readFileSync(target, 'utf8');
211
- // Function replacement (not a string): String.replace treats $&, $1, $$ etc.
212
- // in a replacement STRING as special, which would silently corrupt a key
213
- // containing a bare `$`. A function return value is spliced verbatim.
214
- const next = contents.replace(/^GOOGLE_AI_STUDIO_KEY=.*$/m, () => `GOOGLE_AI_STUDIO_KEY=${input}`);
215
- fs.writeFileSync(target, next);
216
- ok('Saved GOOGLE_AI_STUDIO_KEY to .env.local');
217
- } else {
218
- skip('left GOOGLE_AI_STUDIO_KEY blank — edit .env.local when you have one');
219
- }
179
+ // No key is asked for here (task 1005189): an optional key at sign-in is noise.
180
+ // The art pipeline says so itself, when art actually needs one.
220
181
  }
221
182
 
222
183
  // ---------------------------------------------------------------------------
@@ -234,7 +195,6 @@ function gdsAuth() {
234
195
  console.log(' Handing off to scripts/bongos/setup.js ...');
235
196
  console.log('');
236
197
  const setupArgs = [path.join(REPO_ROOT, 'scripts', 'bongos', 'setup.js')];
237
- if (SKIP_KEYS) setupArgs.push('--skip-keys');
238
198
  const status = run(process.execPath, setupArgs); // shell defaults off
239
199
  if (status === 0) {
240
200
  ok('GDS session ready');
@@ -149,7 +149,7 @@ async function recentShipsForRollup(builderId) {
149
149
  // preference, exactly as before. A port that cannot be asked leaves its numbers
150
150
  // null ("could not ask"), never 0. Never throws: a failed read drops its craft's
151
151
  // numbers, not the report.
152
- const ROLLUP_CRAFTS = Object.freeze(['engineer', 'artist', 'ideator']);
152
+ const ROLLUP_CRAFTS = Object.freeze(['artisan', 'artist']);
153
153
  const countOrNull = (v) => (Number.isInteger(v) && v >= 0 ? v : null);
154
154
  async function readCraftRollup(builderId, { shipped = 0, preferred = [] } = {}) {
155
155
  const fallback = { counts: null, disciplines: Array.isArray(preferred) ? preferred : [] };
@@ -172,20 +172,20 @@ async function readCraftRollup(builderId, { shipped = 0, preferred = [] } = {})
172
172
  };
173
173
  const [artist, ideator] = await Promise.all([
174
174
  earned.includes('artist') ? ask('copy-desk.artistStats') : null,
175
- earned.includes('ideator') ? ask('ideas.ideatorStats') : null,
175
+ earned.includes('artist') ? ask('ideas.ideatorStats') : null,
176
176
  ]);
177
177
  const credits = (c) => countOrNull(Math.round(Number(byCraft[c])));
178
178
  const counts = {};
179
- if (earned.includes('engineer')) counts.engineer = { shipped: countOrNull(shipped), credits: credits('engineer') };
179
+ if (earned.includes('artisan')) counts.artisan = { shipped: countOrNull(shipped), credits: credits('artisan') };
180
+ // One artist block since the ideator folded in (task 1005141): the page count
181
+ // and the idea counts side by side, under the craft's one credit split.
180
182
  if (earned.includes('artist')) {
181
- counts.artist = { pages_approved: countOrNull(artist && artist.pages_approved), credits: credits('artist') };
182
- }
183
- if (earned.includes('ideator')) {
184
- counts.ideator = {
183
+ counts.artist = {
184
+ pages_approved: countOrNull(artist && artist.pages_approved),
185
185
  filed: countOrNull(ideator && ideator.filed),
186
186
  full_ideas: countOrNull(ideator && ideator.full_ideas),
187
187
  ratified: countOrNull(ideator && ideator.ratified),
188
- credits: credits('ideator'),
188
+ credits: credits('artist'),
189
189
  };
190
190
  }
191
191
  return { counts, disciplines: earned };
@@ -77,6 +77,8 @@ function roleLine(mode) {
77
77
  // so this adds no prompt-injection surface to the session.
78
78
  function describeCraftMode(discipline, modes) {
79
79
  if (!discipline || !modes) return null;
80
+ // A row a rolled-back release wrote may still say engineer/ideator (task 1005141).
81
+ discipline = require('../crafts').normalizeCraft(discipline);
80
82
  const mode = modes[discipline];
81
83
  if (!mode || typeof mode !== 'object' || !mode.directive) return null;
82
84
  const parts = [`${roleLine(mode)} (the craft of the task claimed in this worktree).`, String(mode.directive).trim()];
package/src/bongos/db.js CHANGED
@@ -25,6 +25,7 @@
25
25
  // grade-count SQL runs on both sides of the kernel↔module line — keep in sync.
26
26
 
27
27
  const { pool } = require('./pool');
28
+ const { normalizeCraft: normalizeCraftName } = require('../crafts'); // task 1005147: setBuilderCharacter
28
29
  const secretBox = require('./secret-box');
29
30
  // Module seam (ADR 0083). The builder-exit path EMITS `builder.secrets-scrub-needed`
30
31
  // AFTER COMMIT (best-effort) so a module that stores a builder-keyed secret can
@@ -172,7 +173,10 @@ const UPDATE_BUILDER_DISCIPLINES_SQL =
172
173
  async function updateBuilderDisciplines(builderId, disciplines, { mainDiscipline } = {}) {
173
174
  const { enabledDisciplines } = require('../modules');
174
175
  const offered = enabledDisciplines();
175
- const list = Array.isArray(disciplines) ? disciplines : [];
176
+ // An old spelling (engineer, ideator) is today's craft, not an invalid one (task 1005141).
177
+ const { normalizeCraft, normalizeCrafts } = require('../crafts');
178
+ const list = Array.isArray(disciplines) ? normalizeCrafts(disciplines) : [];
179
+ if (typeof mainDiscipline === 'string') mainDiscipline = normalizeCraft(mainDiscipline);
176
180
  const bad = list.filter((d) => !offered.includes(d));
177
181
  if (bad.length) {
178
182
  const err = new Error(`invalid discipline(s): ${bad.join(', ')}`);
@@ -207,6 +211,38 @@ async function getBuilderRoleChoice(builderId) {
207
211
  return rows[0] ?? null;
208
212
  }
209
213
 
214
+ // The character a builder holds (task 1005147, core_297): 'artisan' | 'artist' |
215
+ // 'patron', or null while they have not picked one. Identity and view only — no
216
+ // gate reads it (criterion character-is-identity-not-authority). Its own read, not a
217
+ // column on getBuilderById, so the kernel's hot identity read never depends on a
218
+ // migration that may not have run yet.
219
+ const CHARACTERS = Object.freeze(['artisan', 'artist', 'patron']);
220
+ async function getBuilderCharacter(builderId) {
221
+ const { rows } = await pool.query(
222
+ `SELECT game_character FROM builders WHERE id = $1`,
223
+ [builderId]
224
+ );
225
+ return rows[0] ? rows[0].game_character : null;
226
+ }
227
+ // Set (a CHARACTERS word, old spellings normalized) or clear (null) the builder's own
228
+ // character. Throws { code: 'invalid_character' } for anything else.
229
+ async function setBuilderCharacter(builderId, character) {
230
+ let value = null;
231
+ if (character !== null) {
232
+ value = normalizeCraftName(character);
233
+ if (!CHARACTERS.includes(value)) {
234
+ const err = new Error(`invalid character: ${character}`);
235
+ err.code = 'invalid_character';
236
+ throw err;
237
+ }
238
+ }
239
+ const { rows } = await pool.query(
240
+ `UPDATE builders SET game_character = $2 WHERE id = $1 RETURNING game_character`,
241
+ [builderId, value]
242
+ );
243
+ return rows[0] ? rows[0].game_character : null;
244
+ }
245
+
210
246
  // The same two facts for a whole list of builders, in ONE statement (task
211
247
  // 1004437): the public leaderboard names every row's roles. Map<idString, row>.
212
248
  async function getBuilderRoleChoices(builderIds) {
@@ -1142,6 +1178,8 @@ module.exports = {
1142
1178
  UPDATE_BUILDER_DISCIPLINES_SQL,
1143
1179
  getBuilderRoleChoice,
1144
1180
  getBuilderRoleChoices,
1181
+ getBuilderCharacter,
1182
+ setBuilderCharacter,
1145
1183
  RANKS,
1146
1184
  setBuilderRank,
1147
1185
  setBuilderTester,
@@ -378,7 +378,85 @@ async function deleteDeployKey(token, { owner, name, keyId, fetchImpl = fetch }
378
378
  throw new GithubApiError(`GitHub deploy-key delete failed (${res.status})`, { status: res.status, code: 'github_error' });
379
379
  }
380
380
 
381
+ // ---- repo collaborators: inviting a person to the repo itself ----
382
+ // A GitHub invite is a SEPARATE door from a Bongos invite. Bongos admission is a row
383
+ // in the project's own database; this puts the person on the repo's collaborator list
384
+ // so they can see (and, only when asked, push to) the code on their own GitHub.
385
+ // ONE permission is offered: 'pull' (read). A builder ships through the server's own
386
+ // publish and needs no write access, so Bongos never grants it (owner decision,
387
+ // 2026-11-04): 'push', 'admin', 'maintain' and 'triage' are all refused. An owner who
388
+ // wants someone to have write access gives it on GitHub directly.
389
+ const REPO_INVITE_PERMISSIONS = Object.freeze(['pull']);
390
+
391
+ function repoPath(owner, name) {
392
+ return `${GH_API}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`;
393
+ }
394
+
395
+ // PUT /collaborators/{login}. 201 = a pending invitation was created, 204 = they
396
+ // already have access. Returns { state: 'invited' | 'already_collaborator', ... }.
397
+ async function inviteRepoCollaborator(token, { owner, name, login, permission = 'pull', fetchImpl = fetch } = {}) {
398
+ if (!REPO_INVITE_PERMISSIONS.includes(permission)) {
399
+ throw new GithubApiError('Only read-only access can be offered', { status: 400, code: 'bad_permission' });
400
+ }
401
+ const res = await fetchImpl(`${repoPath(owner, name)}/collaborators/${encodeURIComponent(login)}`, {
402
+ method: 'PUT',
403
+ headers: ghHeaders(token, { 'Content-Type': 'application/json' }),
404
+ body: JSON.stringify({ permission }),
405
+ });
406
+ if (res.status === 201) {
407
+ const inv = await res.json().catch(() => ({}));
408
+ return { state: 'invited', invitation_id: inv.id || null, permission };
409
+ }
410
+ if (res.status === 204) return { state: 'already_collaborator', permission };
411
+ if (res.status === 401) {
412
+ throw new GithubApiError('GitHub rejected the stored token', { status: 401, code: 'github_unauthorized' });
413
+ }
414
+ if (res.status === 403) {
415
+ throw new GithubApiError('GitHub refused — adding people needs repository admin', { status: 403, code: 'not_repo_admin' });
416
+ }
417
+ if (res.status === 404) {
418
+ throw new GithubApiError('GitHub answered 404 for that repository or user', { status: 404, code: 'repo_not_found' });
419
+ }
420
+ if (res.status === 422) {
421
+ throw new GithubApiError('GitHub would not invite that account', { status: 422, code: 'cannot_invite' });
422
+ }
423
+ throw new GithubApiError(`GitHub collaborator invite failed (${res.status})`, { status: res.status, code: 'github_error' });
424
+ }
425
+
426
+ // Where one person stands on the repo: 'collaborator' (accepted), 'pending' (the
427
+ // invitation is out and not yet accepted), or 'none' (never invited, declined or
428
+ // expired). Two reads: the collaborator check, then the open invitations.
429
+ async function repoInviteState(token, { owner, name, login, fetchImpl = fetch } = {}) {
430
+ const who = encodeURIComponent(login);
431
+ const c = await fetchImpl(`${repoPath(owner, name)}/collaborators/${who}`, { headers: ghHeaders(token) });
432
+ if (c.status === 204) return { state: 'collaborator' };
433
+ if (c.status === 401) {
434
+ throw new GithubApiError('GitHub rejected the stored token', { status: 401, code: 'github_unauthorized' });
435
+ }
436
+ if (c.status === 403) {
437
+ throw new GithubApiError('GitHub refused — reading collaborators needs repository admin', { status: 403, code: 'not_repo_admin' });
438
+ }
439
+ if (c.status !== 404) {
440
+ throw new GithubApiError(`GitHub collaborator read failed (${c.status})`, { status: c.status, code: 'github_error' });
441
+ }
442
+ const i = await fetchImpl(`${repoPath(owner, name)}/invitations?per_page=${PER_PAGE}`, { headers: ghHeaders(token) });
443
+ if (i.status === 404) {
444
+ throw new GithubApiError('GitHub answered 404 for that repository', { status: 404, code: 'repo_not_found' });
445
+ }
446
+ if (!i.ok) {
447
+ throw new GithubApiError(`GitHub invitations read failed (${i.status})`, { status: i.status, code: 'github_error' });
448
+ }
449
+ const rows = await i.json();
450
+ const mine = (Array.isArray(rows) ? rows : []).find(
451
+ (r) => r && r.invitee && String(r.invitee.login || '').toLowerCase() === String(login).toLowerCase(),
452
+ );
453
+ return mine
454
+ ? { state: 'pending', invitation_id: mine.id, permission: mine.permissions || null }
455
+ : { state: 'none' };
456
+ }
457
+
381
458
  module.exports = {
459
+ inviteRepoCollaborator, repoInviteState, REPO_INVITE_PERMISSIONS,
382
460
  listUserRepos, createUserRepo, getUserRepo, makeRepoPublic, makeRepoPrivate,
383
461
  listUserOrgs, getOrgAccess, createOrgRepo, validGithubLogin,
384
462
  listDeployKeys, addDeployKey, deleteDeployKey,
@@ -366,6 +366,28 @@ const MODULE_GLOBS = {
366
366
  agents: [
367
367
  'modules/agents/', '.claude/agents/', 'scripts/bongos/agents-sync.js',
368
368
  ],
369
+ // --- the MODULE STORE (ADR 0338, task 1004793): publishing, listing, entitlements,
370
+ // testing channel, promotion, usage reports, payouts and the assessment CLIs.
371
+ // It is core code — it lives in src/bongos/, not modules/ — so it has no
372
+ // module.json and this key is a scope like kernel or devsecops. Before this
373
+ // key NO key resolved these files, so no goal could own a change to the store
374
+ // (the 1002690 / 1002736 precedent). Its HTTP surface, src/bongos/routes/modules.js,
375
+ // stays kernel's with the rest of routes/. module-scope-map.js is kernel's too:
376
+ // the name prefix is shared, the job is not. ---
377
+ 'module-store': [
378
+ 'src/bongos/module-store.js', 'src/bongos/module-listing.js',
379
+ 'src/bongos/module-entitlements.js', 'src/bongos/module-overrides.js',
380
+ 'src/bongos/module-payouts.js', 'src/bongos/module-promotion.js',
381
+ 'src/bongos/module-rechecks.js', 'src/bongos/module-subcategories.js',
382
+ 'src/bongos/module-submissions.js', 'src/bongos/module-testing.js',
383
+ 'src/bongos/module-usage-reports.js',
384
+ 'scripts/bongos/module-store-cli.js', 'scripts/bongos/module-artifact.js',
385
+ 'scripts/bongos/module-assess-docs.js', 'scripts/bongos/module-assess-installs.js',
386
+ 'scripts/bongos/module-assess-score.js', 'scripts/bongos/module-assess-security.js',
387
+ 'scripts/bongos/module-assess-tests.js', 'scripts/bongos/module-assess-version.js',
388
+ 'scripts/bongos/module-error-report.js', 'scripts/bongos/module-remove.js',
389
+ 'scripts/bongos/module-test-runner.js',
390
+ ],
369
391
  // --- the hub identity provider + the PROJECTS CATALOG it owns (ADR 0141): platform
370
392
  // accounts, the SSO client registry, project memberships/invites, and the
371
393
  // platform_identity_projects rows the Explore feed reads. Carved already, so the