@bongos/core 1.24.79 → 1.24.81

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 (413) hide show
  1. package/.bongos-core.json +554 -394
  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/0402-helm-is-the-halls-look-and-the-hall-is-dark-only.md +142 -0
  17. package/docs/adr/README.md +4 -0
  18. package/docs/api/openapi.json +195 -3
  19. package/docs/api-reference.md +8 -4
  20. package/docs/architecture.md +7 -3
  21. package/docs/copy-inventory.md +393 -378
  22. package/docs/copy-registry.json +722 -578
  23. package/docs/design/characters/character-sprites-source.webp +0 -0
  24. package/docs/file-map.md +7 -8
  25. package/docs/module-api-changelog.md +4 -0
  26. package/docs/modules-contract.md +5 -0
  27. package/docs/onboarding/slash-commands.md +1 -1
  28. package/docs/packs/{engineer.md → artisan.md} +5 -5
  29. package/docs/packs/artist.md +50 -4
  30. package/docs/page-inventory.json +16 -7
  31. package/docs/page-readings.json +3184 -3338
  32. package/docs/{packs/ideator.md → recipes/ideation-playbook.md} +5 -48
  33. package/migrations/core_296_crafts_artisan_artist.sql +72 -0
  34. package/migrations/core_297_builder_character.sql +30 -0
  35. package/modules/copy-desk/proposals.js +1 -1
  36. package/modules/economy/credits-by-craft.js +29 -15
  37. package/modules/hall-ui/public/ai-studio.html +6 -4
  38. package/modules/hall-ui/public/ai-studio.states.json +1 -0
  39. package/modules/hall-ui/public/atlas.html +5 -5
  40. package/modules/hall-ui/public/atlas.js +3 -1
  41. package/modules/hall-ui/public/atlas.states.json +1 -0
  42. package/modules/hall-ui/public/blockers.html +5 -5
  43. package/modules/hall-ui/public/board-room.html +4 -4
  44. package/modules/hall-ui/public/board-room.states.json +1 -0
  45. package/modules/hall-ui/public/characters/artisan.png +0 -0
  46. package/modules/hall-ui/public/characters/artist.png +0 -0
  47. package/modules/hall-ui/public/characters/patron.png +0 -0
  48. package/modules/hall-ui/public/city.css +3 -4
  49. package/modules/hall-ui/public/city.html +9 -9
  50. package/modules/hall-ui/public/city.js +17 -56
  51. package/modules/hall-ui/public/city.states.json +7 -32
  52. package/modules/hall-ui/public/collab.html +4 -4
  53. package/modules/hall-ui/public/collab.states.json +1 -0
  54. package/modules/hall-ui/public/commands.html +4 -4
  55. package/modules/hall-ui/public/commands.states.json +1 -0
  56. package/modules/hall-ui/public/controls.css +63 -11
  57. package/modules/hall-ui/public/copy-desk.html +4 -4
  58. package/modules/hall-ui/public/copy-desk.states.json +1 -0
  59. package/modules/hall-ui/public/craft-mode.js +258 -64
  60. package/modules/hall-ui/public/deploy.html +4 -4
  61. package/modules/hall-ui/public/deploy.states.json +1 -0
  62. package/modules/hall-ui/public/diagram-viewer.js +2 -1
  63. package/modules/hall-ui/public/dom-utils.js +1 -1
  64. package/modules/hall-ui/public/goal-map.html +9 -6
  65. package/modules/hall-ui/public/goals.html +5 -5
  66. package/modules/hall-ui/public/goals.states.json +1 -0
  67. package/modules/hall-ui/public/government.html +4 -4
  68. package/modules/hall-ui/public/government.states.json +1 -0
  69. package/modules/hall-ui/public/hall-render.js +3 -1
  70. package/modules/hall-ui/public/home-city-draw.js +3 -3
  71. package/modules/hall-ui/public/home-city.css +4 -0
  72. package/modules/hall-ui/public/home-city.js +17 -9
  73. package/modules/hall-ui/public/home-gallery.js +8 -1
  74. package/modules/hall-ui/public/home-idea-box.css +2 -2
  75. package/modules/hall-ui/public/home-idea-box.js +3 -3
  76. package/modules/hall-ui/public/idea.html +5 -5
  77. package/modules/hall-ui/public/idea.states.json +1 -0
  78. package/modules/hall-ui/public/ideas.html +5 -5
  79. package/modules/hall-ui/public/ideas.states.json +1 -0
  80. package/modules/hall-ui/public/index-founding.states.json +1 -0
  81. package/modules/hall-ui/public/index-genesis-decide.states.json +1 -0
  82. package/modules/hall-ui/public/index-genesis-demo.states.json +1 -0
  83. package/modules/hall-ui/public/index-genesis.states.json +1 -0
  84. package/modules/hall-ui/public/index.html +11 -7
  85. package/modules/hall-ui/public/index.states.json +1 -0
  86. package/modules/hall-ui/public/market-watch-item.html +4 -4
  87. package/modules/hall-ui/public/market-watch-item.states.json +1 -0
  88. package/modules/hall-ui/public/market-watch-rules.html +4 -4
  89. package/modules/hall-ui/public/market-watch-rules.states.json +1 -0
  90. package/modules/hall-ui/public/market-watch.html +6 -9
  91. package/modules/hall-ui/public/market-watch.js +22 -45
  92. package/modules/hall-ui/public/market-watch.states.json +1 -0
  93. package/modules/hall-ui/public/modules.html +4 -4
  94. package/modules/hall-ui/public/modules.states.json +1 -0
  95. package/modules/hall-ui/public/money.html +4 -4
  96. package/modules/hall-ui/public/money.states.json +1 -0
  97. package/modules/hall-ui/public/my-agents.html +4 -4
  98. package/modules/hall-ui/public/my-agents.states.json +1 -0
  99. package/modules/hall-ui/public/options-menu.js +409 -0
  100. package/modules/hall-ui/public/paintbrush.js +3 -3
  101. package/modules/hall-ui/public/palette.js +11 -13
  102. package/modules/hall-ui/public/primer.html +5 -5
  103. package/modules/hall-ui/public/primer.states.json +1 -0
  104. package/modules/hall-ui/public/profile-crafts.js +1 -1
  105. package/modules/hall-ui/public/profile-roles.js +23 -19
  106. package/modules/hall-ui/public/profile.css +1 -1
  107. package/modules/hall-ui/public/profile.html +19 -18
  108. package/modules/hall-ui/public/profile.js +19 -18
  109. package/modules/hall-ui/public/profile.states.json +5 -4
  110. package/modules/hall-ui/public/project-settings.html +4 -4
  111. package/modules/hall-ui/public/roster.html +4 -4
  112. package/modules/hall-ui/public/roster.js +3 -3
  113. package/modules/hall-ui/public/roster.states.json +1 -0
  114. package/modules/hall-ui/public/settings.css +24 -4
  115. package/modules/hall-ui/public/settings.html +21 -17
  116. package/modules/hall-ui/public/settings.js +44 -44
  117. package/modules/hall-ui/public/settings.states.json +1 -0
  118. package/modules/hall-ui/public/shell-game.css +110 -0
  119. package/modules/hall-ui/public/shell-game.js +316 -3
  120. package/modules/hall-ui/public/shell.js +15 -66
  121. package/modules/hall-ui/public/sky-interaction.js +7 -0
  122. package/modules/hall-ui/public/speciality-tools.js +1 -2
  123. package/modules/hall-ui/public/street-card.states.json +1 -0
  124. package/modules/hall-ui/public/street-inside.js +3 -4
  125. package/modules/hall-ui/public/street.css +1 -5
  126. package/modules/hall-ui/public/street.html +6 -10
  127. package/modules/hall-ui/public/street.js +8 -59
  128. package/modules/hall-ui/public/street.states.json +7 -32
  129. package/modules/hall-ui/public/studio.html +5 -5
  130. package/modules/hall-ui/public/studio.states.json +1 -0
  131. package/modules/hall-ui/public/style.css +197 -112
  132. package/modules/hall-ui/public/task-detail.js +5 -4
  133. package/modules/hall-ui/public/task.html +5 -5
  134. package/modules/hall-ui/public/task.states.json +1 -0
  135. package/modules/hall-ui/public/thinking.css +10 -0
  136. package/modules/hall-ui/public/thinking.html +9 -6
  137. package/modules/hall-ui/public/thinking.states.json +1 -0
  138. package/modules/hall-ui/public/tweak-editor.html +5 -5
  139. package/modules/hall-ui/public/tweak-editor.js +5 -1
  140. package/modules/hall-ui/public/tweak-editor.states.json +1 -0
  141. package/modules/hall-ui/public/update-pill.js +91 -2
  142. package/modules/hall-ui/public/visor.css +80 -0
  143. package/modules/hall-ui/public/visor.js +316 -0
  144. package/modules/hall-ui/public/watch-repo-invite.js +159 -0
  145. package/modules/hall-ui/public/watch.html +6 -5
  146. package/modules/hall-ui/public/watch.js +17 -2
  147. package/modules/hall-ui/public/watch.states.json +1 -0
  148. package/modules/hall-ui/public/welcome-boot.js +15 -6
  149. package/modules/hall-ui/public/work-founding.states.json +1 -0
  150. package/modules/hall-ui/public/work.html +5 -5
  151. package/modules/hall-ui/public/work.states.json +1 -0
  152. package/modules/hall-ui/records/builders-roster.md +2 -2
  153. package/modules/hall-ui/records/craft-mode.md +13 -11
  154. package/modules/hall-ui/records/game-mode.md +2 -2
  155. package/modules/hall-ui/records/governor-street.md +1 -1
  156. package/modules/hall-ui/records/home-city.md +3 -1
  157. package/modules/hall-ui/records/market-watch-settings.md +2 -0
  158. package/modules/hall-ui/records/options-menu.md +15 -0
  159. package/modules/hall-ui/records/profile-roles.md +11 -11
  160. package/modules/hall-ui/records/sky.md +3 -3
  161. package/modules/hall-ui/records/start-screen.md +25 -0
  162. package/modules/hall-ui/records/tweak-editor.md +1 -1
  163. package/modules/hall-ui/records/update-pill.md +12 -0
  164. package/modules/hall-ui/records/visor.md +23 -0
  165. package/modules/hall-ui/records/welcome-tower.md +5 -5
  166. package/modules/hall-ui/specimens/components.states.json +2 -1
  167. package/modules/ideas/ideator-stats.js +3 -1
  168. package/modules/ideas/inbox.js +2 -2
  169. package/modules/lifecycle/activity.js +1 -1
  170. package/modules/lifecycle/cascade.js +4 -4
  171. package/modules/lifecycle/goal-spec-task.js +1 -1
  172. package/modules/lifecycle/help-requests.js +19 -15
  173. package/modules/lifecycle/kickoff-checklist.js +3 -3
  174. package/modules/lifecycle/kickoff-seed.js +1 -1
  175. package/modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql +29 -0
  176. package/modules/lifecycle/mingle.js +4 -4
  177. package/modules/lifecycle/page-tweak-write.js +1 -1
  178. package/modules/lifecycle/routes/task-write-routes.js +3 -1
  179. package/modules/lifecycle/routes/tasks.js +3 -3
  180. package/modules/lifecycle/ship-card.js +2 -3
  181. package/modules/lifecycle/task-classifier.js +39 -29
  182. package/modules/lifecycle/tests/cascade.mjs +7 -7
  183. package/modules/onboarding/founding-mode.js +3 -0
  184. package/modules/onboarding/genesis-home.js +21 -1
  185. package/modules/onboarding/routes/onboarding.js +2 -0
  186. package/modules/page-notes/notes.js +3 -2
  187. package/modules/page-notes/split.js +1 -1
  188. package/modules/platform-identity/craft-rollup.js +35 -7
  189. package/modules/platform-identity/migrations/platform_identity_036_crafts_artisan_artist.sql +45 -0
  190. package/modules/platform-identity/platform-identity.js +2 -2
  191. package/modules/platform-identity/routes/community-leaderboard.js +2 -1
  192. package/modules/platform-identity/routes/community-search.js +2 -1
  193. package/modules/platform-identity/routes/sso.js +2 -1
  194. package/modules/platform-identity/tests/platform-identity.mjs +6 -6
  195. package/modules/public-landing/public/community.html +3 -5
  196. package/modules/public-landing/public/index.html +1 -1
  197. package/modules/public-landing/public/projects.html +1 -1
  198. package/modules/specialities/migrations/specialities_004_crafts_artisan_artist.sql +30 -0
  199. package/modules/specialities/routes/specialities.js +2 -2
  200. package/modules/status-ui/public/status.js +1 -1
  201. package/modules/ui-design/kit/fixtures/community-leaderboard.json +10 -10
  202. package/modules/ui-design/kit/fixtures/community-search.json +2 -2
  203. package/modules/ui-design/kit/fixtures/scouting-recruiter.json +1 -1
  204. package/modules/ui-design/public/brand.html +2 -2
  205. package/package-lock.json +2 -2
  206. package/package.json +1 -1
  207. package/release-notes.json +76 -0
  208. package/scripts/bongos/autobongos-loop.js +1 -1
  209. package/scripts/bongos/discipline-modes.json +7 -12
  210. package/scripts/bongos/fitness.js +1 -1
  211. package/scripts/bongos/gen-hall-sitemap.js +7 -7
  212. package/scripts/bongos/grade-smoke.js +2 -2
  213. package/scripts/bongos/init.js +2 -2
  214. package/scripts/bongos/newcomer-floor.config.json +1 -2
  215. package/scripts/bongos/newcomer-floor.js +4 -3
  216. package/scripts/bongos/newcomer-floor.templates.json +10 -12
  217. package/scripts/bongos/newcomer-floor.test.js +47 -38
  218. package/scripts/bongos/role-pack-guard.js +5 -4
  219. package/scripts/bongos/seed-tasks.example.json +3 -3
  220. package/scripts/bongos/setup.js +20 -107
  221. package/scripts/bongos/start.js +2 -2
  222. package/scripts/bongos/task.js +5 -4
  223. package/scripts/bongos/triage.js +1 -1
  224. package/scripts/bongos/worktree.js +16 -4
  225. package/scripts/hall-preview/README.md +2 -3
  226. package/scripts/setup-builder.js +6 -46
  227. package/src/bongos/auth-admission.js +8 -8
  228. package/src/bongos/craft-mode.js +2 -0
  229. package/src/bongos/db.js +39 -1
  230. package/src/bongos/github-repos.js +78 -0
  231. package/src/bongos/module-scope-map.js +22 -0
  232. package/src/bongos/role-stats.js +27 -11
  233. package/src/bongos/routes/github.js +100 -2
  234. package/src/bongos/routes/me.js +33 -0
  235. package/src/bongos/serve-internal.js +33 -11
  236. package/src/crafts.js +66 -0
  237. package/src/hall-helm.js +79 -0
  238. package/src/module-api.js +6 -1
  239. package/src/module-loader/manifest-schema.js +16 -0
  240. package/src/modules.js +2 -2
  241. package/src/role-responsibilities.js +6 -5
  242. package/tests/acclaim_feed_evidence_db.mjs +1 -1
  243. package/tests/activity_rollup_as_of.mjs +1 -1
  244. package/tests/api_alias_caller_ratchet.mjs +3 -3
  245. package/tests/application_lifecycle.mjs +1 -1
  246. package/tests/artist_review.mjs +1 -1
  247. package/tests/artist_review_goal.mjs +1 -1
  248. package/tests/blocker_hall_live_proof.mjs +1 -1
  249. package/tests/blocker_tasks_named_db.mjs +2 -2
  250. package/tests/canonical_profile_url.mjs +1 -1
  251. package/tests/cap_newcomer_tasks.mjs +13 -13
  252. package/tests/character_creation.mjs +294 -0
  253. package/tests/claim_gate_open_blocker.mjs +2 -2
  254. package/tests/claim_gate_rebase.mjs +6 -6
  255. package/tests/claude_materialize.mjs +1 -1
  256. package/tests/clawback_confirmed_backlog.mjs +3 -3
  257. package/tests/collab_page.mjs +8 -9
  258. package/tests/collab_self_settling.mjs +2 -2
  259. package/tests/community_leaderboard_route.mjs +13 -5
  260. package/tests/community_proof_db.mjs +3 -0
  261. package/tests/community_search_route.mjs +7 -6
  262. package/tests/conductor.mjs +1 -1
  263. package/tests/conductor_main_e2e.mjs +8 -8
  264. package/tests/copy_desk_flags_db.mjs +1 -1
  265. package/tests/copy_desk_page_approve.mjs +2 -2
  266. package/tests/copy_desk_page_claim.mjs +2 -2
  267. package/tests/copy_desk_page_next.mjs +1 -1
  268. package/tests/copy_desk_page_release.mjs +1 -1
  269. package/tests/copy_desk_page_submit.mjs +2 -2
  270. package/tests/core_258_box_session_claims_db.mjs +1 -1
  271. package/tests/craft_mode.mjs +48 -34
  272. package/tests/craft_mode_home.mjs +20 -20
  273. package/tests/craft_mode_looks.mjs +16 -15
  274. package/tests/craft_mode_nav.mjs +31 -29
  275. package/tests/craft_review_mode.mjs +46 -41
  276. package/tests/crafts_vocabulary.mjs +70 -0
  277. package/tests/cross_discipline_priority_direction.mjs +5 -5
  278. package/tests/design_reference.mjs +1 -1
  279. package/tests/fitness.mjs +7 -11
  280. package/tests/funded_project_pays_proof_db.mjs +1 -1
  281. package/tests/genesis_home.mjs +21 -1
  282. package/tests/github_chip.mjs +3 -3
  283. package/tests/github_repo_invite.mjs +379 -0
  284. package/tests/goal_criterion_confirm_ui.mjs +1 -1
  285. package/tests/goal_invite_consent.mjs +1 -1
  286. package/tests/goal_map_page.mjs +2 -2
  287. package/tests/goal_sort_staleness.mjs +1 -1
  288. package/tests/goal_spec_task.mjs +1 -1
  289. package/tests/goal_spec_task_db.mjs +3 -3
  290. package/tests/grade_engine.mjs +2 -2
  291. package/tests/grader_no_artifact.mjs +1 -1
  292. package/tests/grader_prompt_fencing.mjs +1 -1
  293. package/tests/grader_prompt_window_bound.mjs +1 -1
  294. package/tests/grader_reader_lens.mjs +2 -2
  295. package/tests/grader_severity_gate.mjs +1 -1
  296. package/tests/grader_unbacked_score.mjs +1 -1
  297. package/tests/hall_approval_queue.mjs +1 -1
  298. package/tests/hall_board_world.mjs +2 -2
  299. package/tests/hall_builders_page_pass.mjs +3 -2
  300. package/tests/hall_city.mjs +16 -30
  301. package/tests/hall_controls.mjs +52 -4
  302. package/tests/hall_fullscreen.mjs +27 -3
  303. package/tests/hall_helm.mjs +187 -0
  304. package/tests/hall_kit_charts.mjs +1 -1
  305. package/tests/hall_landing_boot.mjs +1 -1
  306. package/tests/hall_my_agents.mjs +16 -16
  307. package/tests/hall_nav.mjs +22 -15
  308. package/tests/hall_options_menu.mjs +247 -0
  309. package/tests/hall_oversight_world.mjs +1 -1
  310. package/tests/hall_palette.mjs +8 -9
  311. package/tests/hall_profile_world.mjs +3 -3
  312. package/tests/hall_record_world.mjs +6 -4
  313. package/tests/hall_settings_world.mjs +2 -1
  314. package/tests/hall_sitemap.mjs +6 -5
  315. package/tests/hall_start_screen.mjs +307 -0
  316. package/tests/hall_street.mjs +15 -28
  317. package/tests/hall_street_inside.mjs +17 -9
  318. package/tests/hall_studio_world.mjs +3 -3
  319. package/tests/hall_topbar_320.mjs +4 -2
  320. package/tests/hall_visor.mjs +224 -0
  321. package/tests/help_requests.mjs +47 -15
  322. package/tests/helpers.mjs +4 -4
  323. package/tests/home_city.mjs +6 -4
  324. package/tests/home_city_widget.mjs +33 -14
  325. package/tests/home_gallery_widget.mjs +4 -4
  326. package/tests/home_idea_box.mjs +6 -6
  327. package/tests/hub_community_page.mjs +3 -3
  328. package/tests/hub_craft_rollup.mjs +75 -36
  329. package/tests/hub_recent_ships.mjs +3 -2
  330. package/tests/idea_cascade.mjs +2 -2
  331. package/tests/idea_credit_streams_db.mjs +1 -1
  332. package/tests/idea_detail_page.mjs +1 -1
  333. package/tests/idea_ideate_full.mjs +5 -2
  334. package/tests/idea_spark_hall.mjs +1 -1
  335. package/tests/init.mjs +3 -3
  336. package/tests/instance_manifest.mjs +2 -2
  337. package/tests/kickoff_checklist.mjs +1 -1
  338. package/tests/leaderboard_roles.mjs +34 -25
  339. package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
  340. package/tests/lightbulb.mjs +14 -10
  341. package/tests/main_discipline.mjs +8 -8
  342. package/tests/markdown_line_endings.mjs +1 -1
  343. package/tests/market_watch_pages.mjs +52 -74
  344. package/tests/me_streak_leaderboard_queries.mjs +3 -3
  345. package/tests/migration_allocation.mjs +1 -1
  346. package/tests/mingle.mjs +36 -21
  347. package/tests/module-scope-map.mjs +22 -2
  348. package/tests/module_api.mjs +1 -0
  349. package/tests/module_contributions.mjs +13 -13
  350. package/tests/module_manifest.mjs +29 -0
  351. package/tests/module_skills_hook.mjs +2 -2
  352. package/tests/modules.mjs +14 -7
  353. package/tests/nav_permission_atoms.mjs +2 -1
  354. package/tests/needs_rebase.mjs +1 -1
  355. package/tests/newcomer_restock.mjs +13 -13
  356. package/tests/page_notes_panel.mjs +22 -22
  357. package/tests/page_tweak_no_block.mjs +1 -1
  358. package/tests/paintbrush.mjs +7 -7
  359. package/tests/privacy_matrix.mjs +1 -0
  360. package/tests/profile_activity.mjs +20 -23
  361. package/tests/profile_earnings.mjs +1 -1
  362. package/tests/profile_nudge_prefs.mjs +4 -4
  363. package/tests/profile_role_headline.mjs +73 -46
  364. package/tests/profile_role_stats.mjs +55 -45
  365. package/tests/profile_rollup_consent.mjs +19 -6
  366. package/tests/profile_route.mjs +12 -12
  367. package/tests/profile_ui_cross_project.mjs +4 -4
  368. package/tests/project_invite_suggestions.mjs +2 -2
  369. package/tests/promote_goal_id.mjs +1 -1
  370. package/tests/publish_manifest.mjs +1 -1
  371. package/tests/rank_floor_wiring.mjs +1 -1
  372. package/tests/regrade_actual_minutes.mjs +1 -1
  373. package/tests/removal_proposal.mjs +2 -2
  374. package/tests/reward_gate.mjs +4 -4
  375. package/tests/reward_mode_cost_plus_only.mjs +1 -1
  376. package/tests/role_responsibilities.mjs +18 -12
  377. package/tests/role_stats_db.mjs +17 -16
  378. package/tests/scouting_rollup_fanout.mjs +5 -5
  379. package/tests/seed_tasks.mjs +5 -2
  380. package/tests/session_earnings_mirror_db.mjs +1 -1
  381. package/tests/session_reward_on_land_db.mjs +1 -1
  382. package/tests/session_reward_reconcile_db.mjs +2 -2
  383. package/tests/sessions_kit_pager.mjs +1 -1
  384. package/tests/settings_art_key_absent.mjs +2 -2
  385. package/tests/settings_main_role.mjs +21 -20
  386. package/tests/setup_flow.mjs +3 -3
  387. package/tests/setup_smoke.mjs +20 -0
  388. package/tests/ship_card.mjs +6 -6
  389. package/tests/specialities.mjs +16 -4
  390. package/tests/speciality_db_shape.mjs +6 -6
  391. package/tests/speciality_injection.mjs +17 -17
  392. package/tests/speciality_offer_ui.mjs +5 -5
  393. package/tests/speciality_offered.mjs +6 -5
  394. package/tests/speciality_session_skills.mjs +7 -7
  395. package/tests/speciality_skills.mjs +11 -11
  396. package/tests/speciality_walkthrough_ui.mjs +2 -2
  397. package/tests/sso_checkin_identity_spoof.mjs +2 -1
  398. package/tests/task_classifier_vocab.mjs +10 -0
  399. package/tests/task_cli.mjs +11 -3
  400. package/tests/task_criteria_unlink_db.mjs +2 -2
  401. package/tests/task_discipline_required.mjs +38 -16
  402. package/tests/task_goal_required.mjs +4 -4
  403. package/tests/task_needs_db.mjs +1 -1
  404. package/tests/tweak_replaces_artist_review.mjs +1 -1
  405. package/tests/update_pill.mjs +153 -2
  406. package/tests/upkeep_home.mjs +1 -1
  407. package/tests/v3_c1_options_proof.mjs +145 -0
  408. package/tests/visual_review.mjs +3 -3
  409. package/tests/watch_applications_queue.mjs +8 -5
  410. package/tests/watch_sealed_floor.mjs +3 -1
  411. package/tests/welcome_boot.mjs +12 -9
  412. package/tests/welcome_boot_browser.mjs +13 -13
  413. package/tests/worktree_helper.mjs +16 -1
