@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.
- package/.bongos-core.json +495 -350
- package/.claude/skills/ask-for-help/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/design/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +11 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/adr/0274-one-kernel-three-role-packs.md +2 -2
- package/docs/adr/0399-the-visor-is-glass-over-the-scene-and-owns-no-camera.md +91 -0
- package/docs/adr/0400-a-chain-reserves-work-and-each-sub-quest-is-still-claimed-alone.md +109 -0
- package/docs/adr/0401-one-options-menu-opens-every-settings-screen-in-a-frame.md +53 -0
- package/docs/adr/README.md +3 -0
- package/docs/api/openapi.json +195 -3
- package/docs/api-reference.md +8 -4
- package/docs/architecture.md +7 -3
- package/docs/copy-inventory.md +390 -370
- package/docs/copy-registry.json +719 -518
- package/docs/design/characters/character-sprites-source.webp +0 -0
- package/docs/file-map.md +7 -8
- package/docs/module-api-changelog.md +2 -0
- package/docs/modules-contract.md +5 -0
- package/docs/onboarding/slash-commands.md +1 -1
- package/docs/packs/{engineer.md → artisan.md} +5 -5
- package/docs/packs/artist.md +50 -4
- package/docs/page-inventory.json +15 -4
- package/docs/page-readings.json +3182 -3336
- package/docs/{packs/ideator.md → recipes/ideation-playbook.md} +5 -48
- package/migrations/core_296_crafts_artisan_artist.sql +72 -0
- package/migrations/core_297_builder_character.sql +30 -0
- package/modules/copy-desk/proposals.js +1 -1
- package/modules/economy/credits-by-craft.js +29 -15
- package/modules/hall-ui/public/ai-studio.html +4 -4
- package/modules/hall-ui/public/atlas.html +5 -5
- package/modules/hall-ui/public/atlas.js +3 -1
- package/modules/hall-ui/public/blockers.html +5 -5
- package/modules/hall-ui/public/board-room.html +4 -4
- package/modules/hall-ui/public/characters/artisan.png +0 -0
- package/modules/hall-ui/public/characters/artist.png +0 -0
- package/modules/hall-ui/public/characters/patron.png +0 -0
- package/modules/hall-ui/public/city.css +2 -1
- package/modules/hall-ui/public/city.html +9 -6
- package/modules/hall-ui/public/city.js +7 -1
- package/modules/hall-ui/public/collab.html +4 -4
- package/modules/hall-ui/public/commands.html +4 -4
- package/modules/hall-ui/public/copy-desk.html +4 -4
- package/modules/hall-ui/public/craft-mode.js +258 -64
- package/modules/hall-ui/public/deploy.html +4 -4
- package/modules/hall-ui/public/diagram-viewer.js +2 -1
- package/modules/hall-ui/public/dom-utils.js +1 -1
- package/modules/hall-ui/public/goal-map.html +9 -6
- package/modules/hall-ui/public/goals.html +5 -5
- package/modules/hall-ui/public/government.html +4 -4
- package/modules/hall-ui/public/hall-render.js +3 -1
- package/modules/hall-ui/public/home-city-draw.js +3 -3
- package/modules/hall-ui/public/home-city.css +4 -0
- package/modules/hall-ui/public/home-city.js +17 -9
- package/modules/hall-ui/public/home-gallery.js +8 -1
- package/modules/hall-ui/public/home-idea-box.css +2 -2
- package/modules/hall-ui/public/home-idea-box.js +3 -3
- package/modules/hall-ui/public/idea.html +5 -5
- package/modules/hall-ui/public/ideas.html +5 -5
- package/modules/hall-ui/public/index.html +11 -7
- package/modules/hall-ui/public/market-watch-item.html +4 -4
- package/modules/hall-ui/public/market-watch-rules.html +4 -4
- package/modules/hall-ui/public/market-watch.html +6 -9
- package/modules/hall-ui/public/market-watch.js +22 -45
- package/modules/hall-ui/public/modules.html +4 -4
- package/modules/hall-ui/public/money.html +4 -4
- package/modules/hall-ui/public/my-agents.html +4 -4
- package/modules/hall-ui/public/options-menu.js +409 -0
- package/modules/hall-ui/public/paintbrush.js +3 -3
- package/modules/hall-ui/public/palette.js +11 -13
- package/modules/hall-ui/public/primer.html +5 -5
- package/modules/hall-ui/public/profile-crafts.js +1 -1
- package/modules/hall-ui/public/profile-roles.js +23 -19
- package/modules/hall-ui/public/profile.css +1 -1
- package/modules/hall-ui/public/profile.html +19 -18
- package/modules/hall-ui/public/profile.js +19 -18
- package/modules/hall-ui/public/profile.states.json +4 -4
- package/modules/hall-ui/public/project-settings.html +4 -4
- package/modules/hall-ui/public/roster.html +4 -4
- package/modules/hall-ui/public/roster.js +3 -3
- package/modules/hall-ui/public/settings.css +24 -4
- package/modules/hall-ui/public/settings.html +21 -17
- package/modules/hall-ui/public/settings.js +44 -44
- package/modules/hall-ui/public/shell-game.css +65 -0
- package/modules/hall-ui/public/shell-game.js +283 -3
- package/modules/hall-ui/public/shell.js +7 -26
- package/modules/hall-ui/public/sky-interaction.js +7 -0
- package/modules/hall-ui/public/speciality-tools.js +1 -2
- package/modules/hall-ui/public/street-inside.js +3 -4
- package/modules/hall-ui/public/street.html +4 -4
- package/modules/hall-ui/public/studio.html +5 -5
- package/modules/hall-ui/public/style.css +73 -47
- package/modules/hall-ui/public/task-detail.js +5 -4
- package/modules/hall-ui/public/task.html +5 -5
- package/modules/hall-ui/public/thinking.css +10 -0
- package/modules/hall-ui/public/thinking.html +9 -6
- package/modules/hall-ui/public/tweak-editor.html +5 -5
- package/modules/hall-ui/public/tweak-editor.js +5 -1
- package/modules/hall-ui/public/update-pill.js +91 -2
- package/modules/hall-ui/public/visor.css +80 -0
- package/modules/hall-ui/public/visor.js +316 -0
- package/modules/hall-ui/public/watch-repo-invite.js +159 -0
- package/modules/hall-ui/public/watch.html +6 -5
- package/modules/hall-ui/public/watch.js +17 -2
- package/modules/hall-ui/public/welcome-boot.js +15 -6
- package/modules/hall-ui/public/work.html +5 -5
- package/modules/hall-ui/records/builders-roster.md +2 -2
- package/modules/hall-ui/records/craft-mode.md +13 -11
- package/modules/hall-ui/records/game-mode.md +2 -2
- package/modules/hall-ui/records/governor-street.md +1 -1
- package/modules/hall-ui/records/home-city.md +3 -1
- package/modules/hall-ui/records/market-watch-settings.md +2 -0
- package/modules/hall-ui/records/options-menu.md +15 -0
- package/modules/hall-ui/records/profile-roles.md +11 -11
- package/modules/hall-ui/records/sky.md +3 -3
- package/modules/hall-ui/records/start-screen.md +21 -0
- package/modules/hall-ui/records/tweak-editor.md +1 -1
- package/modules/hall-ui/records/update-pill.md +12 -0
- package/modules/hall-ui/records/visor.md +23 -0
- package/modules/hall-ui/records/welcome-tower.md +5 -5
- package/modules/ideas/ideator-stats.js +3 -1
- package/modules/ideas/inbox.js +2 -2
- package/modules/lifecycle/activity.js +1 -1
- package/modules/lifecycle/cascade.js +4 -4
- package/modules/lifecycle/goal-spec-task.js +1 -1
- package/modules/lifecycle/help-requests.js +19 -15
- package/modules/lifecycle/kickoff-checklist.js +3 -3
- package/modules/lifecycle/kickoff-seed.js +1 -1
- package/modules/lifecycle/migrations/lifecycle_021_crafts_artisan_artist.sql +29 -0
- package/modules/lifecycle/mingle.js +4 -4
- package/modules/lifecycle/page-tweak-write.js +1 -1
- package/modules/lifecycle/routes/task-write-routes.js +3 -1
- package/modules/lifecycle/routes/tasks.js +3 -3
- package/modules/lifecycle/ship-card.js +2 -3
- package/modules/lifecycle/task-classifier.js +39 -29
- package/modules/lifecycle/tests/cascade.mjs +7 -7
- package/modules/onboarding/founding-mode.js +3 -0
- package/modules/onboarding/genesis-home.js +21 -1
- package/modules/onboarding/routes/onboarding.js +2 -0
- package/modules/page-notes/notes.js +3 -2
- package/modules/page-notes/split.js +1 -1
- package/modules/platform-identity/craft-rollup.js +35 -7
- package/modules/platform-identity/migrations/platform_identity_036_crafts_artisan_artist.sql +45 -0
- package/modules/platform-identity/platform-identity.js +2 -2
- package/modules/platform-identity/routes/community-leaderboard.js +2 -1
- package/modules/platform-identity/routes/community-search.js +2 -1
- package/modules/platform-identity/routes/sso.js +2 -1
- package/modules/platform-identity/tests/platform-identity.mjs +6 -6
- package/modules/public-landing/public/community.html +3 -5
- package/modules/public-landing/public/index.html +1 -1
- package/modules/public-landing/public/projects.html +1 -1
- package/modules/specialities/migrations/specialities_004_crafts_artisan_artist.sql +30 -0
- package/modules/specialities/routes/specialities.js +2 -2
- package/modules/status-ui/public/status.js +1 -1
- package/modules/ui-design/kit/fixtures/community-leaderboard.json +10 -10
- package/modules/ui-design/kit/fixtures/community-search.json +2 -2
- package/modules/ui-design/kit/fixtures/scouting-recruiter.json +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +70 -0
- package/scripts/bongos/autobongos-loop.js +1 -1
- package/scripts/bongos/discipline-modes.json +7 -12
- package/scripts/bongos/fitness.js +1 -1
- package/scripts/bongos/gen-hall-sitemap.js +7 -7
- package/scripts/bongos/grade-smoke.js +2 -2
- package/scripts/bongos/init.js +2 -2
- package/scripts/bongos/newcomer-floor.config.json +1 -2
- package/scripts/bongos/newcomer-floor.js +4 -3
- package/scripts/bongos/newcomer-floor.templates.json +10 -12
- package/scripts/bongos/newcomer-floor.test.js +47 -38
- package/scripts/bongos/role-pack-guard.js +5 -4
- package/scripts/bongos/seed-tasks.example.json +3 -3
- package/scripts/bongos/setup.js +20 -107
- package/scripts/bongos/start.js +2 -2
- package/scripts/bongos/task.js +5 -4
- package/scripts/bongos/triage.js +1 -1
- package/scripts/bongos/worktree.js +16 -4
- package/scripts/setup-builder.js +6 -46
- package/src/bongos/auth-admission.js +8 -8
- package/src/bongos/craft-mode.js +2 -0
- package/src/bongos/db.js +39 -1
- package/src/bongos/github-repos.js +78 -0
- package/src/bongos/module-scope-map.js +22 -0
- package/src/bongos/role-stats.js +27 -11
- package/src/bongos/routes/github.js +100 -2
- package/src/bongos/routes/me.js +33 -0
- package/src/bongos/serve-internal.js +26 -9
- package/src/crafts.js +66 -0
- package/src/module-api.js +6 -1
- package/src/module-loader/manifest-schema.js +16 -0
- package/src/modules.js +2 -2
- package/src/role-responsibilities.js +6 -5
- package/tests/acclaim_feed_evidence_db.mjs +1 -1
- package/tests/activity_rollup_as_of.mjs +1 -1
- package/tests/api_alias_caller_ratchet.mjs +3 -3
- package/tests/application_lifecycle.mjs +1 -1
- package/tests/artist_review.mjs +1 -1
- package/tests/artist_review_goal.mjs +1 -1
- package/tests/blocker_hall_live_proof.mjs +1 -1
- package/tests/blocker_tasks_named_db.mjs +2 -2
- package/tests/canonical_profile_url.mjs +1 -1
- package/tests/cap_newcomer_tasks.mjs +13 -13
- package/tests/character_creation.mjs +294 -0
- package/tests/claim_gate_open_blocker.mjs +2 -2
- package/tests/claim_gate_rebase.mjs +6 -6
- package/tests/claude_materialize.mjs +1 -1
- package/tests/clawback_confirmed_backlog.mjs +3 -3
- package/tests/collab_page.mjs +8 -9
- package/tests/collab_self_settling.mjs +2 -2
- package/tests/community_leaderboard_route.mjs +13 -5
- package/tests/community_proof_db.mjs +3 -0
- package/tests/community_search_route.mjs +7 -6
- package/tests/conductor.mjs +1 -1
- package/tests/conductor_main_e2e.mjs +8 -8
- package/tests/copy_desk_flags_db.mjs +1 -1
- package/tests/copy_desk_page_approve.mjs +2 -2
- package/tests/copy_desk_page_claim.mjs +2 -2
- package/tests/copy_desk_page_next.mjs +1 -1
- package/tests/copy_desk_page_release.mjs +1 -1
- package/tests/copy_desk_page_submit.mjs +2 -2
- package/tests/core_258_box_session_claims_db.mjs +1 -1
- package/tests/craft_mode.mjs +48 -34
- package/tests/craft_mode_home.mjs +20 -20
- package/tests/craft_mode_looks.mjs +16 -15
- package/tests/craft_mode_nav.mjs +31 -29
- package/tests/craft_review_mode.mjs +46 -41
- package/tests/crafts_vocabulary.mjs +70 -0
- package/tests/cross_discipline_priority_direction.mjs +5 -5
- package/tests/fitness.mjs +7 -11
- package/tests/funded_project_pays_proof_db.mjs +1 -1
- package/tests/genesis_home.mjs +21 -1
- package/tests/github_chip.mjs +3 -3
- package/tests/github_repo_invite.mjs +379 -0
- package/tests/goal_criterion_confirm_ui.mjs +1 -1
- package/tests/goal_invite_consent.mjs +1 -1
- package/tests/goal_map_page.mjs +2 -2
- package/tests/goal_sort_staleness.mjs +1 -1
- package/tests/goal_spec_task.mjs +1 -1
- package/tests/goal_spec_task_db.mjs +3 -3
- package/tests/grade_engine.mjs +2 -2
- package/tests/grader_no_artifact.mjs +1 -1
- package/tests/grader_prompt_fencing.mjs +1 -1
- package/tests/grader_prompt_window_bound.mjs +1 -1
- package/tests/grader_reader_lens.mjs +2 -2
- package/tests/grader_severity_gate.mjs +1 -1
- package/tests/grader_unbacked_score.mjs +1 -1
- package/tests/hall_approval_queue.mjs +1 -1
- package/tests/hall_board_world.mjs +2 -2
- package/tests/hall_builders_page_pass.mjs +1 -1
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_fullscreen.mjs +27 -3
- package/tests/hall_kit_charts.mjs +1 -1
- package/tests/hall_landing_boot.mjs +1 -1
- package/tests/hall_my_agents.mjs +16 -16
- package/tests/hall_nav.mjs +22 -15
- package/tests/hall_options_menu.mjs +247 -0
- package/tests/hall_oversight_world.mjs +1 -1
- package/tests/hall_palette.mjs +8 -9
- package/tests/hall_profile_world.mjs +3 -3
- package/tests/hall_record_world.mjs +2 -2
- package/tests/hall_settings_world.mjs +2 -1
- package/tests/hall_sitemap.mjs +6 -5
- package/tests/hall_start_screen.mjs +244 -0
- package/tests/hall_street.mjs +2 -2
- package/tests/hall_street_inside.mjs +17 -9
- package/tests/hall_studio_world.mjs +3 -3
- package/tests/hall_visor.mjs +224 -0
- package/tests/help_requests.mjs +47 -15
- package/tests/helpers.mjs +4 -4
- package/tests/home_city.mjs +6 -4
- package/tests/home_city_widget.mjs +33 -14
- package/tests/home_gallery_widget.mjs +4 -4
- package/tests/home_idea_box.mjs +6 -6
- package/tests/hub_community_page.mjs +3 -3
- package/tests/hub_craft_rollup.mjs +75 -36
- package/tests/hub_recent_ships.mjs +3 -2
- package/tests/idea_cascade.mjs +2 -2
- package/tests/idea_credit_streams_db.mjs +1 -1
- package/tests/idea_detail_page.mjs +1 -1
- package/tests/idea_ideate_full.mjs +5 -2
- package/tests/idea_spark_hall.mjs +1 -1
- package/tests/init.mjs +3 -3
- package/tests/instance_manifest.mjs +2 -2
- package/tests/kickoff_checklist.mjs +1 -1
- package/tests/leaderboard_roles.mjs +34 -25
- package/tests/lifecycle_claim_ladder_refs.mjs +55 -0
- package/tests/lightbulb.mjs +14 -10
- package/tests/main_discipline.mjs +8 -8
- package/tests/markdown_line_endings.mjs +1 -1
- package/tests/market_watch_pages.mjs +52 -74
- package/tests/me_streak_leaderboard_queries.mjs +3 -3
- package/tests/migration_allocation.mjs +1 -1
- package/tests/mingle.mjs +36 -21
- package/tests/module-scope-map.mjs +22 -2
- package/tests/module_api.mjs +1 -0
- package/tests/module_contributions.mjs +13 -13
- package/tests/module_manifest.mjs +29 -0
- package/tests/module_skills_hook.mjs +2 -2
- package/tests/modules.mjs +14 -7
- package/tests/nav_permission_atoms.mjs +2 -1
- package/tests/needs_rebase.mjs +1 -1
- package/tests/newcomer_restock.mjs +13 -13
- package/tests/page_notes_panel.mjs +22 -22
- package/tests/page_tweak_no_block.mjs +1 -1
- package/tests/paintbrush.mjs +7 -7
- package/tests/privacy_matrix.mjs +1 -0
- package/tests/profile_activity.mjs +20 -23
- package/tests/profile_earnings.mjs +1 -1
- package/tests/profile_nudge_prefs.mjs +4 -4
- package/tests/profile_role_headline.mjs +73 -46
- package/tests/profile_role_stats.mjs +55 -45
- package/tests/profile_rollup_consent.mjs +19 -6
- package/tests/profile_route.mjs +12 -12
- package/tests/profile_ui_cross_project.mjs +4 -4
- package/tests/project_invite_suggestions.mjs +2 -2
- package/tests/promote_goal_id.mjs +1 -1
- package/tests/publish_manifest.mjs +1 -1
- package/tests/rank_floor_wiring.mjs +1 -1
- package/tests/regrade_actual_minutes.mjs +1 -1
- package/tests/removal_proposal.mjs +2 -2
- package/tests/reward_gate.mjs +4 -4
- package/tests/reward_mode_cost_plus_only.mjs +1 -1
- package/tests/role_responsibilities.mjs +18 -12
- package/tests/role_stats_db.mjs +17 -16
- package/tests/scouting_rollup_fanout.mjs +5 -5
- package/tests/seed_tasks.mjs +5 -2
- package/tests/session_earnings_mirror_db.mjs +1 -1
- package/tests/session_reward_on_land_db.mjs +1 -1
- package/tests/session_reward_reconcile_db.mjs +2 -2
- package/tests/sessions_kit_pager.mjs +1 -1
- package/tests/settings_art_key_absent.mjs +2 -2
- package/tests/settings_main_role.mjs +21 -20
- package/tests/setup_flow.mjs +3 -3
- package/tests/setup_smoke.mjs +20 -0
- package/tests/ship_card.mjs +6 -6
- package/tests/specialities.mjs +16 -4
- package/tests/speciality_db_shape.mjs +6 -6
- package/tests/speciality_injection.mjs +17 -17
- package/tests/speciality_offer_ui.mjs +5 -5
- package/tests/speciality_offered.mjs +6 -5
- package/tests/speciality_session_skills.mjs +7 -7
- package/tests/speciality_skills.mjs +11 -11
- package/tests/speciality_walkthrough_ui.mjs +2 -2
- package/tests/sso_checkin_identity_spoof.mjs +2 -1
- package/tests/task_classifier_vocab.mjs +10 -0
- package/tests/task_cli.mjs +11 -3
- package/tests/task_criteria_unlink_db.mjs +2 -2
- package/tests/task_discipline_required.mjs +38 -16
- package/tests/task_goal_required.mjs +4 -4
- package/tests/task_needs_db.mjs +1 -1
- package/tests/tweak_replaces_artist_review.mjs +1 -1
- package/tests/update_pill.mjs +153 -2
- package/tests/upkeep_home.mjs +1 -1
- package/tests/v3_c1_options_proof.mjs +145 -0
- package/tests/visual_review.mjs +3 -3
- package/tests/watch_applications_queue.mjs +8 -5
- package/tests/watch_sealed_floor.mjs +3 -1
- package/tests/welcome_boot.mjs +12 -9
- package/tests/welcome_boot_browser.mjs +13 -13
- package/tests/worktree_helper.mjs +16 -1
package/src/bongos/role-stats.js
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
// WHAT IT ANSWERS. For one builder: which of the three crafts they are shown in,
|
|
9
9
|
// which one leads, and each craft's own headline numbers.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
11
|
+
// artisan `shipped`, works shipped (unchanged from today), and building credits
|
|
12
12
|
// artist pages approved, "N of M tweaked" per surface, credits from pages,
|
|
13
13
|
// this week (copy-desk's studio tally, the `copy-desk.artistStats`
|
|
14
14
|
// port)
|
|
15
|
-
//
|
|
15
|
+
// AND (task 1005141, the ideator craft folded in) thoughts filed, ignited, Full Ideas, ratified by the board, total
|
|
16
16
|
// size across every idea stream with the historical-estimate
|
|
17
17
|
// marker, and the share still alive (the sky's own reads, the
|
|
18
18
|
// `ideas.ideatorStats` port)
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
const seams = require('../module-seams');
|
|
42
42
|
|
|
43
|
-
const CRAFTS = Object.freeze(['
|
|
43
|
+
const CRAFTS = Object.freeze(['artisan', 'artist']);
|
|
44
44
|
// A discipline as a craft, asked of economy (reward.craftForPreference), the
|
|
45
45
|
// module that decides which craft a credit belongs to. One table, so a builder's
|
|
46
46
|
// chips and their credits cannot disagree (`ui`, the design discipline, is the
|
|
@@ -76,7 +76,7 @@ function resolveMainRole({ mainDiscipline = null, preferred = [], byCraft = null
|
|
|
76
76
|
}
|
|
77
77
|
const pref = preferredCrafts(preferred);
|
|
78
78
|
if (pref.length) return { main: pref[0], basis: 'preferred' };
|
|
79
|
-
return { main: '
|
|
79
|
+
return { main: 'artisan', basis: 'default' };
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// shownCrafts — pure. Main first, then every craft with credits, in the
|
|
@@ -97,7 +97,7 @@ function composeRoleStats({ choice = null, byCraft = null, shippedCount = 0, art
|
|
|
97
97
|
const { main, basis } = resolveMainRole({ mainDiscipline: choice && choice.main_discipline, preferred, byCraft });
|
|
98
98
|
const shown = shownCrafts({ main, preferred, byCraft });
|
|
99
99
|
const credits = byCraft
|
|
100
|
-
? {
|
|
100
|
+
? { artisan: byCraft.artisan, artist: byCraft.artist, other: byCraft.other, total: byCraft.total }
|
|
101
101
|
: null;
|
|
102
102
|
return {
|
|
103
103
|
main,
|
|
@@ -105,16 +105,32 @@ function composeRoleStats({ choice = null, byCraft = null, shippedCount = 0, art
|
|
|
105
105
|
shown,
|
|
106
106
|
preferred: preferredCrafts(preferred),
|
|
107
107
|
credits_by_craft: credits,
|
|
108
|
-
|
|
109
|
-
? { shipped: Number(shippedCount) || 0, credits: credits ? credits.
|
|
108
|
+
artisan: shown.includes('artisan')
|
|
109
|
+
? { shipped: Number(shippedCount) || 0, credits: credits ? credits.artisan : null }
|
|
110
110
|
: null,
|
|
111
|
-
artist: shown.includes('artist') ? (artist
|
|
112
|
-
ideator: shown.includes('ideator') ? (ideator || emptyIdeator()) : null,
|
|
111
|
+
artist: shown.includes('artist') ? artistBlock(artist, ideator, credits) : null,
|
|
113
112
|
};
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
// The shapes a shown craft takes when its port could not be asked: every number
|
|
117
116
|
// null, so a renderer says "unknown", never "0".
|
|
117
|
+
// The artist craft holds what the artist AND the old ideator craft did (task 1005141,
|
|
118
|
+
// BONGOS-V3: ideation folds into the artist). One block, two sources: the page
|
|
119
|
+
// numbers from copy-desk and the idea numbers from the sky. The two share no field
|
|
120
|
+
// name but `unavailable` (true only when NEITHER could be asked) and `credits`,
|
|
121
|
+
// which is the craft's ledger split when there is one, since that now carries
|
|
122
|
+
// both the page and the idea credit streams (economy credits-by-craft.js).
|
|
123
|
+
function artistBlock(artist, ideator, credits) {
|
|
124
|
+
const pages = artist || emptyArtist();
|
|
125
|
+
const ideas = ideator || emptyIdeator();
|
|
126
|
+
return {
|
|
127
|
+
...ideas,
|
|
128
|
+
...pages,
|
|
129
|
+
credits: credits ? credits.artist : pages.credits,
|
|
130
|
+
unavailable: Boolean(pages.unavailable && ideas.unavailable),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
118
134
|
function emptyArtist() {
|
|
119
135
|
return { pages_approved: null, pages: null, surfaces_touched: null, credits: null, this_week: null, surfaces: [], unavailable: true };
|
|
120
136
|
}
|
|
@@ -141,7 +157,7 @@ async function readRoleStats(builderId, { shippedCount = 0, getRoleChoice } = {}
|
|
|
141
157
|
const shown = shownCrafts({ main, preferred, byCraft });
|
|
142
158
|
|
|
143
159
|
const artistPort = shown.includes('artist') ? seams.resolveOptional('copy-desk.artistStats') : null;
|
|
144
|
-
const ideatorPort = shown.includes('
|
|
160
|
+
const ideatorPort = shown.includes('artist') ? seams.resolveOptional('ideas.ideatorStats') : null;
|
|
145
161
|
const [artist, ideator] = await Promise.all([
|
|
146
162
|
typeof artistPort === 'function' ? artistPort(builderId) : null,
|
|
147
163
|
typeof ideatorPort === 'function' ? ideatorPort(builderId) : null,
|
|
@@ -176,7 +192,7 @@ async function readRolesForBuilders(builderIds, { getRoleChoices } = {}) {
|
|
|
176
192
|
main_basis: basis,
|
|
177
193
|
shown: shownCrafts({ main, preferred, byCraft }),
|
|
178
194
|
credits_by_craft: byCraft
|
|
179
|
-
? {
|
|
195
|
+
? { artisan: byCraft.artisan, artist: byCraft.artist, other: byCraft.other, total: byCraft.total }
|
|
180
196
|
: null,
|
|
181
197
|
});
|
|
182
198
|
}
|
|
@@ -19,11 +19,12 @@ const express = require('express');
|
|
|
19
19
|
const auth = require('../auth');
|
|
20
20
|
const db = require('../db');
|
|
21
21
|
const secretBox = require('../secret-box');
|
|
22
|
-
const { listUserRepos, createUserRepo, listUserOrgs, getOrgAccess, createOrgRepo, validGithubLogin } = require('../github-repos');
|
|
22
|
+
const { listUserRepos, createUserRepo, listUserOrgs, getOrgAccess, createOrgRepo, validGithubLogin, inviteRepoCollaborator, repoInviteState, REPO_INVITE_PERMISSIONS } = require('../github-repos');
|
|
23
23
|
// Namespace require for the visibility helpers (task 1003045): the route tests
|
|
24
24
|
// stub these on the cached module object, which a load-time destructure would
|
|
25
25
|
// pin to the real implementations.
|
|
26
26
|
const githubRepos = require('../github-repos');
|
|
27
|
+
const repoInfo = require('../repo-info'); // namespace, so a test can stub loadRepoInfo
|
|
27
28
|
const { validateOrRespond } = require('./_helpers');
|
|
28
29
|
const prelaunch = require('../prelaunch-gate');
|
|
29
30
|
const log = require('../logger').logger.child({ src: 'github' });
|
|
@@ -43,6 +44,28 @@ function orgNotFound(res, org) {
|
|
|
43
44
|
return res.fail('org_not_found', { status: 404, message: `GitHub didn’t show your account an organisation called ${org}.` });
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
// The failures the two repo-invite routes share, each in the owner's words.
|
|
48
|
+
async function repoInviteFailure(req, res, err, where) {
|
|
49
|
+
if (err && err.status === 401) {
|
|
50
|
+
await db.deleteGithubToken({ builderId: req.builder.id }).catch(() => {});
|
|
51
|
+
return res.fail('repo_scope_required', { status: 403, message: 'Your GitHub authorization expired. Reconnect GitHub to invite people to the repository.' });
|
|
52
|
+
}
|
|
53
|
+
if (err && err.code === 'not_repo_admin') {
|
|
54
|
+
return res.fail('not_repo_admin', { status: 409, message: 'GitHub says your account can’t add people to this repository. Only its owner or an admin can: ask them, or sign in with that account.' });
|
|
55
|
+
}
|
|
56
|
+
if (err && err.code === 'repo_not_found') {
|
|
57
|
+
return res.fail('repo_not_found', { status: 404, message: 'GitHub didn’t show that repository or that username to your account. Check the spelling of the name and try again.' });
|
|
58
|
+
}
|
|
59
|
+
if (err && err.code === 'bad_permission') {
|
|
60
|
+
return res.fail('bad_permission', { status: 400, message: 'Only read-only access can be offered on the repository.' });
|
|
61
|
+
}
|
|
62
|
+
if (err && err.code === 'cannot_invite') {
|
|
63
|
+
return res.fail('cannot_invite', { status: 409, message: 'GitHub wouldn’t invite that account. It may already own the repository, or be unable to join. Check the name, or add them on GitHub yourself.' });
|
|
64
|
+
}
|
|
65
|
+
log.error(`[gds] ${where}`, err && err.message);
|
|
66
|
+
return res.fail('github_error', { status: 502, message: 'GitHub could not be reached. Try again in a moment.' });
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
// Parse + validate the owner/name ref, or send a 400 and return null.
|
|
47
70
|
function parseRepoRef(raw, res) {
|
|
48
71
|
const ref = String(raw || '').trim();
|
|
@@ -79,9 +102,84 @@ async function resolveRepoToken(req, res) {
|
|
|
79
102
|
return row.token;
|
|
80
103
|
}
|
|
81
104
|
|
|
82
|
-
|
|
105
|
+
// The repo THIS project's code lives in, or a 409 and null. The invite routes below
|
|
106
|
+
// never take a repo from the request: the owner invites people to the project's own
|
|
107
|
+
// repo, so a typed name cannot point the owner's token at some other repository.
|
|
108
|
+
function projectRepoRef(res) {
|
|
109
|
+
const info = repoInfo.loadRepoInfo();
|
|
110
|
+
if (!info || info.error) {
|
|
111
|
+
res.fail('no_project_repo', { status: 409, message: 'This project has no GitHub repository connected, so there is nothing to invite them to. Connect one on the project’s page on the hub, then try again.' });
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return { owner: info.owner, name: info.repo };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A typed GitHub name, with the leading @ allowed, or a 400 and null.
|
|
118
|
+
function parseInviteLogin(raw, res) {
|
|
119
|
+
const login = String(raw || '').trim().replace(/^@/, '');
|
|
120
|
+
if (!validGithubLogin(login)) {
|
|
121
|
+
res.fail('bad_github_login', { status: 400, message: 'That does not look like a GitHub username.' });
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return login;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// `deps` is the test seam for the two repo-invite helpers: they are imported directly
|
|
128
|
+
// (so the dead-code check sees them used) and a test hands in stand-ins here, because a
|
|
129
|
+
// destructured import cannot be stubbed on the module object the way the older
|
|
130
|
+
// namespace-called helpers below are.
|
|
131
|
+
module.exports = function buildGithubRouter(overrides = {}) {
|
|
132
|
+
const deps = { inviteRepoCollaborator, repoInviteState, ...overrides };
|
|
83
133
|
const router = express.Router();
|
|
84
134
|
|
|
135
|
+
// rank: archon (permission access_request.review, the same gate as a hall invite) —
|
|
136
|
+
// invites a person to THIS project's GitHub repository, acting as the caller's OWN
|
|
137
|
+
// GitHub account (their stored repo token), never a platform credential. GitHub
|
|
138
|
+
// decides whether the caller may add people. The access is always READ-ONLY:
|
|
139
|
+
// write ('push') and above are refused, since a builder ships through the server.
|
|
140
|
+
// It grants no Bongos rank and does not admit anyone to the hall: those are the
|
|
141
|
+
// hall invite's own record.
|
|
142
|
+
router.post('/github/repo-invite', auth.requireBuilder, auth.requirePermission('access_request.review'), async (req, res) => {
|
|
143
|
+
// No act-alone gate here, on purpose: that gate decides who may admit people to the
|
|
144
|
+
// hall (the hall invite carries it). This records a read-only share on GitHub, and
|
|
145
|
+
// GitHub itself only lets an account that administers the repo do it.
|
|
146
|
+
if (validateOrRespond(req, res, {
|
|
147
|
+
github_login: { required: true, type: 'string', maxLength: 60 },
|
|
148
|
+
permission: { type: 'string', enum: REPO_INVITE_PERMISSIONS },
|
|
149
|
+
})) return;
|
|
150
|
+
const login = parseInviteLogin(req.body.github_login, res);
|
|
151
|
+
if (!login) return;
|
|
152
|
+
const ref = projectRepoRef(res);
|
|
153
|
+
if (!ref) return;
|
|
154
|
+
const token = await resolveRepoToken(req, res);
|
|
155
|
+
if (!token) return;
|
|
156
|
+
try {
|
|
157
|
+
const out = await deps.inviteRepoCollaborator(token, { ...ref, login, permission: req.body.permission || 'pull' });
|
|
158
|
+
res.status(out.state === 'invited' ? 201 : 200).json({ ok: true, repo: `${ref.owner}/${ref.name}`, github_login: login, ...out });
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return repoInviteFailure(req, res, err, 'POST /github/repo-invite');
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// rank: archon (permission access_request.review) — where one person stands on the
|
|
165
|
+
// project's repo: collaborator, pending (invite sent, not yet accepted) or none.
|
|
166
|
+
router.get('/github/repo-invite', auth.requireBuilder, auth.requirePermission('access_request.review'), async (req, res) => {
|
|
167
|
+
// Strict, like the POST: a stray `repo=` (or anything else) is refused, never ignored.
|
|
168
|
+
if (validateOrRespond(req, res, {}, { query: { github_login: { required: true, type: 'string', maxLength: 60 } } })) return;
|
|
169
|
+
const login = parseInviteLogin(req.query.github_login, res);
|
|
170
|
+
if (!login) return;
|
|
171
|
+
const ref = projectRepoRef(res);
|
|
172
|
+
if (!ref) return;
|
|
173
|
+
const token = await resolveRepoToken(req, res);
|
|
174
|
+
if (!token) return;
|
|
175
|
+
try {
|
|
176
|
+
const out = await deps.repoInviteState(token, { ...ref, login });
|
|
177
|
+
res.json({ ok: true, repo: `${ref.owner}/${ref.name}`, github_login: login, ...out });
|
|
178
|
+
} catch (err) {
|
|
179
|
+
return repoInviteFailure(req, res, err, 'GET /github/repo-invite');
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
85
183
|
// rank: any-builder — acts only on the caller's OWN GitHub identity (the token
|
|
86
184
|
// they granted). No rank gate beyond being a signed-in builder.
|
|
87
185
|
router.get('/github/repos', auth.requireBuilder, async (req, res) => {
|
package/src/bongos/routes/me.js
CHANGED
|
@@ -217,6 +217,12 @@ module.exports = function buildMeRouter() {
|
|
|
217
217
|
const openPayoutHoldsP = (async () => {
|
|
218
218
|
try { return await require('../payout-gates').openHoldsFor(req.builder.id); } catch (_) { return null; }
|
|
219
219
|
})();
|
|
220
|
+
// task 1005147: the character this builder holds, or null (not picked yet — the
|
|
221
|
+
// hall then shows character creation). Fail-open to null: a core that runs
|
|
222
|
+
// before core_297 has no column, and that must never break /me.
|
|
223
|
+
const characterP = (async () => {
|
|
224
|
+
try { return await db.getBuilderCharacter(req.builder.id); } catch (_) { return null; }
|
|
225
|
+
})();
|
|
220
226
|
const stuckPageTweaksP = (async () => {
|
|
221
227
|
try {
|
|
222
228
|
const port = seams.resolveOptional('copy-desk.stuckPageTweaksFor');
|
|
@@ -476,6 +482,9 @@ module.exports = function buildMeRouter() {
|
|
|
476
482
|
// when lifecycle is disabled or the read failed, and the shell treats null
|
|
477
483
|
// as OFF — a mode that cannot be read is not on.
|
|
478
484
|
social: socialMode,
|
|
485
|
+
// task 1005147: the builder's character ('artisan' | 'artist' | 'patron'),
|
|
486
|
+
// or null until they pick one. Identity and view only; rank still gates.
|
|
487
|
+
character: await characterP,
|
|
479
488
|
// task 1004422 (BV2.PS03, goal 1000121): PROJECT FOUNDING MODE. Genesis is a
|
|
480
489
|
// mode the EXISTING hall wears (the blend rule, docs/design/project-startup-
|
|
481
490
|
// direction.md), so it rides /me exactly like social: the shell stamps
|
|
@@ -724,6 +733,30 @@ module.exports = function buildMeRouter() {
|
|
|
724
733
|
}
|
|
725
734
|
});
|
|
726
735
|
|
|
736
|
+
// task 1005147 (BONGOS-V3 G2): character creation. Body: { character } —
|
|
737
|
+
// 'artisan' | 'artist' | 'patron', or null to clear it (the hall clears it and
|
|
738
|
+
// signs out: switching character is signing out and back in, spec settled point 2).
|
|
739
|
+
// Identity and view only: no claim, vote, deploy or spend gate reads it
|
|
740
|
+
// (criterion character-is-identity-not-authority). 422 invalid_character otherwise.
|
|
741
|
+
// rank: any-builder — a builder writes only their own row (req.builder.id), the
|
|
742
|
+
// /me/disciplines posture.
|
|
743
|
+
router.put('/me/character', auth.requireBuilder, async (req, res) => {
|
|
744
|
+
// Nullable by the validator's default: null is the deliberate clear.
|
|
745
|
+
if (validateOrRespond(req, res, { character: { type: 'string', maxLength: 32 } }, { strict: true })) return;
|
|
746
|
+
const body = req.body || {};
|
|
747
|
+
if (!Object.prototype.hasOwnProperty.call(body, 'character')) {
|
|
748
|
+
return res.fail('character_required', 400);
|
|
749
|
+
}
|
|
750
|
+
const raw = body.character;
|
|
751
|
+
try {
|
|
752
|
+
const character = await db.setBuilderCharacter(req.builder.id, raw);
|
|
753
|
+
res.json({ character });
|
|
754
|
+
} catch (err) {
|
|
755
|
+
if (err.code === 'invalid_character') return res.fail('invalid_character', 422);
|
|
756
|
+
throw err;
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
|
|
727
760
|
// V3.R10 (#235), criterion C3 — link the authed builder's Anthropic account.
|
|
728
761
|
// Body: { anthropic_email, anthropic_user_id }. Self-attestation: the builder's
|
|
729
762
|
// own Claude Code reads ~/.claude.json oauthAccount during /builder-setup,
|
|
@@ -965,18 +965,35 @@ function mountInternalSurfaces(app) {
|
|
|
965
965
|
return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/roster${qs}`);
|
|
966
966
|
});
|
|
967
967
|
|
|
968
|
-
//
|
|
969
|
-
//
|
|
970
|
-
//
|
|
971
|
-
//
|
|
972
|
-
//
|
|
973
|
-
|
|
968
|
+
// THE ONE OPTIONS MENU (task 1005144, BONGOS-V3 R04, ADR 0401). Every setting is
|
|
969
|
+
// reached from Settings, which is the options menu now; the screens that were pages
|
|
970
|
+
// of their own open INSIDE it, in a frame that asks for the old address with
|
|
971
|
+
// ?embed=1. A plain visit to an old address (a bookmark, a doc, a Discord message)
|
|
972
|
+
// lands on the menu with that screen open (?open=<entry>), and the browser carries
|
|
973
|
+
// the old #anchor across the redirect because the Location names none. The framed
|
|
974
|
+
// request passes through to the page and its own page gate below. 302 and no gate,
|
|
975
|
+
// as the redirects that follow: the redirect discloses nothing, Settings has its own
|
|
976
|
+
// sign-in gate, and each screen keeps its page gate and its routes (ADR 0016).
|
|
977
|
+
// options-menu.js RETIRED is this map's twin (tests/hall_options_menu.mjs holds them
|
|
978
|
+
// equal). market-watch-settings was the owner's tab on /market-watch (task 1005045)
|
|
979
|
+
// and a page before that; it has no page left, so it never passes through.
|
|
980
|
+
const RETIRED_TO_OPTIONS = Object.freeze({
|
|
981
|
+
'my-agents': 'my-agents',
|
|
982
|
+
'project-settings': 'project-settings',
|
|
983
|
+
modules: 'modules',
|
|
984
|
+
government: 'permissions',
|
|
985
|
+
'market-watch-rules': 'market-watch-rules',
|
|
986
|
+
'market-watch-settings': 'market-watch',
|
|
987
|
+
});
|
|
988
|
+
const RETIRED_OPTIONS_RE = /^\/builders\/(my-agents|project-settings|modules|government|market-watch-rules|market-watch-settings)(?:\.html)?\/?$/;
|
|
974
989
|
app.use((req, res, next) => {
|
|
975
990
|
if (isModuleSurfaceHost(req)) return next();
|
|
976
991
|
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
992
|
+
const m = RETIRED_OPTIONS_RE.exec(path.posix.normalize(req.path));
|
|
993
|
+
if (!m) return next();
|
|
994
|
+
const embed = new URLSearchParams(req.url.includes('?') ? req.url.slice(req.url.indexOf('?') + 1) : '').get('embed');
|
|
995
|
+
if (embed === '1' && m[1] !== 'market-watch-settings') return next();
|
|
996
|
+
return res.redirect(302, `${isBuildersHost(req) ? '' : '/builders'}/settings?open=${RETIRED_TO_OPTIONS[m[1]]}`);
|
|
980
997
|
});
|
|
981
998
|
|
|
982
999
|
// Pages folded into another page; 302 and no gate, as above. Task 1005047: Fleet costs is
|
package/src/crafts.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// src/crafts.js — the craft vocabulary, in one place (task 1005141, BONGOS-V3 C1).
|
|
3
|
+
//
|
|
4
|
+
// The game conversion (docs/specs/bongos-v3-game-conversion.md, "Task outline / G1",
|
|
5
|
+
// settled point 6) names the three characters a builder holds:
|
|
6
|
+
//
|
|
7
|
+
// artisan — builds: code, infra, Bongos itself (was 'engineer')
|
|
8
|
+
// artist — how the project looks and reads, AND its ideas (was 'artist' + 'ideator')
|
|
9
|
+
// patron — watches, proposes, and (with a seat) governs (was the 'governing' craft mode)
|
|
10
|
+
//
|
|
11
|
+
// 'ui' stays a module-contributed discipline (ADR 0272); it is not a character.
|
|
12
|
+
//
|
|
13
|
+
// THE OLD SPELLINGS STILL ARRIVE. A rolled-back release, a co-tenant instance on an
|
|
14
|
+
// older core (federated craft rollups), an old CLI, a bookmarked ?discipline=engineer
|
|
15
|
+
// and a help request's historical 'builder' all still say the old word. Every input
|
|
16
|
+
// boundary runs a value through normalizeCraft() so the old word lands as the new one;
|
|
17
|
+
// nothing downstream should ever compare against 'engineer' or 'ideator' again. The
|
|
18
|
+
// database keeps ACCEPTING the old words too (core_296's CHECKs are a superset) because
|
|
19
|
+
// `bongos upgrade` rolls code back without rolling the database back — a contraction
|
|
20
|
+
// that drops them is a later, separate migration.
|
|
21
|
+
//
|
|
22
|
+
// The crafts a TASK can carry are artisan and artist. Patron is a character, not a
|
|
23
|
+
// kind of work: a task is built (artisan) or made/imagined (artist), and governing
|
|
24
|
+
// work is a vote, not a task.
|
|
25
|
+
//
|
|
26
|
+
// ONLY THE TWO FUNCTIONS ARE EXPORTED. A list of the crafts, display labels and a
|
|
27
|
+
// label helper were exported here once and nothing read them, which the dead-code
|
|
28
|
+
// ratchet (knip) counts as a regression. Add one back when a caller needs it.
|
|
29
|
+
|
|
30
|
+
// Old spelling → new spelling. 'builder' is the help-request spelling of the
|
|
31
|
+
// engineering craft (ADR 0174); 'building'/'artistry'/'ideating'/'governing' are the
|
|
32
|
+
// hall's old craft-mode ids (modules/hall-ui/public/craft-mode.js).
|
|
33
|
+
const LEGACY = Object.freeze({
|
|
34
|
+
engineer: 'artisan',
|
|
35
|
+
engineering: 'artisan',
|
|
36
|
+
builder: 'artisan',
|
|
37
|
+
building: 'artisan',
|
|
38
|
+
ideator: 'artist',
|
|
39
|
+
ideating: 'artist',
|
|
40
|
+
artistry: 'artist',
|
|
41
|
+
governing: 'patron',
|
|
42
|
+
governor: 'patron',
|
|
43
|
+
ruler: 'patron',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Any spelling → today's. Non-strings pass through untouched (callers validate);
|
|
47
|
+
// an unknown word passes through too, so a module-contributed craft is never eaten.
|
|
48
|
+
function normalizeCraft(value) {
|
|
49
|
+
if (typeof value !== 'string') return value;
|
|
50
|
+
const v = value.trim().toLowerCase();
|
|
51
|
+
return Object.prototype.hasOwnProperty.call(LEGACY, v) ? LEGACY[v] : v;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A list of crafts → today's spellings, deduped in first-seen order. The fold matters:
|
|
55
|
+
// ['artist', 'ideator'] is one craft now, not two.
|
|
56
|
+
function normalizeCrafts(list) {
|
|
57
|
+
if (!Array.isArray(list)) return list;
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const item of list) {
|
|
60
|
+
const c = normalizeCraft(item);
|
|
61
|
+
if (!out.includes(c)) out.push(c);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { normalizeCraft, normalizeCrafts };
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.80'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -134,6 +134,11 @@ module.exports = {
|
|
|
134
134
|
// sleep(ms) — the one server-side "wait N milliseconds" (a Promise over setTimeout).
|
|
135
135
|
// The browser twin is window.OTB.sleep; fitness fails on a third copy (task 1004893).
|
|
136
136
|
sleep,
|
|
137
|
+
// crafts — the craft vocabulary (artisan · artist · patron, plus module 'ui') and
|
|
138
|
+
// normalizeCraft/normalizeCrafts, which turn an old spelling ('artisan',
|
|
139
|
+
// 'artist', 'builder') into today's. Every craft read from outside goes through
|
|
140
|
+
// it (task 1005141, src/crafts.js).
|
|
141
|
+
get crafts() { return require('./crafts'); },
|
|
137
142
|
|
|
138
143
|
// --- auth & rank (ADR 0016) — the gates the loader composes around a module's
|
|
139
144
|
// routes; a module declares the rank it needs and never wires auth itself.
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
// // what update, rollout channels and re-assessment name.
|
|
17
17
|
// "coreVersion": "^1.0.0", // REQUIRED. semver range of src/module-api.js CORE_VERSION it supports.
|
|
18
18
|
// "default": false, // OPTIONAL (default false). Enablement fail-safe when no config names it.
|
|
19
|
+
// "language": "javascript", // OPTIONAL (default "javascript", task 1004794). What the module's code is
|
|
20
|
+
// // written in — one of MODULE_LANGUAGES. A closed list: only languages
|
|
21
|
+
// // the core can actually run are on it.
|
|
19
22
|
// "author": "…", // OPTIONAL. Attribution (ADR 0107 §2) — portable, not a cross-GDS FK.
|
|
20
23
|
// "origin": "…", // OPTIONAL. Origin instance/project this module was built in.
|
|
21
24
|
// "license": "…", // OPTIONAL. e.g. "AGPL-3.0" — the license this module is offered under.
|
|
@@ -138,6 +141,14 @@ function isClaudeArtifactUrl(v) {
|
|
|
138
141
|
return u.protocol === 'https:' && !u.username && !u.password && (host === 'claude.ai' || host.endsWith('.claude.ai'));
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
// The languages a module may declare (task 1004794). Closed on purpose: a value is on
|
|
145
|
+
// this list only once the core can RUN a module written in it, so a manifest that says
|
|
146
|
+
// "python" today fails loudly at validation instead of being loaded as JavaScript and
|
|
147
|
+
// failing somewhere far from the cause. How a separate-program module runs beside
|
|
148
|
+
// Bongos is decided in its own ADR (task 1004797); that work adds its languages here.
|
|
149
|
+
const MODULE_LANGUAGES = Object.freeze(['javascript']);
|
|
150
|
+
const DEFAULT_MODULE_LANGUAGE = 'javascript';
|
|
151
|
+
|
|
141
152
|
const MAINTENANCE_STATUSES = ['core-maintained', 'maintained', 'deprecated', 'orphaned'];
|
|
142
153
|
const MAINTENANCE_KEYS = ['status', 'since', 'removeAfter', 'successor', 'note'];
|
|
143
154
|
// Statuses that describe a module on its way out — these owe a deprecation path.
|
|
@@ -213,6 +224,11 @@ function validateManifest(obj) {
|
|
|
213
224
|
// --- optional default ---
|
|
214
225
|
if ('default' in obj && typeof obj.default !== 'boolean') E('default: must be a boolean when present');
|
|
215
226
|
|
|
227
|
+
// --- optional language (task 1004794) — absent means "javascript" ---
|
|
228
|
+
if ('language' in obj && !MODULE_LANGUAGES.includes(obj.language)) {
|
|
229
|
+
E(`language: ${JSON.stringify(obj.language)} is not a supported module language (allowed: ${MODULE_LANGUAGES.join(', ')}; omit it for "${DEFAULT_MODULE_LANGUAGE}")`);
|
|
230
|
+
}
|
|
231
|
+
|
|
216
232
|
// --- optional attribution (ADR 0107 §2 — portable credit, not a cross-GDS FK) ---
|
|
217
233
|
for (const k of ATTRIBUTION_KEYS) {
|
|
218
234
|
if (k in obj && !isStr(obj[k])) E(`${k}: must be a non-empty string when present`);
|
package/src/modules.js
CHANGED
|
@@ -86,7 +86,7 @@ const MODULE_REGISTRY = {};
|
|
|
86
86
|
// shipping a pixel-art pipeline. Making it core aligns the one outlier list with
|
|
87
87
|
// the DB CHECK and keeps the copyright exclusion untouched. Order mirrors the
|
|
88
88
|
// migration 176 enum (engineer | artist | ideator | ui).
|
|
89
|
-
const CORE_DISCIPLINES = ['
|
|
89
|
+
const CORE_DISCIPLINES = ['artisan', 'artist'];
|
|
90
90
|
|
|
91
91
|
const KNOWN_MODULES = Object.keys(MODULE_REGISTRY);
|
|
92
92
|
|
|
@@ -475,7 +475,7 @@ function isModuleEnabled(name, set = modules()) {
|
|
|
475
475
|
const enabledModules = (set = modules()) => knownModules().filter((k) => set[k] === true);
|
|
476
476
|
|
|
477
477
|
// Disciplines this instance offers = core + every enabled module's declared
|
|
478
|
-
// disciplines, deduped, stable order. Vanilla = ['
|
|
478
|
+
// disciplines, deduped, stable order. Vanilla = ['artisan','artist'].
|
|
479
479
|
function enabledDisciplines(set = modules()) {
|
|
480
480
|
const reg = effectiveRegistry();
|
|
481
481
|
const out = [...CORE_DISCIPLINES];
|
|
@@ -29,15 +29,16 @@
|
|
|
29
29
|
'use strict';
|
|
30
30
|
|
|
31
31
|
// discipline key (tasks.discipline / builders.preferred_disciplines) → the owner's
|
|
32
|
-
// sentence. Keys mirror CORE_DISCIPLINES order (
|
|
33
|
-
//
|
|
32
|
+
// sentence. Keys mirror CORE_DISCIPLINES order (artisan | artist), the
|
|
33
|
+
// core_296 order. A module-contributed discipline (ui-design → 'ui') has
|
|
34
34
|
// no statement here on purpose: a craft a module brings is that module's to
|
|
35
35
|
// describe, and inventing one for it would be exactly the paraphrase this file
|
|
36
36
|
// forbids. Consumers must therefore treat "no statement" as normal, not as an error.
|
|
37
37
|
const ROLE_RESPONSIBILITIES = Object.freeze({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
artisan: 'Running and optimizing the running of Claude nonstop, and ensuring Artists can continue to interface with that system effectively.',
|
|
39
|
+
// The artist holds both of the owner's sentences since the ideator folded in
|
|
40
|
+
// (task 1005141, BONGOS-V3): the craft names changed, the words did not.
|
|
41
|
+
artist: 'No slop in the appearance and text of the project; the story, emotion and ideology of the project are communicated effectively; project purpose and gravitas are upheld. Make good ideas; be a philosopher / thought leader for the project; enable Artisans to scope and Artists to create with maximum efficiency — a baseline for creating scopes of work.',
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
// Provenance, carried with the text so every rendering can say where it came from
|
|
@@ -111,7 +111,7 @@ try {
|
|
|
111
111
|
]) {
|
|
112
112
|
await q(
|
|
113
113
|
`INSERT INTO tasks (id, version_id, title, status, shipped_by, shipped_at, discipline, kind)
|
|
114
|
-
VALUES ($1, $2, $3, 'shipped', $4, now(), '
|
|
114
|
+
VALUES ($1, $2, $3, 'shipped', $4, now(), 'artisan', 'feature')`,
|
|
115
115
|
[id, VER, title, SHIPPER],
|
|
116
116
|
);
|
|
117
117
|
}
|
|
@@ -57,7 +57,7 @@ const pin = (rel, exports) => {
|
|
|
57
57
|
await t('reportActivityRollup sends Bongos-Report-As-Of, stamped before the totals were read', async () => {
|
|
58
58
|
const senderPool = mockPool((sql) => {
|
|
59
59
|
if (/FROM builders WHERE id/.test(sql)) {
|
|
60
|
-
return { rows: [{ github_id: 42, total_credits: 7, karma: 1, rank: 'metic', preferred_disciplines: ['
|
|
60
|
+
return { rows: [{ github_id: 42, total_credits: 7, karma: 1, rank: 'metic', preferred_disciplines: ['artisan'] }] };
|
|
61
61
|
}
|
|
62
62
|
if (/count\(\*\)/.test(sql)) return { rows: [{ n: 3 }] };
|
|
63
63
|
return { rows: [] };
|
|
@@ -89,7 +89,7 @@ test('the exclusions are real and each one is reachable', () => {
|
|
|
89
89
|
'docs/architecture.md',
|
|
90
90
|
'docs/canonical-permissions.md',
|
|
91
91
|
'docs/recipes/parallel-goal-waves.md',
|
|
92
|
-
'docs/packs/
|
|
92
|
+
'docs/packs/artisan.md',
|
|
93
93
|
'docs/onboarding/drachmae-and-karma.md',
|
|
94
94
|
]) {
|
|
95
95
|
assert.equal(scan.isExcludedPath(p), false, `${p} must NOT be excluded`);
|
|
@@ -112,11 +112,11 @@ test('docs/ is SPLIT: the frozen record stays out, live instruction is now count
|
|
|
112
112
|
'docs/governance/rank-map-2026-08-06.md': 'GET /api/gds/tasks\n',
|
|
113
113
|
'docs/recipes/failed-ship-recovery-proof.md': 'POST /api/gds/claims/1/resolve\n',
|
|
114
114
|
'docs/recipes/how-to-do-a-thing.md': 'node scripts/bongos/api.js GET /api/gds/tasks/1\n',
|
|
115
|
-
'docs/packs/
|
|
115
|
+
'docs/packs/artisan.md': 'create a task via POST /api/gds/tasks\n',
|
|
116
116
|
}));
|
|
117
117
|
assert.deepEqual(
|
|
118
118
|
Object.keys(r.byFile).sort(),
|
|
119
|
-
['docs/packs/
|
|
119
|
+
['docs/packs/artisan.md', 'docs/recipes/how-to-do-a-thing.md'],
|
|
120
120
|
'only the instructional docs count — and they MUST count, or the narrowing did nothing',
|
|
121
121
|
);
|
|
122
122
|
assert.equal(r.total, 2);
|
|
@@ -661,7 +661,7 @@ test('list-pending: the real vouch writer’s row resolves its profile and guild
|
|
|
661
661
|
|
|
662
662
|
test('list-pending: a vouched TAKEOVER resolves the vouched applicant, not whoever filed first', async () => {
|
|
663
663
|
reset();
|
|
664
|
-
hubViews['8001'] = { profile: { view: 'sliver', sliver: { disciplines: ['
|
|
664
|
+
hubViews['8001'] = { profile: { view: 'sliver', sliver: { disciplines: ['artisan'] } }, guild: null };
|
|
665
665
|
await applyAs('ada', { [VOUCH_HEADER]: vouchFor('ada', { githubId: 8001, handle: 'ada-h' }) });
|
|
666
666
|
const r = await call('GET', '/api/gds/access-requests');
|
|
667
667
|
const ada = r.json.requests.find((x) => x.github_login === 'ada');
|
package/tests/artist_review.mjs
CHANGED
|
@@ -214,7 +214,7 @@ test('the panel names a public page as site:<first path segment>, and its ideas
|
|
|
214
214
|
|
|
215
215
|
test('the public button and the hall palette wear one name', () => {
|
|
216
216
|
const craft = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/craft-mode.js'), 'utf8');
|
|
217
|
-
const tool = /id: 'palette', icon: '
|
|
217
|
+
const tool = /id: 'palette', icon: '[^']+', short: '([^']+)', label: '([^']+)'/.exec(craft);
|
|
218
218
|
assert.ok(tool, 'the palette tool');
|
|
219
219
|
assert.ok(LOADER.includes("var SHORT = '" + tool[1] + "';"), 'the short name');
|
|
220
220
|
assert.ok(LOADER.includes("var LABEL = '" + tool[2] + "';"), 'the full name');
|
|
@@ -100,7 +100,7 @@ await test('createTask with artistReview lands in the goal; without one the catc
|
|
|
100
100
|
await createTask({ versionId: 'BONGOS-V3', title: 'Tweak: Home (builders:home)', discipline: 'artist', artistReview: true }, { client: c });
|
|
101
101
|
assert.equal(c.state.tasks[0].goal_id, 91, 'the studio task names the artist review goal');
|
|
102
102
|
await assert.rejects(
|
|
103
|
-
() => createTask({ versionId: 'BONGOS-V3', title: 'plain', discipline: '
|
|
103
|
+
() => createTask({ versionId: 'BONGOS-V3', title: 'plain', discipline: 'artisan' }, { client: fakeClient() }),
|
|
104
104
|
/goalId is required/, 'an ordinary goal-less create still throws');
|
|
105
105
|
});
|
|
106
106
|
|
|
@@ -183,7 +183,7 @@ async function seed() {
|
|
|
183
183
|
// since core_247 (task 1002988); every real create path resolves one.
|
|
184
184
|
await q(
|
|
185
185
|
`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline)
|
|
186
|
-
VALUES ($1,$2,'bhlp fixture task','ready',10,'feature','
|
|
186
|
+
VALUES ($1,$2,'bhlp fixture task','ready',10,'feature','artisan')`,
|
|
187
187
|
[TASK, VER],
|
|
188
188
|
);
|
|
189
189
|
}
|
|
@@ -43,8 +43,8 @@ try {
|
|
|
43
43
|
await q(`INSERT INTO versions (id,name,status,track) VALUES ($1,'names','shipped','internal') ON CONFLICT (id) DO NOTHING`, [VER]);
|
|
44
44
|
// T2 first and with no name: the read must order by id and keep a null name.
|
|
45
45
|
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,discipline,short_name)
|
|
46
|
-
VALUES ($1,$3,'Payout approvals','backlog',10,'feature','
|
|
47
|
-
($2,$3,'#12','backlog',10,'feature','
|
|
46
|
+
VALUES ($1,$3,'Payout approvals','backlog',10,'feature','artisan','payout approvals'),
|
|
47
|
+
($2,$3,'#12','backlog',10,'feature','artisan',NULL)`, [T1, T2, VER]);
|
|
48
48
|
const holding = await blockers.createBlocker({ title: 'names fixture: holds two', bodyMd: '', source: 'test', sourceRef: `${REF}-a` });
|
|
49
49
|
const empty = await blockers.createBlocker({ title: 'names fixture: holds none', bodyMd: '', source: 'test', sourceRef: `${REF}-b` });
|
|
50
50
|
await blockers.linkBlockerToTask({ blockerId: holding.id, taskId: T2 }); // linked out of id order on purpose
|
|
@@ -280,7 +280,7 @@ const BUILDER = {
|
|
|
280
280
|
total_credits: 12,
|
|
281
281
|
karma: 3,
|
|
282
282
|
created_at: '2026-01-01T00:00:00.000Z',
|
|
283
|
-
preferred_disciplines: ['
|
|
283
|
+
preferred_disciplines: ['artisan'],
|
|
284
284
|
// The private columns the whitelist exists to keep out. Sentinel values so a
|
|
285
285
|
// raw-text scan of the serialized body bites even if a key were renamed.
|
|
286
286
|
anthropic_email: 'SENTINEL-anthropic-email',
|