@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
@@ -46,7 +46,7 @@ const vm = require('vm');
46
46
  const ROOT = path.resolve(__dirname, '..', '..');
47
47
  const HALL = 'modules/hall-ui/public';
48
48
  const OUT = 'docs/hall-sitemap.md';
49
- const CRAFTS = ['building', 'artistry', 'ideating', 'governing'];
49
+ const CRAFTS = ['artisan', 'artist', 'patron']; // the game's characters (task 1005141)
50
50
 
51
51
  // URLs the server answers that are not a page file in the hall. A link to one is not broken.
52
52
  // A NEW route a page links to belongs here, with where the server serves it; the audit fails
@@ -69,7 +69,7 @@ const ROUTES = {
69
69
  // Targets that leave the hall on purpose: sign-in, the hub's /account (platform-server.js;
70
70
  // settings.js points the link at the hub), the API, module assets, and any file with an extension.
71
71
  const EXTERNAL = [/^\/api\//, /^\/auth\b/, /^\/login\b/, /^\/logout\b/, /^\/signin\b/, /^\/status\b/,
72
- /^\/account\b/, /^\/modules\//, /^\/docs\b/, /^\/diagrams\//, /^\/healthz\b/, /^\/version\b/, /\.[a-z0-9]{2,5}$/i];
72
+ /^\/account\b/, /^\/modules\//, /^\/docs\b/, /^\/diagrams\//, /^\/characters\//, /^\/healthz\b/, /^\/version\b/, /\.[a-z0-9]{2,5}$/i];
73
73
 
74
74
  // Pages whose words come from a markdown file they fetch. A link in that file to a hall page's
75
75
  // own file (../../modules/hall-ui/public/<page>.html) opens the page in the hall (primer.js,
@@ -155,6 +155,7 @@ const LINK_RES = [
155
155
  /href=\\?["']\$\{(?![^}]*\bhub)[^}]*\}(\/[a-z0-9-][^"'\\\s$`{]*)/g,
156
156
  /Href\([^)]*\)\s*\{\s*return\s+'(\/[^'\s$]*)'/g, // function itemHref(id) { return '/x?id=' + … }
157
157
  /\bhref\s*:\s*'(\/[^'\s$]*)'/g, // { href: '/x' }
158
+ /\bembed\s*:\s*'(\/[^'\s$]*)'/g, // { embed: '/x' } — an options menu entry opens it in a frame (task 1005144)
158
159
  /\.href\s*=\s*'(\/[^'\s$]*)'/g, // a.href = '/x'
159
160
  /setAttribute\(\s*'href'\s*,\s*'(\/[^'\s$]*)'/g, // setAttribute('href', '/x')
160
161
  /location\.(?:assign|replace)\(\s*'(\/[^'\s$]*)'/g, // location.assign('/x')
@@ -288,12 +289,11 @@ function dedupe(list) {
288
289
  }
289
290
 
290
291
  // ---- the document ----
291
- const CRAFT_NAME = { building: 'Building', artistry: 'Artistry', ideating: 'Ideating', governing: 'Governing' };
292
+ const CRAFT_NAME = { artisan: 'Artisan', artist: 'Artist', patron: 'Patron' };
292
293
  const CRAFT_HOME = {
293
- building: '/ — the project city',
294
- artistry: '/ — the gallery',
295
- ideating: '/ — the sky over the city',
296
- governing: '/street — the governor\'s street (Home hops there)',
294
+ artisan: '/ — the project city',
295
+ artist: '/ — the gallery',
296
+ patron: '/street — the governor\'s street (Home hops there)',
297
297
  };
298
298
  function list(xs) { return xs.length ? xs.map((x) => '`' + x + '`').join(', ') : '—'; }
299
299
  function render(m) {
@@ -35,7 +35,7 @@ Acceptance:
35
35
  - One unit test in tests/util.mjs covers two cases (positive + negative).
36
36
  Out of scope: anything to do with timezones; calendar-aware durations.`,
37
37
  kind: 'feature',
38
- discipline: 'engineer',
38
+ discipline: 'artisan',
39
39
  version_id: 'GDS-V3',
40
40
  };
41
41
  const GOOD_VALUE_SUMMARY =
@@ -85,7 +85,7 @@ Acceptance:
85
85
  - One smoke test that exercises the charge → row → rollback paths.
86
86
  Out of scope: refund flow.`,
87
87
  kind: 'feature',
88
- discipline: 'engineer',
88
+ discipline: 'artisan',
89
89
  version_id: 'GDS-V3',
90
90
  };
91
91
  const BAD_VALUE_SUMMARY =
