@bongos/core 1.24.79 → 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 (366) hide show
  1. package/.bongos-core.json +495 -350
  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 +2 -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 +70 -0
  167. package/scripts/bongos/autobongos-loop.js +1 -1
  168. package/scripts/bongos/discipline-modes.json +7 -12
  169. package/scripts/bongos/fitness.js +1 -1
  170. package/scripts/bongos/gen-hall-sitemap.js +7 -7
  171. package/scripts/bongos/grade-smoke.js +2 -2
  172. package/scripts/bongos/init.js +2 -2
  173. package/scripts/bongos/newcomer-floor.config.json +1 -2
  174. package/scripts/bongos/newcomer-floor.js +4 -3
  175. package/scripts/bongos/newcomer-floor.templates.json +10 -12
  176. package/scripts/bongos/newcomer-floor.test.js +47 -38
  177. package/scripts/bongos/role-pack-guard.js +5 -4
  178. package/scripts/bongos/seed-tasks.example.json +3 -3
  179. package/scripts/bongos/setup.js +20 -107
  180. package/scripts/bongos/start.js +2 -2
  181. package/scripts/bongos/task.js +5 -4
  182. package/scripts/bongos/triage.js +1 -1
  183. package/scripts/bongos/worktree.js +16 -4
  184. package/scripts/setup-builder.js +6 -46
  185. package/src/bongos/auth-admission.js +8 -8
  186. package/src/bongos/craft-mode.js +2 -0
  187. package/src/bongos/db.js +39 -1
  188. package/src/bongos/github-repos.js +78 -0
  189. package/src/bongos/module-scope-map.js +22 -0
  190. package/src/bongos/role-stats.js +27 -11
  191. package/src/bongos/routes/github.js +100 -2
  192. package/src/bongos/routes/me.js +33 -0
  193. package/src/bongos/serve-internal.js +26 -9
  194. package/src/crafts.js +66 -0
  195. package/src/module-api.js +6 -1
  196. package/src/module-loader/manifest-schema.js +16 -0
  197. package/src/modules.js +2 -2
  198. package/src/role-responsibilities.js +6 -5
  199. package/tests/acclaim_feed_evidence_db.mjs +1 -1
  200. package/tests/activity_rollup_as_of.mjs +1 -1
  201. package/tests/api_alias_caller_ratchet.mjs +3 -3
  202. package/tests/application_lifecycle.mjs +1 -1
  203. package/tests/artist_review.mjs +1 -1
  204. package/tests/artist_review_goal.mjs +1 -1
  205. package/tests/blocker_hall_live_proof.mjs +1 -1
  206. package/tests/blocker_tasks_named_db.mjs +2 -2
  207. package/tests/canonical_profile_url.mjs +1 -1
  208. package/tests/cap_newcomer_tasks.mjs +13 -13
  209. package/tests/character_creation.mjs +294 -0
  210. package/tests/claim_gate_open_blocker.mjs +2 -2
  211. package/tests/claim_gate_rebase.mjs +6 -6
  212. package/tests/claude_materialize.mjs +1 -1
  213. package/tests/clawback_confirmed_backlog.mjs +3 -3
  214. package/tests/collab_page.mjs +8 -9
  215. package/tests/collab_self_settling.mjs +2 -2
  216. package/tests/community_leaderboard_route.mjs +13 -5
  217. package/tests/community_proof_db.mjs +3 -0
  218. package/tests/community_search_route.mjs +7 -6
  219. package/tests/conductor.mjs +1 -1
  220. package/tests/conductor_main_e2e.mjs +8 -8
  221. package/tests/copy_desk_flags_db.mjs +1 -1
  222. package/tests/copy_desk_page_approve.mjs +2 -2
  223. package/tests/copy_desk_page_claim.mjs +2 -2
  224. package/tests/copy_desk_page_next.mjs +1 -1
  225. package/tests/copy_desk_page_release.mjs +1 -1
  226. package/tests/copy_desk_page_submit.mjs +2 -2
  227. package/tests/core_258_box_session_claims_db.mjs +1 -1
  228. package/tests/craft_mode.mjs +48 -34
  229. package/tests/craft_mode_home.mjs +20 -20
  230. package/tests/craft_mode_looks.mjs +16 -15
  231. package/tests/craft_mode_nav.mjs +31 -29
  232. package/tests/craft_review_mode.mjs +46 -41
  233. package/tests/crafts_vocabulary.mjs +70 -0
  234. package/tests/cross_discipline_priority_direction.mjs +5 -5
  235. package/tests/fitness.mjs +7 -11
  236. package/tests/funded_project_pays_proof_db.mjs +1 -1
  237. package/tests/genesis_home.mjs +21 -1
  238. package/tests/github_chip.mjs +3 -3
  239. package/tests/github_repo_invite.mjs +379 -0
  240. package/tests/goal_criterion_confirm_ui.mjs +1 -1
  241. package/tests/goal_invite_consent.mjs +1 -1
  242. package/tests/goal_map_page.mjs +2 -2
  243. package/tests/goal_sort_staleness.mjs +1 -1
  244. package/tests/goal_spec_task.mjs +1 -1
  245. package/tests/goal_spec_task_db.mjs +3 -3
  246. package/tests/grade_engine.mjs +2 -2
  247. package/tests/grader_no_artifact.mjs +1 -1
  248. package/tests/grader_prompt_fencing.mjs +1 -1
  249. package/tests/grader_prompt_window_bound.mjs +1 -1
  250. package/tests/grader_reader_lens.mjs +2 -2
  251. package/tests/grader_severity_gate.mjs +1 -1
  252. package/tests/grader_unbacked_score.mjs +1 -1
  253. package/tests/hall_approval_queue.mjs +1 -1
  254. package/tests/hall_board_world.mjs +2 -2
  255. package/tests/hall_builders_page_pass.mjs +1 -1
  256. package/tests/hall_city.mjs +1 -1
  257. package/tests/hall_fullscreen.mjs +27 -3
  258. package/tests/hall_kit_charts.mjs +1 -1
  259. package/tests/hall_landing_boot.mjs +1 -1
  260. package/tests/hall_my_agents.mjs +16 -16
  261. package/tests/hall_nav.mjs +22 -15
  262. package/tests/hall_options_menu.mjs +247 -0
  263. package/tests/hall_oversight_world.mjs +1 -1
  264. package/tests/hall_palette.mjs +8 -9
  265. package/tests/hall_profile_world.mjs +3 -3
  266. package/tests/hall_record_world.mjs +2 -2
  267. package/tests/hall_settings_world.mjs +2 -1
  268. package/tests/hall_sitemap.mjs +6 -5
  269. package/tests/hall_start_screen.mjs +244 -0
  270. package/tests/hall_street.mjs +2 -2
  271. package/tests/hall_street_inside.mjs +17 -9
  272. package/tests/hall_studio_world.mjs +3 -3
  273. package/tests/hall_visor.mjs +224 -0
  274. package/tests/help_requests.mjs +47 -15
  275. package/tests/helpers.mjs +4 -4
  276. package/tests/home_city.mjs +6 -4
  277. package/tests/home_city_widget.mjs +33 -14
  278. package/tests/home_gallery_widget.mjs +4 -4
  279. package/tests/home_idea_box.mjs +6 -6
  280. package/tests/hub_community_page.mjs +3 -3
  281. package/tests/hub_craft_rollup.mjs +75 -36
  282. package/tests/hub_recent_ships.mjs +3 -2
  283. package/tests/idea_cascade.mjs +2 -2
  284. package/tests/idea_credit_streams_db.mjs +1 -1
  285. package/tests/idea_detail_page.mjs +1 -1
  286. package/tests/idea_ideate_full.mjs +5 -2
  287. package/tests/idea_spark_hall.mjs +1 -1
  288. package/tests/init.mjs +3 -3
  289. package/tests/instance_manifest.mjs +2 -2
  290. package/tests/kickoff_checklist.mjs +1 -1
  291. package/tests/leaderboard_roles.mjs +34 -25
  292. package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
  293. package/tests/lightbulb.mjs +14 -10
  294. package/tests/main_discipline.mjs +8 -8
  295. package/tests/markdown_line_endings.mjs +1 -1
  296. package/tests/market_watch_pages.mjs +52 -74
  297. package/tests/me_streak_leaderboard_queries.mjs +3 -3
  298. package/tests/migration_allocation.mjs +1 -1
  299. package/tests/mingle.mjs +36 -21
  300. package/tests/module-scope-map.mjs +22 -2
  301. package/tests/module_api.mjs +1 -0
  302. package/tests/module_contributions.mjs +13 -13
  303. package/tests/module_manifest.mjs +29 -0
  304. package/tests/module_skills_hook.mjs +2 -2
  305. package/tests/modules.mjs +14 -7
  306. package/tests/nav_permission_atoms.mjs +2 -1
  307. package/tests/needs_rebase.mjs +1 -1
  308. package/tests/newcomer_restock.mjs +13 -13
  309. package/tests/page_notes_panel.mjs +22 -22
  310. package/tests/page_tweak_no_block.mjs +1 -1
  311. package/tests/paintbrush.mjs +7 -7
  312. package/tests/privacy_matrix.mjs +1 -0
  313. package/tests/profile_activity.mjs +20 -23
  314. package/tests/profile_earnings.mjs +1 -1
  315. package/tests/profile_nudge_prefs.mjs +4 -4
  316. package/tests/profile_role_headline.mjs +73 -46
  317. package/tests/profile_role_stats.mjs +55 -45
  318. package/tests/profile_rollup_consent.mjs +19 -6
  319. package/tests/profile_route.mjs +12 -12
  320. package/tests/profile_ui_cross_project.mjs +4 -4
  321. package/tests/project_invite_suggestions.mjs +2 -2
  322. package/tests/promote_goal_id.mjs +1 -1
  323. package/tests/publish_manifest.mjs +1 -1
  324. package/tests/rank_floor_wiring.mjs +1 -1
  325. package/tests/regrade_actual_minutes.mjs +1 -1
  326. package/tests/removal_proposal.mjs +2 -2
  327. package/tests/reward_gate.mjs +4 -4
  328. package/tests/reward_mode_cost_plus_only.mjs +1 -1
  329. package/tests/role_responsibilities.mjs +18 -12
  330. package/tests/role_stats_db.mjs +17 -16
  331. package/tests/scouting_rollup_fanout.mjs +5 -5
  332. package/tests/seed_tasks.mjs +5 -2
  333. package/tests/session_earnings_mirror_db.mjs +1 -1
  334. package/tests/session_reward_on_land_db.mjs +1 -1
  335. package/tests/session_reward_reconcile_db.mjs +2 -2
  336. package/tests/sessions_kit_pager.mjs +1 -1
  337. package/tests/settings_art_key_absent.mjs +2 -2
  338. package/tests/settings_main_role.mjs +21 -20
  339. package/tests/setup_flow.mjs +3 -3
  340. package/tests/setup_smoke.mjs +20 -0
  341. package/tests/ship_card.mjs +6 -6
  342. package/tests/specialities.mjs +16 -4
  343. package/tests/speciality_db_shape.mjs +6 -6
  344. package/tests/speciality_injection.mjs +17 -17
  345. package/tests/speciality_offer_ui.mjs +5 -5
  346. package/tests/speciality_offered.mjs +6 -5
  347. package/tests/speciality_session_skills.mjs +7 -7
  348. package/tests/speciality_skills.mjs +11 -11
  349. package/tests/speciality_walkthrough_ui.mjs +2 -2
  350. package/tests/sso_checkin_identity_spoof.mjs +2 -1
  351. package/tests/task_classifier_vocab.mjs +10 -0
  352. package/tests/task_cli.mjs +11 -3
  353. package/tests/task_criteria_unlink_db.mjs +2 -2
  354. package/tests/task_discipline_required.mjs +38 -16
  355. package/tests/task_goal_required.mjs +4 -4
  356. package/tests/task_needs_db.mjs +1 -1
  357. package/tests/tweak_replaces_artist_review.mjs +1 -1
  358. package/tests/update_pill.mjs +153 -2
  359. package/tests/upkeep_home.mjs +1 -1
  360. package/tests/v3_c1_options_proof.mjs +145 -0
  361. package/tests/visual_review.mjs +3 -3
  362. package/tests/watch_applications_queue.mjs +8 -5
  363. package/tests/watch_sealed_floor.mjs +3 -1
  364. package/tests/welcome_boot.mjs +12 -9
  365. package/tests/welcome_boot_browser.mjs +13 -13
  366. package/tests/worktree_helper.mjs +16 -1
