@bongos/core 1.18.3 → 1.18.19

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 (471) hide show
  1. package/.bongos-core.json +1576 -286
  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/migrate.sh +99 -14
  347. package/scripts/status-mirror-sync.js +114 -84
  348. package/src/bongos/api-errors.js +1 -0
  349. package/src/bongos/auth.js +447 -102
  350. package/src/bongos/db-kernel.js +27 -0
  351. package/src/bongos/db.js +39 -0
  352. package/src/bongos/module-scope-map.js +27 -1
  353. package/src/bongos/permission-path-check.js +136 -102
  354. package/src/bongos/repo-info.js +47 -11
  355. package/src/bongos/route-rank-check.js +429 -46
  356. package/src/bongos/routes/_helpers.js +12 -1
  357. package/src/bongos/routes/auth.js +219 -2
  358. package/src/bongos/routes/backup.js +58 -2
  359. package/src/bongos/routes/me.js +35 -1
  360. package/src/bongos/serve-internal.js +84 -15
  361. package/src/branding.js +32 -0
  362. package/src/instance-config.js +30 -0
  363. package/src/module-api.js +124 -1
  364. package/src/platform-server.js +86 -9
  365. package/tests/abandon_task.mjs +59 -3
  366. package/tests/api_cli_flags.mjs +53 -0
  367. package/tests/auth_page_gate.mjs +13 -0
  368. package/tests/backfill_sessions.mjs +127 -1
  369. package/tests/backup_cli.mjs +54 -0
  370. package/tests/bongos_box_connect.mjs +77 -0
  371. package/tests/box_access.mjs +130 -1
  372. package/tests/box_credential.mjs +125 -0
  373. package/tests/box_host_keys.mjs +24 -0
  374. package/tests/box_infra_resolve.mjs +149 -0
  375. package/tests/box_scope_session.mjs +6 -2
  376. package/tests/box_sync.mjs +55 -0
  377. package/tests/box_task_scope.mjs +99 -0
  378. package/tests/branding.mjs +12 -0
  379. package/tests/cf_ruleset_preflight.mjs +142 -0
  380. package/tests/cf_tunnel.mjs +37 -0
  381. package/tests/claim_gate_rebase.mjs +98 -0
  382. package/tests/clawback_confirmed_backlog.mjs +103 -0
  383. package/tests/cli_lib_formatters.mjs +20 -0
  384. package/tests/confirm_attribution.mjs +30 -46
  385. package/tests/credit_grant.mjs +198 -10
  386. package/tests/criterion_authoring_gate.mjs +272 -0
  387. package/tests/criterion_progress.mjs +33 -1
  388. package/tests/dev_lib.mjs +253 -0
  389. package/tests/devbox_artifact_gate.mjs +75 -0
  390. package/tests/devbox_dmg_backdrop.mjs +137 -0
  391. package/tests/devbox_icons.mjs +304 -0
  392. package/tests/devbox_installer_brand.mjs +232 -0
  393. package/tests/devbox_key_register.mjs +234 -0
  394. package/tests/docs_openapi_route.mjs +110 -0
  395. package/tests/doctor_hooks.mjs +141 -0
  396. package/tests/extract_json.mjs +140 -0
  397. package/tests/fitness.mjs +89 -0
  398. package/tests/gen_freshness.mjs +96 -0
  399. package/tests/goal_advisory.mjs +251 -0
  400. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  401. package/tests/goal_routes.mjs +59 -4
  402. package/tests/goal_tier.mjs +12 -3
  403. package/tests/governance_catalog.mjs +129 -0
  404. package/tests/governance_claim_eligibility.mjs +262 -0
  405. package/tests/governance_config.mjs +187 -0
  406. package/tests/governance_ownership_scope.mjs +410 -0
  407. package/tests/governance_page_gates.mjs +324 -0
  408. package/tests/governance_parity.mjs +336 -0
  409. package/tests/governance_principals.mjs +232 -0
  410. package/tests/governance_protected_surfaces.mjs +208 -0
  411. package/tests/governance_require_permission.mjs +223 -0
  412. package/tests/governance_resolver.mjs +84 -0
  413. package/tests/governance_role_sync.mjs +231 -0
  414. package/tests/governance_seed.mjs +127 -0
  415. package/tests/governance_session_scope.mjs +434 -0
  416. package/tests/governance_vanilla_boot.mjs +203 -0
  417. package/tests/grade_attribution.mjs +7 -7
  418. package/tests/hall_audit.mjs +223 -0
  419. package/tests/hall_nav.mjs +191 -0
  420. package/tests/hall_palette.mjs +206 -0
  421. package/tests/hall_signout.mjs +124 -0
  422. package/tests/hook_user_agents.mjs +10 -0
  423. package/tests/idea_triage.mjs +162 -0
  424. package/tests/idp_device_exchange.mjs +93 -0
  425. package/tests/init.mjs +51 -0
  426. package/tests/instance_config.mjs +65 -0
  427. package/tests/instance_mismatch_guard.mjs +23 -6
  428. package/tests/instance_release_config.mjs +104 -0
  429. package/tests/linkify_refs.mjs +15 -2
  430. package/tests/migrate_root_resolution.mjs +124 -1
  431. package/tests/module_contributions.mjs +55 -0
  432. package/tests/module_loader.mjs +10 -5
  433. package/tests/module_scope_active_claims.mjs +94 -0
  434. package/tests/needs_rebase.mjs +92 -0
  435. package/tests/one_active_claim_per_session.mjs +76 -0
  436. package/tests/override_attribution.mjs +10 -8
  437. package/tests/override_request_ownership.mjs +8 -4
  438. package/tests/parse_cookie_decode.mjs +42 -0
  439. package/tests/pay_on_land.mjs +129 -0
  440. package/tests/platform_boot.mjs +100 -1
  441. package/tests/post_login_landing.mjs +42 -0
  442. package/tests/preflight_checks.mjs +72 -0
  443. package/tests/provision.mjs +132 -7
  444. package/tests/publish_branch_route.mjs +53 -0
  445. package/tests/publish_reconciler.mjs +187 -2
  446. package/tests/repo_info_binding.mjs +248 -0
  447. package/tests/reward_gate.mjs +11 -2
  448. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  449. package/tests/reward_mode_surfacing.mjs +168 -0
  450. package/tests/route_permission_check.mjs +301 -0
  451. package/tests/route_rank_check.mjs +254 -12
  452. package/tests/route_validation.mjs +30 -0
  453. package/tests/sequence.mjs +267 -0
  454. package/tests/session_digest.mjs +44 -0
  455. package/tests/session_index.mjs +18 -6
  456. package/tests/session_records.mjs +39 -1
  457. package/tests/session_reward_on_land.mjs +381 -0
  458. package/tests/session_reward_reconcile.mjs +229 -0
  459. package/tests/ship_api_client_pathspec.mjs +100 -0
  460. package/tests/ship_base_flag.mjs +93 -0
  461. package/tests/ship_ci_deploy.mjs +80 -3
  462. package/tests/ship_upstream_deletion.mjs +57 -0
  463. package/tests/smoke_target.mjs +233 -0
  464. package/tests/start_env_guard.mjs +87 -0
  465. package/tests/start_rebase_warning.mjs +103 -0
  466. package/tests/status_mirror_sync.mjs +122 -0
  467. package/tests/stop_refcheck.mjs +19 -1
  468. package/tests/task_goal_link.mjs +15 -6
  469. package/tests/upgrade.mjs +139 -0
  470. package/tests/web_admission_status.mjs +162 -0
  471. package/tests/write_validation_gaps.mjs +110 -0
