@bongos/core 1.18.3 → 1.18.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/.bongos-core.json +1575 -285
  2. package/.claude/skills/builder-box/SKILL.md +6 -0
  3. package/.claude/skills/builder-sequence/SKILL.md +97 -0
  4. package/.claude/skills/builder-ship/SKILL.md +8 -2
  5. package/.claude/skills/builder-start/SKILL.md +11 -0
  6. package/.claude/skills/builder-sync/SKILL.md +41 -0
  7. package/.claude/skills/fix-task/SKILL.md +64 -0
  8. package/.claude/skills/goal-create/SKILL.md +158 -0
  9. package/.claude/skills/goal-review/SKILL.md +3 -1
  10. package/.claude/skills/idea-triage/SKILL.md +14 -8
  11. package/.claude/skills/merge-mode/SKILL.md +2 -2
  12. package/.claude/skills/new-project/SKILL.md +1 -0
  13. package/.claude/skills/planning-session/SKILL.md +45 -18
  14. package/.env.local.example +4 -3
  15. package/README.md +4 -2
  16. package/bin/bongos.js +1 -0
  17. package/clients/bongos-client/README.md +2 -2
  18. package/clients/bongos-client/bongos-client.global.js +82 -18
  19. package/clients/bongos-client/index.cjs +82 -18
  20. package/clients/bongos-client/index.d.ts +131 -29
  21. package/clients/bongos-client/index.mjs +82 -18
  22. package/config/branding.neutral.json +6 -1
  23. package/config/governance.neutral.json +21 -0
  24. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
  25. package/docs/adr/0090-metic-task-authoring.md +1 -1
  26. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  27. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  28. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  29. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  30. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  31. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  32. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  33. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  34. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  35. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  36. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  37. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  38. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  39. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  40. package/docs/adr/README.md +10 -0
  41. package/docs/api/openapi.json +843 -17
  42. package/docs/api-reference.md +23 -10
  43. package/docs/architecture.md +4 -2
  44. package/docs/branding-contract.md +3 -1
  45. package/docs/canonical-permissions.md +21 -8
  46. package/docs/file-map.md +8 -0
  47. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  48. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  49. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  50. package/docs/onboarding/diagrams/assertions.json +4 -7
  51. package/docs/onboarding/drachmae-and-karma.md +3 -3
  52. package/docs/onboarding/primer.md +17 -16
  53. package/docs/onboarding/slash-commands.md +124 -0
  54. package/docs/recipes/builder-box-lifecycle.md +23 -0
  55. package/docs/recipes/devbox-app-release.md +14 -4
  56. package/docs/recipes/instance-first-run.md +16 -0
  57. package/docs/recipes/local-dev.md +81 -0
  58. package/docs/recipes/ops-gotchas.md +19 -1
  59. package/docs/recipes/overnight-builder.md +1 -1
  60. package/docs/recipes/parallel-goal-waves.md +174 -0
  61. package/docs/recipes/ship-and-regrade.md +2 -2
  62. package/docs/recipes/standalone-live-docs.md +48 -0
  63. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  64. package/migrations/003_pms.sql +6 -7
  65. package/migrations/006_pms_v2.sql +3 -31
  66. package/migrations/core_193_task_needs_rebase.sql +28 -0
  67. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  68. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  69. package/migrations/core_196_session_reward_topup.sql +69 -0
  70. package/modules/autonomy/pollers/routine-timers.js +9 -8
  71. package/modules/builder-settings/wandering-prefs.js +61 -18
  72. package/modules/dev-box/app/README.md +73 -0
  73. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  74. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  75. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  76. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  77. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  78. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  79. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  80. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  81. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  82. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  83. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  84. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  85. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  86. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  87. package/modules/dev-box/app/build-resources/icon.png +0 -0
  88. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  89. package/modules/dev-box/app/electron-builder.yml +95 -0
  90. package/modules/dev-box/app/package-lock.json +5291 -0
  91. package/modules/dev-box/app/package.json +30 -0
  92. package/modules/dev-box/app/renderer/app.js +319 -0
  93. package/modules/dev-box/app/renderer/index.html +45 -0
  94. package/modules/dev-box/app/renderer/styles.css +246 -0
  95. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  96. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  97. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  98. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  99. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  100. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  101. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  102. package/modules/dev-box/app/src/box-controller.js +287 -0
  103. package/modules/dev-box/app/src/brand.generated.json +13 -0
  104. package/modules/dev-box/app/src/brand.js +16 -0
  105. package/modules/dev-box/app/src/config.js +19 -0
  106. package/modules/dev-box/app/src/gds-client.js +90 -0
  107. package/modules/dev-box/app/src/main.js +610 -0
  108. package/modules/dev-box/app/src/pairing.js +57 -0
  109. package/modules/dev-box/app/src/preload.js +29 -0
  110. package/modules/dev-box/app/src/session-store.js +58 -0
  111. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  112. package/modules/dev-box/app/src/task-picker.js +70 -0
  113. package/modules/dev-box/app/src/tray.js +99 -0
  114. package/modules/dev-box/app/src/updater.js +55 -0
  115. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  116. package/modules/dev-box/app/test/unit.mjs +526 -0
  117. package/modules/dev-box/box-access.js +121 -4
  118. package/modules/dev-box/boxes.js +20 -0
  119. package/modules/dev-box/module.json +1 -1
  120. package/modules/dev-box/routes/box.js +87 -3
  121. package/modules/economy/credits.js +116 -25
  122. package/modules/economy/reward.js +8 -0
  123. package/modules/economy/streak.js +2 -1
  124. package/modules/governance/CLAUDE.md +15 -0
  125. package/modules/governance/catalog.js +419 -0
  126. package/modules/governance/config.js +174 -0
  127. package/modules/governance/db.js +125 -0
  128. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  129. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  130. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  131. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  132. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  133. package/modules/governance/module.json +10 -0
  134. package/modules/governance/protected-surfaces.js +88 -0
  135. package/modules/governance/protected-surfaces.json +80 -0
  136. package/modules/governance/rank-roles.js +83 -0
  137. package/modules/governance/resolver.js +221 -0
  138. package/modules/governance/routes/governance.js +89 -0
  139. package/modules/governance/routes/role-sync.js +49 -0
  140. package/modules/governance/session-scopes.js +127 -0
  141. package/modules/governance/session-scopes.json +38 -0
  142. package/modules/hall-ui/public/atlas.css +117 -0
  143. package/modules/hall-ui/public/atlas.html +3 -114
  144. package/modules/hall-ui/public/brand-apply.js +34 -6
  145. package/modules/hall-ui/public/builders.js +187 -219
  146. package/modules/hall-ui/public/diagrams.html +2 -0
  147. package/modules/hall-ui/public/dom-utils.js +27 -0
  148. package/modules/hall-ui/public/drachmae.html +2 -0
  149. package/modules/hall-ui/public/drachmae.js +54 -1
  150. package/modules/hall-ui/public/gate.css +98 -0
  151. package/modules/hall-ui/public/gate.html +3 -95
  152. package/modules/hall-ui/public/goal-inbox.js +4 -1
  153. package/modules/hall-ui/public/goals-page.js +626 -114
  154. package/modules/hall-ui/public/goals.css +79 -1
  155. package/modules/hall-ui/public/goals.html +38 -11
  156. package/modules/hall-ui/public/hall-widgets.js +62 -52
  157. package/modules/hall-ui/public/harbor.css +123 -0
  158. package/modules/hall-ui/public/harbor.html +3 -120
  159. package/modules/hall-ui/public/index.html +11 -4
  160. package/modules/hall-ui/public/instances-panel.js +4 -1
  161. package/modules/hall-ui/public/modules.css +63 -0
  162. package/modules/hall-ui/public/modules.html +3 -60
  163. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  164. package/modules/hall-ui/public/not-ready.html +2 -0
  165. package/modules/hall-ui/public/pair.css +73 -0
  166. package/modules/hall-ui/public/pair.html +3 -70
  167. package/modules/hall-ui/public/palette.js +302 -0
  168. package/modules/hall-ui/public/primer.css +13 -0
  169. package/modules/hall-ui/public/primer.html +2 -0
  170. package/modules/hall-ui/public/profile.html +2 -0
  171. package/modules/hall-ui/public/ranks.html +2 -0
  172. package/modules/hall-ui/public/roadmap.css +179 -0
  173. package/modules/hall-ui/public/roadmap.html +9 -172
  174. package/modules/hall-ui/public/roadmap.js +329 -316
  175. package/modules/hall-ui/public/sessions.css +16 -0
  176. package/modules/hall-ui/public/sessions.html +3 -13
  177. package/modules/hall-ui/public/settings.html +2 -0
  178. package/modules/hall-ui/public/settings.js +4 -4
  179. package/modules/hall-ui/public/shell.js +143 -18
  180. package/modules/hall-ui/public/style.css +231 -39
  181. package/modules/hall-ui/public/task-detail.js +347 -26
  182. package/modules/hall-ui/public/task.html +6 -1
  183. package/modules/hall-ui/public/watch.html +2 -0
  184. package/modules/hall-ui/public/work.html +8 -6
  185. package/modules/hall-ui/public/work.js +1 -1
  186. package/modules/ideas/inbox.js +61 -11
  187. package/modules/ideas/routes/inbox.js +23 -0
  188. package/modules/lifecycle/claim-eligibility.js +72 -0
  189. package/modules/lifecycle/db.js +519 -139
  190. package/modules/lifecycle/done-when.js +18 -3
  191. package/modules/lifecycle/github-push.js +19 -2
  192. package/modules/lifecycle/goal-advisory.js +114 -0
  193. package/modules/lifecycle/goal-authz.js +62 -0
  194. package/modules/lifecycle/lifecycle.js +9 -0
  195. package/modules/lifecycle/publish-reconciler.js +109 -6
  196. package/modules/lifecycle/routes/claims.js +68 -23
  197. package/modules/lifecycle/routes/done-when.js +110 -15
  198. package/modules/lifecycle/routes/goals.js +8 -38
  199. package/modules/lifecycle/routes/tasks.js +38 -6
  200. package/modules/lifecycle/ship-card.js +6 -0
  201. package/modules/onboarding/onboarding-state.js +1 -1
  202. package/modules/platform-identity/CLAUDE.md +15 -0
  203. package/modules/platform-identity/hub-device.js +147 -0
  204. package/modules/platform-identity/hub-keys.js +151 -0
  205. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  206. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  207. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  208. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  209. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  210. package/modules/platform-identity/module.json +18 -0
  211. package/modules/platform-identity/platform-identity.js +531 -0
  212. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  213. package/modules/platform-identity/routes/my-projects.js +140 -0
  214. package/modules/platform-identity/routes/projects.js +107 -0
  215. package/modules/platform-identity/routes/scouting.js +35 -0
  216. package/modules/platform-identity/routes/sso.js +402 -0
  217. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  218. package/modules/platform-identity/ui/.gitkeep +0 -0
  219. package/modules/security/routes/reports.js +10 -0
  220. package/modules/sessions/db.js +166 -21
  221. package/modules/sessions/module.json +1 -1
  222. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  223. package/modules/sessions/routes/sessions.js +33 -0
  224. package/package-lock.json +2 -2
  225. package/package.json +1 -1
  226. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  227. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  228. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  229. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  230. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  231. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  232. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  233. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  234. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  235. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  236. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  237. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  238. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  239. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  270. package/public-landing/create.html +51 -58
  271. package/public-landing/gate/README.md +137 -0
  272. package/public-landing/gate/dispmaps/bar.png +0 -0
  273. package/public-landing/gate/dispmaps/flat.png +0 -0
  274. package/public-landing/gate/dispmaps/key.png +0 -0
  275. package/public-landing/gate/index.html +587 -0
  276. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  277. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  278. package/public-landing/gate/space.css +799 -0
  279. package/public-landing/gate/space.js +1826 -0
  280. package/public-landing/gate/sprites/enter.png +0 -0
  281. package/public-landing/gate/sprites/search.png +0 -0
  282. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  283. package/public-landing/gate/sprites/tile.png +0 -0
  284. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  285. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  286. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  287. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  288. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  289. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  290. package/scripts/gds/abandon.js +6 -4
  291. package/scripts/gds/api.js +10 -5
  292. package/scripts/gds/api.ps1 +26 -0
  293. package/scripts/gds/api.sh +6 -0
  294. package/scripts/gds/audit-ships.js +7 -1
  295. package/scripts/gds/backfill-sessions.js +78 -18
  296. package/scripts/gds/backup.js +23 -1
  297. package/scripts/gds/box-connect-lib.js +124 -31
  298. package/scripts/gds/box-sync.js +66 -0
  299. package/scripts/gds/box.js +225 -53
  300. package/scripts/gds/cf-tunnel.js +145 -6
  301. package/scripts/gds/claim.js +34 -3
  302. package/scripts/gds/cli-lib.js +161 -27
  303. package/scripts/gds/code.js +6 -2
  304. package/scripts/gds/deploy-divergence.js +209 -0
  305. package/scripts/gds/dev-lib.js +309 -0
  306. package/scripts/gds/dev.js +322 -0
  307. package/scripts/gds/doctor.js +90 -61
  308. package/scripts/gds/fetch-art-key.js +1 -1
  309. package/scripts/gds/fetch-sound-prefs.js +2 -2
  310. package/scripts/gds/fitness.js +228 -12
  311. package/scripts/gds/gds-db-backup.service +16 -1
  312. package/scripts/gds/gds-db-offsite.service +9 -1
  313. package/scripts/gds/gen-api-client.js +1 -1
  314. package/scripts/gds/gen-api-docs.js +36 -14
  315. package/scripts/gds/gen-diagrams.js +45 -28
  316. package/scripts/gds/gen-file-map.js +2 -1
  317. package/scripts/gds/gen-freshness.js +32 -0
  318. package/scripts/gds/gen-repo-map.js +3 -2
  319. package/scripts/gds/gen-session-index.js +14 -5
  320. package/scripts/gds/hooks/pre-push.js +2 -2
  321. package/scripts/gds/init.js +60 -3
  322. package/scripts/gds/install-git-hooks.js +1 -1
  323. package/scripts/gds/key-card.js +15 -6
  324. package/scripts/gds/key-status.js +1 -1
  325. package/scripts/gds/linkify-refs.js +35 -9
  326. package/scripts/gds/login.js +16 -1
  327. package/scripts/gds/paste-token.js +7 -1
  328. package/scripts/gds/preflight.js +126 -0
  329. package/scripts/gds/provision.js +123 -12
  330. package/scripts/gds/publish-manifest.js +7 -1
  331. package/scripts/gds/reauth.js +7 -3
  332. package/scripts/gds/regen-instance-docs.js +47 -0
  333. package/scripts/gds/run-unit-tests.js +4 -2
  334. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  335. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  336. package/scripts/gds/sequence.js +425 -0
  337. package/scripts/gds/session-cost.js +10 -1
  338. package/scripts/gds/setup.js +31 -5
  339. package/scripts/gds/shell.js +5 -3
  340. package/scripts/gds/ship.js +319 -29
  341. package/scripts/gds/smoke-gds.js +139 -8
  342. package/scripts/gds/start.js +171 -22
  343. package/scripts/gds/status.js +1 -1
  344. package/scripts/gds/triage.js +10 -9
  345. package/scripts/gds/upgrade.js +124 -2
  346. package/scripts/status-mirror-sync.js +114 -84
  347. package/src/bongos/api-errors.js +1 -0
  348. package/src/bongos/auth.js +447 -102
  349. package/src/bongos/db-kernel.js +27 -0
  350. package/src/bongos/db.js +39 -0
  351. package/src/bongos/module-scope-map.js +27 -1
  352. package/src/bongos/permission-path-check.js +136 -102
  353. package/src/bongos/repo-info.js +47 -11
  354. package/src/bongos/route-rank-check.js +429 -46
  355. package/src/bongos/routes/_helpers.js +12 -1
  356. package/src/bongos/routes/auth.js +219 -2
  357. package/src/bongos/routes/backup.js +58 -2
  358. package/src/bongos/routes/me.js +35 -1
  359. package/src/bongos/serve-internal.js +84 -15
  360. package/src/branding.js +32 -0
  361. package/src/instance-config.js +30 -0
  362. package/src/module-api.js +112 -1
  363. package/src/platform-server.js +86 -9
  364. package/tests/abandon_task.mjs +59 -3
  365. package/tests/api_cli_flags.mjs +53 -0
  366. package/tests/auth_page_gate.mjs +13 -0
  367. package/tests/backfill_sessions.mjs +127 -1
  368. package/tests/backup_cli.mjs +54 -0
  369. package/tests/bongos_box_connect.mjs +77 -0
  370. package/tests/box_access.mjs +130 -1
  371. package/tests/box_credential.mjs +125 -0
  372. package/tests/box_host_keys.mjs +24 -0
  373. package/tests/box_infra_resolve.mjs +149 -0
  374. package/tests/box_scope_session.mjs +6 -2
  375. package/tests/box_sync.mjs +55 -0
  376. package/tests/box_task_scope.mjs +99 -0
  377. package/tests/branding.mjs +12 -0
  378. package/tests/cf_ruleset_preflight.mjs +142 -0
  379. package/tests/cf_tunnel.mjs +37 -0
  380. package/tests/claim_gate_rebase.mjs +98 -0
  381. package/tests/clawback_confirmed_backlog.mjs +103 -0
  382. package/tests/cli_lib_formatters.mjs +20 -0
  383. package/tests/confirm_attribution.mjs +30 -46
  384. package/tests/credit_grant.mjs +198 -10
  385. package/tests/criterion_authoring_gate.mjs +272 -0
  386. package/tests/criterion_progress.mjs +33 -1
  387. package/tests/dev_lib.mjs +253 -0
  388. package/tests/devbox_artifact_gate.mjs +75 -0
  389. package/tests/devbox_dmg_backdrop.mjs +137 -0
  390. package/tests/devbox_icons.mjs +304 -0
  391. package/tests/devbox_installer_brand.mjs +232 -0
  392. package/tests/devbox_key_register.mjs +234 -0
  393. package/tests/docs_openapi_route.mjs +110 -0
  394. package/tests/doctor_hooks.mjs +141 -0
  395. package/tests/extract_json.mjs +140 -0
  396. package/tests/fitness.mjs +89 -0
  397. package/tests/gen_freshness.mjs +96 -0
  398. package/tests/goal_advisory.mjs +251 -0
  399. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  400. package/tests/goal_routes.mjs +59 -4
  401. package/tests/goal_tier.mjs +12 -3
  402. package/tests/governance_catalog.mjs +129 -0
  403. package/tests/governance_claim_eligibility.mjs +262 -0
  404. package/tests/governance_config.mjs +187 -0
  405. package/tests/governance_ownership_scope.mjs +410 -0
  406. package/tests/governance_page_gates.mjs +324 -0
  407. package/tests/governance_parity.mjs +336 -0
  408. package/tests/governance_principals.mjs +232 -0
  409. package/tests/governance_protected_surfaces.mjs +208 -0
  410. package/tests/governance_require_permission.mjs +223 -0
  411. package/tests/governance_resolver.mjs +84 -0
  412. package/tests/governance_role_sync.mjs +231 -0
  413. package/tests/governance_seed.mjs +127 -0
  414. package/tests/governance_session_scope.mjs +434 -0
  415. package/tests/governance_vanilla_boot.mjs +203 -0
  416. package/tests/grade_attribution.mjs +7 -7
  417. package/tests/hall_audit.mjs +223 -0
  418. package/tests/hall_nav.mjs +191 -0
  419. package/tests/hall_palette.mjs +206 -0
  420. package/tests/hall_signout.mjs +124 -0
  421. package/tests/hook_user_agents.mjs +10 -0
  422. package/tests/idea_triage.mjs +162 -0
  423. package/tests/idp_device_exchange.mjs +93 -0
  424. package/tests/init.mjs +51 -0
  425. package/tests/instance_config.mjs +65 -0
  426. package/tests/instance_mismatch_guard.mjs +23 -6
  427. package/tests/instance_release_config.mjs +104 -0
  428. package/tests/linkify_refs.mjs +15 -2
  429. package/tests/module_contributions.mjs +55 -0
  430. package/tests/module_loader.mjs +10 -5
  431. package/tests/module_scope_active_claims.mjs +94 -0
  432. package/tests/needs_rebase.mjs +92 -0
  433. package/tests/one_active_claim_per_session.mjs +76 -0
  434. package/tests/override_attribution.mjs +10 -8
  435. package/tests/override_request_ownership.mjs +8 -4
  436. package/tests/parse_cookie_decode.mjs +42 -0
  437. package/tests/pay_on_land.mjs +129 -0
  438. package/tests/platform_boot.mjs +100 -1
  439. package/tests/post_login_landing.mjs +42 -0
  440. package/tests/preflight_checks.mjs +72 -0
  441. package/tests/provision.mjs +132 -7
  442. package/tests/publish_branch_route.mjs +53 -0
  443. package/tests/publish_reconciler.mjs +187 -2
  444. package/tests/repo_info_binding.mjs +248 -0
  445. package/tests/reward_gate.mjs +11 -2
  446. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  447. package/tests/reward_mode_surfacing.mjs +168 -0
  448. package/tests/route_permission_check.mjs +301 -0
  449. package/tests/route_rank_check.mjs +254 -12
  450. package/tests/route_validation.mjs +30 -0
  451. package/tests/sequence.mjs +267 -0
  452. package/tests/session_digest.mjs +44 -0
  453. package/tests/session_index.mjs +18 -6
  454. package/tests/session_records.mjs +39 -1
  455. package/tests/session_reward_on_land.mjs +381 -0
  456. package/tests/session_reward_reconcile.mjs +229 -0
  457. package/tests/ship_api_client_pathspec.mjs +100 -0
  458. package/tests/ship_base_flag.mjs +93 -0
  459. package/tests/ship_ci_deploy.mjs +80 -3
  460. package/tests/ship_upstream_deletion.mjs +57 -0
  461. package/tests/smoke_target.mjs +233 -0
  462. package/tests/start_env_guard.mjs +87 -0
  463. package/tests/start_rebase_warning.mjs +103 -0
  464. package/tests/status_mirror_sync.mjs +122 -0
  465. package/tests/stop_refcheck.mjs +19 -1
  466. package/tests/task_goal_link.mjs +15 -6
  467. package/tests/upgrade.mjs +139 -0
  468. package/tests/web_admission_status.mjs +162 -0
  469. package/tests/write_validation_gaps.mjs +110 -0