@@ -76,7 +76,7 @@ async function fileNote(note, { grade, capture, db, log } = {}) {
76
76
  // an empty list means nothing filed and the caller says so.
77
77
  async function fileIdeatorNotes(submit, { grade, capture, log, authorId, authorName } = {}) {
78
78
  const page = { page_id: submit.page_id, page_path: submit.page_path, page_title: submit.page_title, body_md: submit.body_md };
79
- let ideas = await aiIdeas({ note: page, grade, voice: 'ideator', timeoutMs: IDEATOR_TIMEOUT_MS });
79
+ let ideas = await aiIdeas({ note: page, grade, voice: 'notes', timeoutMs: IDEATOR_TIMEOUT_MS });
80
80
  let splitBy = 'ai';
81
81
  if (!ideas) { ideas = [N.wholeDocIdea({ pageTitle: page.page_title, pageId: page.page_id, bodyMd: page.body_md })]; splitBy = 'whole'; }
82
82
  const filed = [];
@@ -5,15 +5,16 @@
5
5
  // A reporting instance sends, per craft the builder has EARNED credits in, that
6
6
  // craft's headline counts (src/bongos/auth-admission.js readCraftRollup):
7
7
  //
8
- // engineer { shipped, credits }
9
- // artist { pages_approved, credits }
10
- // ideator { filed, full_ideas, ratified, credits }
8
+ // artisan { shipped, credits }
9
+ // artist { pages_approved, filed, full_ideas, ratified, credits }
10
+ //
11
+ // (the old engineer / artist / ideator shape is folded in at ingest, foldLegacy)
11
12
  //
12
13
  // Two halves live here, next to each other so they cannot disagree on the shape:
13
14
  //
14
15
  // sanitizeCrafts — what upsertActivity BINDS. The report is third-party
15
16
  // self-report (ADR 0171 D7), so the shape and the caps are the hub's, not
16
- // the sender's: only the three crafts, only the named counts, each a
17
+ // the sender's: only the two crafts, only the named counts, each a
17
18
  // non-negative integer under a ceiling or null. Anything else is dropped.
18
19
  // A count is never text, so nothing here can carry a title or wording.
19
20
  //
@@ -30,10 +31,36 @@
30
31
  'use strict';
31
32
 
32
33
  const CRAFT_FIELDS = Object.freeze({
33
- engineer: Object.freeze(['shipped', 'credits']),
34
- artist: Object.freeze(['pages_approved', 'credits']),
35
- ideator: Object.freeze(['filed', 'full_ideas', 'ratified', 'credits']),
34
+ artisan: Object.freeze(['shipped', 'credits']),
35
+ artist: Object.freeze(['pages_approved', 'filed', 'full_ideas', 'ratified', 'credits']),
36
36
  });
37
+
38
+ // A reporting instance on a core older than task 1005141 still sends the old
39
+ // crafts: `engineer`, and `artist` + `ideator` as two blocks. They are folded
40
+ // into today's shape BEFORE the hub's caps run, so an old report and a new one
41
+ // land identically: engineer becomes artisan, and the ideator's counts join the
42
+ // artist block with the two credit figures summed. A craft already reported under
43
+ // its new name wins over an old-name block for the same craft.
44
+ function foldLegacy(raw) {
45
+ const out = { ...raw };
46
+ if (out.engineer && !out.artisan) out.artisan = out.engineer;
47
+ delete out.engineer;
48
+ const ideator = out.ideator;
49
+ delete out.ideator;
50
+ if (ideator && typeof ideator === 'object' && !Array.isArray(ideator)) {
51
+ const art = out.artist && typeof out.artist === 'object' && !Array.isArray(out.artist) ? out.artist : {};
52
+ // Each half is capped BEFORE the sum: a hostile -3 on one side must not
53
+ // quietly shave the other side's honest count.
54
+ const a = cleanCount(art.credits);
55
+ const i = cleanCount(ideator.credits);
56
+ out.artist = {
57
+ ...art,
58
+ filed: ideator.filed, full_ideas: ideator.full_ideas, ratified: ideator.ratified,
59
+ credits: a == null && i == null ? null : (a || 0) + (i || 0),
60
+ };
61
+ }
62
+ return out;
63
+ }
37
64
  // Far above any honest count, low enough that a sum over every project stays a
38
65
  // safe integer and a hostile report cannot dominate a profile by one number.
39
66
  const MAX_CRAFT_COUNT = 10_000_000;
@@ -45,6 +72,7 @@ function cleanCount(v) {
45
72
  function sanitizeCrafts(raw) {
46
73
  const out = {};
47
74
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return out;
75
+ raw = foldLegacy(raw);
48
76
  for (const [craft, fields] of Object.entries(CRAFT_FIELDS)) {
49
77
  const block = raw[craft];
50
78
  if (!block || typeof block !== 'object' || Array.isArray(block)) continue;
@@ -0,0 +1,45 @@
1
+ -- platform_identity_036 — the hub's per-project activity rollup follows core_296's craft
2
+ -- rename (task 1005141, BONGOS-V3 C1): engineer → artisan, ideator → artist.
3
+ --
4
+ -- `disciplines` is mapped and deduped. `crafts` (platform_identity_029) is keyed by
5
+ -- craft: the engineer block becomes the artisan block, and the ideator block's counts
6
+ -- (filed, full_ideas, ratified) merge into the artist block, with credits summed —
7
+ -- the shape modules/platform-identity/craft-rollup.js now sanitizes. A report from an
8
+ -- instance still on an older core is folded the same way at ingest, so these rows and
9
+ -- fresh reports agree.
10
+
11
+ BEGIN;
12
+
13
+ UPDATE platform_identity_builder_project_activity a
14
+ SET disciplines = ARRAY(
15
+ SELECT m.craft FROM (
16
+ SELECT CASE d WHEN 'engineer' THEN 'artisan' WHEN 'ideator' THEN 'artist' ELSE d END AS craft,
17
+ min(ord) AS first_seen
18
+ FROM unnest(a.disciplines) WITH ORDINALITY AS u(d, ord)
19
+ GROUP BY 1
20
+ ) m ORDER BY m.first_seen)
21
+ WHERE a.disciplines && ARRAY['engineer', 'ideator']::text[];
22
+
23
+ UPDATE platform_identity_builder_project_activity
24
+ SET crafts = (crafts - 'engineer') || jsonb_build_object('artisan', crafts -> 'engineer')
25
+ WHERE crafts ? 'engineer' AND NOT crafts ? 'artisan';
26
+
27
+ UPDATE platform_identity_builder_project_activity
28
+ SET crafts = (crafts - 'ideator') || jsonb_build_object('artist',
29
+ coalesce(crafts -> 'artist', '{}'::jsonb)
30
+ || jsonb_strip_nulls(jsonb_build_object(
31
+ 'filed', crafts -> 'ideator' -> 'filed',
32
+ 'full_ideas', crafts -> 'ideator' -> 'full_ideas',
33
+ 'ratified', crafts -> 'ideator' -> 'ratified'))
34
+ || jsonb_build_object('credits',
35
+ CASE WHEN jsonb_typeof(crafts -> 'artist' -> 'credits') = 'number'
36
+ OR jsonb_typeof(crafts -> 'ideator' -> 'credits') = 'number'
37
+ THEN to_jsonb(coalesce((crafts -> 'artist' ->> 'credits')::bigint, 0)
38
+ + coalesce((crafts -> 'ideator' ->> 'credits')::bigint, 0))
39
+ ELSE 'null'::jsonb END))
40
+ WHERE crafts ? 'ideator';
41
+
42
+ INSERT INTO schema_migrations (version) VALUES ('platform_identity_036_crafts_artisan_artist')
43
+ ON CONFLICT (version) DO NOTHING;
44
+
45
+ COMMIT;
@@ -671,8 +671,8 @@ async function upsertActivity({ clientId, githubId, credits = 0, tasksShipped =
671
671
  // api.LIVE_RANK_LADDER is a published doorway export (src/module-api.js, a
672
672
  // lazy getter over db-kernel's canonical four-rank ladder).
673
673
  const safeRank = api.LIVE_RANK_LADDER.includes(rank) ? rank : null;
674
- const disc = (Array.isArray(disciplines) ? disciplines : [])
675
- .filter((d) => typeof d === 'string' && d.length > 0 && d.length <= MAX_DISCIPLINE_LENGTH)
674
+ const disc = api.crafts.normalizeCrafts((Array.isArray(disciplines) ? disciplines : []) // an older core's engineer/ideator → today's crafts, as foldLegacy (task 1005193)
675
+ .filter((d) => typeof d === 'string' && d.length > 0 && d.length <= MAX_DISCIPLINE_LENGTH))
676
676
  .slice(0, MAX_ROLLUP_DISCIPLINES);
677
677
  const ships = sanitizeRecentShips(recentShips, clientOrigin);
678
678
  // Each craft's own counts (task 1004440), shaped and capped here — see craft-rollup.js.
@@ -55,7 +55,8 @@ module.exports = function communityLeaderboardRoutes() {
55
55
  if (!leaderboard.isWindow(window)) {
56
56
  return res.fail('bad_window', { status: 400, message: `window must be one of: ${leaderboard.WINDOWS.join(', ')}` });
57
57
  }
58
- const discipline = (typeof req.query.discipline === 'string' ? req.query.discipline : '').trim();
58
+ // An old link's engineer/ideator reads as today's craft (task 1005141).
59
+ const discipline = require('../../../src/module-api').crafts.normalizeCraft((typeof req.query.discipline === 'string' ? req.query.discipline : '').trim());
59
60
  if (discipline.length > leaderboard.MAX_DISCIPLINE_CHARS) {
60
61
  return res.fail('discipline_too_long', { status: 400, message: `discipline must be at most ${leaderboard.MAX_DISCIPLINE_CHARS} characters` });
61
62
  }
@@ -63,7 +63,8 @@ module.exports = function communitySearchRoutes() {
63
63
  if (q.length > builderSearch.MAX_QUERY_CHARS) {
64
64
  return res.fail('query_too_long', { status: 400, message: `q must be at most ${builderSearch.MAX_QUERY_CHARS} characters` });
65
65
  }
66
- const discipline = (typeof req.query.discipline === 'string' ? req.query.discipline : '').trim();
66
+ // An old link's engineer/ideator reads as today's craft (task 1005141).
67
+ const discipline = require('../../../src/module-api').crafts.normalizeCraft((typeof req.query.discipline === 'string' ? req.query.discipline : '').trim());
67
68
  if (discipline.length > builderSearch.MAX_DISCIPLINE_CHARS) {
68
69
  return res.fail('discipline_too_long', { status: 400, message: `discipline must be at most ${builderSearch.MAX_DISCIPLINE_CHARS} characters` });
69
70
  }
@@ -691,7 +691,8 @@ module.exports = function ssoRoutes() {
691
691
  tasksShipped: b.tasks_shipped,
692
692
  karma: b.karma,
693
693
  rank: b.rank || null,
694
- disciplines: Array.isArray(b.disciplines) ? b.disciplines : [],
694
+ // An instance on an older core still reports engineer/ideator (task 1005141).
695
+ disciplines: Array.isArray(b.disciplines) ? api.crafts.normalizeCrafts(b.disciplines) : [],
695
696
  recentShips: b.recent_ships,
696
697
  crafts: b.crafts,
697
698
  // The AUTHENTICATED client's registered origin — the bound every pushed
@@ -606,14 +606,14 @@ const upsertCall = (pool) => pool.calls.find((c) => /INSERT INTO platform_identi
606
606
 
607
607
  await ta('upsertActivity upserts per (client_id, github_id) with the talent snapshot', async () => {
608
608
  const pool = activityPool();
609
- await P.upsertActivity({ clientId: 'c1', githubId: 42, credits: 100, tasksShipped: 5, karma: 3, rank: 'metic', disciplines: ['engineer'] }, { pool });
609
+ await P.upsertActivity({ clientId: 'c1', githubId: 42, credits: 100, tasksShipped: 5, karma: 3, rank: 'metic', disciplines: ['artisan'] }, { pool });
610
610
  const c = upsertCall(pool);
611
611
  assert.match(c.text, /ON CONFLICT \(client_id, github_id\) DO UPDATE/);
612
612
  // recent_ships (task 1002803) rides the same row as a jsonb STRING param —
613
613
  // an empty list when the sender reported none, never NULL. Its own cap,
614
614
  // shape and link bound are covered by tests/hub_recent_ships.mjs. The last param
615
615
  // is the report's as-of stamp (task 1004268): null when the sender gave none.
616
- assert.deepEqual(c.params, ['c1', 42, 100, 5, 3, 'metic', ['engineer'], '[]', null, '{}']);
616
+ assert.deepEqual(c.params, ['c1', 42, 100, 5, 3, 'metic', ['artisan'], '[]', null, '{}']);
617
617
  });
618
618
 
619
619
  await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-ladder rank → null, disciplines validated + capped', async () => {
@@ -622,7 +622,7 @@ await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-la
622
622
  clientId: 'c1', githubId: 42,
623
623
  rank: 'Supreme Overlord', // not one of the four core rank keys → stored null
624
624
  disciplines: [
625
- 'engineer', 42, null, { evil: true }, // non-strings dropped
625
+ 'artisan', 42, null, { evil: true }, // non-strings dropped
626
626
  'x'.repeat(33), // over-length dropped
627
627
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', // valid tail — capped to 8 total
628
628
  ],
@@ -633,7 +633,7 @@ await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-la
633
633
  assert.ok(disc.length <= 8, `disciplines capped at 8 (got ${disc.length})`);
634
634
  assert.ok(disc.every((d) => typeof d === 'string' && d.length > 0 && d.length <= 32),
635
635
  'only bounded string elements survive');
636
- assert.equal(disc[0], 'engineer', 'valid elements pass through in order');
636
+ assert.equal(disc[0], 'artisan', 'valid elements pass through in order');
637
637
  });
638
638
 
639
639
  await ta('upsertActivity keeps a legitimate core rank key', async () => {
@@ -733,7 +733,7 @@ await ta('getRollupForBuilder guards non-numeric github_id BEFORE any query (no
733
733
 
734
734
  await ta('getRollupForBuilder aggregates un-fanned + names only publicly-catalogued projects', async () => {
735
735
  const pool = mockPool([
736
- { rows: [{ projects: 2, credits: 150, works_shipped: 7, karma: 4, last_active: 'T', disciplines: ['engineer', 'ui'] }] },
736
+ { rows: [{ projects: 2, credits: 150, works_shipped: 7, karma: 4, last_active: 'T', disciplines: ['artisan', 'ui'] }] },
737
737
  { rows: [
738
738
  { name: 'Public Proj', origin: 'https://pub.example', rank_held: 'metic', is_public: true },
739
739
  { name: 'Secret Proj', origin: 'https://sec.example', rank_held: 'archon', is_public: false },
@@ -741,7 +741,7 @@ await ta('getRollupForBuilder aggregates un-fanned + names only publicly-catalog
741
741
  ]);
742
742
  const out = await P.getRollupForBuilder(42, { pool });
743
743
  assert.deepEqual(out.totals, { projects: 2, credits: 150, works_shipped: 7, karma: 4 });
744
- assert.deepEqual(out.disciplines, ['engineer', 'ui']);
744
+ assert.deepEqual(out.disciplines, ['artisan', 'ui']);
745
745
  assert.equal(out.projects.length, 1, 'only the public catalog row is named');
746
746
  // recent_ships rides the project row it belongs to (task 1002803), so a work
747
747
  // title can only surface where the project name already may.
@@ -262,9 +262,8 @@
262
262
  <div class="commFilters">
263
263
  <div class="commTabs" id="craftTabs" role="group" aria-label="Role">
264
264
  <button class="commTab" type="button" data-craft="" aria-pressed="true">Everyone</button>
265
- <button class="commTab" type="button" data-craft="engineer" aria-pressed="false">Engineers</button>
265
+ <button class="commTab" type="button" data-craft="artisan" aria-pressed="false">Artisans</button>
266
266
  <button class="commTab" type="button" data-craft="artist" aria-pressed="false">Artists</button>
267
- <button class="commTab" type="button" data-craft="ideator" aria-pressed="false">Ideators</button>
268
267
  <button class="commTab" type="button" data-craft="ui" aria-pressed="false">Designers</button>
269
268
  </div>
270
269
  <div class="commWhen">
@@ -314,9 +313,8 @@
314
313
  <label class="vh" for="findCraft">Role</label>
315
314
  <select id="findCraft">
316
315
  <option value="">Any role</option>
317
- <option value="engineer">Engineers</option>
316
+ <option value="artisan">Artisans</option>
318
317
  <option value="artist">Artists</option>
319
- <option value="ideator">Ideators</option>
320
318
  <option value="ui">Designers</option>
321
319
  </select>
322
320
  <button id="findBtn" type="submit" class="btn small">Search</button>
@@ -365,7 +363,7 @@
365
363
  var WINDOWS = ['7d', '30d', '365d', 'all'];
366
364
  var QUERY_MIN = 2;
367
365
  var QUERY_MAX = 100;
368
- var CRAFT_WORD = { engineer: 'engineer', artist: 'artist', ideator: 'ideator', ui: 'designer' };
366
+ var CRAFT_WORD = { artisan: 'artisan', artist: 'artist', ui: 'designer' };
369
367
  // The five sub-pages this file is served as (platform-server.js's /community routes);
370
368
  // the path picks one, and its menu items are marked current. The home adds no title.
371
369
  var VIEWS = {
@@ -774,7 +774,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
774
774
  <div class="layer">
775
775
  <h1 data-brand-tagline><span class="l1">Building Digitally,</span><span class="l2">as simple as Bongos.</span></h1>
776
776
 
777
- <p class="lede">Bringing AI to the builder, ideator, and artist in everyone.</p>
777
+ <p class="lede">Bringing AI to the artisan and the artist in everyone.</p>
778
778
 
779
779
  <div class="doors">
780
780
  <div class="doorbar">
@@ -1389,7 +1389,7 @@ summary{min-height:24px;padding:3px 0;}
1389
1389
  <div class="card mSec" id="mInvite">
1390
1390
  <h2>Invite a builder</h2>
1391
1391
  <div id="mInv">
1392
- <p class="finePrint">By GitHub username. The invite is sent from your project’s own hall — that is the invite that lets them in — and they see it on their projects page here too.</p>
1392
+ <p class="finePrint">By GitHub username. The invite is sent from your project’s own hall — that is the invite that lets them in — and they see it on their projects page here too. The hall’s invite box can also invite them to the project’s GitHub repository, which is a separate invite they accept on GitHub.</p>
1393
1393
  <div class="attach">
1394
1394
  <label class="vh" for="mInviteWho">GitHub username</label>
1395
1395
  <input type="text" id="mInviteWho" placeholder="GitHub username" autocomplete="off" autocapitalize="off" spellcheck="false" maxlength="200">
@@ -0,0 +1,30 @@
1
+ -- specialities_004 — specialities follow core_296's craft rename (task 1005141,
2
+ -- BONGOS-V3 C1): engineer → artisan, ideator → artist.
3
+ --
4
+ -- A speciality's discipline is immutable to its OWNER (the update path refuses a
5
+ -- change); this is a vocabulary rename, not a move between crafts, so it rewrites the
6
+ -- catalog and the adoption copy together.
7
+ --
8
+ -- The fold has one collision: specialities_adoptions allows ONE active adoption per
9
+ -- builder per craft. A builder with an active artist speciality AND an active ideator
10
+ -- one would hold two artist adoptions after the rename, so the ideator adoption is
11
+ -- deactivated first (deactivating is how a builder switches; nothing is deleted, and
12
+ -- they can re-adopt it from their speciality page).
13
+
14
+ BEGIN;
15
+
16
+ UPDATE specialities_adoptions i
17
+ SET active = FALSE
18
+ WHERE i.active AND i.discipline = 'ideator'
19
+ AND EXISTS (SELECT 1 FROM specialities_adoptions a
20
+ WHERE a.builder_id = i.builder_id AND a.active AND a.discipline = 'artist');
21
+
22
+ UPDATE specialities_catalog SET discipline = 'artisan' WHERE discipline = 'engineer';
23
+ UPDATE specialities_catalog SET discipline = 'artist' WHERE discipline = 'ideator';
24
+ UPDATE specialities_adoptions SET discipline = 'artisan' WHERE discipline = 'engineer';
25
+ UPDATE specialities_adoptions SET discipline = 'artist' WHERE discipline = 'ideator';
26
+
27
+ INSERT INTO schema_migrations (version) VALUES ('specialities_004_crafts_artisan_artist')
28
+ ON CONFLICT (version) DO NOTHING;
29
+
30
+ COMMIT;
@@ -192,7 +192,7 @@ module.exports = function buildSpecialitiesRouter() {
192
192
 
193
193
  const draft = {
194
194
  name: String(b.name).trim(),
195
- discipline: b.discipline,
195
+ discipline: api.crafts.normalizeCraft(b.discipline), // old spellings read as today's (task 1005141)
196
196
  summary: b.summary || '',
197
197
  contract_md: b.contract_md || '',
198
198
  visibility: b.visibility || 'private',
@@ -247,7 +247,7 @@ module.exports = function buildSpecialitiesRouter() {
247
247
  const b = req.body || {};
248
248
  const draft = {
249
249
  name: String(b.name).trim(),
250
- discipline: b.discipline,
250
+ discipline: api.crafts.normalizeCraft(b.discipline), // old spellings read as today's (task 1005141)
251
251
  summary: b.summary || '',
252
252
  contract_md: b.contract_md || '',
253
253
  visibility: b.visibility || 'private',
@@ -987,7 +987,7 @@ function renderAchievementRow(ids) {
987
987
  // task 1004437 (WA6.RP07): each row names its roles, main first — the crafts
988
988
  // the builder has EARNED in, from the row's own `roles.shown`, not only what
989
989
  // they declared. The same words the hall's roster uses. Text only, never markup.
990
- const ROLE_WORD = { engineer: 'Engineer', artist: 'Artist', ideator: 'Ideator' };
990
+ const ROLE_WORD = { artisan: 'Artisan', artist: 'Artist' };
991
991
  function renderRoles(roles) {
992
992
  const shown = roles && Array.isArray(roles.shown) ? roles.shown.filter((c) => ROLE_WORD[c]) : [];
993
993
  if (!shown.length) return null;
@@ -2,27 +2,27 @@
2
2
  "_": "GET /community/leaderboard for the kit stub (task 1003363): one ranked list per window, already in the route's order (credits, ships, karma, handle). The stub filters by discipline and numbers `place`. Names are invented; the long one tests the row's wrap.",
3
3
  "boards": {
4
4
  "all": [
5
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 18420, "tasks_shipped": 212, "karma": 64, "disciplines": ["engineer", "ui"] },
6
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 9310, "tasks_shipped": 131, "karma": 40, "disciplines": ["engineer"] },
5
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 18420, "tasks_shipped": 212, "karma": 64, "disciplines": ["artisan", "ui"] },
6
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 9310, "tasks_shipped": 131, "karma": 40, "disciplines": ["artisan"] },
7
7
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 6125, "tasks_shipped": 77, "karma": 52, "disciplines": ["artist"] },
8
- { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["ideator", "engineer"] },
8
+ { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["artist", "artisan"] },
9
9
  { "handle": "maximiliana-van-der-berghe-lindqvist", "github_login": "mvdbl", "display_name": "Maximiliana van der Berghe-Lindqvist", "avatar_url": "/__kit/avatar.svg", "credits": 940, "tasks_shipped": 12, "karma": 7, "disciplines": ["ui"] },
10
- { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["ideator"] }
10
+ { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["artist"] }
11
11
  ],
12
12
  "365d": [
13
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 12200, "tasks_shipped": 150, "karma": 41, "disciplines": ["engineer", "ui"] },
13
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 12200, "tasks_shipped": 150, "karma": 41, "disciplines": ["artisan", "ui"] },
14
14
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 6125, "tasks_shipped": 77, "karma": 52, "disciplines": ["artist"] },
15
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 4020, "tasks_shipped": 58, "karma": 22, "disciplines": ["engineer"] },
16
- { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["ideator", "engineer"] }
15
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": 4020, "tasks_shipped": 58, "karma": 22, "disciplines": ["artisan"] },
16
+ { "handle": "lin-wei", "github_login": "linwei", "display_name": "Lin Wei", "avatar_url": null, "credits": 2480, "tasks_shipped": 31, "karma": 18, "disciplines": ["artist", "artisan"] }
17
17
  ],
18
18
  "30d": [
19
19
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 1340, "tasks_shipped": 19, "karma": 9, "disciplines": ["artist"] },
20
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 880, "tasks_shipped": 11, "karma": 4, "disciplines": ["engineer", "ui"] },
21
- { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["ideator"] }
20
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "credits": 880, "tasks_shipped": 11, "karma": 4, "disciplines": ["artisan", "ui"] },
21
+ { "handle": "jo-park", "github_login": "jopark", "display_name": null, "avatar_url": "/__kit/avatar.svg", "credits": 310, "tasks_shipped": 1, "karma": 0, "disciplines": ["artist"] }
22
22
  ],
23
23
  "7d": [
24
24
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "credits": 420, "tasks_shipped": 6, "karma": 3, "disciplines": ["artist"] },
25
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": -15, "tasks_shipped": 0, "karma": 1, "disciplines": ["engineer"] }
25
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "credits": -15, "tasks_shipped": 0, "karma": 1, "disciplines": ["artisan"] }
26
26
  ]
27
27
  }
28
28
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "_": "GET /community/search for the kit stub (task 1003363): the public, existence-visible builders the stub matches by name or handle. Sam Rivera hides their numbers, so `disciplines` is null: found by name, dropped by any craft filter (builder-search.js).",
3
3
  "directory": [
4
- { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "disciplines": ["engineer", "ui"] },
4
+ { "handle": "priya-nair", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artisan", "ui"] },
5
5
  { "handle": "priyanka-shah", "github_login": "pshah", "display_name": "Priyanka Shah", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artist"] },
6
- { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "disciplines": ["engineer"] },
6
+ { "handle": "tomas-ek", "github_login": "tomasek", "display_name": "Tomas Ek", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artisan"] },
7
7
  { "handle": "ada-okafor", "github_login": "adaokafor", "display_name": "Ada Okafor", "avatar_url": "/__kit/avatar.svg", "disciplines": ["artist"] },
8
8
  { "handle": "sam-rivera", "github_login": "samrivera", "display_name": "Sam Rivera", "avatar_url": "/__kit/avatar.svg", "disciplines": null }
9
9
  ]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_": "GET /scouting for a viewer the recruiting door admits — a project owner (task 1003363). `builders` is the ranked tier, which the community page never draws (the leaderboard already shows those numbers to everyone); `name_only` is ADR 0334's hidden-but-open-to-recruiters tier, login, name and avatar only, alphabetical by login as listScoutingNameOnly sends it.",
3
3
  "builders": [
4
- { "github_id": "9001", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "project_count": 3, "total_credits": 18420, "total_tasks_shipped": 212, "total_karma": 64, "last_active": "2026-10-20T10:00:00Z", "disciplines": ["engineer", "ui"] }
4
+ { "github_id": "9001", "github_login": "priyanair", "display_name": "Priya Nair", "avatar_url": "/__kit/avatar.svg", "project_count": 3, "total_credits": 18420, "total_tasks_shipped": 212, "total_karma": 64, "last_active": "2026-10-20T10:00:00Z", "disciplines": ["artisan", "ui"] }
5
5
  ],
6
6
  "name_only": [
7
7
  { "github_login": "kemi-a", "display_name": "Kemi Adeyemi", "avatar_url": "/__kit/avatar.svg" },
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.79",
3
+ "version": "1.24.80",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.79",
9
+ "version": "1.24.80",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.79",
3
+ "version": "1.24.80",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11513,5 +11513,75 @@
11513
11513
  "id": "1005186",
11514
11514
  "text": "Closed a gap opened by letting Autobongos run on any device. The runner now keeps its own sign-in in its own folder, and the rule that stops an AI session from opening sign-in files did not yet cover that folder. It does now,"
11515
11515
  }
11516
+ ],
11517
+ "1.24.80": [
11518
+ {
11519
+ "id": "1005193",
11520
+ "text": "New versions can be published again: the checks broken by the craft rename pass, and projects on older versions no longer show the old craft name on the hub."
11521
+ },
11522
+ {
11523
+ "id": "1005148",
11524
+ "text": "In game mode, each builder now sees the project through a visor: their character's actions on the glass, their own character and credits in the corner, and a list of every action on the A key."
11525
+ },
11526
+ {
11527
+ "id": "1005145",
11528
+ "text": "Checked and locked in: after signing in and pressing Start there is no sidebar, and sound, the command-line key and module switches are all reached inside the one options menu; the old settings pages now only send you there."
11529
+ },
11530
+ {
11531
+ "id": "1004794",
11532
+ "text": "Modules can now say what language they are written in, and one naming a language Bongos cannot run yet is turned away with a clear reason."
11533
+ },
11534
+ {
11535
+ "id": "1004793",
11536
+ "text": "Work on the module store can now be planned under a goal, because its code finally has an owner in the project map."
11537
+ },
11538
+ {
11539
+ "id": "1005147",
11540
+ "text": "Builders now pick one of three characters (Artisan, Artist or Patron, drawn from the owner's art) when they join; the pick follows them to every device and shows as their sprite in the top bar."
11541
+ },
11542
+ {
11543
+ "id": "1005144",
11544
+ "text": "Every setting now lives in one options menu with a search box, and in game mode Escape opens it and closes it again."
11545
+ },
11546
+ {
11547
+ "id": "1005191",
11548
+ "text": "A project that is behind now asks its owner, in a clear pop-up, whether to update the builders hall."
11549
+ },
11550
+ {
11551
+ "id": "1005143",
11552
+ "text": "Signing in now opens a start screen (Start, Options, Achievements, Extras, Quit). Start plays a short load screen where the project city builds up week by week with a tip, then the hall opens full screen."
11553
+ },
11554
+ {
11555
+ "id": "1005158",
11556
+ "text": "Design written for claiming work as a chain: a chain reserves a run of tasks for you and your party, while each task inside it is still claimed on its own through the same safety checks as today. Nobody outside the party can g"
11557
+ },
11558
+ {
11559
+ "id": "1005190",
11560
+ "text": "A project owner can now skip the setup stages and open the full hall straight away, with the skip recorded."
11561
+ },
11562
+ {
11563
+ "id": "1005189",
11564
+ "text": "Setting up as a new builder no longer stops to ask for an optional image-generation key."
11565
+ },
11566
+ {
11567
+ "id": "1005146",
11568
+ "text": "Decided how the game's first-person visor sits over the city and the sky, and what the Escape key closes first. Also found a small bug where closing a city card brings the sidebar back."
11569
+ },
11570
+ {
11571
+ "id": "1005066",
11572
+ "text": "Project owners can now invite someone to the project's GitHub repository from the hall's invite box, always read-only, so a person who signs in to the hall can also see the code on their own GitHub once they accept GitHub's em"
11573
+ },
11574
+ {
11575
+ "id": "1005185",
11576
+ "text": "Making a new worktree on a hosted project now tells you the claim command that actually works there (npx bongos claim), instead of one that fails."
11577
+ },
11578
+ {
11579
+ "id": "1005183",
11580
+ "text": "The notes that guide agents around the claim rules pointed at the wrong lines of code. They now name the code directly, and a test stops them going stale again."
11581
+ },
11582
+ {
11583
+ "id": "1005141",
11584
+ "text": "The builders' crafts now carry their game names: Engineers are Artisans, Ideators join the Artists, and Patron is the third character; old links and old records keep working."
11585
+ }
11516
11586
  ]
11517
11587
  }
@@ -82,7 +82,7 @@ function buildWorkerArgs({ sessionId, model = DEFAULT_MODEL, isRoot = false } =
82
82
  // runs unattended with the power to ship, so that text must never be able to
83
83
  // instruct it. Reuses the grader's hardened fence rather than re-typing one, so
84
84
  // there is a single place this class of text is scrubbed and delimited.
85
- function buildWorkerPrompt({ task, packPath = 'docs/packs/engineer.md' } = {}) {
85
+ function buildWorkerPrompt({ task, packPath = 'docs/packs/artisan.md' } = {}) {
86
86
  if (!task || !task.id) throw new Error('buildWorkerPrompt: a task with an id is required');
87
87
  return [
88
88
  `You are an autonomous builder working ONE claimed task end to end. Read CLAUDE.md and ${packPath} for context. The task is ${task.id}.`,
@@ -1,20 +1,15 @@
1
1
  {
2
- "_doc": "The ROLE REGISTRY: discipline -> the operating playbook that craft works from (task 1267; reshaped by task 1002990). Since the kernel/pack split, a CORE craft names a `pack` — a markdown role pack under docs/packs/ that carries everything the root CLAUDE.md kernel deliberately does not, because it is true for one craft only. A module-contributed discipline may instead name a `skill` (a slash-command playbook under .claude/skills/), which is how `ui` -> /design still works (ADR 0272 keeps ui-design's contribution unchanged). An entry may carry both; it must carry at least one. GET /me reads this (src/bongos/craft-mode.js) and puts each active claim's entry — `directive` plus pack path — on the claim as `craft_mode`; the Conductor hook injects the one for the claim bound to the session's worktree, so the session opens the right playbook without anyone reading terminal output (task 1002992). claim.js reads it too, printing only the one role line and running `materializeModuleSkills`. There is NO classification step — `discipline` is already a column on the task. One registry, never a server-side copy. A missing or invalid version of this file must never break a claim (claim.js fails open) — but fitness.js DOES hard-fail CI when an entry names a pack that is not in the tree, so the fail-open runtime cannot hide a broken registry from the build. GOVERNOR IS DEFERRED: three packs, not four, until the owner decides (criterion wa6-kernel-and-packs). A mode entry may also set \"materializeModuleSkills\": true to have the claim land that mode's module-owned skills into this checkout's .claude/skills/ (task 1003486) — opt-in per discipline, core-checkout only, and fail-open like the hint itself.",
2
+ "_doc": "The ROLE REGISTRY: discipline -> the operating playbook that craft works from (task 1267; reshaped by task 1002990). Since the kernel/pack split, a CORE craft names a `pack` — a markdown role pack under docs/packs/ that carries everything the root CLAUDE.md kernel deliberately does not, because it is true for one craft only. A module-contributed discipline may instead name a `skill` (a slash-command playbook under .claude/skills/), which is how `ui` -> /design still works (ADR 0272 keeps ui-design's contribution unchanged). An entry may carry both; it must carry at least one. GET /me reads this (src/bongos/craft-mode.js) and puts each active claim's entry — `directive` plus pack path — on the claim as `craft_mode`; the Conductor hook injects the one for the claim bound to the session's worktree, so the session opens the right playbook without anyone reading terminal output (task 1002992). claim.js reads it too, printing only the one role line and running `materializeModuleSkills`. There is NO classification step — `discipline` is already a column on the task. One registry, never a server-side copy. A missing or invalid version of this file must never break a claim (claim.js fails open) — but fitness.js DOES hard-fail CI when an entry names a pack that is not in the tree, so the fail-open runtime cannot hide a broken registry from the build. THE GAME'S CHARACTERS (task 1005141, BONGOS-V3): engineer is now artisan, and the ideator folded into the artist, so there are two core packs — artisan.md and artist.md (which carries the ideation half). Patron is a character, not a task craft, and has no pack yet. A task row still holding an old spelling is read as the new one (src/crafts.js normalizeCraft). A mode entry may also set \"materializeModuleSkills\": true to have the claim land that mode's module-owned skills into this checkout's .claude/skills/ (task 1003486) — opt-in per discipline, core-checkout only, and fail-open like the hint itself.",
3
3
  "modes": {
4
- "engineer": {
5
- "pack": "docs/packs/engineer.md",
6
- "label": "Engineering session",
7
- "directive": "This is an ENGINEER task. Read the Engineer pack now, before working — it is the operating playbook for this craft and the only place the standard build loop is written down: understand the code before editing -> make the smallest correct, scoped change -> VERIFY it (smoke tests + the preview/verification workflow when observable) -> /builder-ship with real notes. Engineering is already the default way you'd work, so the pack is the standard made explicit (plus the autonomous-mode guardrails: be conservative, verify hard, don't wander), not a redirect."
8
- },
9
- "ideator": {
10
- "pack": "docs/packs/ideator.md",
11
- "label": "Ideation session",
12
- "directive": "This is an IDEATOR task. Read the Ideator pack now and work from it — it is the operating playbook for ideation and the only place the experience is defined. It inverts the usual dynamic: you (the human ideator) are the creative driver and Claude is your sounding board / research arm / devil's advocate, not a content generator filing ideas on command. Quick ideas stay ONE LINE, and after one is filed Claude asks whether to develop it into a Full Idea — five questions, drawn out of the conversation in the ideator's own words and scored on completeness. If no human is present (autonomous / bypass-permissions), the pack runs a self-adversarial divergence-and-critique loop instead of dumping a fixed batch. THE SURFACE, not just the behavior (ADR 0271): an ideator session is QUIET about its own machinery. Report outcomes, not mechanism — do not narrate tool calls, file paths, script names, API routes or task/claim bookkeeping unless they ask or something actually failed. Speak in the language of the idea, not the plumbing. If a step breaks, say plainly what broke and what you need — quiet never means hiding a problem."
4
+ "artisan": {
5
+ "pack": "docs/packs/artisan.md",
6
+ "label": "Artisan session",
7
+ "directive": "This is an ARTISAN task (the craft once called engineer). Read the Artisan pack now, before working — it is the operating playbook for this craft and the only place the standard build loop is written down: understand the code before editing -> make the smallest correct, scoped change -> VERIFY it (smoke tests + the preview/verification workflow when observable) -> /builder-ship with real notes. Building is already the default way you'd work, so the pack is the standard made explicit (plus the autonomous-mode guardrails: be conservative, verify hard, don't wander), not a redirect."
13
8
  },
14
9
  "artist": {
15
10
  "pack": "docs/packs/artist.md",
16
- "label": "Art session",
17
- "directive": "This is an ARTIST task. Read the Artist pack now and work from it — it is the operating playbook for art sessions and the only place the experience is defined. It is show-first and low-text: communicate in pictures and rendered surfaces, not paragraphs about what ran. Interpret as little as possible — take the brief literally, you are the artist's hands and not their art director — and arrive precalculated: load the look, have the options already rendered, never open with a form of intake questions. The artist's subject is how the project LOOKS AND READS, which is core (ADR 0272); a pixel-art or asset pipeline is a host module the pack drives only when the instance ships one. If no human is present (autonomous / bypass-permissions), make it, gate it hard, stage the passes and park the visuals for human review."
11
+ "label": "Artist session",
12
+ "directive": "This is an ARTIST task. Read the Artist pack now and work from it — it is the operating playbook for the artist craft, which holds both halves of what used to be two crafts (task 1005141): how the project LOOKS AND READS, and its IDEAS. It is show-first and low-text: communicate in pictures and rendered surfaces, not paragraphs about what ran. Interpret as little as possible — take the brief literally, you are the artist's hands and not their art director — and arrive precalculated: load the look, have the options already rendered, never open with a form of intake questions. The artist's subject is how the project LOOKS AND READS, which is core (ADR 0272); a pixel-art or asset pipeline is a host module the pack drives only when the instance ships one. If no human is present (autonomous / bypass-permissions), make it, gate it hard, stage the passes and park the visuals for human review. When the work is an idea rather than a surface, the pack's ideation half applies: It inverts the usual dynamic: you (the human artist) are the creative driver and Claude is your sounding board / research arm / devil's advocate, not a content generator filing ideas on command. Quick ideas stay ONE LINE, and after one is filed Claude asks whether to develop it into a Full Idea — five questions, drawn out of the conversation in the ideator's own words and scored on completeness. If no human is present (autonomous / bypass-permissions), the pack runs a self-adversarial divergence-and-critique loop instead of dumping a fixed batch. THE SURFACE, not just the behavior (ADR 0271): an ideation session is QUIET about its own machinery. Report outcomes, not mechanism — do not narrate tool calls, file paths, script names, API routes or task/claim bookkeeping unless they ask or something actually failed. Speak in the language of the idea, not the plumbing. If a step breaks, say plainly what broke and what you need — quiet never means hiding a problem."
18
13
  },
19
14
  "ui": {
20
15
  "skill": "design",
@@ -524,7 +524,7 @@ const KERNEL_FILES = [
524
524
  'src/module-api.js', // the published doorway
525
525
  'src/modules.js', // the registry/resolver facade
526
526
  'src/module-seams.js', // the seam registry (ports/events/contributions)
527
- 'src/role-responsibilities.js', 'src/html-escape.js', 'src/sleep.js', // owner's role statements (task 1003732) + THE server HTML escaper (task 1004876) + THE server sleep (task 1004893) — all import nothing
527
+ 'src/crafts.js', 'src/role-responsibilities.js', 'src/html-escape.js', 'src/sleep.js', // the craft vocabulary (task 1005141) + owner's role statements (task 1003732) + THE server HTML escaper (task 1004876) + THE server sleep (task 1004893) — all import nothing
528
528
  'src/module-loader/loader.js', // the disk loader (discover → validate → mount)
529
529
  'src/module-loader/manifest-schema.js', // the manifest validator
530
530
  'src/module-loader/semver.js', // coreVersion compatibility