@@ -3,17 +3,127 @@
3
3
  // Works on Windows (Git Bash / PowerShell / cmd), macOS, and Linux.
4
4
  //
5
5
  // Usage:
6
- // node scripts/gds/smoke-gds.js # against production
6
+ // node scripts/gds/smoke-gds.js # against THIS instance
7
7
  // GDS_API_BASE=http://localhost:3000 node scripts/gds/smoke-gds.js
8
8
  //
9
- // Exit 0 = all checks passed. Exit 1 = one or more failures.
9
+ // Exit 0 = all checks passed (or the local-target skip). Exit 1 = one or more
10
+ // failures, or no resolvable target (see resolveSmokeTarget).
10
11
 
11
12
  const https = require('https');
12
13
  const http = require('http');
13
14
  const net = require('net');
14
15
  const { URL } = require('url');
16
+ const ic = require('../../src/instance-config');
17
+ const { loadSessionSync } = require('./cli-lib');
15
18
 
16
- const BASE = process.env.GDS_API_BASE || process.env.PMS_API_BASE || 'https://amazonprimea.com';
19
+ function safeBranding() {
20
+ try { return require('../../src/branding').branding(); } catch (_) { return {}; }
21
+ }
22
+
23
+ // readSessionApiBase — the signed-in session's persisted api_base (the instance
24
+ // this CLI is actually pointed at), or null when not signed in / unreadable. The
25
+ // session's OWN value only — deliberately NOT cli-lib's readSessionToken().apiBase,
26
+ // which already folds in the env override + branding default; resolveSmokeTarget
27
+ // orders that precedence itself so it stays pure over its injected inputs. Twin of
28
+ // ship.js's readSessionApiBase.
29
+ function readSessionApiBase() {
30
+ try {
31
+ const s = loadSessionSync();
32
+ return (s && typeof s.api_base === 'string' && s.api_base.trim()) ? s.api_base.trim() : null;
33
+ } catch (_) {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ const normalizeBase = (s) => (typeof s === 'string' && s.trim() ? s.trim().replace(/\/+$/, '') : null);
39
+
40
+ // Which <PREFIX>_API_BASE supplied the override — the same prefix list, in the
41
+ // same order, that ic.resolveEnv() itself walks. A lookup of the winning NAME,
42
+ // not a second copy of the precedence.
43
+ function matchedApiBaseVar(env) {
44
+ for (const p of [ic.envPrefix(), ...ic.LEGACY_ENV_PREFIXES]) {
45
+ const k = `${p}_API_BASE`;
46
+ if (env[k] !== undefined && env[k] !== '') return k;
47
+ }
48
+ return null;
49
+ }
50
+
51
+ // resolveSmokeTarget — WHICH instance these checks are run against. Precedence
52
+ // mirrors ship.js's resolveInstanceBase(): <PREFIX>_API_BASE env (incl. legacy
53
+ // GDS_/OTB_/PMS_) → the signed-in session's api_base → branding publicOrigin →
54
+ // null.
55
+ //
56
+ // There is deliberately NO hardcoded host. This file baked in
57
+ // `https://amazonprimea.com` as its fallback, so a builder signed into any OTHER
58
+ // instance who shipped without an env override ran the ship's verification smoke
59
+ // against the founder's box — which PASSED, because that box is healthy. The ship
60
+ // then reported "smoke=clean" on the strength of testing a system it never
61
+ // touched (task 1002418; the smoke sibling of resolveInstanceBase's task 1002232).
62
+ //
63
+ // FAILS CLOSED: a null base is an error, not a fallback. Verifying nothing and
64
+ // saying so beats verifying the wrong instance and calling it a pass.
65
+ //
66
+ // Pure over its injected inputs (env / sessionApiBase / branding) so the
67
+ // precedence is unit-tested without fs/env/network; the defaults read the real
68
+ // session file + brand pack.
69
+ function resolveSmokeTarget({
70
+ env = process.env,
71
+ sessionApiBase = readSessionApiBase(),
72
+ branding = safeBranding(),
73
+ } = {}) {
74
+ const envBase = normalizeBase(ic.resolveEnv('API_BASE', { env }));
75
+ const session = normalizeBase(sessionApiBase);
76
+ const brandOrigin = normalizeBase(branding && branding.domains && branding.domains.publicOrigin);
77
+
78
+ const base = envBase || session || brandOrigin || null;
79
+ let source = null;
80
+ if (envBase) source = 'env';
81
+ else if (session) source = 'session';
82
+ else if (brandOrigin) source = 'branding';
83
+
84
+ const same = (a, b) => String(a).toLowerCase() === String(b).toLowerCase();
85
+ return {
86
+ base,
87
+ source,
88
+ envVar: envBase ? matchedApiBaseVar(env) : null,
89
+ sessionApiBase: session,
90
+ mismatch: Boolean(base && session && !same(base, session)),
91
+ };
92
+ }
93
+
94
+ const SOURCE_LABELS = {
95
+ session: 'signed-in session api_base',
96
+ branding: 'branding domains.publicOrigin',
97
+ };
98
+
99
+ // The env leg names the variable that actually matched, so "clear the override"
100
+ // is actionable — a builder with a stale OTB_API_BASE shouldn't have to guess
101
+ // which of four names to unset (and shouldn't be shown a literal "<PREFIX>").
102
+ const sourceLabel = ({ source, envVar }) =>
103
+ (source === 'env' ? `${envVar || 'API_BASE'} env override` : SOURCE_LABELS[source] || source);
104
+
105
+ // formatTargetReport — the lines main() prints BEFORE any check runs, so every run
106
+ // states what it is about to verify. A target that disagrees with the signed-in
107
+ // session gets a loud banner: silence about the mismatch is what made the old
108
+ // hardcoded fallback dangerous. Returns lines so the warning is unit-testable.
109
+ function formatTargetReport({ base, source, envVar, sessionApiBase, mismatch }) {
110
+ const label = sourceLabel({ source, envVar });
111
+ const lines = [`=== GDS smoke target: ${base} (source: ${label}) ===`];
112
+ if (!mismatch) return lines;
113
+ const bar = '!'.repeat(72);
114
+ lines.push(
115
+ bar,
116
+ '!! WARNING: the smoke target is NOT the instance you are signed into.',
117
+ `!! smoke target : ${base} (${label})`,
118
+ `!! your session : ${sessionApiBase}`,
119
+ '!! These checks verify the smoke target ONLY. A ship that reports',
120
+ '!! "smoke=clean" off this run is making a claim about the target above,',
121
+ `!! not about your own instance. Unset ${envVar || 'the API_BASE env override'} to verify`,
122
+ '!! the instance you are signed into.',
123
+ bar,
124
+ );
125
+ return lines;
126
+ }
17
127
 
18
128
  // Laptop-ergonomics preflight (#958 / V4.R20). The smoke checks need a running
19
129
  // GDS server. When BASE points at a LOCAL listener that isn't up, every check
@@ -40,7 +150,10 @@ function probeListener(host, port, timeoutMs = 1500) {
40
150
  });
41
151
  }