@@ -784,7 +784,7 @@ async function seedViaApi(spec, { dryRun, log = console.log, api = apiCall } = {
784
784
  attempted++;
785
785
  const res = await api('POST', '/api/bongos/tasks', {
786
786
  version_id: vp.id, goal_id: goalId, title: t.title, description: t.description || '', status: 'ready',
787
- kind: t.kind || 'infra', discipline: t.discipline || 'engineer', source: 'bongos-init', ...extra,
787
+ kind: t.kind || 'infra', discipline: t.discipline || 'artisan', source: 'bongos-init', ...extra,
788
788
  });
789
789
  if (!res.ok) failures.push({ title: t.title, status: res.status, detail: JSON.stringify(res.data) });
790
790
  return res.ok;
@@ -794,7 +794,7 @@ async function seedViaApi(spec, { dryRun, log = console.log, api = apiCall } = {
794
794
  let created = 0;
795
795
  for (const t of starters) {
796
796
  // task 1002988: `discipline: undefined` used to sit in this extras object, and
797
- // because `...extra` spreads AFTER postTask's own `t.discipline || 'engineer'`
797
+ // because `...extra` spreads AFTER postTask's own `t.discipline || 'artisan'`
798
798
  // it clobbered that default back to nothing — which is why a freshly scaffolded
799
799
  // instance was born with role-less starter tasks. Dropped, so starters inherit
800
800
  // the same default every other seeded task already got.
@@ -6,9 +6,8 @@
6
6
  "auto_promote": true,
7
7
  "max_generate_per_run": 8,
8
8
  "disciplines": {
9
- "engineer": { "floor": 2 },
9
+ "artisan": { "floor": 2 },
10
10
  "artist": { "floor": 3 },
11
- "ideator": { "floor": 2 },
12
11
  "ui": { "floor": 2 }
13
12
  }
14
13
  }
@@ -10,8 +10,8 @@
10
10
  // session, so the floor silently drained to 0/0/0 and stayed there.
11
11
  //
12
12
  // WHAT THIS DOES NOW: each discipline defines a LIST of EVERGREEN chores in
13
- // `newcomer-floor.templates.json` (engineer = "add a unit test" + the constrained
14
- // docs-hygiene sweep; artist = generate an asset; ideator = file ideas). A chore's
13
+ // `newcomer-floor.templates.json` (artisan = "add a unit test" + the constrained
14
+ // docs-hygiene sweep; artist = generate an asset, or file ideas). A chore's
15
15
  // definition never changes, yet running it is always valuable. Each chore keeps its
16
16
  // OWN standing pool to its OWN per-template `floor`; when a chore's ready copies drop
17
17
  // below that floor, this script stamps fresh IDENTICAL claimable COPIES of it. No AI,
@@ -64,7 +64,7 @@ const HARD_MAX_MINUTES = 60; // newcomer scope ceiling, never exceeded (task 614
64
64
  // It is the craft VOCABULARY, which is why 'unclassified' is absent. Keep in step
65
65
  // with the keys in newcomer-floor.templates.json ('ui' added by task 1003487); the
66
66
  // server-side restock reads the live offered set instead (newcomer-restock.js).
67
- const VALID_DISCIPLINES = new Set(['engineer', 'artist', 'ideator', 'ui']);
67
+ const VALID_DISCIPLINES = new Set(['artisan', 'artist', 'ui']);
68
68
  // Every status a copy's source_ref could occupy — scanned so `seq` is globally
69
69
  // monotonic and never recycles a ref already used by a shipped/abandoned copy.
70
70
  const ALL_STATUSES = ['backlog', 'ready', 'active', 'completed', 'confirmed', 'shipped', 'blocked', 'abandoned'];
@@ -307,6 +307,7 @@ async function main() {
307
307
  discipline: arg('--discipline', args),
308
308
  configPath: arg('--config', args) || DEFAULT_CONFIG_PATH,
309
309
  };
310
+ if (FLAGS.discipline) FLAGS.discipline = ({ engineer: 'artisan', ideator: 'artist' })[FLAGS.discipline] || FLAGS.discipline;
310
311
  if (FLAGS.discipline && !VALID_DISCIPLINES.has(FLAGS.discipline)) {
311
312
  console.error(`bad --discipline "${FLAGS.discipline}"; valid: ${[...VALID_DISCIPLINES].join(', ')}`);
312
313
  process.exit(2);
@@ -1,16 +1,16 @@
1
1
  {
2
- "_doc": "The evergreen newcomer chores, grouped BY DISCIPLINE. Each discipline maps to a LIST of chore templates (was a single object before task 1488 — a discipline can now offer several first-builder chores so newcomers are not funnelled into one). The deterministic replenisher (scripts/bongos/newcomer-floor.js) and the on-claim restock (modules/onboarding/newcomer-restock.js) stamp disposable claimable COPIES from each template; the TEMPLATE is never claimed and never ships. Each copy's source_ref is tmpl:<discipline>:<template_id>:<seq> (monotonic), so identical-title copies never collide with the dedup. Per-template `floor` sets that chore’s standing pool depth; if a template omits it, the discipline default in newcomer-floor.config.json applies. credits_reward, est_minutes and floor all live here (per chore). version_id is per-template so each chore lands on the correct track; it MUST point at an open (non-shipped) version — update it at version close, or the replenisher's open-version guard refuses to stamp and the floor silently stops refilling (task 1002958: the engineer pins still read the long-shipped GDS-V4 and artist+ideator read V2, a version this ladder never had, so NO discipline could restock). touches[] is intentionally empty so multiple same-discipline newcomers can claim copies concurrently; git-merge is the real collision detector. To ADD a chore: append an entry to the discipline's array. To CHANGE one: edit it here — no code change needed. UI offers two browser-free chores (a token-drift fix measured by validate-design.js, and an accessible-name fix) so a newcomer whose only craft is ui is never starved for work on a machine with no Chrome/Edge for the kit — task 1003487. Engineer offers a SAFE coding chore (add a unit test — touches only tests/, which auto-ships) and the docs-hygiene sweep (constrained to non-protected docs so a sub-Metic ship does not stall on the authority core).",
3
- "engineer": [
2
+ "_doc": "The evergreen newcomer chores, grouped BY DISCIPLINE. Each discipline maps to a LIST of chore templates (was a single object before task 1488 — a discipline can now offer several first-builder chores so newcomers are not funnelled into one). The deterministic replenisher (scripts/bongos/newcomer-floor.js) and the on-claim restock (modules/onboarding/newcomer-restock.js) stamp disposable claimable COPIES from each template; the TEMPLATE is never claimed and never ships. Each copy's source_ref is tmpl:<discipline>:<template_id>:<seq> (monotonic), so identical-title copies never collide with the dedup. Per-template `floor` sets that chore’s standing pool depth; if a template omits it, the discipline default in newcomer-floor.config.json applies. credits_reward, est_minutes and floor all live here (per chore). version_id is per-template so each chore lands on the correct track; it MUST point at an open (non-shipped) version — update it at version close, or the replenisher's open-version guard refuses to stamp and the floor silently stops refilling (task 1002958: the engineer pins still read the long-shipped GDS-V4 and artist+ideator read V2, a version this ladder never had, so NO discipline could restock). touches[] is intentionally empty so multiple same-discipline newcomers can claim copies concurrently; git-merge is the real collision detector. To ADD a chore: append an entry to the discipline's array. To CHANGE one: edit it here — no code change needed. UI offers two browser-free chores (a token-drift fix measured by validate-design.js, and an accessible-name fix) so a newcomer whose only craft is ui is never starved for work on a machine with no Chrome/Edge for the kit — task 1003487. Artisan offers a SAFE coding chore (add a unit test — touches only tests/, which auto-ships) and the docs-hygiene sweep (constrained to non-protected docs so a sub-Metic ship does not stall on the authority core).",
3
+ "artisan": [
4
4
  {
5
5
  "template_id": "unit-test-pass",
6
6
  "version_id": "BONGOS-V1",
7
- "discipline": "engineer",
7
+ "discipline": "artisan",
8
8
  "kind": "feature",
9
9
  "est_minutes": 45,
10
10
  "credits_reward": 15,
11
11
  "floor": 2,
12
12
  "title": "Add a unit test for an untested pure exported helper (assert its real behaviour)",
13
- "description": "## What you're doing\n\nThe repo's safety net is its **unit suite** — every `tests/*.mjs` file, run by `scripts/bongos/run-unit-tests.js` in CI on every PR. New pure tests are **auto-discovered** (there is no allowlist to edit), so the moment you add one it joins the gate. Your first ship as an engineer: pick ONE exported helper that has thin or no test coverage and add a focused, DB-free unit test for it.\n\nIt is identical for every newcomer and **always valuable** — coverage is never \"done\", the codebase has hundreds of small pure helpers, and a good test both guards behaviour and teaches you how a corner of the system works. It is also **safe to auto-ship**: you add ONE file under `tests/`, which is not a protected surface, so a clean test merges on green with no owner approval.\n\n## How to pick a target (a PURE helper)\n\nPick a **pure** function — deterministic, with no database, network, filesystem, or live-server dependency — so the test runs standalone. Do not hunt by hand: run the finder, which reads the live tree every time (so this task never goes stale the way a written list does):\n\n```\nnode scripts/bongos/find-untested.js\n```\n\nIt lists modules that **no test file names**, best candidates first — ones no test reaches at all, then ones that look pure, then the smallest. Its purity column is a heuristic: open the module and confirm the helper you pick really needs no database, network or files. If the list is empty, run it with `--all` and pick a module that still exports a helper you can test DB-free.\n\n## How to write it (mirror an existing test — there is no framework)\n\nThese tests are plain Node scripts using `node:assert`; there is no test runner. Open a small existing one as your model — for example `tests/criterion_ref_parse.mjs` or `tests/task_classifier_vocab.mjs` — and copy the shape:\n1. `import` / `require` the helper from its module.\n2. Assert **several** cases, including at least one **edge / boundary** case (empty input, null, a tricky value).\n3. Print a one-line success message at the end (an assert that throws exits non-zero, which is how the gate sees a failure).\n\nName the file `tests/<helper-or-module>.mjs`.\n\n## Verify + ship\n\nRun BOTH and confirm both are green:\n- `node tests/<your-file>.mjs` — your test alone, exits 0.\n- `node scripts/bongos/run-unit-tests.js` — the whole gate, with your test auto-included.\n\nThen `/builder-ship`. The ship notes must name the helper and file you tested.\n\n## Stay safe (off-limits)\n\nAdd exactly ONE new file under `tests/`. Do NOT edit `scripts/bongos/run-unit-tests.js` (it auto-discovers your test, and it is a protected file you may not touch), and do NOT change the helper you are testing. Your test must be **DB-free**: anything needing Postgres, the network, or a live server belongs in the integration gate and will fail standalone here. Touch no permission, pipeline, migration, or infra file.",
13
+ "description": "## What you're doing\n\nThe repo's safety net is its **unit suite** — every `tests/*.mjs` file, run by `scripts/bongos/run-unit-tests.js` in CI on every PR. New pure tests are **auto-discovered** (there is no allowlist to edit), so the moment you add one it joins the gate. Your first ship as an artisan: pick ONE exported helper that has thin or no test coverage and add a focused, DB-free unit test for it.\n\nIt is identical for every newcomer and **always valuable** — coverage is never \"done\", the codebase has hundreds of small pure helpers, and a good test both guards behaviour and teaches you how a corner of the system works. It is also **safe to auto-ship**: you add ONE file under `tests/`, which is not a protected surface, so a clean test merges on green with no owner approval.\n\n## How to pick a target (a PURE helper)\n\nPick a **pure** function — deterministic, with no database, network, filesystem, or live-server dependency — so the test runs standalone. Do not hunt by hand: run the finder, which reads the live tree every time (so this task never goes stale the way a written list does):\n\n```\nnode scripts/bongos/find-untested.js\n```\n\nIt lists modules that **no test file names**, best candidates first — ones no test reaches at all, then ones that look pure, then the smallest. Its purity column is a heuristic: open the module and confirm the helper you pick really needs no database, network or files. If the list is empty, run it with `--all` and pick a module that still exports a helper you can test DB-free.\n\n## How to write it (mirror an existing test — there is no framework)\n\nThese tests are plain Node scripts using `node:assert`; there is no test runner. Open a small existing one as your model — for example `tests/criterion_ref_parse.mjs` or `tests/task_classifier_vocab.mjs` — and copy the shape:\n1. `import` / `require` the helper from its module.\n2. Assert **several** cases, including at least one **edge / boundary** case (empty input, null, a tricky value).\n3. Print a one-line success message at the end (an assert that throws exits non-zero, which is how the gate sees a failure).\n\nName the file `tests/<helper-or-module>.mjs`.\n\n## Verify + ship\n\nRun BOTH and confirm both are green:\n- `node tests/<your-file>.mjs` — your test alone, exits 0.\n- `node scripts/bongos/run-unit-tests.js` — the whole gate, with your test auto-included.\n\nThen `/builder-ship`. The ship notes must name the helper and file you tested.\n\n## Stay safe (off-limits)\n\nAdd exactly ONE new file under `tests/`. Do NOT edit `scripts/bongos/run-unit-tests.js` (it auto-discovers your test, and it is a protected file you may not touch), and do NOT change the helper you are testing. Your test must be **DB-free**: anything needing Postgres, the network, or a live server belongs in the integration gate and will fail standalone here. Touch no permission, pipeline, migration, or infra file.",
14
14
  "done_when": [
15
15
  "A NEW tests/<name>.mjs file was added (it did not exist before) that unit-tests a PURE exported helper — no Postgres, network, filesystem, or live server.",
16
16
  "`node tests/<name>.mjs` exits 0 and the test makes real assertions about the helper's behaviour — at least two cases including one edge/boundary case — not a trivial always-true assertion.",
@@ -23,12 +23,12 @@
23
23
  {
24
24
  "template_id": "docs-hygiene-pass",
25
25
  "version_id": "BONGOS-V1",
26
- "discipline": "engineer",
26
+ "discipline": "artisan",
27
27
  "kind": "cleanup",
28
28
  "est_minutes": 40,
29
29
  "credits_reward": 15,
30
30
  "title": "Docs-hygiene sweep: fix one piece of real drift (broken link, dead anchor, or bare #NNN ref) in a Markdown doc",
31
- "description": "## What you're doing\n\nThe project's docs drift constantly — links break when files are renamed, heading anchors go dead, formatting breaks, and bare `#NNN` refs creep back (a bare `#123` autolinks to GitHub's issue space and 404s — see CLAUDE.md §8). Your job: find **one** genuine piece of drift in **one** SAFE doc and ship the fix.\n\n## First — do NOT open these (they will stall your ship)\n\nA newcomer (Xenos / Thetes, sub-Metic) may not modify the authority / pipeline core. Picking one of these as your target will **fail your grade or block your ship**, so don't even go looking for drift in them: `CLAUDE.md`, `docs/canonical-permissions.md`, the authority ADRs (`docs/adr/0016-*`, `docs/adr/0043-*`), and anything under `migrations/`, `infra/`, `.github/`, `.husky/`, `.claude/hooks/`, plus `package.json` and the ship / grade / deploy scripts. (The authoritative list is the glob set in `src/bongos/permission-path-check.js`.) The most *visible* drift often lives in `CLAUDE.md` itself — resist it; it is off-limits to you.\n\n## Pick from the SAFE docs only\n\nAll fair game: a recipe under `docs/recipes/`, a session log under `docs/session-logs/`, `docs/architecture.md`, `docs/file-map.md`, `docs/TRACKS.md`, a **non-authority** ADR, `README.md`, or `art/README.md`.\n\n## How to find a real target (don't ship a no-op)\n\n1. **Bare `#NNN` refs (easiest):** run `node scripts/bongos/linkify-refs.js` (read-only) — it lists docs with upgradeable refs. Pick one from the SAFE list above and fix it with `node scripts/bongos/linkify-refs.js --apply <safe-file.md>`.\n2. **Broken / renamed links:** open a safe doc and check its `](path)` links resolve to a real file (cross-check `docs/file-map.md`).\n3. **Dead anchors:** `](#heading)` where that heading no longer exists in the target doc.\n4. **Formatting drift:** a broken list nest, an unclosed code fence, a mangled table.\n\n## Verify + ship\n\nRun `node scripts/bongos/linkify-refs.js --check <your-file.md>` (scoped to YOUR one safe file — exit 0) and confirm any link you touched resolves, then `/builder-ship`. The ship notes must name the specific drift you fixed and the one file.\n\n_Prefer writing code to prose? Claim the \"add a unit test\" engineer chore instead — it's the other first-builder engineer task._",
31
+ "description": "## What you're doing\n\nThe project's docs drift constantly — links break when files are renamed, heading anchors go dead, formatting breaks, and bare `#NNN` refs creep back (a bare `#123` autolinks to GitHub's issue space and 404s — see CLAUDE.md §8). Your job: find **one** genuine piece of drift in **one** SAFE doc and ship the fix.\n\n## First — do NOT open these (they will stall your ship)\n\nA newcomer (Xenos / Thetes, sub-Metic) may not modify the authority / pipeline core. Picking one of these as your target will **fail your grade or block your ship**, so don't even go looking for drift in them: `CLAUDE.md`, `docs/canonical-permissions.md`, the authority ADRs (`docs/adr/0016-*`, `docs/adr/0043-*`), and anything under `migrations/`, `infra/`, `.github/`, `.husky/`, `.claude/hooks/`, plus `package.json` and the ship / grade / deploy scripts. (The authoritative list is the glob set in `src/bongos/permission-path-check.js`.) The most *visible* drift often lives in `CLAUDE.md` itself — resist it; it is off-limits to you.\n\n## Pick from the SAFE docs only\n\nAll fair game: a recipe under `docs/recipes/`, a session log under `docs/session-logs/`, `docs/architecture.md`, `docs/file-map.md`, `docs/TRACKS.md`, a **non-authority** ADR, `README.md`, or `art/README.md`.\n\n## How to find a real target (don't ship a no-op)\n\n1. **Bare `#NNN` refs (easiest):** run `node scripts/bongos/linkify-refs.js` (read-only) — it lists docs with upgradeable refs. Pick one from the SAFE list above and fix it with `node scripts/bongos/linkify-refs.js --apply <safe-file.md>`.\n2. **Broken / renamed links:** open a safe doc and check its `](path)` links resolve to a real file (cross-check `docs/file-map.md`).\n3. **Dead anchors:** `](#heading)` where that heading no longer exists in the target doc.\n4. **Formatting drift:** a broken list nest, an unclosed code fence, a mangled table.\n\n## Verify + ship\n\nRun `node scripts/bongos/linkify-refs.js --check <your-file.md>` (scoped to YOUR one safe file — exit 0) and confirm any link you touched resolves, then `/builder-ship`. The ship notes must name the specific drift you fixed and the one file.\n\n_Prefer writing code to prose? Claim the \"add a unit test\" artisan chore instead — it's the other first-builder artisan task._",
32
32
  "done_when": [
33
33
  "Exactly one non-protected Markdown file was changed (NOT CLAUDE.md, canonical-permissions.md, an authority ADR, or anything under migrations/ infra/ .github/ .husky/ .claude/hooks/ — see src/bongos/permission-path-check.js), and the change is a genuine hygiene fix (a broken/renamed link path, a dead heading anchor, a bare `#NNN` ref linkified, or formatting drift) — not a content/meaning edit and not a cosmetic no-op.",
34
34
  "`node scripts/bongos/linkify-refs.js --check <your-file.md>` (scoped to the one file you changed) exits 0.",
@@ -49,7 +49,7 @@
49
49
  "est_minutes": 55,
50
50
  "credits_reward": 15,
51
51
  "title": "Generate one new in-world art asset of your choice (a prop, plant, animal, or accessory) via the pixel-art pipeline",
52
- "description": "## What you're doing\n\nThis instance's world always needs more to look at. Your first ship as an artist is to **generate ONE new in-world asset of your own choosing** and add it to the world. You pick the subject — that's the fun part. Good candidates, all in-world (Mediterranean coast just south of Athens, mythic 'discovered secret' tone):\n- **Props**: amphora, fishing net, basket, crate, urn, torch, market awning, oar, clay pot.\n- **Plants**: olive tree, cypress, reeds, grapevine, shrub, wild herbs.\n- **Animals**: goat, chicken, duck, donkey, fish, gull, lizard.\n- **Accessories**: a sandal, a sun hat, a length of rope, a string of beads.\n\nIt is identical for every newcomer and **always valuable**, because a growing world has effectively unbounded demand for new props, flora, fauna, and detail — every newcomer picks a different subject, so two runs never collide.\n\n## How to find your way in\n\n1. Read `art/README.md` and `docs/recipes/pixel-art-pipeline.md`, plus `modules/art-pipeline/template/style_guide.md`, `modules/art-pipeline/template/rubric.json`, and `modules/art-pipeline/template/palette.json` — that's the look you must hit: 16-bit pixel art, early-Pokémon (FireRed) touchstone, top-down, 32×32, locked 64-colour Mediterranean palette (olive greens, warm tans, turquoise sea, weathered marble — NOT spring green, NOT navy, NOT pine).\n2. **Pick ONE subject.** Keep it to a SINGLE 32×32 tile/sprite — not a whole spritesheet or family (that's a bigger task).\n3. **Check the budget first**: `python3 modules/art-pipeline/pipeline/cost_ledger.py status` (the pipeline hard-stops at $80; one asset is ~$0.05–0.45).\n4. **Generate via the pipeline — do not hand-draw or freelance.** Use the `/otb-tile-generate` skill: it appends your new entry to `modules/art-pipeline/template/vocabulary.json`, runs the generate → post-process-to-32×32 → score-against-rubric → retry orchestrator, saves the final PNG to `modules/art-pipeline/generated/tiles/<id>.png`, and logs cost. (This needs the art pipeline's `GOOGLE_AI_STUDIO_KEY`, which resolves from `~/.config/otb/env`; most builder setups have it. If yours doesn't, claim the engineer or ideator newcomer chore instead, or ask an Archon to enable it.)\n5. **Confirm it PASSED** the rubric (`modules/art-pipeline/iterations/run_summary.json` shows `status: \"pass\"`). If it only reached best-effort, regenerate with the fix_hint — never paper over a fail.\n6. **See it in-game**: `node scripts/bongos/art-stage.js` rebuilds the atlas and surfaces it on your sandbox preview (`sandbox-<login>.<instance-apex>`).\n7. Ship via `/builder-ship`; the ship notes name your asset and its rubric score.\n\n## Stay clear of (READ-ONLY)\n\nNever modify the locked palette (`modules/art-pipeline/template/palette.json`) or the rubric (`modules/art-pipeline/template/rubric.json`) — those are theme-defining and changing them needs an ADR (out of scope for a first ship). Touch only art assets/vocabulary under `art/` (and, only if your asset is a genuinely new base-tile TYPE, the `public/game/world/tilePalette.js` mapping). Do not touch any permission/pipeline-core/migration/infra file.",
52
+ "description": "## What you're doing\n\nThis instance's world always needs more to look at. Your first ship as an artist is to **generate ONE new in-world asset of your own choosing** and add it to the world. You pick the subject — that's the fun part. Good candidates, all in-world (Mediterranean coast just south of Athens, mythic 'discovered secret' tone):\n- **Props**: amphora, fishing net, basket, crate, urn, torch, market awning, oar, clay pot.\n- **Plants**: olive tree, cypress, reeds, grapevine, shrub, wild herbs.\n- **Animals**: goat, chicken, duck, donkey, fish, gull, lizard.\n- **Accessories**: a sandal, a sun hat, a length of rope, a string of beads.\n\nIt is identical for every newcomer and **always valuable**, because a growing world has effectively unbounded demand for new props, flora, fauna, and detail — every newcomer picks a different subject, so two runs never collide.\n\n## How to find your way in\n\n1. Read `art/README.md` and `docs/recipes/pixel-art-pipeline.md`, plus `modules/art-pipeline/template/style_guide.md`, `modules/art-pipeline/template/rubric.json`, and `modules/art-pipeline/template/palette.json` — that's the look you must hit: 16-bit pixel art, early-Pokémon (FireRed) touchstone, top-down, 32×32, locked 64-colour Mediterranean palette (olive greens, warm tans, turquoise sea, weathered marble — NOT spring green, NOT navy, NOT pine).\n2. **Pick ONE subject.** Keep it to a SINGLE 32×32 tile/sprite — not a whole spritesheet or family (that's a bigger task).\n3. **Check the budget first**: `python3 modules/art-pipeline/pipeline/cost_ledger.py status` (the pipeline hard-stops at $80; one asset is ~$0.05–0.45).\n4. **Generate via the pipeline — do not hand-draw or freelance.** Use the `/otb-tile-generate` skill: it appends your new entry to `modules/art-pipeline/template/vocabulary.json`, runs the generate → post-process-to-32×32 → score-against-rubric → retry orchestrator, saves the final PNG to `modules/art-pipeline/generated/tiles/<id>.png`, and logs cost. (This needs the art pipeline's `GOOGLE_AI_STUDIO_KEY`, which resolves from `~/.config/otb/env`; most builder setups have it. If yours doesn't, claim the artisan newcomer chore or the artist idea-seed chore instead, or ask an Archon to enable it.)\n5. **Confirm it PASSED** the rubric (`modules/art-pipeline/iterations/run_summary.json` shows `status: \"pass\"`). If it only reached best-effort, regenerate with the fix_hint — never paper over a fail.\n6. **See it in-game**: `node scripts/bongos/art-stage.js` rebuilds the atlas and surfaces it on your sandbox preview (`sandbox-<login>.<instance-apex>`).\n7. Ship via `/builder-ship`; the ship notes name your asset and its rubric score.\n\n## Stay clear of (READ-ONLY)\n\nNever modify the locked palette (`modules/art-pipeline/template/palette.json`) or the rubric (`modules/art-pipeline/template/rubric.json`) — those are theme-defining and changing them needs an ADR (out of scope for a first ship). Touch only art assets/vocabulary under `art/` (and, only if your asset is a genuinely new base-tile TYPE, the `public/game/world/tilePalette.js` mapping). Do not touch any permission/pipeline-core/migration/infra file.",
53
53
  "done_when": [
54
54
  "Exactly one NEW in-world asset (your chosen subject) was generated via the pixel-art pipeline (`/otb-tile-generate` / orchestrator.py); the final PNG exists at modules/art-pipeline/generated/tiles/<id>.png.",
55
55
  "It PASSED the rubric (modules/art-pipeline/iterations/run_summary.json status 'pass', or /otb-design-review numbers reported at/above the bar) and every pixel is on the locked palette (modules/art-pipeline/template/palette.json).",
@@ -59,18 +59,16 @@
59
59
  ],
60
60
  "touches": [],
61
61
  "floor": 3
62
- }
63
- ],
64
- "ideator": [
62
+ },
65
63
  {
66
64
  "template_id": "idea-seed-pass",
67
65
  "version_id": "BONGOS-V1",
68
- "discipline": "ideator",
66
+ "discipline": "artist",
69
67
  "kind": "feature",
70
68
  "est_minutes": 45,
71
69
  "credits_reward": 12,
72
70
  "title": "Idea-seed pass: file 5-8 fresh in-world ideas (names, lore, or builder-UX) into the idea inbox",
73
- "description": "## What you'll do\n\nThe seed of almost everything built here is the **idea_inbox** — a queue anyone can drop ideas into, which trusted builders later triage into real tasks. Your first ship as an ideator is simple and concrete: **add a small batch of fresh ideas to that inbox.** You write no code, touch no game, change no system — you think, write a few short well-shaped ideas, and file them with one command each.\n\nIt is identical for every newcomer and **always valuable**: the inbox is never \"done\" — every triage pass consumes ideas (promote/discard/merge), so it is permanently hungry for fresh input, and a growing world always wants more names, more lore, more texture. Your batch is brand-new content, so it can never duplicate \"the work\" — the supply IS the work.\n\n## Pick ONE branch\n\n- **Branch A — PRODUCT flavour (default).** Ideas that grow the world players see: **names + one line of lore** for a character, structure, landmark, district, ship, or festival; or a world-background snippet (a custom, a superstition, a line a named NPC says). Tone: mythic, mysterious, a \"discovered secret\"; Mediterranean ancient-Greek coast (just south of Athens); NOT modern, NOT tongue-in-cheek-modern. Ground it in CLAUDE.md §3/§6/§7.\n- **Branch B — INTERNAL / builder-UX.** Small user-experience improvement ideas for the GDS / builders' hall (the surfaces builders use, not the game): a clearer label, a confusing onboarding message, a quality-of-life tweak to a status page or a slash-command's output. See `docs/TRACKS.md` for product vs internal.\n\n## How to find your way in\n\n1. (Branch A) Read CLAUDE.md §3/§6/§7 for tone. (Branch B) Skim `docs/TRACKS.md` and click around your instance's /builders hall for the rough edge you'll write up.\n2. See what's already filed so you don't repeat it: `node scripts/bongos/api.js GET /api/bongos/inbox`.\n3. File each idea with `node scripts/bongos/capture.js \"<title>\" --body-file <path>` (filing an idea is the open-to-all path — a Xenos/Thetes session is allowed to do it; you just can't triage them). Put one line of detail + a `track: product` or `track: internal` line in each body file.",
71
+ "description": "## What you'll do\n\nThe seed of almost everything built here is the **idea_inbox** — a queue anyone can drop ideas into, which trusted builders later triage into real tasks. Your first ship here is simple and concrete: **add a small batch of fresh ideas to that inbox.** You write no code, touch no game, change no system — you think, write a few short well-shaped ideas, and file them with one command each.\n\nIt is identical for every newcomer and **always valuable**: the inbox is never \"done\" — every triage pass consumes ideas (promote/discard/merge), so it is permanently hungry for fresh input, and a growing world always wants more names, more lore, more texture. Your batch is brand-new content, so it can never duplicate \"the work\" — the supply IS the work.\n\n## Pick ONE branch\n\n- **Branch A — PRODUCT flavour (default).** Ideas that grow the world players see: **names + one line of lore** for a character, structure, landmark, district, ship, or festival; or a world-background snippet (a custom, a superstition, a line a named NPC says). Tone: mythic, mysterious, a \"discovered secret\"; Mediterranean ancient-Greek coast (just south of Athens); NOT modern, NOT tongue-in-cheek-modern. Ground it in CLAUDE.md §3/§6/§7.\n- **Branch B — INTERNAL / builder-UX.** Small user-experience improvement ideas for the GDS / builders' hall (the surfaces builders use, not the game): a clearer label, a confusing onboarding message, a quality-of-life tweak to a status page or a slash-command's output. See `docs/TRACKS.md` for product vs internal.\n\n## How to find your way in\n\n1. (Branch A) Read CLAUDE.md §3/§6/§7 for tone. (Branch B) Skim `docs/TRACKS.md` and click around your instance's /builders hall for the rough edge you'll write up.\n2. See what's already filed so you don't repeat it: `node scripts/bongos/api.js GET /api/bongos/inbox`.\n3. File each idea with `node scripts/bongos/capture.js \"<title>\" --body-file <path>` (filing an idea is the open-to-all path — a Xenos/Thetes session is allowed to do it; you just can't triage them). Put one line of detail + a `track: product` or `track: internal` line in each body file.",
74
72
  "done_when": [
75
73
  "You filed 5–8 fresh ideas into the idea_inbox via `node scripts/bongos/capture.js` (each prints a `Captured idea #N` confirmation), all in ONE branch (A product, or B internal/UX).",
76
74
  "Each idea has a clear title and a body with at least one sentence of detail — for Branch A, one line of in-world lore (who/what it is); for Branch B, what the rough edge is and what 'better' looks like.",
@@ -24,13 +24,13 @@ function test(name, fn) {
24
24
  }
25
25
 
26
26
  // per-discipline DEFAULT floors (the fallback when a template omits its own).
27
- const FLOOR_DEFAULTS = { engineer: 2, artist: 3, ideator: 2 };
27
+ const FLOOR_DEFAULTS = { artisan: 2, artist: 3 };
28
28
 
29
29
  // templates-by-discipline fixture (each discipline -> LIST of chores).
30
30
  const TEMPLATES = {
31
- engineer: [
32
- { template_id: 'unit-test-pass', version_id: 'GDS-V4', discipline: 'engineer', floor: 2, title: 'Add a unit test', done_when: ['a', 'b'], est_minutes: 45 },
33
- { template_id: 'docs-hygiene-pass', version_id: 'GDS-V4', discipline: 'engineer', floor: 1, title: 'Docs sweep', done_when: ['a', 'b'], est_minutes: 40 },
31
+ artisan: [
32
+ { template_id: 'unit-test-pass', version_id: 'GDS-V4', discipline: 'artisan', floor: 2, title: 'Add a unit test', done_when: ['a', 'b'], est_minutes: 45 },
33
+ { template_id: 'docs-hygiene-pass', version_id: 'GDS-V4', discipline: 'artisan', floor: 1, title: 'Docs sweep', done_when: ['a', 'b'], est_minutes: 40 },
34
34
  ],
35
35
  artist: [
36
36
  { template_id: 'asset-generate-pass', version_id: 'V2', discipline: 'artist', title: 'Asset', done_when: ['a', 'b'], est_minutes: 55 }, // no own floor -> default
@@ -40,7 +40,7 @@ const TEMPLATES = {
40
40
  // ---- templateFloor ----
41
41
 
42
42
  test('templateFloor: own floor wins over the discipline default', () => {
43
- assert.strictEqual(nf.templateFloor({ floor: 5 }, 'engineer', FLOOR_DEFAULTS), 5);
43
+ assert.strictEqual(nf.templateFloor({ floor: 5 }, 'artisan', FLOOR_DEFAULTS), 5);
44
44
  });
45
45
 
46
46
  test('templateFloor: falls back to the discipline default when omitted', () => {
@@ -48,7 +48,7 @@ test('templateFloor: falls back to the discipline default when omitted', () => {
48
48
  });
49
49
 
50
50
  test('templateFloor: 0 is a valid explicit floor (not treated as missing)', () => {
51
- assert.strictEqual(nf.templateFloor({ floor: 0 }, 'engineer', FLOOR_DEFAULTS), 0);
51
+ assert.strictEqual(nf.templateFloor({ floor: 0 }, 'artisan', FLOOR_DEFAULTS), 0);
52
52
  });
53
53
 
54
54
  test('templateFloor: unknown discipline with no default -> 0', () => {
@@ -69,7 +69,7 @@ test('computeTemplateDeficits: empty supply -> each chore needs its full floor',
69
69
  });
70
70
 
71
71
  test('computeTemplateDeficits: ready + backlog both count as supply (idempotency)', () => {
72
- const supply = { 'engineer:unit-test-pass': { ready: 1, backlog: 1 } }; // fills floor 2
72
+ const supply = { 'artisan:unit-test-pass': { ready: 1, backlog: 1 } }; // fills floor 2
73
73
  const rows = nf.computeTemplateDeficits(supply, TEMPLATES, FLOOR_DEFAULTS);
74
74
  assert.strictEqual(deficitFor(rows, 'unit-test-pass').needed, 0);
75
75
  assert.strictEqual(deficitFor(rows, 'docs-hygiene-pass').needed, 1, 'sibling chore is independent');
@@ -77,7 +77,7 @@ test('computeTemplateDeficits: ready + backlog both count as supply (idempotency
77
77
 
78
78
  test('computeTemplateDeficits: supply is per-template, not shared across a discipline', () => {
79
79
  // over-supplying one engineer chore must NOT satisfy the other.
80
- const supply = { 'engineer:unit-test-pass': { ready: 9, backlog: 0 } };
80
+ const supply = { 'artisan:unit-test-pass': { ready: 9, backlog: 0 } };
81
81
  const rows = nf.computeTemplateDeficits(supply, TEMPLATES, FLOOR_DEFAULTS);
82
82
  assert.strictEqual(deficitFor(rows, 'unit-test-pass').needed, 0);
83
83
  assert.strictEqual(deficitFor(rows, 'docs-hygiene-pass').needed, 1);
@@ -102,48 +102,48 @@ test('computeTemplateDeficits: ignores non-floor disciplines', () => {
102
102
  // ---- instanceSourceRef + parseSeq + parseRef ----
103
103
 
104
104
  test('instanceSourceRef: tmpl:<disc>:<template_id>:<seq>', () => {
105
- assert.strictEqual(nf.instanceSourceRef('engineer', 'unit-test-pass', 7), 'tmpl:engineer:unit-test-pass:7');
105
+ assert.strictEqual(nf.instanceSourceRef('artisan', 'unit-test-pass', 7), 'tmpl:artisan:unit-test-pass:7');
106
106
  });
107
107
 
108
108
  test('parseSeq: returns seq only for the matching discipline+template', () => {
109
- assert.strictEqual(nf.parseSeq('tmpl:engineer:unit-test-pass:4', 'engineer', 'unit-test-pass'), 4);
110
- assert.strictEqual(nf.parseSeq('tmpl:engineer:unit-test-pass:4', 'artist', 'unit-test-pass'), null, 'wrong discipline');
111
- assert.strictEqual(nf.parseSeq('tmpl:engineer:other:4', 'engineer', 'unit-test-pass'), null, 'wrong template');
112
- assert.strictEqual(nf.parseSeq('manual:engineer:some-title', 'engineer', 'unit-test-pass'), null, 'foreign ref');
113
- assert.strictEqual(nf.parseSeq('tmpl:engineer:unit-test-pass:abc', 'engineer', 'unit-test-pass'), null, 'malformed seq');
109
+ assert.strictEqual(nf.parseSeq('tmpl:artisan:unit-test-pass:4', 'artisan', 'unit-test-pass'), 4);
110
+ assert.strictEqual(nf.parseSeq('tmpl:artisan:unit-test-pass:4', 'artist', 'unit-test-pass'), null, 'wrong discipline');
111
+ assert.strictEqual(nf.parseSeq('tmpl:artisan:other:4', 'artisan', 'unit-test-pass'), null, 'wrong template');
112
+ assert.strictEqual(nf.parseSeq('manual:artisan:some-title', 'artisan', 'unit-test-pass'), null, 'foreign ref');
113
+ assert.strictEqual(nf.parseSeq('tmpl:artisan:unit-test-pass:abc', 'artisan', 'unit-test-pass'), null, 'malformed seq');
114
114
  });
115
115
 
116
116
  test('parseRef: decomposes a chore ref into discipline/template/seq', () => {
117
- assert.deepStrictEqual(nf.parseRef('tmpl:engineer:unit-test-pass:7'), { discipline: 'engineer', templateId: 'unit-test-pass', seq: 7 });
118
- assert.strictEqual(nf.parseRef('manual:engineer:x'), null, 'not a chore ref');
119
- assert.strictEqual(nf.parseRef('tmpl:engineer:unit-test-pass:0'), null, 'seq must be positive');
120
- assert.strictEqual(nf.parseRef('tmpl:engineer:unit-test-pass'), null, 'too few parts');
117
+ assert.deepStrictEqual(nf.parseRef('tmpl:artisan:unit-test-pass:7'), { discipline: 'artisan', templateId: 'unit-test-pass', seq: 7 });
118
+ assert.strictEqual(nf.parseRef('manual:artisan:x'), null, 'not a chore ref');
119
+ assert.strictEqual(nf.parseRef('tmpl:artisan:unit-test-pass:0'), null, 'seq must be positive');
120
+ assert.strictEqual(nf.parseRef('tmpl:artisan:unit-test-pass'), null, 'too few parts');
121
121
  assert.strictEqual(nf.parseRef('tmpl:a:b:c:d'), null, 'too many parts');
122
122
  });
123
123
 
124
124
  // ---- nextSeq (monotonic across all refs) ----
125
125
 
126
126
  test('nextSeq: empty set -> 1', () => {
127
- assert.strictEqual(nf.nextSeq(new Set(), 'engineer', 'unit-test-pass'), 1);
127
+ assert.strictEqual(nf.nextSeq(new Set(), 'artisan', 'unit-test-pass'), 1);
128
128
  });
129
129
 
130
130
  test('nextSeq: 1 + max matching seq, ignoring other templates/disciplines', () => {
131
131
  const refs = new Set([
132
- 'tmpl:engineer:unit-test-pass:1',
133
- 'tmpl:engineer:unit-test-pass:3', // gap is fine; max is 3
134
- 'tmpl:engineer:unit-test-pass:2',
135
- 'tmpl:engineer:docs-hygiene-pass:9', // sibling chore — ignored
132
+ 'tmpl:artisan:unit-test-pass:1',
133
+ 'tmpl:artisan:unit-test-pass:3', // gap is fine; max is 3
134
+ 'tmpl:artisan:unit-test-pass:2',
135
+ 'tmpl:artisan:docs-hygiene-pass:9', // sibling chore — ignored
136
136
  'tmpl:artist:asset-generate-pass:5', // different discipline — ignored
137
- 'manual:engineer:something', // foreign — ignored
137
+ 'manual:artisan:something', // foreign — ignored
138
138
  ]);
139
- assert.strictEqual(nf.nextSeq(refs, 'engineer', 'unit-test-pass'), 4);
140
- assert.strictEqual(nf.nextSeq(refs, 'engineer', 'docs-hygiene-pass'), 10, 'sibling seq is independent');
141
- assert.strictEqual(nf.nextSeq(refs, 'ideator', 'idea-seed-pass'), 1);
139
+ assert.strictEqual(nf.nextSeq(refs, 'artisan', 'unit-test-pass'), 4);
140
+ assert.strictEqual(nf.nextSeq(refs, 'artisan', 'docs-hygiene-pass'), 10, 'sibling seq is independent');
141
+ assert.strictEqual(nf.nextSeq(refs, 'artist', 'idea-seed-pass'), 1);
142
142
  });
143
143
 
144
144
  test('nextSeq: never recycles a shipped/abandoned seq (max over ALL statuses)', () => {
145
- const refs = new Set(['tmpl:ideator:idea-seed-pass:1', 'tmpl:ideator:idea-seed-pass:2']);
146
- assert.strictEqual(nf.nextSeq(refs, 'ideator', 'idea-seed-pass'), 3);
145
+ const refs = new Set(['tmpl:artist:idea-seed-pass:1', 'tmpl:artist:idea-seed-pass:2']);
146
+ assert.strictEqual(nf.nextSeq(refs, 'artist', 'idea-seed-pass'), 3);
147
147
  });
148
148
 
149
149
  // ---- buildProposalFromTemplate ----
@@ -151,7 +151,7 @@ test('nextSeq: never recycles a shipped/abandoned seq (max over ALL statuses)',
151
151
  const SAMPLE_TEMPLATE = {
152
152
  template_id: 'docs-hygiene-pass',
153
153
  version_id: 'GDS-V4',
154
- discipline: 'engineer',
154
+ discipline: 'artisan',
155
155
  kind: 'cleanup',
156
156
  est_minutes: 40,
157
157
  credits_reward: 15,
@@ -163,14 +163,14 @@ const SAMPLE_TEMPLATE = {
163
163
  };
164
164
 
165
165
  test('buildProposalFromTemplate: carries fields, normalizes touches, sets seq ref', () => {
166
- const p = nf.buildProposalFromTemplate(SAMPLE_TEMPLATE, 5, { discipline: 'engineer' });
166
+ const p = nf.buildProposalFromTemplate(SAMPLE_TEMPLATE, 5, { discipline: 'artisan' });
167
167
  assert.strictEqual(p.title, 'Docs-hygiene sweep', 'trimmed');
168
168
  assert.strictEqual(p.version_id, 'GDS-V4');
169
169
  assert.strictEqual(p.kind, 'cleanup');
170
170
  assert.strictEqual(p.credits_reward, 15);
171
171
  assert.strictEqual(p.est_minutes, 40);
172
172
  assert.deepStrictEqual(p.touches, ['docs/', '42'], 'empties dropped, coerced to strings');
173
- assert.strictEqual(p.source_ref, 'tmpl:engineer:docs-hygiene-pass:5');
173
+ assert.strictEqual(p.source_ref, 'tmpl:artisan:docs-hygiene-pass:5');
174
174
  assert.deepStrictEqual(p.done_when, ['a', 'b']);
175
175
  });
176
176
 
@@ -191,20 +191,22 @@ test('buildProposalFromTemplate: null on a titleless template', () => {
191
191
  test('buildDescription: folds done-when + evergreen footer', () => {
192
192
  const desc = nf.buildDescription(
193
193
  { title: 't', description: 'Body text.', done_when: ['x', 'y'], est_minutes: 30 },
194
- 'engineer'
194
+ 'artisan'
195
195
  );
196
196
  assert.ok(desc.includes('Body text.'));
197
197
  assert.ok(desc.includes('**Done when:**'));
198
198
  assert.ok(desc.includes('- x') && desc.includes('- y'));
199
199
  assert.ok(desc.includes('Evergreen newcomer chore'), 'footer present');
200
- assert.ok(desc.includes('engineer'), 'discipline in footer');
200
+ assert.ok(desc.includes('artisan'), 'discipline in footer');
201
201
  });
202
202
 
203
203
  // ---- loadTemplates: the REAL templates.json is well-formed (multi-chore shape) ----
204
204
 
205
205
  test('loadTemplates: real templates.json is a valid per-discipline list', () => {
206
206
  const templates = nf.loadTemplates(path.join(__dirname, 'newcomer-floor.templates.json'));
207
- for (const disc of ['engineer', 'artist', 'ideator']) {
207
+ // task 1005141: two crafts, not three — ideator folded into artist, engineer is artisan.
208
+ assert.ok(!('engineer' in templates) && !('ideator' in templates), 'no old-spelling keys remain');
209
+ for (const disc of ['artisan', 'artist']) {
208
210
  const list = templates[disc];
209
211
  assert.ok(Array.isArray(list) && list.length >= 1, `${disc} is a non-empty list`);
210
212
  for (const t of list) {
@@ -219,15 +221,22 @@ test('loadTemplates: real templates.json is a valid per-discipline list', () =>
219
221
  }
220
222
  });
221
223
 
222
- test('loadTemplates: engineer now offers BOTH the unit-test chore and the docs sweep', () => {
224
+ test('loadTemplates: artist holds BOTH the asset chore and the (former ideator) idea-seed chore', () => {
223
225
  const templates = nf.loadTemplates(path.join(__dirname, 'newcomer-floor.templates.json'));
224
- const ids = templates.engineer.map((t) => t.template_id);
226
+ const ids = templates.artist.map((t) => t.template_id);
227
+ assert.ok(ids.includes('asset-generate-pass'), 'the asset chore is present');
228
+ assert.ok(ids.includes('idea-seed-pass'), 'the idea chore moved under artist');
229
+ });
230
+
231
+ test('loadTemplates: artisan offers BOTH the unit-test chore and the docs sweep', () => {
232
+ const templates = nf.loadTemplates(path.join(__dirname, 'newcomer-floor.templates.json'));
233
+ const ids = templates.artisan.map((t) => t.template_id);
225
234
  assert.ok(ids.includes('unit-test-pass'), 'the safe coding chore is present');
226
235
  assert.ok(ids.includes('docs-hygiene-pass'), 'the docs sweep is retained');
227
236
  // every engineer chore touches only safe (non-protected) surfaces by design:
228
237
  // touches[] is empty (git-merge is the collision detector), so this asserts the
229
238
  // contract that engineer chores carry no protected-path touches.
230
- for (const t of templates.engineer) {
239
+ for (const t of templates.artisan) {
231
240
  assert.deepStrictEqual(t.touches || [], [], `${t.template_id} declares no touches[]`);
232
241
  }
233
242
  });
@@ -123,10 +123,11 @@ function checkRolePacks(fsImpl = fs) {
123
123
  }
124
124
 
125
125
  // The split's own shape, asserted so a later edit cannot quietly drop a craft
126
- // back into the kernel: three core packs, and Governor deliberately absent
127
- // (criterion wa6-kernel-and-packs — "three packs, not four, until the owner decides").
128
- if (packCount < 3) {
129
- violations.push(`only ${packCount} craft(s) name a pack — the split is one kernel plus three packs (Engineer, Artist, Ideator). A craft with no pack has nowhere to keep what the role-neutral kernel does not.`);
126
+ // back into the kernel: two core packs since the game conversion folded the
127
+ // ideator into the artist (task 1005141, BONGOS-V3) — Artisan and Artist; Patron
128
+ // is a character, not a task craft, and has no pack.
129
+ if (packCount < 2) {
130
+ violations.push(`only ${packCount} craft(s) name a pack — the split is one kernel plus two packs (Artisan, Artist). A craft with no pack has nowhere to keep what the role-neutral kernel does not.`);
130
131
  }
131
132
 
132
133
  return {
@@ -10,7 +10,7 @@
10
10
  "title": "Spike: where the welcome checklist is stored",
11
11
  "description": "Output an ADR naming the table and who writes it.",
12
12
  "kind": "spike",
13
- "discipline": "engineer",
13
+ "discipline": "artisan",
14
14
  "priority": 4,
15
15
  "est_minutes": 90,
16
16
  "criterion_ids": ["newcomer-knows-what-to-do-next"]
@@ -20,7 +20,7 @@
20
20
  "title": "The welcome checklist API",
21
21
  "description": "Build to R01's ADR. Done-when: a new builder's checklist is returned by one GET.",
22
22
  "kind": "feature",
23
- "discipline": "engineer",
23
+ "discipline": "artisan",
24
24
  "priority": 4,
25
25
  "est_minutes": 180,
26
26
  "criterion_ids": ["newcomer-knows-what-to-do-next"],
@@ -42,7 +42,7 @@
42
42
  "title": "Proof: a newcomer finds their next step without asking",
43
43
  "description": "A brand-new builder signs in and finds what to do next unaided.",
44
44
  "kind": "verify",
45
- "discipline": "engineer",
45
+ "discipline": "artisan",
46
46
  "priority": 3,
47
47
  "est_minutes": 60,
48
48
  "criterion_ids": ["newcomer-knows-what-to-do-next"],