@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
|
@@ -78,7 +78,7 @@ const PUBLISH = [
|
|
|
78
78
|
'docs/project-context.template.md', // the neutral scaffold (NOT the OTB-filled one)
|
|
79
79
|
'config/design-tokens.schema.json', // generic contract schema
|
|
80
80
|
'.claude/skills/status/SKILL.md',
|
|
81
|
-
'docs/packs/
|
|
81
|
+
'docs/packs/artisan.md', // the role packs the kernel/pack split moved the methodology into (task 1002990)
|
|
82
82
|
'package.json',
|
|
83
83
|
'Dockerfile',
|
|
84
84
|
'.env.local.example',
|
|
@@ -55,7 +55,7 @@ async function createdFloor({ scopeModules, needsMigration = false, requiresRank
|
|
|
55
55
|
// Stated rather than inferred (task 1002988): createTask now refuses a craft it
|
|
56
56
|
// cannot work out, and "a task" says nothing. This fixture is about the RANK
|
|
57
57
|
// FLOOR, so it should not be at the mercy of the discipline heuristic.
|
|
58
|
-
discipline: '
|
|
58
|
+
discipline: 'artisan',
|
|
59
59
|
goalId: '1000111',
|
|
60
60
|
needsMigration,
|
|
61
61
|
requiresRank,
|
|
@@ -439,7 +439,7 @@ await test('the generated task is a backlog REQUEST carrying the preamble', () =
|
|
|
439
439
|
assert.equal(spec.source, cascade.CASCADE_SOURCE);
|
|
440
440
|
assert.match(spec.description, /it is a REQUEST/);
|
|
441
441
|
assert.match(spec.description, /task 1003944/);
|
|
442
|
-
assert.equal(spec.discipline, '
|
|
442
|
+
assert.equal(spec.discipline, 'artisan');
|
|
443
443
|
});
|
|
444
444
|
|
|
445
445
|
await test('cascadesFor threads ctx into the body, not just the predicate', () => {
|
|
@@ -503,7 +503,7 @@ await test('nothing in the proposal path changes a status or a credit', () => {
|
|
|
503
503
|
|
|
504
504
|
const shippedTask = (over = {}) => ({
|
|
505
505
|
id: '7002', version_id: 'BONGOS-V2', goal_id: '1000095',
|
|
506
|
-
title: 'Delete the old scorer', discipline: '
|
|
506
|
+
title: 'Delete the old scorer', discipline: 'artisan',
|
|
507
507
|
kind: 'feature', status: 'shipped', source: 'manual', ...over,
|
|
508
508
|
});
|
|
509
509
|
|
package/tests/reward_gate.mjs
CHANGED
|
@@ -224,7 +224,7 @@ async function withStubbedCreatePool(run) {
|
|
|
224
224
|
|
|
225
225
|
t('a task CREATED with NO reward is born with a positive, capped value', async () => {
|
|
226
226
|
await withStubbedCreatePool(async (captured) => {
|
|
227
|
-
const row = await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: '
|
|
227
|
+
const row = await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: 'artisan', kind: 'feature', estMinutes: 40 });
|
|
228
228
|
assert.ok(captured.creditsReward > 0, 'INSERT must bind a positive credits_reward');
|
|
229
229
|
assert.equal(captured.creditsReward, 40, 'feature @40min -> 40 drachmae');
|
|
230
230
|
assert.ok(captured.creditsReward <= db.REWARD_SUGGESTION_CAP, 'must respect the cap');
|
|
@@ -234,7 +234,7 @@ t('a task CREATED with NO reward is born with a positive, capped value', async (
|
|
|
234
234
|
|
|
235
235
|
t('a task CREATED with credits_reward 0 (the route default) is auto-assigned', async () => {
|
|
236
236
|
await withStubbedCreatePool(async (captured) => {
|
|
237
|
-
await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: '
|
|
237
|
+
await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: 'artisan', creditsReward: 0, kind: 'bug', estMinutes: 40 });
|
|
238
238
|
// bug weight 1.25 -> 50; 0 is treated as "unset" so the default kicks in.
|
|
239
239
|
assert.equal(captured.creditsReward, 50);
|
|
240
240
|
});
|
|
@@ -242,14 +242,14 @@ t('a task CREATED with credits_reward 0 (the route default) is auto-assigned', a
|
|
|
242
242
|
|
|
243
243
|
t('a huge-est new task is CAPPED at creation, not ballooned', async () => {
|
|
244
244
|
await withStubbedCreatePool(async (captured) => {
|
|
245
|
-
await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: '
|
|
245
|
+
await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: 'artisan', kind: 'feature', estMinutes: 9999 });
|
|
246
246
|
assert.equal(captured.creditsReward, db.REWARD_SUGGESTION_CAP);
|
|
247
247
|
});
|
|
248
248
|
});
|
|
249
249
|
|
|
250
250
|
t('an EXPLICIT positive credits_reward is PRESERVED (never overridden)', async () => {
|
|
251
251
|
await withStubbedCreatePool(async (captured) => {
|
|
252
|
-
await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: '
|
|
252
|
+
await db.createTask({ versionId: 1, goalId: 77, title: 't', discipline: 'artisan', creditsReward: 17, kind: 'feature', estMinutes: 40 });
|
|
253
253
|
assert.equal(captured.creditsReward, 17, 'author-set positive value must survive untouched');
|
|
254
254
|
});
|
|
255
255
|
});
|
|
@@ -80,7 +80,7 @@ try {
|
|
|
80
80
|
await q(`INSERT INTO builders (id,github_id,github_login,display_name,rank) VALUES ($1,'rm-w','rm-worker','RM Worker','archon')`, [WORKER]);
|
|
81
81
|
// A confirmed task owned by WORKER with a real estimate. In DEFAULT mode this
|
|
82
82
|
// would pay 60 × 1.0 (feature) at ship; in cost-plus-only it must pay nothing.
|
|
83
|
-
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by,discipline) VALUES ($1,$2,'rm task','confirmed',60,'feature',$3,'
|
|
83
|
+
await q(`INSERT INTO tasks (id,version_id,title,status,credits_reward,kind,shipped_by,discipline) VALUES ($1,$2,'rm task','confirmed',60,'feature',$3,'artisan')`, [TASK, VER, WORKER]);
|
|
84
84
|
await q(`INSERT INTO claims (task_id,builder_id,claimed_at) VALUES ($1,$2, now())`, [TASK, WORKER]);
|
|
85
85
|
|
|
86
86
|
// Drive the REAL ship transition (the same call the reconciler/ship.js use).
|
|
@@ -39,21 +39,27 @@ function test(name, fn) {
|
|
|
39
39
|
|
|
40
40
|
// --- the source ---------------------------------------------------------
|
|
41
41
|
|
|
42
|
-
// The owner's sentences, verbatim from criterion wa6-written-responsibilities
|
|
43
|
-
//
|
|
44
|
-
//
|
|
42
|
+
// The owner's sentences, verbatim from criterion wa6-written-responsibilities,
|
|
43
|
+
// as carried through the BONGOS-V3 craft rename (task 1005141): the engineer is the
|
|
44
|
+
// ARTISAN, and the ideator folded into the ARTIST, so the artist's statement is the
|
|
45
|
+
// old artist sentence followed by the old ideator sentence. Only craft NAMES moved
|
|
46
|
+
// ("Ideators and Artists" → "Artists", "Engineers" → "Artisans"); every other
|
|
47
|
+
// word is the owner's. Duplicated here deliberately — a test that read the value it
|
|
48
|
+
// is checking would assert nothing. This copy is the fixture; the source file is
|
|
49
|
+
// the subject.
|
|
45
50
|
const OWNER_TEXT = {
|
|
46
|
-
|
|
47
|
-
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.'
|
|
48
|
-
|
|
51
|
+
artisan: 'Running and optimizing the running of Claude nonstop, and ensuring Artists can continue to interface with that system effectively.',
|
|
52
|
+
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.'
|
|
53
|
+
+ ' 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.',
|
|
49
54
|
};
|
|
50
55
|
|
|
51
|
-
test('the
|
|
56
|
+
test('the two statements are the owner\'s text, character for character', () => {
|
|
52
57
|
assert.deepEqual({ ...src.ROLE_RESPONSIBILITIES }, OWNER_TEXT);
|
|
53
58
|
});
|
|
54
59
|
|
|
55
|
-
test('exactly the
|
|
56
|
-
assert.deepEqual(Object.keys(src.ROLE_RESPONSIBILITIES), ['
|
|
60
|
+
test('exactly the two CORE crafts carry a statement — no more, no fewer', () => {
|
|
61
|
+
assert.deepEqual(Object.keys(src.ROLE_RESPONSIBILITIES), ['artisan', 'artist']);
|
|
62
|
+
assert.equal(src.responsibilityFor('ideator'), null, 'there is no ideator statement: it lives in the artist\'s');
|
|
57
63
|
// Governor is deferred (ADR 0274) and `ui` is module-contributed (ADR 0272):
|
|
58
64
|
// inventing a sentence for either is the paraphrase this task forbids.
|
|
59
65
|
assert.equal(src.responsibilityFor('ui'), null);
|
|
@@ -65,7 +71,7 @@ test('the map is frozen — a consumer cannot edit the standard at runtime', ()
|
|
|
65
71
|
});
|
|
66
72
|
|
|
67
73
|
test('responsibilityFor: case-insensitive, and absence is null not a placeholder', () => {
|
|
68
|
-
assert.equal(src.responsibilityFor('
|
|
74
|
+
assert.equal(src.responsibilityFor('ARTISAN'), OWNER_TEXT.artisan);
|
|
69
75
|
for (const bad of [null, undefined, '', 'unclassified', 42, {}]) {
|
|
70
76
|
assert.equal(src.responsibilityFor(bad), null, `${JSON.stringify(bad)} must be null`);
|
|
71
77
|
}
|
|
@@ -157,9 +163,9 @@ test('no consumer hardcodes a statement — the packs\' generated blocks are the
|
|
|
157
163
|
// criterion record, or this fixture is a second copy that can drift.
|
|
158
164
|
const ALLOWED = new Set([
|
|
159
165
|
'src/role-responsibilities.js', // the source
|
|
160
|
-
'docs/packs/
|
|
166
|
+
'docs/packs/artisan.md', // generated blocks
|
|
167
|
+
'docs/packs/artist.md',
|
|
161
168
|
'docs/packs/artist.md',
|
|
162
|
-
'docs/packs/ideator.md',
|
|
163
169
|
'tests/role_responsibilities.mjs', // this fixture
|
|
164
170
|
]);
|
|
165
171
|
const scan = ['src', 'scripts', 'modules', 'tests', 'docs/packs'];
|
package/tests/role_stats_db.mjs
CHANGED
|
@@ -102,7 +102,7 @@ async function fixture() {
|
|
|
102
102
|
const other = await insertRow('builders', { github_id: `${tag}-gh2`, github_login: `${tag}-2`, display_name: `${tag}-2`, total_credits: 0 });
|
|
103
103
|
const version = await insertRow('versions', { id: `${tag}-v`, status: 'building' });
|
|
104
104
|
const mk = (title, discipline) => insertRow('tasks', { title: `${tag} ${title}`, status: 'shipped', version_id: version.id, discipline });
|
|
105
|
-
const eng = await mk('eng', '
|
|
105
|
+
const eng = await mk('eng', 'artisan');
|
|
106
106
|
const page = await mk('page', 'artist');
|
|
107
107
|
const ui = await mk('ui', 'ui');
|
|
108
108
|
const sid = `${tag}-session`;
|
|
@@ -139,23 +139,24 @@ async function main() {
|
|
|
139
139
|
await test('credits by craft: every reason lands in its craft and the parts sum to the ledger exactly', async () => {
|
|
140
140
|
const map = await byCraft.creditsByCraft([f.b.id], { client });
|
|
141
141
|
const row = map.get(String(f.b.id));
|
|
142
|
-
//
|
|
143
|
-
// artist: 30 page + the ui half of the session (4.5)
|
|
144
|
-
//
|
|
142
|
+
// artisan: 10 shipped + half of the 9 session + the 3 lone session = 17.5
|
|
143
|
+
// artist: 30 page + the ui half of the session (4.5)
|
|
144
|
+
// + the idea streams the old ideator craft held
|
|
145
|
+
// (4 stream 1 + 5 full pass), folded in by task 1005141 = 43.5
|
|
145
146
|
// other: 2 achievement + 7 wildcard twin = 9
|
|
146
|
-
assert.equal(row.ideator, 9);
|
|
147
147
|
assert.equal(row.other, 9);
|
|
148
148
|
assert.equal(row.total, 70);
|
|
149
|
-
assert.equal(row.
|
|
150
|
-
// 17.5 and
|
|
151
|
-
// remainder, and on a tie to the earlier craft,
|
|
152
|
-
assert.equal(row.
|
|
153
|
-
assert.equal(row.artist,
|
|
149
|
+
assert.equal(row.artisan + row.artist + row.other, row.total, 'the parts are exact to the whole');
|
|
150
|
+
// 17.5 and 43.5 floor to 60 of 61; the one left over goes to the larger
|
|
151
|
+
// remainder, and on a tie to the earlier craft, artisan.
|
|
152
|
+
assert.equal(row.artisan, 18);
|
|
153
|
+
assert.equal(row.artist, 43);
|
|
154
|
+
assert.equal(row.ideas, 9, 'the idea credits are still told apart inside the artist craft');
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
await test('credits by craft: another builder on the same task is never counted', async () => {
|
|
157
158
|
const map = await byCraft.creditsByCraft([f.other.id], { client });
|
|
158
|
-
assert.equal(map.get(String(f.other.id)).
|
|
159
|
+
assert.equal(map.get(String(f.other.id)).artisan, 50);
|
|
159
160
|
assert.equal(map.get(String(f.other.id)).total, 50);
|
|
160
161
|
});
|
|
161
162
|
|
|
@@ -177,11 +178,11 @@ async function main() {
|
|
|
177
178
|
const { UPDATE_BUILDER_DISCIPLINES_SQL: sql } = require('../src/bongos/db.js');
|
|
178
179
|
const run = async (list, setMain, main) =>
|
|
179
180
|
(await client.query(sql, [f.b.id, list, setMain, main])).rows[0].main_discipline;
|
|
180
|
-
assert.equal(await run(['
|
|
181
|
-
assert.equal(await run(['
|
|
182
|
-
assert.equal(await run(['
|
|
183
|
-
assert.equal(await run(['
|
|
184
|
-
assert.equal(await run(['
|
|
181
|
+
assert.equal(await run(['artisan', 'artist'], true, 'artist'), 'artist', 'chosen');
|
|
182
|
+
assert.equal(await run(['artisan', 'artist', 'artist'], false, null), 'artist', 'not mentioned, still one of the crafts: kept');
|
|
183
|
+
assert.equal(await run(['artisan'], false, null), null, 'the craft was dropped: a main role they no longer practise is cleared');
|
|
184
|
+
assert.equal(await run(['artisan'], true, 'artisan'), 'artisan');
|
|
185
|
+
assert.equal(await run(['artisan'], true, null), null, 'cleared on purpose');
|
|
185
186
|
});
|
|
186
187
|
|
|
187
188
|
await test('artist page read: only rounds whose LATEST web claim is theirs, never a CLI claim, never an ordinary task', async () => {
|
|
@@ -82,9 +82,9 @@ try {
|
|
|
82
82
|
await q(
|
|
83
83
|
`INSERT INTO platform_identity_builder_project_activity
|
|
84
84
|
(client_id, github_id, credits, tasks_shipped, karma, disciplines, last_active)
|
|
85
|
-
VALUES ($1, $3, 100, 5, 3, '{
|
|
86
|
-
($2, $3, 50, 2, 1, '{artist,
|
|
87
|
-
($1, $4, 60, 4, 2, '{
|
|
85
|
+
VALUES ($1, $3, 100, 5, 3, '{artisan,ui}', '2026-01-01T00:00:00Z'),
|
|
86
|
+
($2, $3, 50, 2, 1, '{artist,artisan,ui}', '2026-02-01T00:00:00Z'),
|
|
87
|
+
($1, $4, 60, 4, 2, '{artisan}', '2026-03-01T00:00:00Z'),
|
|
88
88
|
($2, $5, 10, 1, 0, '{}', '2026-04-01T00:00:00Z')`,
|
|
89
89
|
[...CLIENTS, MULTI, SOLO, GHOST],
|
|
90
90
|
);
|
|
@@ -99,14 +99,14 @@ try {
|
|
|
99
99
|
ok(m.total_tasks_shipped === 7, `total_tasks_shipped is 5+2=7 (got ${m.total_tasks_shipped})`);
|
|
100
100
|
ok(m.total_karma === 4, `total_karma is 3+1=4 (got ${m.total_karma})`);
|
|
101
101
|
ok(new Date(m.last_active).toISOString() === '2026-02-01T00:00:00.000Z', 'last_active is the max across projects');
|
|
102
|
-
assert.deepEqual([...m.disciplines].sort(), ['
|
|
102
|
+
assert.deepEqual([...m.disciplines].sort(), ['artisan', 'artist', 'ui']);
|
|
103
103
|
pass++; console.log(' ok - disciplines are the deduped union across projects');
|
|
104
104
|
ok(m.github_login === 'multi', 'account display fields join through');
|
|
105
105
|
|
|
106
106
|
const s = by[SOLO];
|
|
107
107
|
ok(s.project_count === 1 && s.total_credits === 60 && s.total_tasks_shipped === 4 && s.total_karma === 2,
|
|
108
108
|
'single-discipline builder totals are unchanged');
|
|
109
|
-
assert.deepEqual(s.disciplines, ['
|
|
109
|
+
assert.deepEqual(s.disciplines, ['artisan']);
|
|
110
110
|
pass++; console.log(' ok - single discipline survives as a one-element array');
|
|
111
111
|
|
|
112
112
|
const g = by[GHOST];
|
package/tests/seed_tasks.mjs
CHANGED
|
@@ -31,6 +31,9 @@ const FIX = path.join(ROOT, 'tests/fixtures/seed-tasks');
|
|
|
31
31
|
const { validateSpec, seedTasks } = require(SEEDER);
|
|
32
32
|
|
|
33
33
|
// spec fixture -> the old script it replaced, and the sha256 of that script's write log.
|
|
34
|
+
// These fixtures are HISTORY and keep the pre-1005141 craft spellings ('engineer',
|
|
35
|
+
// 'ideator') on purpose: rewriting them moves the hash. seed-tasks.js passes
|
|
36
|
+
// discipline through verbatim; the task-write route normalizes an old spelling.
|
|
34
37
|
const HISTORICAL = [
|
|
35
38
|
{ spec: 'bongos-v1-idea-routing.json', old: 'seed-bongos-v1-idea-routing-tasks.js', hash: 'f5bc176890077a62754812cf69666bd49ba0da441dafa84702e1883156c302f8' },
|
|
36
39
|
{ spec: 'bv1-profiles.json', old: 'seed-bv1-profiles-tasks.js', hash: 'cf81ee14131a20fe5a07c05b7c5fe3362bc940d5c5592031ad911b709db129a7' },
|
|
@@ -111,8 +114,8 @@ await test('an invalid spec is refused before any write', async () => {
|
|
|
111
114
|
const base = () => ({
|
|
112
115
|
version_id: 'V', goal_id: 7, source_ref_prefix: 'p',
|
|
113
116
|
tasks: [
|
|
114
|
-
{ ref: 'R1', title: 't1', description: 'd', kind: 'feature', discipline: '
|
|
115
|
-
{ ref: 'R2', title: 't2', description: 'd', kind: 'verify', discipline: '
|
|
117
|
+
{ ref: 'R1', title: 't1', description: 'd', kind: 'feature', discipline: 'artisan', priority: 3, criterion_ids: ['slug-a'] },
|
|
118
|
+
{ ref: 'R2', title: 't2', description: 'd', kind: 'verify', discipline: 'artisan', priority: 3, criterion_ids: ['slug-a'], deps: ['R1'] },
|
|
116
119
|
],
|
|
117
120
|
});
|
|
118
121
|
|
|
@@ -126,7 +126,7 @@ async function fixture(client) {
|
|
|
126
126
|
status: 'shipped',
|
|
127
127
|
version_id: version.id,
|
|
128
128
|
// Named, not placeholder-filled: tasks_discipline_check admits only the live crafts.
|
|
129
|
-
discipline: '
|
|
129
|
+
discipline: 'artisan',
|
|
130
130
|
});
|
|
131
131
|
const sessionId = `${tag}-session`;
|
|
132
132
|
await insertRow(client, 'session_records', {
|
|
@@ -61,7 +61,7 @@ await test('real-DB: fixture setup', async () => {
|
|
|
61
61
|
[ctx.versionId, `V ${suffix}`]
|
|
62
62
|
);
|
|
63
63
|
const tk = await pool.query(
|
|
64
|
-
`INSERT INTO tasks (version_id, title, status, discipline) VALUES ($1,$2,'shipped','
|
|
64
|
+
`INSERT INTO tasks (version_id, title, status, discipline) VALUES ($1,$2,'shipped','artisan') RETURNING id`,
|
|
65
65
|
[ctx.versionId, `shipped task ${suffix}`]
|
|
66
66
|
);
|
|
67
67
|
ctx.taskId = Number(tk.rows[0].id);
|
|
@@ -61,12 +61,12 @@ await test('real-DB: fixture setup', async () => {
|
|
|
61
61
|
[ctx.versionId, `V ${suffix}`]
|
|
62
62
|
);
|
|
63
63
|
const shipped = await pool.query(
|
|
64
|
-
`INSERT INTO tasks (version_id, title, status, discipline) VALUES ($1,$2,'shipped','
|
|
64
|
+
`INSERT INTO tasks (version_id, title, status, discipline) VALUES ($1,$2,'shipped','artisan') RETURNING id`,
|
|
65
65
|
[ctx.versionId, `shipped ${suffix}`]
|
|
66
66
|
);
|
|
67
67
|
ctx.shippedTaskId = Number(shipped.rows[0].id);
|
|
68
68
|
const confirmed = await pool.query(
|
|
69
|
-
`INSERT INTO tasks (version_id, title, status, discipline) VALUES ($1,$2,'confirmed','
|
|
69
|
+
`INSERT INTO tasks (version_id, title, status, discipline) VALUES ($1,$2,'confirmed','artisan') RETURNING id`,
|
|
70
70
|
[ctx.versionId, `confirmed ${suffix}`]
|
|
71
71
|
);
|
|
72
72
|
ctx.confirmedTaskId = Number(confirmed.rows[0].id);
|
|
@@ -123,7 +123,7 @@ function harness({ total = 320, failAfter = null, viewerRank = 'metic', perPageP
|
|
|
123
123
|
// the same reason categoryChip is: an undefined helper throws mid-render.
|
|
124
124
|
roleAxisLabel: () => 'Role',
|
|
125
125
|
roleLabel: (d) => (!d ? '' : d === 'unclassified' ? 'No role yet' : d === 'ui' ? 'UI' : d.charAt(0).toUpperCase() + d.slice(1)),
|
|
126
|
-
roleOptions: () => [{ value: '', label: '— any —' }, { value: '
|
|
126
|
+
roleOptions: () => [{ value: '', label: '— any —' }, { value: 'artisan', label: 'Artisan' }, { value: 'artist', label: 'Artist' }],
|
|
127
127
|
roleIsUnset: (d) => !d || d === 'unclassified',
|
|
128
128
|
ROLE_UNSET: 'unclassified',
|
|
129
129
|
escapeHtml: (s) => String(s == null ? '' : s),
|
|
@@ -52,7 +52,7 @@ async function boot(artKeyStatus) {
|
|
|
52
52
|
// The markup ships both hidden (settings.html), so the stub starts them hidden.
|
|
53
53
|
$('#art-key').hidden = true;
|
|
54
54
|
$('#artkey-banner').hidden = true;
|
|
55
|
-
const builder = { id: 4, github_login: 'b4', rank: 'thetes', preferred_disciplines: ['
|
|
55
|
+
const builder = { id: 4, github_login: 'b4', rank: 'thetes', preferred_disciplines: ['artisan'] };
|
|
56
56
|
const me = { builder, needs: { items: [needs.artKeyNeed({ rank: 'thetes' })] } };
|
|
57
57
|
const api = {
|
|
58
58
|
async request(method, url) {
|
|
@@ -74,7 +74,7 @@ async function boot(artKeyStatus) {
|
|
|
74
74
|
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
75
75
|
BongosClient: { createClient: () => api },
|
|
76
76
|
OTB: {
|
|
77
|
-
$, escapeHtml, toast() {}, disciplines: () => ['
|
|
77
|
+
$, escapeHtml, toast() {}, disciplines: () => ['artisan'], roleLabel: (d) => d,
|
|
78
78
|
hubOrigin: () => null, settingsPanels: () => [],
|
|
79
79
|
},
|
|
80
80
|
OTBKit: { emptyStateHtml: (m) => m, makeSectionRail: () => ({ refresh() {}, activate() {} }) },
|
|
@@ -33,7 +33,8 @@ async function t(name, fn) {
|
|
|
33
33
|
catch (e) { failed++; console.error(` FAIL ${name}\n ${e.message}`); }
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// The two core crafts since task 1005141 (the ideator folded into the artist), plus ui.
|
|
37
|
+
const DISCIPLINES = ['artisan', 'artist', 'ui'];
|
|
37
38
|
|
|
38
39
|
// A permissive element: everything settings.js touches at boot, with listeners
|
|
39
40
|
// recorded so a test can fire them.
|
|
@@ -120,7 +121,7 @@ async function boot({ builder }) {
|
|
|
120
121
|
return { $, options, checked, toggleRole, pickMain, save, patches };
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
const B = (over = {}) => ({ id: 4, github_login: 'b4', display_name: 'Builder 4', preferred_disciplines: ['
|
|
124
|
+
const B = (over = {}) => ({ id: 4, github_login: 'b4', display_name: 'Builder 4', preferred_disciplines: ['artisan', 'artist'], main_discipline: null, ...over });
|
|
124
125
|
|
|
125
126
|
console.log('\nthe main-role picker:');
|
|
126
127
|
|
|
@@ -139,39 +140,39 @@ await t('GET /me carries the saved main role (getBuilderById selects the column)
|
|
|
139
140
|
|
|
140
141
|
await t('options are "Let my work decide" plus exactly the ticked roles; nothing saved → by work', async () => {
|
|
141
142
|
const p = await boot({ builder: B() });
|
|
142
|
-
assert.deepEqual(p.options().map((o) => o.value), ['', '
|
|
143
|
+
assert.deepEqual(p.options().map((o) => o.value), ['', 'artisan', 'artist']);
|
|
143
144
|
assert.equal(p.options()[0].label, 'Let my work decide');
|
|
144
|
-
assert.deepEqual(p.options().map((o) => o.label).slice(1), ['
|
|
145
|
+
assert.deepEqual(p.options().map((o) => o.label).slice(1), ['Artisan', 'Artist']);
|
|
145
146
|
assert.equal(p.checked(), '', 'no saved main role → "Let my work decide" is chosen');
|
|
146
147
|
});
|
|
147
148
|
|
|
148
149
|
await t('a saved main role is the one chosen on load', async () => {
|
|
149
|
-
const p = await boot({ builder: B({ main_discipline: '
|
|
150
|
-
assert.equal(p.checked(), '
|
|
150
|
+
const p = await boot({ builder: B({ main_discipline: 'artist' }) });
|
|
151
|
+
assert.equal(p.checked(), 'artist');
|
|
151
152
|
});
|
|
152
153
|
|
|
153
154
|
await t('ticking a role adds its option; unticking the MAIN role hands the choice back to your work', async () => {
|
|
154
|
-
const p = await boot({ builder: B({ main_discipline: '
|
|
155
|
-
p.toggleRole('
|
|
156
|
-
assert.deepEqual(p.options().map((o) => o.value), ['', '
|
|
157
|
-
assert.equal(p.checked(), '
|
|
158
|
-
p.toggleRole('
|
|
159
|
-
assert.deepEqual(p.options().map((o) => o.value), ['', '
|
|
155
|
+
const p = await boot({ builder: B({ main_discipline: 'artist' }) });
|
|
156
|
+
p.toggleRole('ui', true);
|
|
157
|
+
assert.deepEqual(p.options().map((o) => o.value), ['', 'artisan', 'artist', 'ui'], 'in the instance\'s role order');
|
|
158
|
+
assert.equal(p.checked(), 'artist', 'adding a role keeps the pick');
|
|
159
|
+
p.toggleRole('artist', false);
|
|
160
|
+
assert.deepEqual(p.options().map((o) => o.value), ['', 'artisan', 'ui']);
|
|
160
161
|
assert.equal(p.checked(), '', 'a role you no longer have cannot lead');
|
|
161
162
|
});
|
|
162
163
|
|
|
163
164
|
await t('Save sends the pick in the same PATCH, and the echo re-seeds it', async () => {
|
|
164
165
|
const p = await boot({ builder: B() });
|
|
165
|
-
p.pickMain('
|
|
166
|
+
p.pickMain('artist');
|
|
166
167
|
await p.save();
|
|
167
168
|
assert.equal(p.patches.length, 1);
|
|
168
|
-
assert.deepEqual(JSON.parse(JSON.stringify(p.patches[0])), { preferred_disciplines: ['
|
|
169
|
-
assert.equal(p.checked(), '
|
|
170
|
-
assert.match(p.$('#craft-status').textContent, /Main role:
|
|
169
|
+
assert.deepEqual(JSON.parse(JSON.stringify(p.patches[0])), { preferred_disciplines: ['artisan', 'artist'], main_discipline: 'artist' });
|
|
170
|
+
assert.equal(p.checked(), 'artist', 'the server\'s echo is what the control shows after saving');
|
|
171
|
+
assert.match(p.$('#craft-status').textContent, /Main role: Artist\./);
|
|
171
172
|
});
|
|
172
173
|
|
|
173
174
|
await t('"Let my work decide" saves null, which clears a saved main role', async () => {
|
|
174
|
-
const p = await boot({ builder: B({ main_discipline: '
|
|
175
|
+
const p = await boot({ builder: B({ main_discipline: 'artisan' }) });
|
|
175
176
|
p.pickMain('');
|
|
176
177
|
await p.save();
|
|
177
178
|
assert.equal(p.patches[0].main_discipline, null);
|
|
@@ -180,10 +181,10 @@ await t('"Let my work decide" saves null, which clears a saved main role', async
|
|
|
180
181
|
});
|
|
181
182
|
|
|
182
183
|
await t('unticking the main role, then saving, sends null — never a role outside the set', async () => {
|
|
183
|
-
const p = await boot({ builder: B({ main_discipline: '
|
|
184
|
-
p.toggleRole('
|
|
184
|
+
const p = await boot({ builder: B({ main_discipline: 'artist' }) });
|
|
185
|
+
p.toggleRole('artist', false);
|
|
185
186
|
await p.save();
|
|
186
|
-
assert.deepEqual(JSON.parse(JSON.stringify(p.patches[0])), { preferred_disciplines: ['
|
|
187
|
+
assert.deepEqual(JSON.parse(JSON.stringify(p.patches[0])), { preferred_disciplines: ['artisan'], main_discipline: null });
|
|
187
188
|
});
|
|
188
189
|
|
|
189
190
|
console.log(`\n${passed} passed, ${failed} failed`);
|
package/tests/setup_flow.mjs
CHANGED
|
@@ -77,7 +77,7 @@ function bootGate({ patchOk = true } = {}) {
|
|
|
77
77
|
activeElement: null,
|
|
78
78
|
};
|
|
79
79
|
// the crafts this project offers (dom-utils.js reads them off __MODULES__)
|
|
80
|
-
const window = { OTB: { ...realOTB(), disciplines: () => ['
|
|
80
|
+
const window = { OTB: { ...realOTB(), disciplines: () => ['artisan', 'artist', 'ui'] }, location: { reload: () => { reloaded += 1; } } };
|
|
81
81
|
const sandbox = { window, document, setInterval: (fn) => { intervals.push(fn); return intervals.length; }, clearInterval() {} };
|
|
82
82
|
vm.createContext(sandbox);
|
|
83
83
|
vm.runInContext(GATE, sandbox, { filename: 'setup-gate.js' });
|
|
@@ -104,7 +104,7 @@ await t('the screen lists every step in order, ticks the cleared ones, and opens
|
|
|
104
104
|
assert.deepEqual(steps.map((s) => s.className), ['setup-gate__step is-done', 'setup-gate__step is-done', 'setup-gate__step is-current', 'setup-gate__step']);
|
|
105
105
|
assert.match(steps[2].textContent, /Hint pick_disciplines/, 'only the current step shows its hint');
|
|
106
106
|
assert.doesNotMatch(steps[3].textContent, /Hint first_start/);
|
|
107
|
-
assert.deepEqual(gate.querySelectorAll('[data-craft]').map((b) => b.textContent), ['
|
|
107
|
+
assert.deepEqual(gate.querySelectorAll('[data-craft]').map((b) => b.textContent), ['Artisan', 'Artist', 'UI'],
|
|
108
108
|
'the crafts this project offers');
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -128,7 +128,7 @@ await t('choosing a craft saves it through PATCH /me/disciplines, the route ever
|
|
|
128
128
|
await t('a craft that does not save says so and can be tried again', async () => {
|
|
129
129
|
const g = bootGate({ patchOk: false });
|
|
130
130
|
g.show({ complete: false, steps: STEPS(['signed_in', 'cli_setup']) });
|
|
131
|
-
g.click(g.gate().querySelector('[data-craft="
|
|
131
|
+
g.click(g.gate().querySelector('[data-craft="artisan"]'));
|
|
132
132
|
g.click(g.gate().querySelector('[data-save-crafts]'));
|
|
133
133
|
await settle();
|
|
134
134
|
const err = g.gate().querySelector('[data-craft-err]');
|
package/tests/setup_smoke.mjs
CHANGED
|
@@ -145,6 +145,26 @@ test('--dry-run respects a pre-existing valid-looking session in HOME', () => {
|
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
// Setup asks for no API key (task 1005189). It used to stop every new builder at
|
|
149
|
+
// a Google AI Studio key question, on every project — including ones with no art
|
|
150
|
+
// pipeline, and agent-run setups where nobody can type an answer. The dry-run
|
|
151
|
+
// plan is the half a spawn can see; the source check covers the prompt itself,
|
|
152
|
+
// which only ever ran on a TTY.
|
|
153
|
+
test('setup asks for no API key — not in the plan, not in either script', () => {
|
|
154
|
+
const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'otb-setup-smoke-'));
|
|
155
|
+
try {
|
|
156
|
+
const r = runSetup(['--dry-run', '--skip-keys'], { HOME: fakeHome });
|
|
157
|
+
assert.equal(r.status, 0, `--skip-keys must still be accepted; stderr: ${r.stderr}`);
|
|
158
|
+
assert.doesNotMatch(r.stdout, /gemini|ai studio|GOOGLE_AI_STUDIO_KEY/i, 'the dry-run plan must not list a key step');
|
|
159
|
+
} finally {
|
|
160
|
+
fs.rmSync(fakeHome, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
for (const rel of ['scripts/bongos/setup.js', 'scripts/setup-builder.js']) {
|
|
163
|
+
const src = fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8');
|
|
164
|
+
assert.doesNotMatch(src, /GOOGLE_AI_STUDIO_KEY|aistudio\.google\.com/, `${rel} must not ask for or print anything about an image key`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
148
168
|
// ---- runner ----
|
|
149
169
|
|
|
150
170
|
let failed = 0;
|
package/tests/ship_card.mjs
CHANGED
|
@@ -225,7 +225,7 @@ test('claimed: neutral tone, scope rows, discipline action, brief in dropdown',
|
|
|
225
225
|
const m = card.buildCardModel({
|
|
226
226
|
stage: 'claimed', taskId: 1275, title: 'Lifecycle card', brand: BRAND,
|
|
227
227
|
description: 'Extend the card to every stage.',
|
|
228
|
-
estMinutes: 120, touchesCount: 4, version: 'GDS-V4', kind: 'feature', priority: 2, discipline: '
|
|
228
|
+
estMinutes: 120, touchesCount: 4, version: 'GDS-V4', kind: 'feature', priority: 2, discipline: 'artisan',
|
|
229
229
|
});
|
|
230
230
|
assert.equal(m.stage, 'claimed');
|
|
231
231
|
assert.equal(m.cfg.tone, 'neutral');
|
|
@@ -233,7 +233,7 @@ test('claimed: neutral tone, scope rows, discipline action, brief in dropdown',
|
|
|
233
233
|
assert.equal(row(m, 'Estimate'), '~2h');
|
|
234
234
|
assert.equal(row(m, 'Touches'), '4 files');
|
|
235
235
|
assert.equal(row(m, 'Value'), undefined); // no value summary pre-work
|
|
236
|
-
assert.equal(m.action.cmd, 'Read docs/packs/
|
|
236
|
+
assert.equal(m.action.cmd, 'Read docs/packs/artisan.md and start the work');
|
|
237
237
|
const html = card.renderCardHtml(m);
|
|
238
238
|
assert.match(html, /Extend the card to every stage\./); // brief in dropdown
|
|
239
239
|
});
|
|
@@ -246,13 +246,13 @@ test('claimed: neutral tone, scope rows, discipline action, brief in dropdown',
|
|
|
246
246
|
test('claimed action routes by discipline — core crafts to their pack, modules to their skill', () => {
|
|
247
247
|
const cmdFor = (discipline) => card.buildCardModel({ stage: 'claimed', taskId: 1, discipline, brand: BRAND }).action.cmd;
|
|
248
248
|
assert.equal(cmdFor('artist'), 'Read docs/packs/artist.md and start the work');
|
|
249
|
-
assert.equal(cmdFor('
|
|
249
|
+
assert.equal(cmdFor('artist'), 'Read docs/packs/artist.md and start the work');
|
|
250
250
|
assert.equal(cmdFor('ui'), '/design');
|
|
251
251
|
// An unknown or unset discipline falls back to the Engineer pack, exactly as it
|
|
252
252
|
// used to fall back to /dev — engineering is the project's default loop.
|
|
253
|
-
assert.equal(cmdFor('unclassified'), 'Read docs/packs/
|
|
254
|
-
assert.equal(cmdFor(undefined), 'Read docs/packs/
|
|
255
|
-
for (const d of ['
|
|
253
|
+
assert.equal(cmdFor('unclassified'), 'Read docs/packs/artisan.md and start the work');
|
|
254
|
+
assert.equal(cmdFor(undefined), 'Read docs/packs/artisan.md and start the work');
|
|
255
|
+
for (const d of ['artisan', 'artist', 'artist']) {
|
|
256
256
|
assert.ok(!cmdFor(d).startsWith('/'), `${d} must not be routed to a slash command — /dev, /paint and /ideate no longer exist`);
|
|
257
257
|
}
|
|
258
258
|
});
|
package/tests/specialities.mjs
CHANGED
|
@@ -14,10 +14,10 @@ const require = createRequire(import.meta.url);
|
|
|
14
14
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
15
|
const S = require(path.join(ROOT, 'modules', 'specialities', 'specialities.js'));
|
|
16
16
|
|
|
17
|
-
const DISCIPLINES = ['
|
|
17
|
+
const DISCIPLINES = ['artisan', 'artist', 'ui'];
|
|
18
18
|
const draft = (over = {}) => ({
|
|
19
19
|
name: 'Economics',
|
|
20
|
-
discipline: '
|
|
20
|
+
discipline: 'artist',
|
|
21
21
|
owner_kind: 'builder',
|
|
22
22
|
owner_builder_id: '1000025',
|
|
23
23
|
...over,
|
|
@@ -130,7 +130,7 @@ test('canAdopt refuses rather than throws on missing inputs', () => {
|
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
// ---------- the injected fragment ----------
|
|
133
|
-
const active = { id: '7', slug: 'economics', name: 'Economics', discipline: '
|
|
133
|
+
const active = { id: '7', slug: 'economics', name: 'Economics', discipline: 'artist', summary: 'Markets and incentives.', contract_md: 'Argue from mechanism, not vibes.' };
|
|
134
134
|
|
|
135
135
|
test('no active speciality injects nothing at all', () => {
|
|
136
136
|
assert.equal(S.describeForApi(null), null);
|
|
@@ -140,7 +140,7 @@ test('the contract carries the name, the craft and the owner terms', () => {
|
|
|
140
140
|
const d = S.describeForApi(active, {});
|
|
141
141
|
assert.equal(d.inject, true);
|
|
142
142
|
assert.match(d.contract, /Economics/);
|
|
143
|
-
assert.match(d.contract, /under
|
|
143
|
+
assert.match(d.contract, /under artist/);
|
|
144
144
|
assert.match(d.contract, /Argue from mechanism/);
|
|
145
145
|
assert.equal(d.speciality_id, '7');
|
|
146
146
|
});
|
|
@@ -212,3 +212,15 @@ test('an empty borrowed speciality still injects nothing', () => {
|
|
|
212
212
|
const bare = { id: '9', slug: 'x', name: '', discipline: '', summary: '', contract_md: '' };
|
|
213
213
|
assert.equal(S.describeForApi(bare, { selfAuthored: false }).inject, false);
|
|
214
214
|
});
|
|
215
|
+
|
|
216
|
+
test('the write routes read an OLD craft spelling as the new one (task 1005141)', () => {
|
|
217
|
+
// An old CLI or form still posts discipline 'engineer'/'ideator'; the routes run
|
|
218
|
+
// it through the doorway's normalizeCraft before validating, on create AND update.
|
|
219
|
+
const fs = require('node:fs');
|
|
220
|
+
const src = fs.readFileSync(path.join(ROOT, 'modules', 'specialities', 'routes', 'specialities.js'), 'utf8');
|
|
221
|
+
assert.equal((src.match(/discipline: api\.crafts\.normalizeCraft\(b\.discipline\)/g) || []).length, 2);
|
|
222
|
+
const { normalizeCraft } = require(path.join(ROOT, 'src', 'crafts.js'));
|
|
223
|
+
assert.equal(normalizeCraft('engineer'), 'artisan');
|
|
224
|
+
assert.equal(normalizeCraft('ideator'), 'artist');
|
|
225
|
+
assert.equal(S.validateDraft(draft({ discipline: normalizeCraft('ideator') }), { disciplines: DISCIPLINES }).ok, true);
|
|
226
|
+
});
|
|
@@ -38,13 +38,13 @@ function fake(results = []) {
|
|
|
38
38
|
|
|
39
39
|
test('adopt DEACTIVATES the craft before inserting — the unique index demands it', async () => {
|
|
40
40
|
const q = fake([{ rows: [], rowCount: 1 }, { rows: [{ builder_id: '1', speciality_id: '7' }], rowCount: 1 }]);
|
|
41
|
-
await db.adopt({ builderId: '1', specialityId: '7', discipline: '
|
|
41
|
+
await db.adopt({ builderId: '1', specialityId: '7', discipline: 'artist' }, q);
|
|
42
42
|
|
|
43
43
|
assert.equal(q.calls.length, 2, 'exactly two statements: the deactivate, then the insert');
|
|
44
44
|
const [first, second] = q.calls;
|
|
45
45
|
assert.match(first.sql, /UPDATE specialities_adoptions SET active = FALSE/);
|
|
46
46
|
assert.match(first.sql, /builder_id = \$1 AND discipline = \$2 AND active/);
|
|
47
|
-
assert.deepEqual(first.params, ['1', '
|
|
47
|
+
assert.deepEqual(first.params, ['1', 'artist'], 'scoped to THIS builder and THIS craft, not all of either');
|
|
48
48
|
assert.match(second.sql, /INSERT INTO specialities_adoptions/);
|
|
49
49
|
// The order is the whole point: insert-then-deactivate would hit the index.
|
|
50
50
|
assert.ok(q.calls.indexOf(first) < q.calls.indexOf(second));
|
|
@@ -68,8 +68,8 @@ test('the injected contract is read with the contract text itself', async () =>
|
|
|
68
68
|
|
|
69
69
|
test('activeForCraft reads ONE craft for ONE builder, and only an active row', async () => {
|
|
70
70
|
const q = fake([{ rows: [{ id: '7' }], rowCount: 1 }]);
|
|
71
|
-
await db.activeForCraft('1', '
|
|
72
|
-
assert.deepEqual(q.calls[0].params, ['1', '
|
|
71
|
+
await db.activeForCraft('1', 'artist', q);
|
|
72
|
+
assert.deepEqual(q.calls[0].params, ['1', 'artist']);
|
|
73
73
|
assert.match(q.calls[0].sql, /a\.active/);
|
|
74
74
|
});
|
|
75
75
|
|
|
@@ -136,8 +136,8 @@ test('describeTrainingFor with no ids asks the database nothing', async () => {
|
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
test('taskDiscipline reads the CORE tasks table by id', async () => {
|
|
139
|
-
const q = fake([{ rows: [{ discipline: '
|
|
140
|
-
assert.equal(await db.taskDiscipline('1234', q), '
|
|
139
|
+
const q = fake([{ rows: [{ discipline: 'artisan' }], rowCount: 1 }]);
|
|
140
|
+
assert.equal(await db.taskDiscipline('1234', q), 'artisan');
|
|
141
141
|
assert.deepEqual(q.calls[0].params, ['1234']);
|
|
142
142
|
});
|
|
143
143
|
|