42
152
 
43
- const CHECKS = [
153
+ // Built per-run from the resolved base (was a module-level const off a hardcoded
154
+ // BASE): resolution now happens inside main(), so requiring this module for its
155
+ // helpers stays side-effect-free.
156
+ const buildChecks = (BASE) => [
44
157
  // Public read-only surface
45
158
  ['GET /api/gds/healthz', `${BASE}/api/gds/healthz`, 200],
46
159
  ['GET /api/gds/versions', `${BASE}/api/gds/versions`, 200],
@@ -100,7 +213,26 @@ function checkUrl(label, url, expectStatus, method = 'GET') {
100
213
  }
101
214
 
102
215
  async function main() {
103
- const parsed = new URL(BASE);
216
+ const target = resolveSmokeTarget();
217
+ if (!target.base) {
218
+ console.log('=== GDS smoke FAILED: no target ===');
219
+ console.log('Could not resolve which instance to verify: no <PREFIX>_API_BASE env var,');
220
+ console.log('no signed-in session (node scripts/gds/setup.js), and no readable branding');
221
+ console.log('domains.publicOrigin. Refusing to guess a host — a smoke run against the');
222
+ console.log('wrong instance passes and certifies nothing (task 1002418).');
223
+ process.exit(1);
224
+ }
225
+ const BASE = target.base;
226
+ for (const line of formatTargetReport(target)) console.log(line);
227
+
228
+ let parsed;
229
+ try {
230
+ parsed = new URL(BASE);
231
+ } catch (_) {
232
+ console.log(`=== GDS smoke FAILED: unusable target ===`);
233
+ console.log(`"${BASE}" is not an absolute URL — it needs a scheme (https://host).`);
234
+ process.exit(1);
235
+ }
104
236
  const host = parsed.hostname;
105
237
  const port = Number(parsed.port) || (parsed.protocol === 'https:' ? 443 : 80);
106
238
 
@@ -112,8 +244,7 @@ async function main() {
112
244
  process.exit(0);
113
245
  }
114
246
 
115
- console.log(`=== GDS smoke against ${BASE} ===`);
116
- const results = await Promise.all(CHECKS.map(([label, url, status, method]) => checkUrl(label, url, status, method)));
247
+ const results = await Promise.all(buildChecks(BASE).map(([label, url, status, method]) => checkUrl(label, url, status, method)));
117
248
  const passed = results.filter(Boolean).length;
118
249
  const failed = results.length - passed;
119
250
  console.log(`=== ${passed} passed, ${failed} failed ===`);
@@ -122,4 +253,4 @@ async function main() {
122
253
 
123
254
  if (require.main === module) main();
124
255
 
125
- module.exports = { isLocalTarget, probeListener };
256
+ module.exports = { isLocalTarget, probeListener, resolveSmokeTarget, formatTargetReport, buildChecks };
@@ -52,8 +52,9 @@
52
52
  //
53
53
  // Returns JSON if --json is passed (for the slash command to parse cleanly).
54
54
 
55
- const { cliClient, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge } = require('./cli-lib');
55
+ const { cliClient, requireSession, fmtUsd, printOnboardingNudge, printCostWarning, printNeedsNudge, hallBase, API_BASE, rewardPolicy, costPlusOnlyNote } = require('./cli-lib');
56
56
  const { contextPack, formatContextPackCompact } = require('./context-pack');
57
+ const { depsFinding } = require('./preflight');
57
58
 
58
59
  // task 1284: the card shows only the top N claimable rows. The table is the bulk
59
60
  // of the widget HTML the model relays verbatim, so fewer rows = fewer tokens +
@@ -200,15 +201,22 @@ function whatItIs(t) {
200
201
  // The claimable table — # / Version / Type / Reward / Est. / What it is.
201
202
  // Ids are emitted as hall links (renders as a clean #N) — a bare "#NNN" in a
202
203
  // relayed chat message autolinks to GitHub and 404s (CLAUDE.md ref rule).
203
- const HALL_TASK = 'https://amazonprimea.com/builders#/task/';
204
- function claimableTable(rows) {
204
+ const HALL_TASK = `${hallBase()}#/task/`;
205
+ function claimableTable(rows, rewardFromServer) {
206
+ // On a cost-plus-only instance the Reward column is an advertised estimate that
207
+ // is never paid — say so in the header so the column can't read as a promise,
208
+ // and footnote what IS paid below the table (task 1002469).
209
+ const perTask = rewardPolicy(rewardFromServer).perTaskCredit;
210
+ const rewardHead = perTask ? 'Reward' : 'Reward (est.)';
205
211
  const out = [
206
- '| # | Version | Type | Reward | Est. | What it is |',
212
+ `| # | Version | Type | ${rewardHead} | Est. | What it is |`,
207
213
  '|---|---|---|---|---|---|',
208
214
  ];
209
215
  for (const t of rows) {
210
216
  out.push(`| [#${t.id}](${HALL_TASK}${t.id}) | ${cell(t.version_id || '?')} | ${cell(disciplineLabel(t) || '—')} | ${t.credits_reward ?? 0}c | ${estimateShort(t)} | ${cell(whatItIs(t))} |`);
211
217
  }
218
+ const cpNote = costPlusOnlyNote(rewardFromServer);
219
+ if (cpNote) out.push('', `_${cpNote}_`);
212
220
  return out.join('\n');
213
221
  }
214
222
 
@@ -228,6 +236,49 @@ function miniBar(pct) {
228
236
  return `<span class="bs-bar"><span class="bs-barfill" style="width:${pct}%;"></span></span>`;
229
237
  }
230
238
 
239
+ // ---------- owed-rebase warning (ADR 0120 part 5) ----------
240
+ // The builder's un-landed confirmed tasks the publish-reconciler flagged
241
+ // needs_rebase (part 2). They GATE new claims (part 3: claimTask refuses with
242
+ // REBASE_REQUIRED) and their per-task reward now lands only on ship (part 1),
243
+ // so this is the single most urgent thing to show at session start — rendered
244
+ // prominently, before the claimable table, in both the markdown card and the
245
+ // widget. /me carries them as `needs_rebase_tasks` (each { id, title, … }).
246
+ // Refs use the `task N` form, NEVER a bare "#N" (a bare #N in relayed chat
247
+ // autolinks to GitHub issues and 404s — the CLAUDE.md linkify rule).
248
+
249
+ // Normalize the /me list into a small render model, or null when nothing is
250
+ // owed. Shared by the markdown + widget renderers and unit-tested directly
251
+ // (no live server needed).
252
+ function rebaseWarningModel(needsRebaseTasks) {
253
+ const owed = Array.isArray(needsRebaseTasks)
254
+ ? needsRebaseTasks.filter((t) => t && t.id != null)
255
+ : [];
256
+ if (owed.length === 0) return null;
257
+ const n = owed.length;
258
+ return {
259
+ count: n,
260
+ refs: owed.map((t) => `task ${t.id}`).join(', '),
261
+ noun: n === 1 ? 'task' : 'tasks',
262
+ pronoun: n === 1 ? 'it' : 'them',
263
+ };
264
+ }
265
+
266
+ // The markdown warning line (prominent, before the claimable table), or null
267
+ // when nothing is owed.
268
+ function rebaseWarningMarkdown(needsRebaseTasks) {
269
+ const w = rebaseWarningModel(needsRebaseTasks);
270
+ if (!w) return null;
271
+ return `⚠️ **Rebase owed — your queue is GATED.** ${w.count} ${w.noun} can't land and block new claims until you rebase + re-ship (or \`/builder-release\`) ${w.pronoun}: ${w.refs}.`;
272
+ }
273
+
274
+ // The widget warning block (Tabler alert icon, danger-tinted), or '' when none.
275
+ function rebaseWarningWidget(needsRebaseTasks) {
276
+ const w = rebaseWarningModel(needsRebaseTasks);
277
+ if (!w) return '';
278
+ return `<div class="bs-warn"><i class="ti ti-alert-triangle" aria-hidden="true"></i>`
279
+ + `<span><strong>Rebase owed — your queue is GATED.</strong> ${w.count} ${w.noun} can't land and block new claims until you rebase + re-ship (or /builder-release) ${w.pronoun}: ${hesc(w.refs)}.</span></div>`;
280
+ }
281
+
231
282
  // Run console.log-based helpers (printCostWarning/printOnboardingNudge) without
232
283
  // polluting stdout (which must be pure HTML in --widget mode); return the lines.
233
284
  function captureLog(fn) {
@@ -272,6 +323,8 @@ const WIDGET_STYLE = `<style>
272
323
  .bs-dot{color:var(--color-text-tertiary);}
273
324
  .bs-bar{display:inline-block;width:34px;height:5px;border-radius:3px;background:var(--color-border-tertiary);overflow:hidden;vertical-align:middle;}
274
325
  .bs-barfill{display:block;height:100%;background:#1D9E75;}
326
+ .bs-warn{display:flex;gap:8px;align-items:flex-start;font-size:13px;margin-top:8px;padding:8px 10px;border-radius:var(--border-radius-md);background:var(--color-background-danger);color:var(--color-text-danger);}
327
+ .bs-warn i{font-size:15px;flex:none;margin-top:1px;}
275
328
  .bs-line{font-size:13px;color:var(--color-text-secondary);margin-top:8px;}
276
329
  .bs-line i{font-size:14px;vertical-align:-2px;margin-right:4px;}
277
330
  .bs-holditem{margin-top:4px;padding-left:18px;}
@@ -291,8 +344,11 @@ const WIDGET_STYLE = `<style>
291
344
  .bs-foot{font-size:13px;color:var(--color-text-tertiary);margin-top:12px;}
292
345
  </style>`;
293
346
 
294
- function renderWidget({ builder, claims, claimable, total, streak, recommendations, header, routingByPreference, preferred, filters, readyToClose }) {
347
+ function renderWidget({ builder, claims, claimable, total, streak, recommendations, header, routingByPreference, preferred, filters, readyToClose, needsRebaseTasks }) {
295
348
  const b = builder || {};
349
+ // ADR 0120 part 5: the owed-rebase gate warning, rendered at the top of the
350
+ // card (before status/holdings) since it blocks new claims. '' when none owed.
351
+ const rebaseRow = rebaseWarningWidget(needsRebaseTasks);
296
352
  // Streak badge — warm amber, the one bold pop of colour + the motivational
297
353
  // stat. Shown only on an active (>0) streak; tooltip carries the record.
298
354
  // (Singular element — left inline; the savings live in the repeated grid.)
@@ -318,7 +374,7 @@ function renderWidget({ builder, claims, claimable, total, streak, recommendatio
318
374
 
319
375
  // holdings — each held task gets quick actions off it (ship / release /
320
376
  // open). Parallel sessions under one builder are normal, so this informs.
321
- const hallUrl = (id) => `https://amazonprimea.com/builders#/task/${encodeURIComponent(id)}`;
377
+ const hallUrl = (id) => `${hallBase()}#/task/${encodeURIComponent(id)}`;
322
378
  const dot = '<span class="bs-dot"> · </span>';
323
379
  const act = (label, msg) => `<a href="#" onclick="event.preventDefault();sendPrompt('${jsq(msg)}')" class="bs-link">${label} ↗</a>`;
324
380
  const actionsFor = (c) => `${act('Ship', 'Ship task ' + c.task_id)}${dot}${act('Release', 'Release task ' + c.task_id)}${dot}<a href="${hesc(hallUrl(c.task_id))}" class="bs-link">Open ↗</a>`;
@@ -395,6 +451,7 @@ function renderWidget({ builder, claims, claimable, total, streak, recommendatio
395
451
  <span class="bs-brand"><span class="bs-title"><i class="ti ti-player-play" aria-hidden="true"></i>builder-start</span>${streakBadge}</span>
396
452
  <span class="bs-meta">${hesc(b.github_login)} · ${hesc(rank)} · ${hesc(credits)} credits · ${hesc(disc)}</span>
397
453
  </div>
454
+ ${rebaseRow}
398
455
  ${statusRow}
399
456
  ${holdings}
400
457
  ${closeBanner}
@@ -439,7 +496,49 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
439
496
  return outside.slice(0, Math.max(0, limit));
440
497
  }
441
498
 
442
- (async () => {
499
+ // ---------- environment guard (task 1002403 / goal 1000052) ----------
500
+ // /builder-start is often the FIRST command run in a freshly-cloned instance —
501
+ // before the local server has ever been brought up. When the API can't be
502
+ // reached, guide the builder to the one-command bring-up instead of dying with a
503
+ // raw fetch stack trace (the old failure). The generated client REJECTS on a
504
+ // network error even under throwOnError:false (see clients/bongos-client), so the
505
+ // Promise.all in main() throws into the guard below. Wrong-instance (exit 3) and
506
+ // no-session (exit 2) are handled upstream in cliClient()/apiCall, and the
507
+ // builder-start skill routes those + the pre-install "bongos not found" case.
508
+ function isConnectionError(err) {
509
+ if (!err) return false;
510
+ const codes = ['ECONNREFUSED', 'ENOTFOUND', 'ECONNRESET', 'EAI_AGAIN', 'ETIMEDOUT', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET'];
511
+ const code = err.code || (err.cause && err.cause.code) || '';
512
+ if (codes.includes(code)) return true;
513
+ const msg = `${err.message || ''} ${(err.cause && err.cause.message) || ''}`;
514
+ return /fetch failed|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|network|socket hang up/i.test(msg);
515
+ }
516
+
517
+ // The guided "instance isn't running" card. Markdown (never leads with `<`), so
518
+ // the builder-start skill relays it verbatim even in --widget mode. Tailors the
519
+ // first step to whether deps are installed — a fresh clone needs `npm install`
520
+ // before the `bongos` CLI (which ships inside @bongos/core) even exists.
521
+ function instanceNotRunningCard({ origin, depsMissing } = {}) {
522
+ const out = [];
523
+ out.push(`**builder-start — this instance isn't reachable yet**`);
524
+ out.push('');
525
+ out.push(`I couldn't reach the Cloud Bongos instance${origin ? ` at \`${origin}\`` : ''}. On a freshly-cloned project that almost always just means it hasn't been brought up yet. To start it locally:`);
526
+ out.push('');
527
+ let n = 1;
528
+ if (depsMissing) {
529
+ out.push(`${n++}. \`npm install\` — installs the toolchain first (the \`bongos\` CLI ships inside \`@bongos/core\`, so nothing else runs until this does).`);
530
+ }
531
+ out.push(`${n++}. \`bongos dev\` — one command: starts Postgres, builds the tables, runs the server, and signs you in.`);
532
+ out.push(`${n++}. Re-run \`/builder-start\`.`);
533
+ out.push('');
534
+ out.push(`Meant to work on a *different* instance that's already running? Sign in to it with \`bongos login <url>\` first.`);
535
+ return out.join('\n');
536
+ }
537
+ function emitInstanceNotRunningCard(opts) {
538
+ console.log(instanceNotRunningCard(opts));
539
+ }
540
+
541
+ async function main() {
443
542
  await requireSession();
444
543
  const args = process.argv.slice(2);
445
544
  const json = args.includes('--json');
@@ -479,16 +578,32 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
479
578
  // endpoint fails, that widget degrades to "N/A" rather than killing
480
579
  // /builder-start.
481
580
  const api = await cliClient();
482
- const [me, r, closeRes, progressRes, costRes, recentRes, inboxRes, blockersRes] = await Promise.all([
483
- api.me.getMe(),
484
- api.tasks.getTasksClaimable({ query: claimableQuery }),
485
- api.versions.getVersionsCloseSuggestions(),
486
- api.public.getPublicProgress(),
487
- api.public.getPublicCostSummary(),
488
- api.public.getPublicRecentShipped(),
489
- api.inbox.getInbox({ query: { status: 'open' } }),
490
- api.blockers.getBlockers(),
491
- ]);
581
+ let me, r, closeRes, progressRes, costRes, recentRes, inboxRes, blockersRes;
582
+ try {
583
+ [me, r, closeRes, progressRes, costRes, recentRes, inboxRes, blockersRes] = await Promise.all([
584
+ api.me.getMe(),
585
+ api.tasks.getTasksClaimable({ query: claimableQuery }),
586
+ api.versions.getVersionsCloseSuggestions(),
587
+ api.public.getPublicProgress(),
588
+ api.public.getPublicCostSummary(),
589
+ api.public.getPublicRecentShipped(),
590
+ api.inbox.getInbox({ query: { status: 'open' } }),
591
+ api.blockers.getBlockers(),
592
+ ]);
593
+ } catch (err) {
594
+ // A fresh-clone instance that was never brought up refuses the connection.
595
+ // Human modes get a guided bring-up card + clean exit; machine modes (--json,
596
+ // --hook) keep the throw so the autonomous runner still sees the failure.
597
+ if (isConnectionError(err) && !json && !hookMode) {
598
+ let depsMissing = false;
599
+ try { depsMissing = depsFinding({ repoRoot: process.cwd() }).level !== 'ok'; } catch (_) { /* best-effort */ }
600
+ let origin = '';
601
+ try { origin = new URL(API_BASE).origin; } catch (_) { origin = API_BASE || ''; }
602
+ emitInstanceNotRunningCard({ origin, depsMissing });
603
+ process.exit(0);
604
+ }
605
+ throw err;
606
+ }
492
607
  // #766: the primer-read gate on the claimable endpoint was removed, so a
493
608
  // newcomer no longer gets a 403 stage_required here. A generic stage_required
494
609
  // (any future onboarding gate) still renders cleanly rather than crashing.
@@ -548,6 +663,13 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
548
663
  ? blockersRes.data.blockers.filter((b) => b.status === 'open' || !b.status).length
549
664
  : null;
550
665
 
666
+ // ADR 0120 part 5: the confirmed tasks this builder owes a rebase on (they
667
+ // GATE new claims; the reward lands only on ship). Shared by every card form;
668
+ // already present verbatim in --json via me.data.needs_rebase_tasks below.
669
+ const needsRebaseTasks = me.ok && Array.isArray(me.data?.needs_rebase_tasks)
670
+ ? me.data.needs_rebase_tasks
671
+ : [];
672
+
551
673
  if (json) {
552
674
  console.log(JSON.stringify(
553
675
  {
@@ -602,6 +724,7 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
602
724
  preferred,
603
725
  filters: { version, discipline, fit },
604
726
  readyToClose,
727
+ needsRebaseTasks,
605
728
  }) + '\n');
606
729
  return;
607
730
  }
@@ -638,6 +761,15 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
638
761
  }
639
762
  }
640
763
 
764
+ // ADR 0120 part 5: the owed-rebase gate warning — rendered directly under the
765
+ // identity line and before the claimable table, since it BLOCKS new claims
766
+ // until the builder lands (rebase + re-ship) or releases the flagged tasks.
767
+ const rebaseLine = rebaseWarningMarkdown(needsRebaseTasks);
768
+ if (rebaseLine) {
769
+ console.log('');
770
+ console.log(rebaseLine);
771
+ }
772
+
641
773
  // ambient status: one quiet line by default; the full widgets + context pack
642
774
  // under --full.
643
775
  if (full) {
@@ -708,7 +840,7 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
708
840
  console.log('');
709
841
  if (claimable.length) {
710
842
  const cardRows = claimable.slice(0, CARD_ROWS);
711
- console.log(claimableTable(cardRows));
843
+ console.log(claimableTable(cardRows, me.ok ? me.data?.reward : null));
712
844
  const moreN = filtered.length - cardRows.length;
713
845
  if (moreN > 0) {
714
846
  console.log('');
@@ -761,6 +893,7 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
761
893
  preferred,
762
894
  filters: { version, discipline, fit },
763
895
  readyToClose,
896
+ needsRebaseTasks,
764
897
  })
765
898
  : null;
766
899
  const markdown = captureLog(emitMarkdownCard).join('\n');
@@ -776,7 +909,23 @@ function rankCrossDisciplineRecommendations(claimable, preferredDisciplines, lim
776
909
  // body to stdout.
777
910
  emitCardDirective('markdown');
778
911
  emitMarkdownCard();
779
- })().catch((err) => {
780
- console.error('fatal:', err);
781
- process.exit(1);
782
- });
912
+ }
913
+
914
+ // Only auto-run when invoked directly (`node start.js …`); a `require()` (the
915
+ // unit test) imports the pure render helpers without executing the fetch flow —
916
+ // the claim.js precedent. The owed-rebase warning helpers (ADR 0120 part 5) are
917
+ // exported so they can be unit-tested without a live server.
918
+ if (require.main === module) {
919
+ main().catch((err) => {
920
+ console.error('fatal:', err);
921
+ process.exit(1);
922
+ });
923
+ }
924
+
925
+ module.exports = {
926
+ rebaseWarningModel,
927
+ rebaseWarningMarkdown,
928
+ rebaseWarningWidget,
929
+ isConnectionError,
930
+ instanceNotRunningCard,
931
+ };
@@ -59,7 +59,7 @@ async function fetchProgress(version) {
59
59
  let res;
60
60
  try {
61
61
  res = await fetch(`${API_BASE}/api/gds/versions/${encodeURIComponent(version)}/progress`, {
62
- headers: { 'User-Agent': 'amazonprimea-status-cli/1.0' },
62
+ headers: { 'User-Agent': require('../../src/branding').userAgent() },
63
63
  });
64
64
  } catch (err) {
65
65
  console.error(`status: could not reach ${API_BASE} (${err.message})`);
@@ -22,13 +22,10 @@
22
22
  // Prompts per idea:
23
23
  // [p]romote → create a backlog task, mark idea 'promoted',
24
24
  // reviewed_at=now, promoted_to_task_id=<new>
25
- // [m]erge with #N → mark idea 'merged' with promoted_to_task_id=N
26
- // (N is the canonical idea or task to keep). Note:
27
- // the current PATCH /tasks/:id only supports
28
- // parent_task_id, so we can't append the body to the
29
- // target task's description via API — we just link
30
- // via promoted_to_task_id. The body remains in the
31
- // merged idea row for history.
25
+ // [m]erge with #N → mark idea 'merged' with merged_into_idea_id=N
26
+ // (N is the canonical IDEA to keep). The body remains
27
+ // on the merged idea row for history — there is no
28
+ // description-append vector, so nothing is copied to N.
32
29
  // [d]iscard → mark idea 'discarded', reviewed_at=now
33
30
  // [s]kip → no patch; idea surfaces again next triage
34
31
  // [q]uit → exit early, print partial summary
@@ -269,7 +266,7 @@ async function main() {
269
266
  const inline = ans.replace(/^m(\s|#)*/, '').trim();
270
267
  if (inline) target = Number(inline.replace(/^#/, ''));
271
268
  if (!Number.isFinite(target) || target <= 0) {
272
- const a = (await prompt(rl, ' merge into id (task or idea #): ')).trim().replace(/^#/, '');
269
+ const a = (await prompt(rl, ' merge into idea #: ')).trim().replace(/^#/, '');
273
270
  target = Number(a);
274
271
  }
275
272
  if (!Number.isFinite(target) || target <= 0) {
@@ -278,9 +275,13 @@ async function main() {
278
275
  continue;
279
276
  }
280
277
  try {
278
+ // task 1002447 / migration core_195: the merge target belongs in
279
+ // merged_into_idea_id. This used to write promoted_to_task_id — a
280
+ // column that is REFERENCES tasks(id), so an idea id there either
281
+ // tripped the FK or silently named an unrelated task.
281
282
  await patchIdea(idea.id, {
282
283
  status: 'merged',
283
- promoted_to_task_id: target,
284
+ merged_into_idea_id: target,
284
285
  reviewed_at: new Date().toISOString(),
285
286
  });
286
287
  summary.merged++;