@@ -217,6 +217,12 @@ module.exports = function buildMeRouter() {
217
217
  const openPayoutHoldsP = (async () => {
218
218
  try { return await require('../payout-gates').openHoldsFor(req.builder.id); } catch (_) { return null; }
219
219
  })();
220
+ // task 1005147: the character this builder holds, or null (not picked yet — the
221
+ // hall then shows character creation). Fail-open to null: a core that runs
222
+ // before core_297 has no column, and that must never break /me.
223
+ const characterP = (async () => {
224
+ try { return await db.getBuilderCharacter(req.builder.id); } catch (_) { return null; }
225
+ })();
220
226
  const stuckPageTweaksP = (async () => {
221
227
  try {
222
228
  const port = seams.resolveOptional('copy-desk.stuckPageTweaksFor');
@@ -476,6 +482,9 @@ module.exports = function buildMeRouter() {
476
482
  // when lifecycle is disabled or the read failed, and the shell treats null
477
483
  // as OFF — a mode that cannot be read is not on.
478
484
  social: socialMode,
485
+ // task 1005147: the builder's character ('artisan' | 'artist' | 'patron'),
486
+ // or null until they pick one. Identity and view only; rank still gates.
487
+ character: await characterP,
479
488
  // task 1004422 (BV2.PS03, goal 1000121): PROJECT FOUNDING MODE. Genesis is a
480
489
  // mode the EXISTING hall wears (the blend rule, docs/design/project-startup-
481
490
  // direction.md), so it rides /me exactly like social: the shell stamps
@@ -724,6 +733,30 @@ module.exports = function buildMeRouter() {
724
733
  }
725
734
  });
726
735
 
736
+ // task 1005147 (BONGOS-V3 G2): character creation. Body: { character } —
737
+ // 'artisan' | 'artist' | 'patron', or null to clear it (the hall clears it and
738
+ // signs out: switching character is signing out and back in, spec settled point 2).
739
+ // Identity and view only: no claim, vote, deploy or spend gate reads it
740
+ // (criterion character-is-identity-not-authority). 422 invalid_character otherwise.
741
+ // rank: any-builder — a builder writes only their own row (req.builder.id), the
742
+ // /me/disciplines posture.
743
+ router.put('/me/character', auth.requireBuilder, async (req, res) => {
744
+ // Nullable by the validator's default: null is the deliberate clear.
745
+ if (validateOrRespond(req, res, { character: { type: 'string', maxLength: 32 } }, { strict: true })) return;
746
+ const body = req.body || {};
747
+ if (!Object.prototype.hasOwnProperty.call(body, 'character')) {
748
+ return res.fail('character_required', 400);
749
+ }
750
+ const raw = body.character;
751
+ try {
752
+ const character = await db.setBuilderCharacter(req.builder.id, raw);
753
+ res.json({ character });
754
+ } catch (err) {
755
+ if (err.code === 'invalid_character') return res.fail('invalid_character', 422);
756
+ throw err;
757
+ }
758
+ });
759
+
727
760
  // V3.R10 (#235), criterion C3 — link the authed builder's Anthropic account.
728
761
  // Body: { anthropic_email, anthropic_user_id }. Self-attestation: the builder's
729
762
  // own Claude Code reads ~/.claude.json oauthAccount during /builder-setup,
@@ -45,6 +45,7 @@ const { buildInfo } = require('../build-info');
45
45
  const { buildBongosDownloads, installScriptReader } = require('../bongos-downloads');
46
46
  const { clientBranding, branding, applyBrandConditionals } = require('../branding');
47
47
  const { themeMarkup } = require('../theme-markup');
48
+ const { applyHelm } = require('../hall-helm');
48
49
  const { linkPreviewTags } = require('../link-preview');
49
50
  const { prelaunchGate } = require('./prelaunch-gate');
50
51
  const { platformVisibilityGate } = require('./platform-visibility-gate');
@@ -126,8 +127,12 @@ function injectBrandingGlobal(html, brand = clientBranding(), mods = clientModul
126
127
  // §4), injected the same way as __BRANDING__ so hall/status scripts can hide
127
128
  // UI sections for off modules synchronously (no fetch, no race). R60 wires the
128
129
  // actual section-hiding; this exposes the data. The link-preview card (task 1005091) rides along.
129
- const stamped = applyBrandTokens(html, brand);
130
- const tag = `${clientBundleScriptTag()}<script>window.__BRANDING__=${jsonForScript(brand)};window.__MODULES__=${jsonForScript(mods)};</script>${linkPreviewTags(stamped, brand, safePublicOrigin())}${themeMarkup(brand.theme)}`;
130
+ // A HALL page (one that links the hall's sheet) is pinned dark and, unless its pack
131
+ // chose faces of its own, takes the Helm faces (task 1005192, ADR 0402, src/hall-helm.js).
132
+ // Every other page comes back from applyHelm untouched.
133
+ const helm = applyHelm(applyBrandTokens(html, brand), brand.theme);
134
+ const stamped = helm.html;
135
+ const tag = `${clientBundleScriptTag()}<script>window.__BRANDING__=${jsonForScript(brand)};window.__MODULES__=${jsonForScript(mods)};</script>${linkPreviewTags(stamped, brand, safePublicOrigin())}${themeMarkup(brand.theme)}${helm.tail}`;
131
136
  return stamped.includes('</head>') ? stamped.replace('</head>', `${tag}</head>`) : tag + stamped;
132
137
  }
133
138
 
@@ -965,18 +970,35 @@ function mountInternalSurfaces(app) {
965
970
  return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/roster${qs}`);
966
971
  });
967
972
 
968
- // task 1005045 (owner decision 2026-11-03, idea 1001310): the owner's Market Watch
969
- // settings left their own page (task 1004938) for an owner-only Settings tab on
970
- // /market-watch, so the sidebar says "Market Watch" once. The old URL lands on that
971
- // tab; 302 for the same reason as the directory above, and no page gate: the redirect
972
- // discloses nothing, /market-watch has its own, and the settings routes are the wall.
973
- const RETIRED_MW_SETTINGS_RE = /^\/builders\/market-watch-settings(?:\.html)?\/?$/;
973
+ // THE ONE OPTIONS MENU (task 1005144, BONGOS-V3 R04, ADR 0401). Every setting is
974
+ // reached from Settings, which is the options menu now; the screens that were pages
975
+ // of their own open INSIDE it, in a frame that asks for the old address with
976
+ // ?embed=1. A plain visit to an old address (a bookmark, a doc, a Discord message)
977
+ // lands on the menu with that screen open (?open=<entry>), and the browser carries
978
+ // the old #anchor across the redirect because the Location names none. The framed
979
+ // request passes through to the page and its own page gate below. 302 and no gate,
980
+ // as the redirects that follow: the redirect discloses nothing, Settings has its own
981
+ // sign-in gate, and each screen keeps its page gate and its routes (ADR 0016).
982
+ // options-menu.js RETIRED is this map's twin (tests/hall_options_menu.mjs holds them
983
+ // equal). market-watch-settings was the owner's tab on /market-watch (task 1005045)
984
+ // and a page before that; it has no page left, so it never passes through.
985
+ const RETIRED_TO_OPTIONS = Object.freeze({
986
+ 'my-agents': 'my-agents',
987
+ 'project-settings': 'project-settings',
988
+ modules: 'modules',
989
+ government: 'permissions',
990
+ 'market-watch-rules': 'market-watch-rules',
991
+ 'market-watch-settings': 'market-watch',
992
+ });
993
+ const RETIRED_OPTIONS_RE = /^\/builders\/(my-agents|project-settings|modules|government|market-watch-rules|market-watch-settings)(?:\.html)?\/?$/;
974
994
  app.use((req, res, next) => {
975
995
  if (isModuleSurfaceHost(req)) return next();
976
996
  if (req.method !== 'GET' && req.method !== 'HEAD') return next();
977
- if (!RETIRED_MW_SETTINGS_RE.test(path.posix.normalize(req.path))) return next();
978
- const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?')) : '';
979
- return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/market-watch${qs}#settings`);
997
+ const m = RETIRED_OPTIONS_RE.exec(path.posix.normalize(req.path));
998
+ if (!m) return next();
999
+ const embed = new URLSearchParams(req.url.includes('?') ? req.url.slice(req.url.indexOf('?') + 1) : '').get('embed');
1000
+ if (embed === '1' && m[1] !== 'market-watch-settings') return next();
1001
+ return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/settings?open=${RETIRED_TO_OPTIONS[m[1]]}`);
980
1002
  });
981
1003
 
982
1004
  // Pages folded into another page; 302 and no gate, as above. Task 1005047: Fleet costs is
package/src/crafts.js ADDED
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+ // src/crafts.js — the craft vocabulary, in one place (task 1005141, BONGOS-V3 C1).
3
+ //
4
+ // The game conversion (docs/specs/bongos-v3-game-conversion.md, "Task outline / G1",
5
+ // settled point 6) names the three characters a builder holds:
6
+ //
7
+ // artisan — builds: code, infra, Bongos itself (was 'engineer')
8
+ // artist — how the project looks and reads, AND its ideas (was 'artist' + 'ideator')
9
+ // patron — watches, proposes, and (with a seat) governs (was the 'governing' craft mode)
10
+ //
11
+ // 'ui' stays a module-contributed discipline (ADR 0272); it is not a character.
12
+ //
13
+ // THE OLD SPELLINGS STILL ARRIVE. A rolled-back release, a co-tenant instance on an
14
+ // older core (federated craft rollups), an old CLI, a bookmarked ?discipline=engineer
15
+ // and a help request's historical 'builder' all still say the old word. Every input
16
+ // boundary runs a value through normalizeCraft() so the old word lands as the new one;
17
+ // nothing downstream should ever compare against 'engineer' or 'ideator' again. The
18
+ // database keeps ACCEPTING the old words too (core_296's CHECKs are a superset) because
19
+ // `bongos upgrade` rolls code back without rolling the database back — a contraction
20
+ // that drops them is a later, separate migration.
21
+ //
22
+ // The crafts a TASK can carry are artisan and artist. Patron is a character, not a
23
+ // kind of work: a task is built (artisan) or made/imagined (artist), and governing
24
+ // work is a vote, not a task.
25
+ //
26
+ // ONLY THE TWO FUNCTIONS ARE EXPORTED. A list of the crafts, display labels and a
27
+ // label helper were exported here once and nothing read them, which the dead-code
28
+ // ratchet (knip) counts as a regression. Add one back when a caller needs it.
29
+
30
+ // Old spelling → new spelling. 'builder' is the help-request spelling of the
31
+ // engineering craft (ADR 0174); 'building'/'artistry'/'ideating'/'governing' are the
32
+ // hall's old craft-mode ids (modules/hall-ui/public/craft-mode.js).
33
+ const LEGACY = Object.freeze({
34
+ engineer: 'artisan',
35
+ engineering: 'artisan',
36
+ builder: 'artisan',
37
+ building: 'artisan',
38
+ ideator: 'artist',
39
+ ideating: 'artist',
40
+ artistry: 'artist',
41
+ governing: 'patron',
42
+ governor: 'patron',
43
+ ruler: 'patron',
44
+ });
45
+
46
+ // Any spelling → today's. Non-strings pass through untouched (callers validate);
47
+ // an unknown word passes through too, so a module-contributed craft is never eaten.
48
+ function normalizeCraft(value) {
49
+ if (typeof value !== 'string') return value;
50
+ const v = value.trim().toLowerCase();
51
+ return Object.prototype.hasOwnProperty.call(LEGACY, v) ? LEGACY[v] : v;
52
+ }
53
+
54
+ // A list of crafts → today's spellings, deduped in first-seen order. The fold matters:
55
+ // ['artist', 'ideator'] is one craft now, not two.
56
+ function normalizeCrafts(list) {
57
+ if (!Array.isArray(list)) return list;
58
+ const out = [];
59
+ for (const item of list) {
60
+ const c = normalizeCraft(item);
61
+ if (!out.includes(c)) out.push(c);
62
+ }
63
+ return out;
64
+ }
65
+
66
+ module.exports = { normalizeCraft, normalizeCrafts };
@@ -0,0 +1,79 @@
1
+ // src/hall-helm.js — Helm, the look the builders' hall wears (task 1005192, ADR 0402).
2
+ //
3
+ // The hall is the game: everything after sign-in. It is dark only, and it reads in
4
+ // the two Helm faces unless its pack chose faces of its own. Both are decided HERE,
5
+ // at serve time, for one reason each:
6
+ //
7
+ // * DARK ONLY. A fixed html[data-theme="dark"] in the served markup selects the
8
+ // hall sheet's dark tier before first paint, with nothing to flash and nothing
9
+ // for the OS preference to reach (the ADR 0204 move, on the hall's own
10
+ // attribute). shell.js sets the same value again for a page read without it.
11
+ // * THE FACES. The pack splices --font-display / --font-body as a plain :root
12
+ // after every sheet, for every surface, so the landing and the hall would
13
+ // always read the same face. The hall sheet carries the Helm stacks as its own
14
+ // tokens (--helm-display / --helm-body) and hands them to the two font tokens
15
+ // under html[data-faces="helm"], which outranks the pack's :root. This file
16
+ // stamps that attribute, and links the two families, only for a hall whose
17
+ // pack still names the platform's default faces. A pack that names its own
18
+ // (config/branding.json, or a look from the style library with faces of its
19
+ // own, ADR 0358) is never stamped and keeps them.
20
+ //
21
+ // A hall page is recognised by what makes it one: it links the hall's sheet. So one
22
+ // seam (serve-internal.js injectBrandingGlobal) covers the server, the preview
23
+ // harness and the design kit's stub, and the landing, the hub and the status page,
24
+ // which never link that sheet, are never touched. PURE given `defaults`.
25
+ 'use strict';
26
+
27
+ const fs = require('node:fs');
28
+ const path = require('node:path');
29
+ const { escapeHtml: esc } = require('./html-escape');
30
+
31
+ // Barlow Condensed (the name voice) and Barlow (everything said to you), swapped in.
32
+ const HELM_FONTS_HREF = 'https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;500;600&family=Barlow:wght@400;500&display=swap';
33
+ const HALL_SHEET_RE = /<link\b[^>]*\bhref="\/builders\/style\.css[^"]*"/i;
34
+ const HTML_TAG_RE = /<html\b([^>]*)>/i;
35
+
36
+ function isHallPage(html) {
37
+ return HALL_SHEET_RE.test(String(html || ''));
38
+ }
39
+
40
+ // The platform's default faces: the neutral pack's two stacks, read once. A hall
41
+ // whose resolved pack still carries exactly these has not chosen faces.
42
+ let _defaults = null;
43
+ function defaultFaces() {
44
+ if (_defaults) return _defaults;
45
+ try {
46
+ const { resolveCoreRoot } = require('./instance-config'); // eslint-disable-line global-require
47
+ const pack = JSON.parse(fs.readFileSync(path.join(resolveCoreRoot(), 'config', 'branding.neutral.json'), 'utf8'));
48
+ const fonts = (pack.theme && pack.theme.ui && pack.theme.ui.fonts) || {};
49
+ _defaults = { display: String(fonts.display || ''), body: String(fonts.body || '') };
50
+ } catch { _defaults = { display: '', body: '' }; }
51
+ return _defaults;
52
+ }
53
+
54
+ // Whether this hall takes the Helm faces: its pack names the default ones (or none).
55
+ function wearsHelmFaces(theme, defaults = defaultFaces()) {
56
+ const fonts = (theme && theme.fonts) || {};
57
+ const same = (mine, theirs) => !mine || mine === theirs;
58
+ return same(fonts.display, defaults.display) && same(fonts.body, defaults.body);
59
+ }
60
+
61
+ // Set one attribute on the document's <html> tag, replacing a value already there.
62
+ function stampHtml(html, name, value) {
63
+ return html.replace(HTML_TAG_RE, (tag, attrs) => {
64
+ const rest = attrs.replace(new RegExp(`\\s${name}(?:="[^"]*")?`, 'i'), '');
65
+ return `<html${rest} ${name}="${esc(value)}">`;
66
+ });
67
+ }
68
+
69
+ // The whole transform for one served page: a page that does not link the hall's
70
+ // sheet comes back untouched. `tail` is markup for the end of <head>.
71
+ function applyHelm(html, theme, defaults = defaultFaces()) {
72
+ if (!isHallPage(html)) return { html, tail: '' };
73
+ let out = stampHtml(html, 'data-theme', 'dark');
74
+ if (!wearsHelmFaces(theme, defaults)) return { html: out, tail: '' };
75
+ out = stampHtml(out, 'data-faces', 'helm');
76
+ return { html: out, tail: `<link rel="stylesheet" href="${esc(HELM_FONTS_HREF)}">` };
77
+ }
78
+
79
+ module.exports = { applyHelm, isHallPage, wearsHelmFaces, stampHtml, HELM_FONTS_HREF };
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.24.79'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.24.81'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -134,6 +134,11 @@ module.exports = {
134
134
  // sleep(ms) — the one server-side "wait N milliseconds" (a Promise over setTimeout).
135
135
  // The browser twin is window.OTB.sleep; fitness fails on a third copy (task 1004893).
136
136
  sleep,
137
+ // crafts — the craft vocabulary (artisan · artist · patron, plus module 'ui') and
138
+ // normalizeCraft/normalizeCrafts, which turn an old spelling ('artisan',
139
+ // 'artist', 'builder') into today's. Every craft read from outside goes through
140
+ // it (task 1005141, src/crafts.js).
141
+ get crafts() { return require('./crafts'); },
137
142
 
138
143
  // --- auth & rank (ADR 0016) — the gates the loader composes around a module's
139
144
  // routes; a module declares the rank it needs and never wires auth itself.
@@ -16,6 +16,9 @@
16
16
  // // what update, rollout channels and re-assessment name.
17
17
  // "coreVersion": "^1.0.0", // REQUIRED. semver range of src/module-api.js CORE_VERSION it supports.
18
18
  // "default": false, // OPTIONAL (default false). Enablement fail-safe when no config names it.
19
+ // "language": "javascript", // OPTIONAL (default "javascript", task 1004794). What the module's code is
20
+ // // written in — one of MODULE_LANGUAGES. A closed list: only languages
21
+ // // the core can actually run are on it.
19
22
  // "author": "…", // OPTIONAL. Attribution (ADR 0107 §2) — portable, not a cross-GDS FK.
20
23
  // "origin": "…", // OPTIONAL. Origin instance/project this module was built in.
21
24
  // "license": "…", // OPTIONAL. e.g. "AGPL-3.0" — the license this module is offered under.
@@ -138,6 +141,14 @@ function isClaudeArtifactUrl(v) {
138
141
  return u.protocol === 'https:' && !u.username && !u.password && (host === 'claude.ai' || host.endsWith('.claude.ai'));
139
142
  }
140
143
 
144
+ // The languages a module may declare (task 1004794). Closed on purpose: a value is on
145
+ // this list only once the core can RUN a module written in it, so a manifest that says
146
+ // "python" today fails loudly at validation instead of being loaded as JavaScript and
147
+ // failing somewhere far from the cause. How a separate-program module runs beside
148
+ // Bongos is decided in its own ADR (task 1004797); that work adds its languages here.
149
+ const MODULE_LANGUAGES = Object.freeze(['javascript']);
150
+ const DEFAULT_MODULE_LANGUAGE = 'javascript';
151
+
141
152
  const MAINTENANCE_STATUSES = ['core-maintained', 'maintained', 'deprecated', 'orphaned'];
142
153
  const MAINTENANCE_KEYS = ['status', 'since', 'removeAfter', 'successor', 'note'];
143
154
  // Statuses that describe a module on its way out — these owe a deprecation path.
@@ -213,6 +224,11 @@ function validateManifest(obj) {
213
224
  // --- optional default ---
214
225
  if ('default' in obj && typeof obj.default !== 'boolean') E('default: must be a boolean when present');
215
226
 
227
+ // --- optional language (task 1004794) — absent means "javascript" ---
228
+ if ('language' in obj && !MODULE_LANGUAGES.includes(obj.language)) {
229
+ E(`language: ${JSON.stringify(obj.language)} is not a supported module language (allowed: ${MODULE_LANGUAGES.join(', ')}; omit it for "${DEFAULT_MODULE_LANGUAGE}")`);
230
+ }
231
+
216
232
  // --- optional attribution (ADR 0107 §2 — portable credit, not a cross-GDS FK) ---
217
233
  for (const k of ATTRIBUTION_KEYS) {
218
234
  if (k in obj && !isStr(obj[k])) E(`${k}: must be a non-empty string when present`);
package/src/modules.js CHANGED
@@ -86,7 +86,7 @@ const MODULE_REGISTRY = {};
86
86
  // shipping a pixel-art pipeline. Making it core aligns the one outlier list with
87
87
  // the DB CHECK and keeps the copyright exclusion untouched. Order mirrors the
88
88
  // migration 176 enum (engineer | artist | ideator | ui).
89
- const CORE_DISCIPLINES = ['engineer', 'artist', 'ideator'];
89
+ const CORE_DISCIPLINES = ['artisan', 'artist'];
90
90
 
91
91
  const KNOWN_MODULES = Object.keys(MODULE_REGISTRY);
92
92
 
@@ -475,7 +475,7 @@ function isModuleEnabled(name, set = modules()) {
475
475
  const enabledModules = (set = modules()) => knownModules().filter((k) => set[k] === true);
476
476
 
477
477
  // Disciplines this instance offers = core + every enabled module's declared
478
- // disciplines, deduped, stable order. Vanilla = ['engineer','ideator'].
478
+ // disciplines, deduped, stable order. Vanilla = ['artisan','artist'].
479
479
  function enabledDisciplines(set = modules()) {
480
480
  const reg = effectiveRegistry();
481
481
  const out = [...CORE_DISCIPLINES];
@@ -29,15 +29,16 @@
29
29
  'use strict';
30
30
 
31
31
  // discipline key (tasks.discipline / builders.preferred_disciplines) → the owner's
32
- // sentence. Keys mirror CORE_DISCIPLINES order (engineer | artist | ideator), the
33
- // migration 176 enum order. A module-contributed discipline (ui-design → 'ui') has
32
+ // sentence. Keys mirror CORE_DISCIPLINES order (artisan | artist), the
33
+ // core_296 order. A module-contributed discipline (ui-design → 'ui') has
34
34
  // no statement here on purpose: a craft a module brings is that module's to
35
35
  // describe, and inventing one for it would be exactly the paraphrase this file
36
36
  // forbids. Consumers must therefore treat "no statement" as normal, not as an error.
37
37
  const ROLE_RESPONSIBILITIES = Object.freeze({
38
- engineer: 'Running and optimizing the running of Claude nonstop, and ensuring Ideators and Artists can continue to interface with that system effectively.',
39
- artist: 'No slop in the appearance and text of the project; the story, emotion and ideology of the project are communicated effectively; project purpose and gravitas are upheld.',
40
- ideator: 'Make good ideas; be a philosopher / thought leader for the project; enable Engineers to scope and Artists to create with maximum efficiency — a baseline for creating scopes of work.',
38
+ artisan: 'Running and optimizing the running of Claude nonstop, and ensuring Artists can continue to interface with that system effectively.',
39
+ // The artist holds both of the owner's sentences since the ideator folded in
40
+ // (task 1005141, BONGOS-V3): the craft names changed, the words did not.
41
+ artist: 'No slop in the appearance and text of the project; the story, emotion and ideology of the project are communicated effectively; project purpose and gravitas are upheld. Make good ideas; be a philosopher / thought leader for the project; enable Artisans to scope and Artists to create with maximum efficiency — a baseline for creating scopes of work.',
41
42
  });
42
43
 
43
44
  // Provenance, carried with the text so every rendering can say where it came from
@@ -111,7 +111,7 @@ try {
111
111
  ]) {
112
112
  await q(
113
113
  `INSERT INTO tasks (id, version_id, title, status, shipped_by, shipped_at, discipline, kind)
114
- VALUES ($1, $2, $3, 'shipped', $4, now(), 'engineer', 'feature')`,
114
+ VALUES ($1, $2, $3, 'shipped', $4, now(), 'artisan', 'feature')`,
115
115
  [id, VER, title, SHIPPER],
116
116
  );
117
117
  }
@@ -57,7 +57,7 @@ const pin = (rel, exports) => {
57
57
  await t('reportActivityRollup sends Bongos-Report-As-Of, stamped before the totals were read', async () => {
58
58
  const senderPool = mockPool((sql) => {
59
59
  if (/FROM builders WHERE id/.test(sql)) {
60
- return { rows: [{ github_id: 42, total_credits: 7, karma: 1, rank: 'metic', preferred_disciplines: ['engineer'] }] };
60
+ return { rows: [{ github_id: 42, total_credits: 7, karma: 1, rank: 'metic', preferred_disciplines: ['artisan'] }] };
61
61
  }
62
62
  if (/count\(\*\)/.test(sql)) return { rows: [{ n: 3 }] };
63
63
  return { rows: [] };
@@ -89,7 +89,7 @@ test('the exclusions are real and each one is reachable', () => {
89
89
  'docs/architecture.md',
90
90
  'docs/canonical-permissions.md',
91
91
  'docs/recipes/parallel-goal-waves.md',
92
- 'docs/packs/engineer.md',
92
+ 'docs/packs/artisan.md',
93
93
  'docs/onboarding/drachmae-and-karma.md',
94
94
  ]) {
95
95
  assert.equal(scan.isExcludedPath(p), false, `${p} must NOT be excluded`);
@@ -112,11 +112,11 @@ test('docs/ is SPLIT: the frozen record stays out, live instruction is now count
112
112
  'docs/governance/rank-map-2026-08-06.md': 'GET /api/gds/tasks\n',
113
113
  'docs/recipes/failed-ship-recovery-proof.md': 'POST /api/gds/claims/1/resolve\n',
114
114
  'docs/recipes/how-to-do-a-thing.md': 'node scripts/bongos/api.js GET /api/gds/tasks/1\n',
115
- 'docs/packs/engineer.md': 'create a task via POST /api/gds/tasks\n',
115
+ 'docs/packs/artisan.md': 'create a task via POST /api/gds/tasks\n',
116
116
  }));
117
117
  assert.deepEqual(
118
118
  Object.keys(r.byFile).sort(),
119
- ['docs/packs/engineer.md', 'docs/recipes/how-to-do-a-thing.md'],
119
+ ['docs/packs/artisan.md', 'docs/recipes/how-to-do-a-thing.md'],
120
120
  'only the instructional docs count — and they MUST count, or the narrowing did nothing',
121
121
  );
122
122
  assert.equal(r.total, 2);
@@ -661,7 +661,7 @@ test('list-pending: the real vouch writer’s row resolves its profile and guild
661
661
 
662
662
  test('list-pending: a vouched TAKEOVER resolves the vouched applicant, not whoever filed first', async () => {
663
663
  reset();
664
- hubViews['8001'] = { profile: { view: 'sliver', sliver: { disciplines: ['engineer'] } }, guild: null };
664
+ hubViews['8001'] = { profile: { view: 'sliver', sliver: { disciplines: ['artisan'] } }, guild: null };
665
665
  await applyAs('ada', { [VOUCH_HEADER]: vouchFor('ada', { githubId: 8001, handle: 'ada-h' }) });
666
666
  const r = await call('GET', '/api/gds/access-requests');
667
667
  const ada = r.json.requests.find((x) => x.github_login === 'ada');
@@ -214,7 +214,7 @@ test('the panel names a public page as site:<first path segment>, and its ideas
214
214
 
215
215
  test('the public button and the hall palette wear one name', () => {
216
216
  const craft = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/craft-mode.js'), 'utf8');
217
- const tool = /id: 'palette', icon: 'artistry', short: '([^']+)', label: '([^']+)'/.exec(craft);
217
+ const tool = /id: 'palette', icon: '[^']+', short: '([^']+)', label: '([^']+)'/.exec(craft);
218
218
  assert.ok(tool, 'the palette tool');
219
219
  assert.ok(LOADER.includes("var SHORT = '" + tool[1] + "';"), 'the short name');
220
220
  assert.ok(LOADER.includes("var LABEL = '" + tool[2] + "';"), 'the full name');
@@ -100,7 +100,7 @@ await test('createTask with artistReview lands in the goal; without one the catc
100
100
  await createTask({ versionId: 'BONGOS-V3', title: 'Tweak: Home (builders:home)', discipline: 'artist', artistReview: true }, { client: c });
101
101
  assert.equal(c.state.tasks[0].goal_id, 91, 'the studio task names the artist review goal');
102
102
  await assert.rejects(
103
- () => createTask({ versionId: 'BONGOS-V3', title: 'plain', discipline: 'engineer' }, { client: fakeClient() }),
103
+ () => createTask({ versionId: 'BONGOS-V3', title: 'plain', discipline: 'artisan' }, { client: fakeClient() }),
104
104
  /goalId is required/, 'an ordinary goal-less create still throws');
105
105
  });
106
106
 
@@ -183,7 +183,7 @@ async function seed() {
183
183
  // since core_247 (task 1002988); every real create path resolves one.
184
184
  await q(
185
185
  `INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline)
186
- VALUES ($1,$2,'bhlp fixture task','ready',10,'feature','engineer')`,
186
+ VALUES ($1,$2,'bhlp fixture task','ready',10,'feature','artisan')`,
187
187
  [TASK, VER],
188
188
  );
189
189
  }
@@ -43,8 +43,8 @@ try {
43
43
  await q(`INSERT INTO versions (id,name,status,track) VALUES ($1,'names','shipped','internal') ON CONFLICT (id) DO NOTHING`, [VER]);
44
44
  // T2 first and with no name: the read must order by id and keep a null name.
45
45
  await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline,short_name)
46
- VALUES ($1,$3,'Payout approvals','backlog',10,'feature','engineer','payout approvals'),
47
- ($2,$3,'#12','backlog',10,'feature','engineer',NULL)`, [T1, T2, VER]);
46
+ VALUES ($1,$3,'Payout approvals','backlog',10,'feature','artisan','payout approvals'),
47
+ ($2,$3,'#12','backlog',10,'feature','artisan',NULL)`, [T1, T2, VER]);
48
48
  const holding = await blockers.createBlocker({ title: 'names fixture: holds two', bodyMd: '', source: 'test', sourceRef: `${REF}-a` });
49
49
  const empty = await blockers.createBlocker({ title: 'names fixture: holds none', bodyMd: '', source: 'test', sourceRef: `${REF}-b` });
50
50
  await blockers.linkBlockerToTask({ blockerId: holding.id, taskId: T2 }); // linked out of id order on purpose
@@ -280,7 +280,7 @@ const BUILDER = {
280
280
  total_credits: 12,
281
281
  karma: 3,
282
282
  created_at: '2026-01-01T00:00:00.000Z',
283
- preferred_disciplines: ['engineer'],
283
+ preferred_disciplines: ['artisan'],
284
284
  // The private columns the whitelist exists to keep out. Sentinel values so a
285
285
  // raw-text scan of the serialized body bites even if a key were renamed.
286
286
  anthropic_email: 'SENTINEL-anthropic-email',
@@ -23,31 +23,31 @@ function t(name, fn) {
23
23
 
24
24
  // A claimable feed row (only the fields capNewcomerTasks reads).
25
25
  const nc = (id, discipline) => ({ id, discipline, newcomer_friendly: true });
26
- const real = (id, discipline = 'engineer') => ({ id, discipline, newcomer_friendly: false });
26
+ const real = (id, discipline = 'artisan') => ({ id, discipline, newcomer_friendly: false });
27
27
  const ids = (rows) => rows.map((r) => r.id);
28
28
 
29
- // ---- the reported case: 3 artist + 2 ideator newcomer seats ----
30
- t('caps an artist+ideator pool of 5 down to 3, both lanes represented', () => {
29
+ // ---- the reported case: 3 artist + 2 artisan newcomer seats (was ideator before task 1005141 folded it into artist) ----
30
+ t('caps an artist+artisan pool of 5 down to 3, both lanes represented', () => {
31
31
  const feed = [
32
32
  nc(1259, 'artist'), nc(1260, 'artist'), nc(1261, 'artist'),
33
- nc(1176, 'ideator'), nc(1177, 'ideator'),
33
+ nc(1176, 'artisan'), nc(1177, 'artisan'),
34
34
  ];
35
35
  const out = capNewcomerTasks(feed);
36
36
  assert.equal(out.length, 3, 'exactly 3 newcomer rows kept');
37
- // Round-robin SELECTS artist[0]=1259, ideator[0]=1176, artist[1]=1260; the
37
+ // Round-robin SELECTS artist[0]=1259, artisan[0]=1176, artist[1]=1260; the
38
38
  // output preserves INPUT order (we filter the ordered array), so 1260 (which
39
39
  // came before 1176 in the feed) lands ahead of it.
40
40
  assert.deepEqual(ids(out), [1259, 1260, 1176]);
41
41
  // Both disciplines are represented before a duplicate fills the third slot.
42
42
  const disciplines = new Set(out.map((r) => r.discipline));
43
- assert.ok(disciplines.has('artist') && disciplines.has('ideator'));
43
+ assert.ok(disciplines.has('artist') && disciplines.has('artisan'));
44
44
  });
45
45
 
46
46
  // ---- non-newcomer (real) rows are never dropped ----
47
47
  t('real version tasks pass through untouched and unbounded', () => {
48
48
  const feed = [
49
49
  real(900), real(901), real(902), real(903),
50
- nc(1259, 'artist'), nc(1260, 'artist'), nc(1261, 'artist'), nc(1176, 'ideator'),
50
+ nc(1259, 'artist'), nc(1260, 'artist'), nc(1261, 'artist'), nc(1176, 'artisan'),
51
51
  ];
52
52
  const out = capNewcomerTasks(feed);
53
53
  const reals = out.filter((r) => r.newcomer_friendly !== true);
@@ -59,19 +59,19 @@ t('real version tasks pass through untouched and unbounded', () => {
59
59
  // ---- ordering is preserved (we filter the input array, not re-sort) ----
60
60
  t('preserves original feed ordering across mixed rows', () => {
61
61
  const feed = [
62
- real(900), nc(1259, 'artist'), real(901), nc(1176, 'ideator'),
63
- nc(1260, 'artist'), nc(1261, 'artist'), nc(1177, 'ideator'),
62
+ real(900), nc(1259, 'artist'), real(901), nc(1176, 'artisan'),
63
+ nc(1260, 'artist'), nc(1261, 'artist'), nc(1177, 'artisan'),
64
64
  ];
65
65
  const out = capNewcomerTasks(feed);
66
- // Kept newcomer ids (round-robin over [artist, ideator] groups by appearance):
67
- // artist[0]=1259, ideator[0]=1176, artist[1]=1260.
66
+ // Kept newcomer ids (round-robin over [artist, artisan] groups by appearance):
67
+ // artist[0]=1259, artisan[0]=1176, artist[1]=1260.
68
68
  // Output keeps input order, dropping only un-kept newcomer rows (1261, 1177).
69
69
  assert.deepEqual(ids(out), [900, 1259, 901, 1176, 1260]);
70
70
  });
71
71
 
72
72
  // ---- no-op when at or under the cap ----
73
73
  t('returns the list unchanged when newcomer rows <= cap', () => {
74
- const feed = [nc(1, 'artist'), nc(2, 'ideator'), real(3)];
74
+ const feed = [nc(1, 'artist'), nc(2, 'artisan'), real(3)];
75
75
  const out = capNewcomerTasks(feed);
76
76
  assert.equal(out.length, 3);
77
77
  assert.deepEqual(ids(out), [1, 2, 3]);
@@ -106,7 +106,7 @@ t('tolerates non-array input', () => {
106
106
 
107
107
  // ---- custom cap argument ----
108
108
  t('honors a custom cap', () => {
109
- const feed = [nc(1, 'artist'), nc(2, 'ideator'), nc(3, 'artist'), nc(4, 'ideator')];
109
+ const feed = [nc(1, 'artist'), nc(2, 'artisan'), nc(3, 'artist'), nc(4, 'artisan')];
110
110
  assert.equal(capNewcomerTasks(feed, 2).length, 2);
111
111
  assert.deepEqual(ids(capNewcomerTasks(feed, 2)), [1, 2]);
112
112
  });