@@ -0,0 +1,126 @@
1
+ // Seed the tasks for goal 1000045 "Public builder profiles" (BONGOS-V1, internal).
2
+ // Source of truth: docs/specs/bongos-v1-public-builder-profiles.md (status: final).
3
+ // Idempotent on (source, source_ref). Re-running resolves to the existing rows.
4
+ //
5
+ // Run: node scripts/gds/seed-bv1-profiles-tasks.js
6
+ const os = require('os');
7
+ const sess = require(os.homedir() + '/.config/cloudbongos/gds-session.json');
8
+ const BASE = sess.api_base.replace(/\/$/, '');
9
+ const TOKEN = sess.token;
10
+ const VER = 'BONGOS-V1';
11
+ const GOAL = 1000045;
12
+ const SRC = 'planning-session';
13
+ const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
14
+
15
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
16
+ async function api(method, path, body, _tries = 0) {
17
+ await sleep(400); // base throttle to stay under the per-IP rate limit
18
+ const res = await fetch(`${BASE}/api/gds${path}`, {
19
+ method, headers: H, body: body ? JSON.stringify(body) : undefined,
20
+ });
21
+ const text = await res.text();
22
+ let json; try { json = JSON.parse(text); } catch { json = { _raw: text.slice(0, 300) }; }
23
+ if (res.status === 429 && _tries < 8) {
24
+ const wait = ((json.error && json.error.details && json.error.details.retry_after_seconds) || 30) + 1;
25
+ console.log(` …rate-limited, waiting ${wait}s`);
26
+ await sleep(wait * 1000);
27
+ return api(method, path, body, _tries + 1);
28
+ }
29
+ return { status: res.status, ok: res.ok, json };
30
+ }
31
+
32
+ // disc: engineer (backend/API/spike) | ui (frontend). kind: spike | feature | verify (proofs).
33
+ const TASKS = [
34
+ // Phase 1 — profile shows cross-project record + hide (C1,C2) — P5
35
+ { ref:'R01', kind:'spike', disc:'engineer', prio:5, est:180, crit:['C1','C2'], deps:[], title:'R01 · Design profile↔cross-project rollup join + consent model', desc:'Needs-exploration: how the public profile reads the platform-identity rollup (listScoutingRollup / platform_identity_builder_project_activity), public-by-default, and where the hide flag (C2) lives. Output an ADR + sub-tasks. See docs/specs/bongos-v1-public-builder-profiles.md.' },
36
+ { ref:'R02', kind:'feature', disc:'engineer', prio:5, est:240, crit:['C1'], deps:['R01'], title:'R02 · Profile API returns cross-project record + project names', desc:'Extend GET /builders/:id/profile to include aggregated cross-project credits/ships/karma/disciplines + the list of projects by name, from the platform-identity rollup; public by default; respects the whitelist + leak test (tests/profile_route.mjs). Done-when: another account sees the aggregate + project names, no private columns. Deps R01.' },
37
+ { ref:'R03', kind:'feature', disc:'engineer', prio:5, est:180, crit:['C2'], deps:['R01'], title:'R03 · Hide flag: builder hides cross-project stats (opt-out, live)', desc:'Self-serve opt-out that hides the cross-project record from the profile + applications, honored live everywhere. Default is shown. Deps R01.' },
38
+ { ref:'R04', kind:'feature', disc:'ui', prio:5, est:180, crit:['C1','C2'], deps:['R02','R03'], title:'R04 · Profile UI: cross-project section + self hide-toggle', desc:'Render the cross-project section (totals + project names) on the profile; show the hide toggle to the profile owner. Deps R02,R03.' },
39
+ { ref:'R05', kind:'feature', disc:'ui', prio:4, est:120, crit:['C1'], deps:['R02'], title:'R05 · Shareable-by-login profile URL (?builder=<login>)', desc:'Add a login-addressable profile URL alongside ?id=. Promotes idea #1000522. Deps R02.' },
40
+ { ref:'R06', kind:'verify', disc:'engineer', prio:4, est:90, crit:['C1','C2'], deps:['R04','R03'], title:'R06 · PROOF: cross-project record shows to a viewer; hide removes it live', desc:'Verify a 2-project builder\'s combined record shows to another account, and hiding removes it within one load. Extends tests/profile_route.mjs. Deps R04,R03.' },
41
+
42
+ // Phase 2 — apply from orbs + application carries profile (C3,C4) — P5
43
+ { ref:'R07', kind:'spike', disc:'engineer', prio:5, est:180, crit:['C3','C4'], deps:[], title:'R07 · Design application model + "project owner (registrant+delegates)" authz', desc:'Needs-exploration: extend access_requests vs a new cross-project applications record; how the profile snapshot travels hub→target; the owner (registrant + delegates) authz; pending queue + approve/decline. Output an ADR + sub-tasks. Deps none.' },
44
+ { ref:'R08', kind:'feature', disc:'engineer', prio:5, est:240, crit:['C3','C4'], deps:['R07'], title:'R08 · Application API: apply / list-pending / approve / decline', desc:'Cross-project application flow reusing the join relay (/my-projects/join → target /access-requests). Deps R07.' },
45
+ { ref:'R09', kind:'feature', disc:'engineer', prio:5, est:150, crit:['C4'], deps:['R08','R02'], title:'R09 · Application carries the applicant\'s cross-project record (+note)', desc:'Attach the applicant\'s cross-project record (subject to the C2 hide, live) + optional note to the application the owner sees. Deps R08,R02.' },
46
+ { ref:'R10', kind:'feature', disc:'ui', prio:5, est:180, crit:['C3'], deps:['R08'], title:'R10 · "Apply" affordance on the in-core browse surface', desc:'Apply action + state (applied/pending/member) on the project cards / My Projects. Deps R08.' },
47
+ { ref:'R11', kind:'feature', disc:'ui', prio:5, est:180, crit:['C4'], deps:['R08'], title:'R11 · Owner pending-applications view (approve/decline)', desc:'A project owner sees pending applications for their project and approves/declines. Deps R08.' },
48
+ { ref:'R12', kind:'feature', disc:'ui', prio:4, est:150, crit:['C3'], deps:['R10'], title:'R12 · Surface "Apply" on the orbs front door', desc:'Wire the Apply affordance onto the orbs page. NOTE: the orbs front door is a separate static site — this likely lands instance-side. Deps R10.' },
49
+ { ref:'R13', kind:'verify', disc:'engineer', prio:5, est:90, crit:['C3','C4'], deps:['R10','R11','R09'], title:'R13 · PROOF: apply → owner sees record → approves → applicant joins', desc:'End-to-end verify of the apply→approve flow carrying the profile. Deps R10,R11,R09.' },
50
+
51
+ // Phase 3 — owners browse builder profiles (C5) — P4
52
+ { ref:'R14', kind:'feature', disc:'engineer', prio:4, est:210, crit:['C5'], deps:['R02'], title:'R14 · Graduate /scouting to project-owner-facing + opt-out-of-recruiting flag', desc:'Widen the Archon-only /scouting to a project owner (registrant+delegates); add a builder opt-out-of-recruiting flag. Touches authz → Metic+ floor. Deps R02.' },
53
+ { ref:'R15', kind:'feature', disc:'ui', prio:4, est:210, crit:['C5'], deps:['R14'], title:'R15 · Owner recruiting UI (search/filter/sort; honors hide + opt-out)', desc:'Owner-facing recruiting surface: search/filter by discipline, sort by track record; honors C2 hide + recruiting opt-out. Deps R14.' },
54
+ { ref:'R16', kind:'feature', disc:'ui', prio:4, est:150, crit:['C5'], deps:['R15','R08'], title:'R16 · Browse → invite/recruit a builder to your project', desc:'Invite/recruit a builder from the browse surface, tying into the application/invite flow. Deps R15,R08.' },
55
+ { ref:'R17', kind:'verify', disc:'engineer', prio:4, est:90, crit:['C5'], deps:['R15','R16'], title:'R17 · PROOF: non-Archon owner browses, filters, invites', desc:'Verify a non-Archon project owner browses builder profiles, filters by discipline, invites one. Deps R15,R16.' },
56
+
57
+ // Phase 4 — builder-to-builder connections + degrees (C6) — P4
58
+ { ref:'R18', kind:'spike', disc:'engineer', prio:4, est:180, crit:['C6'], deps:[], title:'R18 · Design connections model incl. 1st/2nd/3rd-degree traversal', desc:'Needs-exploration: platform-level connections (account↔account) with request/accept/decline/remove; degree-of-separation traversal (1st/2nd/3rd); "people you may know" from native signals. Output an ADR + sub-tasks. Deps none.' },
59
+ { ref:'R19', kind:'feature', disc:'engineer', prio:4, est:240, crit:['C6'], deps:['R18'], title:'R19 · Connections API: request/accept/decline/remove/list + degree queries', desc:'The connection graph API incl. degree-of-separation queries. Deps R18.' },
60
+ { ref:'R20', kind:'feature', disc:'ui', prio:4, est:180, crit:['C6'], deps:['R19','R04'], title:'R20 · Connections on the profile (with degree labels) + connect button', desc:'Show a builder\'s connections (public) with 1st/2nd/3rd-degree labels; connect/pending/accept from the profile. Deps R19,R04.' },
61
+ { ref:'R21', kind:'feature', disc:'engineer', prio:4, est:150, crit:['C6'], deps:['R19'], title:'R21 · "Builders you may know" from mutuals + shared projects/guilds', desc:'Suggest connections from mutual connections + shared projects/guilds (2nd/3rd degree). Deps R19.' },
62
+ { ref:'R22', kind:'verify', disc:'engineer', prio:4, est:90, crit:['C6'], deps:['R20','R21'], title:'R22 · PROOF: two builders connect; each sees the other + a 2nd-degree suggestion', desc:'Verify request→accept, mutual visibility, and a 2nd-degree suggestion appears. Deps R20,R21.' },
63
+
64
+ // Phase 5 — guilds + apply/invite + guild map (C7,C8,C9) — P3
65
+ { ref:'R23', kind:'spike', disc:'engineer', prio:3, est:210, crit:['C7','C8','C9'], deps:['R18','R07'], title:'R23 · Design guild model (tables, roles, public/unlisted, rollup, map ranking)', desc:'Needs-exploration: platform-level guilds + guild_members (multi-guild, distinct from goal_members); roles; public/unlisted visibility; collective rollup (excludes hidden members); guild↔project application reuse; map ranking. Output an ADR + sub-tasks. Deps R18,R07.' },
66
+ { ref:'R24', kind:'feature', disc:'engineer', prio:3, est:240, crit:['C7'], deps:['R23'], title:'R24 · Guild CRUD + membership API + public/unlisted visibility', desc:'Create guild, invite/accept/join-request, roster, leave; owner sets public/unlisted. Deps R23.' },
67
+ { ref:'R25', kind:'feature', disc:'ui', prio:3, est:210, crit:['C7'], deps:['R24'], title:'R25 · Guild page UI (identity, roster, collective record)', desc:'Guild page: identity, roster, collective track record (N of M counted). Deps R24.' },
68
+ { ref:'R26', kind:'feature', disc:'engineer', prio:3, est:180, crit:['C7'], deps:['R24','R02'], title:'R26 · Guild collective track-record rollup (excludes hidden members)', desc:'Aggregate members\' cross-project stats into the guild total; exclude C2-hidden members ("N of M counted"). Deps R24,R02.' },
69
+ { ref:'R27', kind:'feature', disc:'engineer', prio:3, est:210, crit:['C8'], deps:['R24','R08'], title:'R27 · Guild → project applications + owner invites a guild (each-member-opt-in)', desc:'Guild applies to / is invited to a project (guild-level mirror of R08); each member opts in before being attached. Deps R24,R08.' },
70
+ { ref:'R28', kind:'feature', disc:'ui', prio:3, est:150, crit:['C8'], deps:['R27','R11'], title:'R28 · Owner view: pending guild applications', desc:'Extends the owner application view (R11) to guild applications. Deps R27,R11.' },
71
+ { ref:'R29', kind:'spike', disc:'engineer', prio:3, est:150, crit:['C9'], deps:['R26'], title:'R29 · Design guild map ranking + orb-sky viz approach', desc:'Needs-exploration: rank guilds by size/success; render on the orb-sky visual language (goal 1000044). Output design + sub-tasks. Deps R26.' },
72
+ { ref:'R30', kind:'feature', disc:'engineer', prio:3, est:150, crit:['C9'], deps:['R26'], title:'R30 · Guild map API (ranked; public guilds only; honors hide)', desc:'Ranked guilds by size/success; only public (not unlisted) guilds; honors member hide. Deps R26.' },
73
+ { ref:'R31', kind:'feature', disc:'ui', prio:3, est:210, crit:['C9'], deps:['R30','R29','R15'], title:'R31 · Guild map UI on the recruiting surface (orb-sky visual)', desc:'Visual guild map on the owner recruiting surface. Deps R30,R29,R15.' },
74
+ { ref:'R32', kind:'verify', disc:'engineer', prio:3, est:120, crit:['C7','C8','C9'], deps:['R25','R27','R31'], title:'R32 · PROOF: form guild → applies (members opt in) → owner approves → ranked on map', desc:'End-to-end verify of the guild lifecycle incl. the map. Deps R25,R27,R31.' },
75
+
76
+ // Phase 6 — connection proximity via orb-sky physics (C10) — P3
77
+ { ref:'R33', kind:'spike', disc:'engineer', prio:3, est:210, crit:['C10'], deps:['R19','R23'], title:'R33 · Design connection→orb-physics proximity + degree model', desc:'Needs-exploration: connection strength (direct + mutuals + shared projects/guilds + co-shipped; no time-decay) feeding orb-world physics (goals 1000044/1000049); cluster detection. NOTE cross-goal dependency on orb-physics infra. Output an ADR + sub-tasks. Deps R19,R23.' },
78
+ { ref:'R34', kind:'feature', disc:'engineer', prio:3, est:210, crit:['C10'], deps:['R33'], title:'R34 · Connection-strength + degree + cluster compute API', desc:'Compute connection strength + degrees + close-knit clusters; feeds orb physics + recruiting. Deps R33.' },
79
+ { ref:'R35', kind:'feature', disc:'ui', prio:3, est:180, crit:['C10'], deps:['R34','R25'], title:'R35 · Builder-side: clusters in the orb sky → invite a close cluster to a guild', desc:'Close connections cluster in the orb sky; invite a cluster to form a guild from it. Deps R34,R25.' },
80
+ { ref:'R36', kind:'feature', disc:'ui', prio:3, est:180, crit:['C10'], deps:['R34','R16'], title:'R36 · Project-side: recruit a close-knit cluster together', desc:'Surface close-knit clusters on the recruiting surface; recruit them together. Deps R34,R16.' },
81
+ { ref:'R37', kind:'verify', disc:'engineer', prio:3, est:120, crit:['C10'], deps:['R35','R36'], title:'R37 · PROOF: cluster forms a guild from the sky; owner recruits a cluster', desc:'End-to-end verify of the proximity-driven guild-forming + cluster-recruiting. Deps R35,R36.' },
82
+ ];
83
+
84
+ const ROOTS = ['R01', 'R07', 'R18']; // zero-dep entry spikes to open for claiming
85
+
86
+ (async () => {
87
+ const idByRef = {};
88
+ let created = 0, existing = 0;
89
+ console.log(`Seeding ${TASKS.length} tasks under goal ${GOAL} (${VER})…`);
90
+ for (const t of TASKS) {
91
+ const r = await api('POST', '/tasks', {
92
+ version_id: VER, goal_id: GOAL, title: t.title, description: t.desc,
93
+ kind: t.kind, discipline: t.disc, priority: t.prio, est_minutes: t.est,
94
+ criterion_ids: t.crit, source: SRC, source_ref: `bv1-profiles-${t.ref}`,
95
+ });
96
+ if (!r.ok) { console.log(` ${t.ref}: FAILED ${r.status} ${JSON.stringify(r.json)}`); continue; }
97
+ const task = r.json.task || r.json;
98
+ idByRef[t.ref] = task.id;
99
+ if (r.json.idempotent || r.json.existing) existing++; else created++;
100
+ console.log(` ${t.ref} → task ${task.id} (${t.kind}/${t.disc}, P${t.prio}, [${t.crit.join(',')}])`);
101
+ }
102
+
103
+ console.log('Wiring dependencies…');
104
+ let edges = 0;
105
+ for (const t of TASKS) {
106
+ if (!t.deps.length) continue;
107
+ const id = idByRef[t.ref];
108
+ const depIds = t.deps.map((d) => idByRef[d]).filter(Boolean);
109
+ if (!id || depIds.length !== t.deps.length) { console.log(` ${t.ref}: dep resolve gap, skipping`); continue; }
110
+ const r = await api('POST', `/tasks/${id}/dependencies`, { depends_on_task_ids: depIds });
111
+ if (!r.ok) { console.log(` ${t.ref}: dep FAILED ${r.status} ${JSON.stringify(r.json)}`); continue; }
112
+ edges += (r.json.added || depIds.length);
113
+ console.log(` ${t.ref} depends on ${t.deps.join(',')}`);
114
+ }
115
+
116
+ console.log('Promoting root spikes to ready…');
117
+ for (const ref of ROOTS) {
118
+ const id = idByRef[ref];
119
+ if (!id) continue;
120
+ const r = await api('POST', `/tasks/${id}/promote`, {});
121
+ console.log(` ${ref} (task ${id}): ${r.ok ? 'ready' : 'promote note ' + JSON.stringify(r.json).slice(0,120)}`);
122
+ }
123
+
124
+ console.log(`\nDONE. created=${created} existing=${existing} depEdges=${edges}. Roots opened: ${ROOTS.join(', ')}.`);
125
+ console.log('ref→id:', JSON.stringify(idByRef));
126
+ })();
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env node
2
+ // /builder-sequence — walk a dependency chain end to end from one kickoff.
3
+ //
4
+ // Two-thirds of the open backlog is dependency-gated. Shipping a task
5
+ // auto-promotes its dependents backlog→ready (the promote_dependents_on_ship
6
+ // trigger, migrations 020/163) — but nothing picks the newly-unblocked task
7
+ // up, so an N-task chain needs N separate kickoffs. This is the plumbing that
8
+ // lets one session walk the whole chain: resolve scope → next → claim → build
9
+ // → ship → next.
10
+ //
11
+ // Calling forms:
12
+ // node scripts/gds/sequence.js --from 1002277 plan the chain below a task
13
+ // node scripts/gds/sequence.js --goal 1000045 plan a goal's open tasks
14
+ // node scripts/gds/sequence.js --from 1002277 --next the one task to claim now
15
+ // node scripts/gds/sequence.js --check 1002277 stop-reasons for one task
16
+ //
17
+ // Flags:
18
+ // --next emit only the next claimable task (or a named stop reason)
19
+ // --check <id> evaluate the halt matrix for one task
20
+ // --json machine-readable output
21
+ // --limit N cap the planned chain at N tasks (default 50)
22
+ //
23
+ // This script DECIDES NOTHING it cannot decide from facts. Every halt is a
24
+ // named reason a human can act on — see HALT_REASONS. The judgement half of
25
+ // the loop (actually building each task) lives in .claude/skills/builder-sequence.
26
+
27
+ const { cliClient, requireSession, fmtMinutes, hallBase } = require('./cli-lib');
28
+ const { matchProtected } = require('../../src/bongos/permission-path-check');
29
+
30
+ // Rank → tier, mirroring REQUIRES_RANK_TIER. Imported shape would drag the
31
+ // server's db.js (and its pool) into a CLI that only needs four integers.
32
+ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
33
+
34
+ // A task in one of these statuses is done as far as the chain is concerned —
35
+ // 'completed'/'confirmed' are mid-ship states, so the chain must not re-claim
36
+ // them, but they are also not yet 'shipped' so successors stay gated.
37
+ const TERMINAL = new Set(['shipped', 'abandoned']);
38
+ const IN_FLIGHT = new Set(['active', 'completed', 'confirmed']);
39
+
40
+ const DEFAULT_LIMIT = 50;
41
+
42
+ // A description shorter than this can't carry acceptance criteria worth
43
+ // verifying against, so the runner refuses to guess at what "done" means.
44
+ const MIN_SPEC_CHARS = 120;
45
+
46
+ // Kinds whose whole point is a judgement call the owner should see before it
47
+ // lands: a decision needs an ADR, a spike's output is a recommendation.
48
+ const JUDGEMENT_KINDS = new Set(['decision', 'spike']);
49
+
50
+ const HALT_REASONS = {
51
+ needs_migration: 'wants a schema migration — needs a reserved number and human eyes',
52
+ security_sensitive: 'flagged security-sensitive',
53
+ protected_path: 'touches the permission/deploy core',
54
+ rank_floor: 'requires a rank above yours',
55
+ thin_spec: 'has no acceptance criteria to verify against',
56
+ judgement_kind: 'is a judgement call (decision/spike), not mechanical work',
57
+ not_claimable: 'is ready but the server will not hand it over',
58
+ deps_unmet: 'is still waiting on a predecessor',
59
+ in_flight: 'is already mid-ship',
60
+ cycle: 'sits in a dependency cycle',
61
+ chain_complete: 'nothing left in this chain',
62
+ };
63
+
64
+ // -------------------------------------------------------------------------
65
+ // Argv
66
+ // -------------------------------------------------------------------------
67
+
68
+ function readArgv(argv) {
69
+ const out = { from: null, goal: null, check: null, next: false, json: false, limit: DEFAULT_LIMIT };
70
+ for (let i = 0; i < argv.length; i++) {
71
+ const a = argv[i];
72
+ if (a === '--next') { out.next = true; continue; }
73
+ if (a === '--json') { out.json = true; continue; }
74
+ if (a === '--from') { out.from = Number(argv[++i]); continue; }
75
+ if (a.startsWith('--from=')) { out.from = Number(a.slice(7)); continue; }
76
+ if (a === '--goal') { out.goal = Number(argv[++i]); continue; }
77
+ if (a.startsWith('--goal=')) { out.goal = Number(a.slice(7)); continue; }
78
+ if (a === '--check') { out.check = Number(argv[++i]); continue; }
79
+ if (a.startsWith('--check=')) { out.check = Number(a.slice(8)); continue; }
80
+ if (a === '--limit') { out.limit = Number(argv[++i]); continue; }
81
+ if (a.startsWith('--limit=')) { out.limit = Number(a.slice(8)); continue; }
82
+ if (!out.from && /^\d+$/.test(a)) { out.from = Number(a); continue; }
83
+ }
84
+ return out;
85
+ }
86
+
87
+ // -------------------------------------------------------------------------
88
+ // Pure core (exported for tests)
89
+ // -------------------------------------------------------------------------
90
+
91
+ // Ids a task object points at, tolerating both the bare-number and the
92
+ // {id,kind} row shapes GET /tasks/:id has returned across schema versions.
93
+ // Ids must come out positive: Number(null) is 0, so a null edge would
94
+ // otherwise enter the graph as task 0 and be chased as a real dependency.
95
+ function edgeIds(list) {
96
+ return (list || [])
97
+ .map((d) => Number(d && typeof d === 'object' ? d.id : d))
98
+ .filter((n) => Number.isFinite(n) && n > 0);
99
+ }
100
+
101
+ // Kahn's algorithm over edges BETWEEN the given tasks. An out-of-scope
102
+ // dependency doesn't block ordering the scope itself — it surfaces later as a
103
+ // deps_unmet halt, which is the honest place for it. Ties break on the same
104
+ // keys the claimable feed sorts by, so a plan reads in the order the board
105
+ // would have offered the work.
106
+ function topoOrder(tasks) {
107
+ const inScope = new Set(tasks.map((t) => Number(t.id)));
108
+ const byId = new Map(tasks.map((t) => [Number(t.id), t]));
109
+ const remaining = new Set(inScope);
110
+ const order = [];
111
+
112
+ const rank = (id) => {
113
+ const t = byId.get(id);
114
+ return [
115
+ t.priority == null ? 99 : Number(t.priority),
116
+ Number(t.est_minutes_calibrated ?? t.est_minutes ?? 0) || 0,
117
+ Number(t.id),
118
+ ];
119
+ };
120
+ const cmp = (a, b) => {
121
+ const [pa, ma, ia] = rank(a);
122
+ const [pb, mb, ib] = rank(b);
123
+ return pa - pb || ma - mb || ia - ib;
124
+ };
125
+
126
+ let progressed = true;
127
+ while (remaining.size && progressed) {
128
+ progressed = false;
129
+ const free = Array.from(remaining).filter((id) => {
130
+ const deps = edgeIds(byId.get(id).dependencies).filter((d) => remaining.has(d));
131
+ return deps.length === 0;
132
+ });
133
+ if (free.length === 0) break;
134
+ free.sort(cmp);
135
+ for (const id of free) {
136
+ order.push(byId.get(id));
137
+ remaining.delete(id);
138
+ progressed = true;
139
+ }
140
+ }
141
+
142
+ return { order, cycle: Array.from(remaining).sort((a, b) => a - b) };
143
+ }
144
+
145
+ // The chain below a head: the head plus every task transitively reachable
146
+ // through dependents[]. Terminal tasks are walked THROUGH (a shipped task's
147
+ // successors are exactly what we want) but not included in the plan.
148
+ function chainFrom(headId, byId, { limit = DEFAULT_LIMIT } = {}) {
149
+ const seen = new Set();
150
+ const picked = [];
151
+ const queue = [Number(headId)];
152
+ while (queue.length) {
153
+ const id = queue.shift();
154
+ if (seen.has(id)) continue;
155
+ seen.add(id);
156
+ const t = byId.get(id);
157
+ if (!t) continue;
158
+ if (!TERMINAL.has(t.status)) picked.push(t);
159
+ for (const d of edgeIds(t.dependents)) queue.push(d);
160
+ if (picked.length >= limit) break;
161
+ }
162
+ return picked;
163
+ }
164
+
165
+ // The halt matrix. Facts only — every entry is something the runner can read
166
+ // off the task row, never a judgement about the work itself.
167
+ function haltReasons(task, { rank, claimableIds = null } = {}) {
168
+ const reasons = [];
169
+ if (!task) return [{ code: 'not_found', detail: 'no such task' }];
170
+
171
+ if (task.needs_migration) {
172
+ reasons.push({ code: 'needs_migration', detail: HALT_REASONS.needs_migration });
173
+ }
174
+ if (task.security_sensitive) {
175
+ reasons.push({ code: 'security_sensitive', detail: HALT_REASONS.security_sensitive });
176
+ }
177
+
178
+ const hits = matchProtected(task.touches || []);
179
+ if (hits.length) {
180
+ reasons.push({
181
+ code: 'protected_path',
182
+ detail: `${HALT_REASONS.protected_path} (${hits.map((h) => h.glob).join(', ')})`,
183
+ });
184
+ }
185
+
186
+ const floor = RANK_TIER[task.requires_rank] ?? 0;
187
+ const mine = RANK_TIER[rank] ?? 0;
188
+ if (floor > mine) {
189
+ reasons.push({
190
+ code: 'rank_floor',
191
+ detail: `needs rank '${task.requires_rank}', you are '${rank || 'unknown'}'`,
192
+ });
193
+ }
194
+
195
+ if (JUDGEMENT_KINDS.has(task.kind)) {
196
+ reasons.push({ code: 'judgement_kind', detail: `kind '${task.kind}' ${HALT_REASONS.judgement_kind}` });
197
+ }
198
+
199
+ const spec = String(task.description || '').trim();
200
+ if (spec.length < MIN_SPEC_CHARS) {
201
+ reasons.push({
202
+ code: 'thin_spec',
203
+ detail: spec ? `description is ${spec.length} chars — too thin to verify against` : 'no description',
204
+ });
205
+ }
206
+
207
+ if (IN_FLIGHT.has(task.status)) {
208
+ reasons.push({ code: 'in_flight', detail: `status is '${task.status}'` });
209
+ } else if (task.status !== 'ready') {
210
+ reasons.push({ code: 'deps_unmet', detail: `status is '${task.status}', not 'ready'` });
211
+ } else if (claimableIds && !claimableIds.has(Number(task.id))) {
212
+ // 'ready' but absent from the server's own claimable feed means an open
213
+ // blocker, a touches collision with a live claim, or a rank filter. Rather
214
+ // than re-deriving that query client-side (and drifting from it), defer.
215
+ reasons.push({ code: 'not_claimable', detail: HALT_REASONS.not_claimable });
216
+ }
217
+
218
+ return reasons;
219
+ }
220
+
221
+ function summarize(tasks) {
222
+ return tasks.reduce(
223
+ (acc, t) => ({
224
+ count: acc.count + 1,
225
+ credits: acc.credits + (Number(t.credits_reward) || 0),
226
+ minutes: acc.minutes + (Number(t.est_minutes_calibrated ?? t.est_minutes) || 0),
227
+ }),
228
+ { count: 0, credits: 0, minutes: 0 },
229
+ );
230
+ }
231
+
232
+ // -------------------------------------------------------------------------
233
+ // Fetching
234
+ // -------------------------------------------------------------------------
235
+
236
+ async function fetchTask(api, id) {
237
+ const r = await api.tasks.getTasksId({ id });
238
+ if (!r.ok) return null;
239
+ return r.data.task || r.data;
240
+ }
241
+
242
+ // Breadth-first over dependents[], fetching each task's detail once. The list
243
+ // endpoint omits the edge arrays, so the detail route is the only source.
244
+ async function fetchChain(api, headId, limit) {
245
+ const byId = new Map();
246
+ const queue = [Number(headId)];
247
+ const seen = new Set();
248
+ while (queue.length && byId.size < limit * 4) {
249
+ const wave = queue.splice(0, 12).filter((id) => !seen.has(id));
250
+ wave.forEach((id) => seen.add(id));
251
+ if (!wave.length) continue;
252
+ const rows = await Promise.all(wave.map((id) => fetchTask(api, id)));
253
+ for (const t of rows) {
254
+ if (!t) continue;
255
+ byId.set(Number(t.id), t);
256
+ for (const d of edgeIds(t.dependents)) if (!seen.has(d)) queue.push(d);
257
+ }
258
+ }
259
+ return byId;
260
+ }
261
+
262
+ async function fetchGoalTasks(api, goalId, limit) {
263
+ const r = await api.tasks.getTasks({ query: { limit: 1000 } });
264
+ const rows = (r.ok ? rowsOf(r.data) : null) || [];
265
+ const mine = rows
266
+ .filter((t) => Number(t.goal_id) === Number(goalId) && !TERMINAL.has(t.status))
267
+ .slice(0, limit);
268
+ const byId = new Map();
269
+ for (let i = 0; i < mine.length; i += 12) {
270
+ const rowsD = await Promise.all(mine.slice(i, i + 12).map((t) => fetchTask(api, t.id)));
271
+ rowsD.forEach((t) => t && byId.set(Number(t.id), t));
272
+ }
273
+ return byId;
274
+ }
275
+
276
+ // GET /tasks/claimable returns its rows under `claimable`; GET /tasks under
277
+ // `tasks`. Returning null (not an empty Set) on an unreadable response matters:
278
+ // haltReasons treats null as "couldn't check" and stays quiet, where an empty
279
+ // Set would mark every task not_claimable and halt the runner on its first step.
280
+ function rowsOf(payload) {
281
+ if (Array.isArray(payload)) return payload;
282
+ if (!payload || typeof payload !== 'object') return null;
283
+ for (const key of ['claimable', 'tasks', 'rows', 'items']) {
284
+ if (Array.isArray(payload[key])) return payload[key];
285
+ }
286
+ return null;
287
+ }
288
+
289
+ async function fetchClaimableIds(api) {
290
+ const r = await api.tasks.getTasksClaimable({ query: { limit: 500 } });
291
+ if (!r.ok) return null;
292
+ const rows = rowsOf(r.data);
293
+ return rows ? new Set(rows.map((t) => Number(t.id))) : null;
294
+ }
295
+
296
+ // -------------------------------------------------------------------------
297
+ // Output
298
+ // -------------------------------------------------------------------------
299
+
300
+ function taskLine(t, i) {
301
+ const est = t.est_minutes_calibrated ?? t.est_minutes;
302
+ return ` ${String(i + 1).padStart(2)}. task ${t.id} · ${t.status} · ${t.credits_reward ?? 0}c · ${est ? fmtMinutes(est) : '—'}\n ${t.title}`;
303
+ }
304
+
305
+ function printPlan({ plan, cycle, scope, base }) {
306
+ if (!plan.length) {
307
+ console.log(`No open tasks in ${scope}. ${HALT_REASONS.chain_complete}.`);
308
+ return;
309
+ }
310
+ const tot = summarize(plan);
311
+ console.log(`Chain for ${scope} — ${tot.count} open task(s), ${tot.credits}c, ${fmtMinutes(tot.minutes)}\n`);
312
+ plan.forEach((t, i) => console.log(taskLine(t, i)));
313
+ if (cycle.length) {
314
+ console.log(`\n⚠ ${cycle.length} task(s) could not be ordered — dependency cycle: ${cycle.join(', ')}`);
315
+ }
316
+ console.log(`\nHall: ${base}#/task/${plan[0].id}`);
317
+ console.log(`Next: node scripts/gds/sequence.js --from ${plan[0].id} --next`);
318
+ }
319
+
320
+ function printHalt(task, reasons) {
321
+ console.log(`HALT — task ${task.id}: ${task.title}\n`);
322
+ for (const r of reasons) console.log(` • [${r.code}] ${r.detail}`);
323
+ console.log('\nThis one needs a human. Everything already shipped stays shipped.');
324
+ }
325
+
326
+ // -------------------------------------------------------------------------
327
+ // Main
328
+ // -------------------------------------------------------------------------
329
+
330
+ async function main() {
331
+ const opts = readArgv(process.argv.slice(2));
332
+ if (!opts.from && !opts.goal && !opts.check) {
333
+ console.error('usage: sequence.js --from <task-id> | --goal <goal-id> | --check <task-id> [--next] [--json]');
334
+ process.exit(2);
335
+ }
336
+
337
+ await requireSession();
338
+ const api = await cliClient();
339
+ const meR = await api.me.getMe();
340
+ const rank = meR.ok ? meR.data.builder?.rank : null;
341
+ const base = hallBase();
342
+
343
+ // --check: one task, halt matrix, non-zero exit if anything fires.
344
+ if (opts.check) {
345
+ const t = await fetchTask(api, opts.check);
346
+ const claimableIds = await fetchClaimableIds(api);
347
+ const reasons = haltReasons(t, { rank, claimableIds });
348
+ if (opts.json) {
349
+ console.log(JSON.stringify({ task_id: opts.check, ok: reasons.length === 0, reasons }, null, 2));
350
+ } else if (!reasons.length) {
351
+ console.log(`task ${opts.check} is clear to run.`);
352
+ } else {
353
+ printHalt(t || { id: opts.check, title: '(not found)' }, reasons);
354
+ }
355
+ process.exit(reasons.length ? 1 : 0);
356
+ }
357
+
358
+ const byId = opts.goal
359
+ ? await fetchGoalTasks(api, opts.goal, opts.limit)
360
+ : await fetchChain(api, opts.from, opts.limit);
361
+
362
+ const scope = opts.goal ? `goal ${opts.goal}` : `task ${opts.from}`;
363
+ const candidates = opts.goal
364
+ ? Array.from(byId.values()).filter((t) => !TERMINAL.has(t.status))
365
+ : chainFrom(opts.from, byId, { limit: opts.limit });
366
+
367
+ const { order, cycle } = topoOrder(candidates);
368
+ const plan = order.slice(0, opts.limit);
369
+
370
+ // --next: the first task in the plan that clears the halt matrix.
371
+ if (opts.next) {
372
+ const claimableIds = await fetchClaimableIds(api);
373
+ const head = plan[0];
374
+ if (!head) {
375
+ const payload = { next: null, stop: 'chain_complete', detail: HALT_REASONS.chain_complete };
376
+ console.log(opts.json ? JSON.stringify(payload, null, 2) : `${HALT_REASONS.chain_complete} in ${scope}.`);
377
+ process.exit(0);
378
+ }
379
+ const reasons = haltReasons(head, { rank, claimableIds });
380
+ if (reasons.length) {
381
+ const payload = { next: null, stop: reasons[0].code, reasons, task: { id: head.id, title: head.title } };
382
+ if (opts.json) console.log(JSON.stringify(payload, null, 2));
383
+ else printHalt(head, reasons);
384
+ process.exit(1);
385
+ }
386
+ const remaining = summarize(plan);
387
+ if (opts.json) {
388
+ console.log(JSON.stringify({ next: head, remaining, scope }, null, 2));
389
+ } else {
390
+ console.log(`Next in ${scope}: task ${head.id} — ${head.title}`);
391
+ console.log(` ${head.credits_reward ?? 0}c · ${fmtMinutes(head.est_minutes_calibrated ?? head.est_minutes ?? 0)} · ${remaining.count - 1} more after this`);
392
+ console.log(` node scripts/gds/claim.js ${head.id}`);
393
+ }
394
+ process.exit(0);
395
+ }
396
+
397
+ if (opts.json) {
398
+ console.log(JSON.stringify({ scope, plan, cycle, totals: summarize(plan) }, null, 2));
399
+ return;
400
+ }
401
+ printPlan({ plan, cycle, scope, base });
402
+ }
403
+
404
+ if (require.main === module) {
405
+ main().catch((err) => {
406
+ console.error('fatal:', err.message || err);
407
+ process.exit(1);
408
+ });
409
+ }
410
+
411
+ module.exports = {
412
+ readArgv,
413
+ edgeIds,
414
+ rowsOf,
415
+ topoOrder,
416
+ chainFrom,
417
+ haltReasons,
418
+ summarize,
419
+ HALT_REASONS,
420
+ RANK_TIER,
421
+ MIN_SPEC_CHARS,
422
+ JUDGEMENT_KINDS,
423
+ TERMINAL,
424
+ IN_FLIGHT,
425
+ };
@@ -42,7 +42,16 @@ function encodeCwd(cwd) {
42
42
  // grader transcript's first user message is the worker system prompt — match it
43
43
  // to exclude it from the newest-file fallback. (The SessionEnd hook path is
44
44
  // unaffected: it passes an explicit transcript_path and never calls this.)
45
- const GRADER_WORKER_SIGNATURE = /You are the \*\*(Quality|Hacker|Narc|Efficiency)/;
45
+ // Match the SHAPE of the worker preamble, not a closed list of persona names: a
46
+ // new worker added to modules/grading/grader-workers/ would otherwise slip past
47
+ // this and be treated as a builder session — which now means real money, since
48
+ // backfill-sessions.js uses this to decide what NOT to pay for. Every worker
49
+ // opens `You are the **<Persona>** … adversarial subagent` (narc/hacker/efficiency
50
+ // say "a specialist adversarial subagent", quality says "specialist — an
51
+ // adversarial subagent"). Still anchored on that preamble rather than the bare
52
+ // phrase, so a real session that merely DISCUSSES adversarial subagents in its
53
+ // first message is not misread as one.
54
+ const GRADER_WORKER_SIGNATURE = /You are the \*\*[^*\n]{1,40}\*\*[^\n]{0,60}?adversarial subagent/;
46
55
 
47
56
  function firstUserText(file) {
48
57
  // Scan only until the first user message — avoids fully parsing a large
@@ -19,7 +19,18 @@
19
19
  // home directory) and prints the builder profile.
20
20
 
21
21
  const readline = require('readline');
22
- const { cliClient, saveSession, SESSION_PATH, loadSession, printOnboardingNudge, API_BASE } = require('./cli-lib');
22
+ const { cliClient, saveSession, SESSION_PATH, loadSession, printOnboardingNudge, API_BASE, hallBase } = require('./cli-lib');
23
+
24
+ // The instance's status dashboard, from branding — never a hardcoded host, so a
25
+ // non-OTB instance points its builders at ITS OWN dashboard (task 1002428).
26
+ // Falls back to the resolved API base when branding is unreadable.
27
+ function statusOrigin() {
28
+ try {
29
+ const d = require('../../src/branding').branding().domains || {};
30
+ if (d.statusOrigin) return d.statusOrigin;
31
+ } catch (_) { /* fall through */ }
32
+ return API_BASE;
33
+ }
23
34
  const onboardingConfig = require('./onboarding-config');
24
35
 
25
36
  const DRY_RUN = process.argv.includes('--dry-run');
@@ -70,9 +81,9 @@ async function promptGeminiKey() {
70
81
  console.log('Google AI Studio key for the pixel-art pipeline (per-builder, optional).');
71
82
  console.log(' Get one at https://aistudio.google.com/app/apikey');
72
83
  console.log(' (Or skip this and add it later with browser clicks: hall Settings →');
73
- console.log(' https://amazonprimea.com/builders/settings#art-key — stored encrypted, ADR 0073.)');
84
+ console.log(` ${hallBase()}/settings#art-key — stored encrypted, ADR 0073.)`);
74
85
  console.log(' Each builder pays their own Gemini bill so spend is attributed to them');
75
- console.log(' on https://status.amazonprimea.com.');
86
+ console.log(` on ${statusOrigin()}.`);
76
87
  let prompt;
77
88
  if (existing && typeof existing === 'string' && existing.length >= 4) {
78
89
  const last4 = existing.slice(-4);
@@ -265,6 +276,21 @@ async function promptOnboardingConsent() {
265
276
  (async () => {
266
277
  const forceFlag = process.argv.includes('--force');
267
278
 
279
+ // Environment preflight (task 1002401, onboarding-audit area 4). Before auth,
280
+ // surface the three things that used to bite a new builder one reactive crash
281
+ // at a time — Node, installed deps, a launchable `claude` CLI — each with a
282
+ // copy-paste fix, instead of `node: command not found` / `Cannot find module
283
+ // 'pg'` / SUBAGENT_SPAWN_FAILED turning up deep in the session. Read-only and
284
+ // NON-blocking: a fail is actionable, not fatal to sign-in, so it also runs in
285
+ // --dry-run. Shares one source of truth with `bongos doctor` (preflight.js).
286
+ try {
287
+ const { preflightChecks, formatPreflight } = require('./preflight');
288
+ console.log(formatPreflight(preflightChecks(), { color: process.stdout.isTTY, heading: true }));
289
+ console.log('');
290
+ } catch (err) {
291
+ console.error(`(environment preflight skipped: ${err.message})`);
292
+ }
293
+
268
294
  // --dry-run: describe every side effect the real flow would perform, without
269
295
  // doing any of them. No API calls, no file writes, no TTY prompts. Suitable
270
296
  // for a newcomer to read before running for real. Intentionally calls
@@ -452,7 +478,7 @@ async function promptOnboardingConsent() {
452
478
  if (poll.data && poll.data.state === 'not_approved') {
453
479
  console.error('');
454
480
  console.error('This GitHub account has not been approved yet.');
455
- console.error('Off the Boats admits new builders by invitation: an Archon must');
481
+ console.error(`${require('./cli-lib').productName()} admits new builders by invitation: an Archon must`);
456
482
  console.error('approve your access request before you can enroll.');
457
483
  console.error(' → Ask an Archon to invite you / approve your access request,');
458
484
  console.error(' then re-run /builder-setup (or: node scripts/gds/setup.js).');
@@ -526,7 +552,7 @@ async function promptOnboardingConsent() {
526
552
  printOnboardingNudge(meRes.data);
527
553
  console.log('');
528
554
  console.log('Newcomer path:');
529
- console.log(' 1. Choose thy craft: open https://amazonprimea.com/builders (creative / engineering)');
555
+ console.log(` 1. Choose thy craft: open ${hallBase()} (creative / engineering)`);
530
556
  console.log(' 2. Survey work: /builder-start');
531
557
  console.log(' 3. Claim + ship: /builder-claim N → /builder-ship');
532
558
  console.log(' 4. Ship three works and you rise to Thetes — the whole queue opens.');