@bongos/core 1.17.0 → 1.17.2

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 (230) hide show
  1. package/.bongos-core.json +363 -312
  2. package/.claude/skills/blocker-review/SKILL.md +1 -1
  3. package/.claude/skills/builder-claim/SKILL.md +2 -0
  4. package/.claude/skills/builder-exit/SKILL.md +2 -2
  5. package/.claude/skills/builder-redteam/SKILL.md +3 -3
  6. package/.claude/skills/builder-setup/SKILL.md +2 -2
  7. package/.claude/skills/builder-start/SKILL.md +1 -1
  8. package/.claude/skills/dev/SKILL.md +1 -1
  9. package/.claude/skills/idea-triage/SKILL.md +2 -2
  10. package/.claude/skills/ideate/SKILL.md +1 -1
  11. package/.claude/skills/merge-mode/SKILL.md +1 -1
  12. package/.claude/skills/new-project/SKILL.md +3 -0
  13. package/.claude/skills/paint/SKILL.md +1 -1
  14. package/CLAUDE.md +11 -238
  15. package/GOVERNANCE.md +1 -1
  16. package/README.md +1 -1
  17. package/bin/bongos.js +1 -0
  18. package/clients/bongos-client/README.md +4 -4
  19. package/clients/bongos-client/bongos-client.global.js +14 -1
  20. package/clients/bongos-client/index.cjs +14 -1
  21. package/clients/bongos-client/index.d.ts +19 -2
  22. package/clients/bongos-client/index.mjs +11 -1
  23. package/clients/bongos-client/package.json +1 -1
  24. package/docs/adr/0001-stack-choice-single-vps.md +1 -1
  25. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  26. package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
  27. package/docs/adr/0020-security-model-public-stub.md +1 -1
  28. package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
  29. package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
  30. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
  31. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
  32. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  33. package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
  34. package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
  35. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  36. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  37. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  38. package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
  39. package/docs/adr/0057-container-cost-ledger.md +3 -3
  40. package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
  41. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
  42. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
  43. package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
  44. package/docs/adr/0096-require-reward-before-workable.md +9 -9
  45. package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
  46. package/docs/adr/0100-two-repo-instance-model.md +1 -1
  47. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
  48. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
  49. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
  50. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
  51. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
  52. package/docs/adr/0134-private-first-npm-distribution.md +20 -6
  53. package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
  54. package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
  55. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
  56. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
  57. package/docs/adr/README.md +14 -10
  58. package/docs/api/openapi.json +315 -6
  59. package/docs/api-reference.md +10 -5
  60. package/docs/architecture.md +3 -3
  61. package/docs/branding-contract.md +5 -4
  62. package/docs/canonical-permissions.md +1 -1
  63. package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
  64. package/docs/file-map.md +20 -19
  65. package/docs/handoff-template.md +2 -2
  66. package/docs/modules-contract.md +8 -1
  67. package/docs/onboarding/diagrams/README.md +7 -7
  68. package/docs/onboarding/drachmae-and-karma.md +18 -18
  69. package/docs/onboarding/primer.md +9 -9
  70. package/docs/recipes/audit-replay.md +2 -2
  71. package/docs/recipes/builder-box-lifecycle.md +2 -2
  72. package/docs/recipes/claude-design-loop.md +1 -1
  73. package/docs/recipes/instance-deploy.md +4 -4
  74. package/docs/recipes/instance-first-run.md +51 -0
  75. package/docs/recipes/ops-gotchas.md +1 -1
  76. package/docs/recipes/packaging-the-core.md +5 -5
  77. package/docs/recipes/private-npm-distribution.md +39 -12
  78. package/docs/recipes/self-host.md +1 -1
  79. package/docs/recipes/staging-environment.md +1 -1
  80. package/docs/recipes/upgrading-the-core.md +63 -8
  81. package/docs/research/cursor-competitive-analysis.md +1 -1
  82. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
  83. package/migrations/core_191_module_submissions.sql +45 -0
  84. package/modules/autonomy/CLAUDE.md +11 -45
  85. package/modules/builder-settings/CLAUDE.md +11 -54
  86. package/modules/dev-box/CLAUDE.md +11 -34
  87. package/modules/discord/CLAUDE.md +11 -46
  88. package/modules/discord/module.json +2 -1
  89. package/modules/economy/CLAUDE.md +11 -44
  90. package/modules/economy/achievements.js +9 -1
  91. package/modules/economy/credits.js +10 -1
  92. package/modules/grading/CLAUDE.md +11 -38
  93. package/modules/hall-ui/CLAUDE.md +11 -57
  94. package/modules/hall-ui/public/builders.js +71 -4
  95. package/modules/hall-ui/public/goal-inbox.js +191 -0
  96. package/modules/hall-ui/public/goals-page.js +227 -22
  97. package/modules/hall-ui/public/goals.html +16 -0
  98. package/modules/hall-ui/public/hall-widgets.js +14 -1
  99. package/modules/hall-ui/public/index.html +3 -0
  100. package/modules/hall-ui/public/new-project-wizard.js +6 -4
  101. package/modules/hall-ui/public/style.css +43 -0
  102. package/modules/ideas/CLAUDE.md +11 -33
  103. package/modules/lifecycle/CLAUDE.md +11 -59
  104. package/modules/lifecycle/routes/goals.js +80 -0
  105. package/modules/memory/CLAUDE.md +11 -36
  106. package/modules/onboarding/CLAUDE.md +11 -51
  107. package/modules/provisioning/CLAUDE.md +15 -0
  108. package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
  109. package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
  110. package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
  111. package/modules/provisioning/module.json +17 -0
  112. package/modules/provisioning/onboard-plan.js +37 -0
  113. package/modules/provisioning/provisioning.js +539 -0
  114. package/modules/provisioning/rate-limit.js +35 -0
  115. package/modules/provisioning/routes/provisioning.js +607 -0
  116. package/modules/provisioning/tests/provisioning.mjs +232 -0
  117. package/modules/provisioning/ui/.gitkeep +0 -0
  118. package/modules/security/CLAUDE.md +11 -40
  119. package/modules/security/secret-scrub.js +10 -0
  120. package/modules/sessions/CLAUDE.md +11 -35
  121. package/modules/status-ui/CLAUDE.md +11 -43
  122. package/package-lock.json +2 -2
  123. package/package.json +7 -2
  124. package/public-landing/create.html +58 -12
  125. package/public-landing/index.html +40 -21
  126. package/scripts/discord/README.md +1 -1
  127. package/scripts/gds/CLAUDE.md +11 -140
  128. package/scripts/gds/claim.js +98 -48
  129. package/scripts/gds/claude-materialize.js +174 -20
  130. package/scripts/gds/claude-md-template.js +84 -0
  131. package/scripts/gds/cli-lib.js +112 -2
  132. package/scripts/gds/cutover-codemod.js +241 -0
  133. package/scripts/gds/fitness.js +54 -0
  134. package/scripts/gds/gen-api-client.js +10 -10
  135. package/scripts/gds/init.js +112 -16
  136. package/scripts/gds/lint-infra-exec.js +70 -0
  137. package/scripts/gds/lint-workflow-exec.js +63 -0
  138. package/scripts/gds/migration-namespace.js +1 -1
  139. package/scripts/gds/mirror-lineage-check.js +14 -5
  140. package/scripts/gds/mirror-redact.js +8 -0
  141. package/scripts/gds/module.js +118 -4
  142. package/scripts/gds/npmrc.js +93 -0
  143. package/scripts/gds/onboard.js +17 -24
  144. package/scripts/gds/package-core.js +74 -17
  145. package/scripts/gds/provision.js +102 -17
  146. package/scripts/gds/publish-manifest.js +60 -2
  147. package/scripts/gds/run-routine.js +51 -13
  148. package/scripts/gds/run-unit-tests.js +4 -0
  149. package/scripts/gds/seed-adopt-tasks.js +1 -1
  150. package/scripts/gds/status.js +6 -1
  151. package/scripts/gds/task.js +166 -0
  152. package/scripts/gds/update-channel.js +186 -0
  153. package/scripts/gds/upgrade.js +172 -25
  154. package/src/bongos/CLAUDE.md +11 -60
  155. package/src/bongos/auth.js +24 -0
  156. package/src/bongos/module-submissions.js +56 -0
  157. package/src/bongos/route-rank-check.js +6 -0
  158. package/src/bongos/routes/CLAUDE.md +11 -39
  159. package/src/bongos/routes/auth.js +6 -1
  160. package/src/bongos/routes/modules.js +123 -21
  161. package/src/bongos/serve-internal.js +10 -1
  162. package/src/branding.js +1 -0
  163. package/src/instance-config.js +1 -1
  164. package/src/module-api.js +24 -1
  165. package/src/module-loader/loader.js +36 -4
  166. package/src/module-loader/manifest-schema.js +16 -1
  167. package/tests/branding.mjs +11 -0
  168. package/tests/claim_error_surface.mjs +107 -0
  169. package/tests/claude_materialize.mjs +125 -0
  170. package/tests/consumer_layout_boot.mjs +302 -0
  171. package/tests/cutover_codemod.mjs +184 -0
  172. package/tests/device_flow_start.mjs +96 -0
  173. package/tests/first_run_engine_check.mjs +79 -0
  174. package/tests/goal_membership_requests.mjs +33 -3
  175. package/tests/hook_core_resolve.mjs +125 -0
  176. package/tests/hook_user_agents.mjs +62 -0
  177. package/tests/infra_exec_paths.mjs +104 -0
  178. package/tests/init.mjs +106 -6
  179. package/tests/instance_mismatch_guard.mjs +101 -0
  180. package/tests/mirror_lineage_proof.mjs +14 -0
  181. package/tests/module_cli.mjs +130 -2
  182. package/tests/module_loader.mjs +42 -0
  183. package/tests/module_manifest.mjs +19 -0
  184. package/tests/module_submissions.mjs +135 -0
  185. package/tests/npmrc.mjs +105 -0
  186. package/tests/onboard.mjs +62 -0
  187. package/tests/package_core.mjs +76 -22
  188. package/tests/platform_boot.mjs +5 -2
  189. package/tests/provision.mjs +107 -8
  190. package/tests/provisioning_github_app_routes.mjs +44 -2
  191. package/tests/publish_manifest.mjs +52 -1
  192. package/tests/run_routine.mjs +26 -1
  193. package/tests/run_routine_two_root.mjs +77 -0
  194. package/tests/scrubber_corpus.mjs +17 -0
  195. package/tests/served_paths_shipped.mjs +2 -2
  196. package/tests/task_cli.mjs +99 -0
  197. package/tests/update_channel.mjs +186 -0
  198. package/tests/upgrade.mjs +220 -25
  199. package/tests/workflow_exec_paths.mjs +98 -0
  200. package/docs/design/container-exterior-tiles.md +0 -49
  201. package/docs/design/container-exterior.md +0 -81
  202. package/docs/design/container-interior.md +0 -77
  203. package/docs/design/world-map-scroll.md +0 -41
  204. package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
  205. package/docs/recipes/cloudbongos-staging-gate.md +0 -139
  206. package/docs/recipes/cloudbongos-standup.md +0 -111
  207. package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
  208. package/docs/recipes/parallel-load-test.md +0 -32
  209. package/docs/recipes/pixel-art-pipeline.md +0 -411
  210. package/docs/recipes/staging-data.md +0 -50
  211. package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
  212. package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
  213. package/migrations/instance/110_medusa_planning_seed.sql +0 -70
  214. package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
  215. package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
  216. package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
  217. package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
  218. package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
  219. package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
  220. package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
  221. package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
  222. package/scripts/gds/art-stage.js +0 -152
  223. package/server.js +0 -212
  224. package/tests/art_key.mjs +0 -121
  225. package/tests/day_cycle.mjs +0 -197
  226. package/tests/snapshot_picker.mjs +0 -233
  227. package/tests/sprite_facing.mjs +0 -35
  228. package/tests/terrain_picker.mjs +0 -298
  229. package/tests/turn_in_place.mjs +0 -280
  230. package/tests/walkability.mjs +0 -188
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // cutover-codemod.js — rewrite HOST files' core-internal require()s to the @bongos/core
4
+ // package subpaths at cutover (R89 / task 2159 [F1], task 2161 [F3]).
5
+ //
6
+ // WHY. Post-split (task 1728) a host file lives at `<instance>/…` with NO co-located `src/`.
7
+ // Its relative core require()s — `require('../../src/module-api')` (host modules, F1),
8
+ // `require('./src/bongos/…')` (the game entrypoint server.js, F3) — point at the DELETED
9
+ // instance `src/` → boot crash (the 2026-07-10 rehearsal, findings F1+F3:
10
+ // docs/reviews/2026-07-10-amazonprimea-decouple-r89-rehearsal.md). Core modules are UNAFFECTED:
11
+ // they ship INSIDE @bongos/core, where `src/` sits next to `modules/`, so their identical
12
+ // relative requires still resolve. The fix is to rewrite the host files' core requires to the
13
+ // package's EXPORTED subpaths (`@bongos/core/module-api`, `@bongos/core/serve-internal`, …).
14
+ //
15
+ // WHY A CUTOVER TRANSFORM, NOT AN IN-PLACE MONOLITH EDIT. In the monolith `@bongos/core` is not
16
+ // a dependency (the specifier resolves nowhere) and fitness's "modules import only
17
+ // src/module-api.js" check would fail the rewritten form. So this must run AT CUTOVER on the
18
+ // partitioned consumer tree — never on the committed monolith source. Mirrors the `.claude/hooks`
19
+ // `_core-resolve.js` step (task 2139); `claude-materialize.js` rewrites `.claude/`, this rewrites
20
+ // host-module + server.js source — the gap that rehearsal F1/F3 named.
21
+ //
22
+ // TWO GUARDS make it structurally impossible to corrupt the monolith:
23
+ // 1. SCOPE ORACLE = publish-manifest. A file is rewritten only when `isPublishable(rel) === false`
24
+ // (host: game / art-pipeline / character-anim / demo + server.js). Core files (publishable)
25
+ // ship with the package and are left untouched — reusing the exact partition logic as the
26
+ // core-vs-host oracle, so this can never drift from what the cutover actually deletes/keeps.
27
+ // 2. PRECONDITION = post-split consumer. Refuse unless the instance core doorway is gone
28
+ // (`src/module-api.js` absent at the root) — the definitive signal core was partitioned out.
29
+ // In the monolith that file exists, so the codemod refuses. `--force` overrides for a
30
+ // read-only preview only.
31
+ //
32
+ // The rewrite is TABLE-DRIVEN (CORE_INTERNAL_MAP), not a blind `src/X → @bongos/core/X` path swap,
33
+ // because the core's export names do not all mirror the src layout: `src/bongos/serve-internal.js`
34
+ // is exported as `./serve-internal`, not `./bongos/serve-internal`. The table encodes each mapping
35
+ // so a rewrite can never produce a specifier the package doesn't export. Idempotent (a rewritten
36
+ // `@bongos/core/*` require no longer matches the relative-into-src pattern) and quote-preserving.
37
+
38
+ const fs = require('node:fs');
39
+ const path = require('node:path');
40
+ const { execFileSync } = require('node:child_process');
41
+ const { isPublishable } = require('./publish-manifest');
42
+
43
+ const CORE_PKG = '@bongos/core';
44
+
45
+ // <subpath after `src/` in the relative require> → <the @bongos/core export subpath>.
46
+ // The core exports map (scripts/gds/package-core.js buildPackageJson) MUST export every value
47
+ // here, or the rewritten require won't resolve. Kept deliberately small + explicit.
48
+ // F1 (task 2159): the host-module doorway.
49
+ // F3 (task 2161): the six core internals the game entrypoint server.js wires — added there,
50
+ // in lockstep with widening the core exports map, so a rewrite never outruns an export.
51
+ const CORE_INTERNAL_MAP = {
52
+ // F1 — every host module imports the core doorway; the ONLY core import a module is allowed.
53
+ 'module-api': 'module-api',
54
+ // F3 (task 2161) — the six core internals the game entrypoint server.js wires. Each LHS is the
55
+ // subpath after `src/` in server.js's relative require; each RHS is the core's EXPORTED subpath
56
+ // (widened in package-core.js buildPackageJson in lockstep). Note the divergence: the file at
57
+ // `src/bongos/serve-internal.js` is exported as `./serve-internal` (a curated name), NOT
58
+ // `./bongos/serve-internal` — this table encodes that so the rewrite can't emit an unexported
59
+ // specifier. The other five mirror their src/ path.
60
+ 'bongos/api-path-404': 'bongos/api-path-404',
61
+ 'bongos/uptime-poller': 'bongos/uptime-poller',
62
+ 'bongos/serve-internal': 'serve-internal',
63
+ 'module-loader/loader': 'module-loader/loader',
64
+ 'modules': 'modules',
65
+ 'module-seams': 'module-seams',
66
+ };
67
+
68
+ // require('<./ or ../…>src/<key>') — a RELATIVE require that reaches into a `src/` dir.
69
+ // Group 1 = quote (preserved), group 2 = the `./`/`../…` prefix, group 3 = the key after `src/`.
70
+ // An already-rewritten `require('@bongos/core/…')` has no `../src/` and never matches → idempotent.
71
+ const REQUIRE_RE = /require\((['"])((?:\.\.?\/)+)src\/([^'"]+)\1\)/g;
72
+
73
+ // Pure. Rewrite the core-internal require()s in one file's text per `map`.
74
+ // Returns { text, changes:[{from,to,key}], unmapped:[{key,spec}] }.
75
+ // - changes: requires rewritten to `@bongos/core/<mapped>`.
76
+ // - unmapped: relative-into-`src/` requires whose key is NOT in the table — left UNCHANGED and
77
+ // surfaced so a new core internal a host file starts depending on can't be silently missed
78
+ // (it would still crash at boot; the codemod flags "add an export + a table entry").
79
+ function rewriteCoreRequires(text, map = CORE_INTERNAL_MAP) {
80
+ const changes = [];
81
+ const unmapped = [];
82
+ const out = String(text == null ? '' : text).replace(REQUIRE_RE, (whole, q, _prefix, key) => {
83
+ const mapped = Object.prototype.hasOwnProperty.call(map, key) ? map[key] : null;
84
+ if (mapped == null) {
85
+ unmapped.push({ key, spec: whole });
86
+ return whole; // leave unknown core internals untouched — never guess a specifier
87
+ }
88
+ const to = `require(${q}${CORE_PKG}/${mapped}${q})`;
89
+ changes.push({ from: whole, to, key });
90
+ return to;
91
+ });
92
+ return { text: out, changes, unmapped };
93
+ }
94
+
95
+ // A file is in scope iff the partition classifies it HOST (not shipped in the core package).
96
+ function isHostFile(rel, isPublishableFn = isPublishable) {
97
+ return isPublishableFn(rel) === false;
98
+ }
99
+
100
+ // Post-split consumer signal: the instance's own core doorway is gone. In the monolith
101
+ // `src/module-api.js` exists, so this is false and the CLI refuses (protecting the monolith).
102
+ function looksLikeConsumer(root, { existsFn = fs.existsSync } = {}) {
103
+ return !existsFn(path.join(root, 'src', 'module-api.js'));
104
+ }
105
+
106
+ // Skip vendored deps, VCS metadata, the `.claude/` topology surface (materialize's job), and
107
+ // build output. `dist/` in particular holds an extracted @bongos/core tarball whose bundled
108
+ // core modules carry the SAME `../../src/module-api` requires — scanning it would be noise
109
+ // (it's gitignored throwaway, never part of the consumer).
110
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.claude', 'dist', 'coverage']);
111
+
112
+ // Preferred source scope: git-TRACKED *.js files. A consumer tree is a git repo at cutover, and
113
+ // tracking is the honest "source" oracle — it excludes every gitignored build artifact (dist/,
114
+ // coverage/, …) without enumerating them. Returns repo-relative POSIX paths, or null when git is
115
+ // unavailable / this isn't a repo (→ caller falls back to the filesystem walk).
116
+ function gitTrackedJsFiles(root, { runGit } = {}) {
117
+ const run = runGit || ((args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }));
118
+ let out;
119
+ try { out = run(['ls-files', '-z', '*.js']); } catch { return null; }
120
+ if (typeof out !== 'string') return null;
121
+ return out.split('\0').filter(Boolean).map((p) => p.split(path.sep).join('/')).sort();
122
+ }
123
+
124
+ // Recursively collect *.js files under `root`, skipping SKIP_DIRS. Returns repo-relative POSIX
125
+ // paths (what isPublishable expects). The fallback when git tracking is unavailable.
126
+ function collectJsFiles(root, { fsImpl = fs } = {}) {
127
+ const found = [];
128
+ const walk = (absDir) => {
129
+ let entries;
130
+ try { entries = fsImpl.readdirSync(absDir, { withFileTypes: true }); } catch { return; }
131
+ for (const ent of entries) {
132
+ if (ent.isDirectory()) {
133
+ if (SKIP_DIRS.has(ent.name)) continue;
134
+ walk(path.join(absDir, ent.name));
135
+ } else if (ent.isFile() && ent.name.endsWith('.js')) {
136
+ found.push(path.relative(root, path.join(absDir, ent.name)).split(path.sep).join('/'));
137
+ }
138
+ }
139
+ };
140
+ walk(root);
141
+ return found.sort();
142
+ }
143
+
144
+ function underSkipDir(rel) {
145
+ return SKIP_DIRS.has(rel.split('/')[0]);
146
+ }
147
+
148
+ // The candidate *.js set to consider: git-tracked source when available, else the filesystem walk.
149
+ // SKIP_DIRS is enforced on BOTH paths — git ls-files does not know about it, and `.claude/` in
150
+ // particular is materialize's + _core-resolve.js's domain (task 2139), never the codemod's.
151
+ function candidateJsFiles(root, { fsImpl = fs, runGit } = {}) {
152
+ const tracked = gitTrackedJsFiles(root, { runGit });
153
+ const list = tracked && tracked.length ? tracked : collectJsFiles(root, { fsImpl });
154
+ return list.filter((rel) => !underSkipDir(rel));
155
+ }
156
+
157
+ // Plan the codemod over a consumer `root`: which host files change, and any unmapped core requires.
158
+ // Pure-ish (reads disk via injectable deps for tests). Does NOT write.
159
+ function planCodemod({ root, fsImpl = fs, runGit, isPublishableFn = isPublishable, map = CORE_INTERNAL_MAP } = {}) {
160
+ const files = [];
161
+ const unmapped = [];
162
+ for (const rel of candidateJsFiles(root, { fsImpl, runGit })) {
163
+ if (!isHostFile(rel, isPublishableFn)) continue; // core file → ships with the package, leave it
164
+ let raw;
165
+ try { raw = fsImpl.readFileSync(path.join(root, rel), 'utf8'); } catch { continue; }
166
+ const res = rewriteCoreRequires(raw, map);
167
+ if (res.unmapped.length) for (const u of res.unmapped) unmapped.push({ rel, ...u });
168
+ if (res.changes.length) files.push({ rel, changes: res.changes, newText: res.text });
169
+ }
170
+ return { files, unmapped };
171
+ }
172
+
173
+ // ---- CLI -------------------------------------------------------------------
174
+ function parseArgs(argv) {
175
+ const a = { root: process.cwd(), apply: false, force: false };
176
+ for (let i = 0; i < argv.length; i++) {
177
+ if (argv[i] === '--root') a.root = argv[++i];
178
+ else if (argv[i] === '--apply') a.apply = true;
179
+ else if (argv[i] === '--force') a.force = true;
180
+ }
181
+ return a;
182
+ }
183
+
184
+ function main(argv = process.argv.slice(2), { log = console.log, err = console.error } = {}) {
185
+ const args = parseArgs(argv);
186
+ const root = path.resolve(args.root);
187
+
188
+ if (!looksLikeConsumer(root) && !args.force) {
189
+ err(`[cutover-codemod] REFUSING: ${path.join(root, 'src/module-api.js')} still exists — this looks like the monolith,`);
190
+ err(' not a post-split consumer. This codemod is a CUTOVER transform and must not edit the monolith');
191
+ err(' (there @bongos/core is not a dep and fitness would reject the rewrite). Run it after the partition,');
192
+ err(' or pass --force for a read-only preview only.');
193
+ return 2;
194
+ }
195
+
196
+ const plan = planCodemod({ root });
197
+
198
+ for (const u of plan.unmapped) {
199
+ err(`[cutover-codemod] WARNING ${u.rel}: unmapped core-internal require ${u.spec} — add a CORE_INTERNAL_MAP`);
200
+ err(` entry + a matching core export before cutover, or this require will crash at boot.`);
201
+ }
202
+
203
+ if (!plan.files.length) {
204
+ log('[cutover-codemod] no host files need rewriting (already converted, or none present).');
205
+ return 0;
206
+ }
207
+
208
+ const verb = args.apply ? 'rewrote' : 'would rewrite';
209
+ for (const f of plan.files) {
210
+ log(`[cutover-codemod] ${verb} ${f.rel} (${f.changes.length} require${f.changes.length === 1 ? '' : 's'}):`);
211
+ for (const c of f.changes) log(` ${c.from} → ${c.to}`);
212
+ if (args.apply) fs.writeFileSync(path.join(root, f.rel), f.newText);
213
+ }
214
+
215
+ const total = plan.files.reduce((n, f) => n + f.changes.length, 0);
216
+ if (args.apply) {
217
+ log(`[cutover-codemod] ✓ rewrote ${total} require(s) across ${plan.files.length} host file(s).`);
218
+ } else {
219
+ log(`[cutover-codemod] dry-run: ${total} require(s) across ${plan.files.length} host file(s). Re-run with --apply to write.`);
220
+ }
221
+ return 0;
222
+ }
223
+
224
+ module.exports = {
225
+ CORE_PKG,
226
+ CORE_INTERNAL_MAP,
227
+ REQUIRE_RE,
228
+ rewriteCoreRequires,
229
+ isHostFile,
230
+ looksLikeConsumer,
231
+ gitTrackedJsFiles,
232
+ collectJsFiles,
233
+ candidateJsFiles,
234
+ planCodemod,
235
+ parseArgs,
236
+ main,
237
+ };
238
+
239
+ if (require.main === module) {
240
+ process.exit(main());
241
+ }
@@ -1170,6 +1170,59 @@ function checkCoreRequiresExcludedConfig() {
1170
1170
  };
1171
1171
  }
1172
1172
 
1173
+ // ============================================================================
1174
+ // Check 16b — core code must not require an instance-EXCLUDED module (task 2135)
1175
+ // ============================================================================
1176
+ //
1177
+ // Sibling of Check 16 for the code→MODULE class. A publishable core file that
1178
+ // require()s a boundary-EXCLUDED module (modules/game, modules/art-pipeline,
1179
+ // modules/character-anim, …) throws MODULE_NOT_FOUND once that module lives in the
1180
+ // consumer's instance repo rather than the core package (R89 split). The cure is
1181
+ // either to reclassify the module INTO core (if it belongs there — e.g. provisioning,
1182
+ // sibling of dev-box) or to exclude the requiring file with the feature it serves
1183
+ // (art tooling). Catches BOTH require forms: a direct literal
1184
+ // require('../modules/<key>/…') AND a require(path.join(<root>, 'modules/<key>/…')).
1185
+ // Only an EXCLUDED module required from a PUBLISHABLE file is a violation; requiring a
1186
+ // publishable (core) module is fine, and the loader's DYNAMIC dispatch
1187
+ // (path.join(dir,'routes',rk) — no literal modules/<key>/ string) is invisible here.
1188
+ const MODULE_PATH_IN_STRING_RE = /['"](?:[^'"]*\/)?modules\/([a-z0-9][a-z0-9-]*)\/[^'"]*['"]/g;
1189
+
1190
+ function checkCoreRequiresExcludedModule() {
1191
+ const { isPublishable } = require('./publish-manifest');
1192
+ const violations = [];
1193
+ const seen = new Set();
1194
+ const allAbs = [
1195
+ ...['src', 'scripts'].flatMap((d) => walkJs(path.join(CORE_ROOT, d))),
1196
+ ...walkJsAllRoots('modules'),
1197
+ ];
1198
+ for (const abs of allAbs) {
1199
+ const relFile = rel(abs);
1200
+ if (seen.has(relFile)) continue;
1201
+ seen.add(relFile);
1202
+ if (!isPublishable(relFile)) continue; // only CORE (publishable) code can crash a vanilla boot
1203
+ for (const raw of fs.readFileSync(abs, 'utf8').split('\n')) {
1204
+ const trimmed = raw.trim();
1205
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
1206
+ if (!/require\s*\(/.test(raw)) continue; // only lines that actually require()
1207
+ let m;
1208
+ MODULE_PATH_IN_STRING_RE.lastIndex = 0;
1209
+ while ((m = MODULE_PATH_IN_STRING_RE.exec(raw))) {
1210
+ const key = m[1];
1211
+ if (isPublishable(`modules/${key}/module.json`)) continue; // requiring a CORE module is fine
1212
+ violations.push(`${relFile} → require(...'modules/${key}/…') pulls in an instance-EXCLUDED module — reclassify it into core (if it belongs there) or exclude ${relFile} with the feature it serves, so a fresh core boots (task 2135).`);
1213
+ }
1214
+ }
1215
+ }
1216
+ return {
1217
+ name: 'core never requires an excluded module (task 2135)',
1218
+ ok: violations.length === 0,
1219
+ hardFail: violations.length > 0,
1220
+ violations,
1221
+ warnings: [],
1222
+ note: "a publishable src/·scripts/·modules/ .js must not require('…modules/<key>/…') when that module is boundary-excluded — MODULE_NOT_FOUND post-split (R89).",
1223
+ };
1224
+ }
1225
+
1173
1226
  // ============================================================================
1174
1227
  // Check 17 — no route hand-rolls a FLAT error (BV1 R03 / task 1990 / ADR 0116)
1175
1228
  // ============================================================================
@@ -1360,6 +1413,7 @@ const CHECKS = [
1360
1413
  checkKernelPromptsNoHardcodedIdentity, // BV1.R76 — kernel prompts read identity from branding()
1361
1414
  checkHallSectionRegistry,
1362
1415
  checkCoreRequiresExcludedConfig, // task 1824 — core→instance-config require guard
1416
+ checkCoreRequiresExcludedModule, // Check 16b — task 2135: core→excluded-module require guard (R89 split)
1363
1417
  checkErrorEnvelope, // Check 17 — BV1 R03 / task 1990: no route hand-rolls a flat error (ADR 0116)
1364
1418
  checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999: every body-reading write route validates (ADR 0118)
1365
1419
  ];
@@ -10,7 +10,7 @@
10
10
  //
11
11
  // Design (mirrors gen-api-docs.js / ADR 0063): deterministic (stable sort, no
12
12
  // clock), zero runtime deps, --check gate. The client itself has ZERO deps — it
13
- // uses the platform `fetch` (Node 18+ / browsers), so `npm i @cloudbongos/client`
13
+ // uses the platform `fetch` (Node 18+ / browsers), so `npm i @bongos/client`
14
14
  // pulls nothing transitive.
15
15
  //
16
16
  // Usage:
@@ -92,7 +92,7 @@ function renderClientJs(spec, ops) {
92
92
  const groups = groupByTag(ops);
93
93
  const defaultBase = (spec.servers && spec.servers[0] && spec.servers[0].url) || '/api/bongos/v1';
94
94
  const L = [];
95
- L.push(`// @cloudbongos/client — GENERATED from the OpenAPI spec by`);
95
+ L.push(`// @bongos/client — GENERATED from the OpenAPI spec by`);
96
96
  L.push(`// scripts/gds/gen-api-client.js (task 1995 / ADR 0118). DO NOT EDIT BY HAND —`);
97
97
  L.push(`// regenerate on spec change. Zero deps; uses the platform fetch.`);
98
98
  L.push(`'use strict';`);
@@ -210,7 +210,7 @@ function stripEsmExports(esm) {
210
210
  // app, which is a zero-dep CJS subproject and vendors this file at package time).
211
211
  function renderClientCjs(esm) {
212
212
  const header =
213
- '// @cloudbongos/client (CommonJS build) — GENERATED from index.mjs by\n' +
213
+ '// @bongos/client (CommonJS build) — GENERATED from index.mjs by\n' +
214
214
  '// scripts/gds/gen-api-client.js. DO NOT EDIT BY HAND. Same behavior as the ESM\n' +
215
215
  '// build; only the module bindings differ. For require()-based consumers.\n';
216
216
  const body = stripEsmExports(esm)
@@ -232,7 +232,7 @@ function renderClientBrowserGlobal(esm) {
232
232
  .replace(/^'use strict';\n/m, '')
233
233
  .replace(/^\/\/ @cloudbongos\/client — GENERATED[^\n]*\n(?:\/\/[^\n]*\n)*/m, '');
234
234
  return (
235
- '// @cloudbongos/client (browser-global build) — GENERATED from index.mjs by\n' +
235
+ '// @bongos/client (browser-global build) — GENERATED from index.mjs by\n' +
236
236
  '// scripts/gds/gen-api-client.js. DO NOT EDIT BY HAND. Exposes window.BongosClient\n' +
237
237
  '// for classic <script> pages. Same behavior as the ESM build.\n' +
238
238
  '(function (global) {\n' +
@@ -316,7 +316,7 @@ function methodSignature(op) {
316
316
  function renderTypes(spec, ops) {
317
317
  const groups = groupByTag(ops);
318
318
  const L = [];
319
- L.push(`// GENERATED — TypeScript types for @cloudbongos/client (task 1995; field-level`);
319
+ L.push(`// GENERATED — TypeScript types for @bongos/client (task 1995; field-level`);
320
320
  L.push(`// request typing added in task 2050 / ADR 0118). DO NOT EDIT BY HAND.`);
321
321
  L.push(`export declare const API_VERSION: string;`);
322
322
  L.push(`export declare const DEFAULT_BASE_URL: string;`);
@@ -352,7 +352,7 @@ function renderTypes(spec, ops) {
352
352
 
353
353
  function renderPackageJson(spec) {
354
354
  return JSON.stringify({
355
- name: '@cloudbongos/client',
355
+ name: '@bongos/client',
356
356
  version: spec.info && spec.info.version ? spec.info.version : '0.0.0',
357
357
  description: 'Generated typed client for the Bongos API (ADR 0118). Do not edit by hand.',
358
358
  type: 'module',
@@ -374,7 +374,7 @@ function renderPackageJson(spec) {
374
374
  license: 'AGPL-3.0-or-later',
375
375
  // Pre-launch: publish PRIVATE (task 2089 / ADR 0134). Scoped + access:"restricted"
376
376
  // = only paid-org members can install. Flip to "public" (or `npm access public
377
- // @cloudbongos/client`) at launch, alongside the core.
377
+ // @bongos/client`) at launch, alongside the core.
378
378
  publishConfig: { access: 'restricted' },
379
379
  sideEffects: false,
380
380
  }, null, 2) + '\n';
@@ -418,7 +418,7 @@ function exampleLines(ops) {
418
418
  function renderReadme(spec, ops) {
419
419
  const base = (spec.servers && spec.servers[0] && spec.servers[0].url) || '/api/bongos/v1';
420
420
  return [
421
- `# @cloudbongos/client`,
421
+ `# @bongos/client`,
422
422
  ``,
423
423
  `A **generated**, zero-dependency typed client for the Bongos API — produced from`,
424
424
  `\`docs/api/openapi.json\` by \`scripts/gds/gen-api-client.js\` (ADR 0118). Do not edit`,
@@ -430,13 +430,13 @@ function renderReadme(spec, ops) {
430
430
  `## Install`,
431
431
  ``,
432
432
  '```bash',
433
- `npm install @cloudbongos/client # internal registry / workspace path`,
433
+ `npm install @bongos/client # internal registry / workspace path`,
434
434
  '```',
435
435
  ``,
436
436
  `## Use`,
437
437
  ``,
438
438
  '```js',
439
- `import { createClient, ApiError } from '@cloudbongos/client';`,
439
+ `import { createClient, ApiError } from '@bongos/client';`,
440
440
  ``,
441
441
  `const api = createClient({`,
442
442
  ` baseUrl: 'https://amazonprimea.com${base}', // or omit for same-origin (browser)`,
@@ -37,7 +37,7 @@
37
37
  // it refuses where config/branding.json exists, and --force OVERWRITES. `--adopt` is
38
38
  // the opposite: it LAYERS Cloud Bongos onto an EXISTING repo (app code, git history,
39
39
  // a package.json). It writes the lean config only where absent, adds the pinned
40
- // @cloudbongos/core dependency to the owner's package.json without touching any other
40
+ // @bongos/core dependency to the owner's package.json without touching any other
41
41
  // field, and materializes .claude/ ADDITIVELY — for any pre-existing file it
42
42
  // MERGES-or-LEAVES-AND-REPORTS, it NEVER overwrites, and it does not reuse the --force
43
43
  // path. It deliberately does NOT seed a synthetic "V1 — Prove the loop": capturing the
@@ -56,11 +56,17 @@
56
56
  // bongos init --dir <path> target dir for the config files (default: cwd)
57
57
  // bongos init --no-seed write config only; skip the version/task seeding
58
58
  // bongos init --force overwrite existing config files (greenfield only; ignored by --adopt)
59
- // bongos init --vendor-core <tgz> greenfield only: vendor the @cloudbongos/core .tgz + pin
59
+ // bongos init --vendor-core <tgz> greenfield only: vendor the @bongos/core .tgz + pin
60
60
  // package.json to file:vendor/… + generate package-lock.json,
61
61
  // so the scaffold is `npm ci`-installable (ADR 0108 gap 1/4).
62
- // Without it the scaffold pins the caret range (unresolvable
63
- // on npm today) — a local/dev scaffold you resolve another way.
62
+ // Without it the scaffold pins the caret range (which now
63
+ // resolves from the PRIVATE registry — see --registry-core).
64
+ // bongos init --registry-core greenfield only: keep the caret pin + write an env-fed
65
+ // .npmrc (`_authToken=${NPM_TOKEN}`) so `npm ci` pulls the
66
+ // PRIVATE @bongos/core from the npm registry (ADR 0134).
67
+ // With NPM_TOKEN set it also generates package-lock.json;
68
+ // without a token it 404s (the expected tokenless outcome).
69
+ // Mutually exclusive with --vendor-core (registry vs tarball).
64
70
 
65
71
  const fs = require('node:fs');
66
72
  const path = require('node:path');
@@ -72,6 +78,7 @@ const { materializeClaude } = require('./claude-materialize'); // ADR 0108 §3 (
72
78
  const { detectStack, formatStackSummary } = require('./adopt-detect'); // ADR 0121 §Decision 4 (task 2043): inspect an existing repo before layering
73
79
  const { resolveBacklogTasks, DEFAULT_MAX: BACKLOG_MAX } = require('./adopt-import'); // ADR 0121 §Decision 3 (task 2044): import an existing backlog
74
80
  const { preflightAdopt, formatPreflight } = require('./adopt-preflight'); // ADR 0121 §Decision 5 (task 2045): the accept-gated conflict pre-flight
81
+ const { writeNpmrc, hasNpmToken } = require('./npmrc'); // ADR 0134 (task 2090): the registry-install path's env-fed .npmrc
75
82
 
76
83
  const KNOWN_MODULES = ['game', 'art-pipeline', 'discord', 'dev-box'];
77
84
 
@@ -87,7 +94,7 @@ const HOSTING_SHAPES = ['co-tenant', 'dedicated', 'standalone'];
87
94
  // `bongos init --adopt` LAYERS this onto an existing repo's package.json; `bongos
88
95
  // upgrade` later re-pins it to a vendored `file:` tarball. Named so the adopt
89
96
  // layering and the upgrade re-pin agree on the dependency name.
90
- const CORE_PKG = '@cloudbongos/core';
97
+ const CORE_PKG = '@bongos/core';
91
98
 
92
99
  const isObj = (v) => v && typeof v === 'object' && !Array.isArray(v);
93
100
 
@@ -114,19 +121,25 @@ function coreVersionSafe() {
114
121
  // (`bash <core>/scripts/migrate.sh`). Keeping them identical means `npm start` /
115
122
  // `npm run migrate` on the instance behave like production, and `bongos upgrade`'s
116
123
  // `npm run migrate` step resolves to the core's migrate.sh (ADR 0108).
117
- const INSTANCE_CORE_PKG_DIR = 'node_modules/@cloudbongos/core';
124
+ const INSTANCE_CORE_PKG_DIR = 'node_modules/@bongos/core';
118
125
  const INSTANCE_START_SCRIPT = `node ${INSTANCE_CORE_PKG_DIR}/src/platform-server.js`;
119
126
  const INSTANCE_MIGRATE_SCRIPT = `bash ${INSTANCE_CORE_PKG_DIR}/scripts/migrate.sh`;
120
127
 
121
128
  // PURE (exported for tests) — build/augment the greenfield instance package.json so a
122
129
  // fresh instance is `npm ci`-installable and runnable (ADR 0108 greenfield gap 1/4,
123
- // task 2053): the @cloudbongos/core dependency (the `file:` tarball pin when a tarball is
130
+ // task 2053): the @bongos/core dependency (the `file:` tarball pin when a tarball is
124
131
  // vendored, else the declared caret range — the CARET RESOLVES NOWHERE on npm, which is
125
132
  // exactly why the vendored path pins `file:`) plus the start/migrate npm scripts a
126
133
  // standalone instance runs. Preserves any pre-existing fields + scripts (fills only what
127
134
  // is missing); greenfield OWNS the core dep, so it is always (re)set to `coreDep`. Never
128
135
  // spawns npm — generating the lockfile is a separate imperative step in main().
129
- function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', coreDep } = {}) {
136
+ // `moduleDeps` (ADR 0138 / task 2160): the merged external npm deps of the modules this
137
+ // instance ENABLES. @bongos/core ships lean (express+pg, ADR 0108), so a bundled module that
138
+ // needs more (e.g. discord → discord.js) declares it in its module.json `dependencies`, and the
139
+ // consumer's package.json must carry it or boot crashes on the missing module (rehearsal F2).
140
+ // Precedence: module deps FILL gaps, an explicit pre-existing pin WINS over them, @bongos/core
141
+ // is always (re)set. With no moduleDeps this is byte-identical to before.
142
+ function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', coreDep, moduleDeps } = {}) {
130
143
  const dep = coreDep || coreDepRange();
131
144
  const pkg = isObj(existing) ? { ...existing } : {};
132
145
  if (!pkg.name) pkg.name = name;
@@ -134,10 +147,37 @@ function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', cor
134
147
  pkg.scripts = { ...(isObj(pkg.scripts) ? pkg.scripts : {}) };
135
148
  if (!pkg.scripts.start) pkg.scripts.start = INSTANCE_START_SCRIPT;
136
149
  if (!pkg.scripts.migrate) pkg.scripts.migrate = INSTANCE_MIGRATE_SCRIPT;
137
- pkg.dependencies = { ...(isObj(pkg.dependencies) ? pkg.dependencies : {}), [CORE_PKG]: dep };
150
+ pkg.dependencies = {
151
+ ...(isObj(moduleDeps) ? moduleDeps : {}),
152
+ ...(isObj(pkg.dependencies) ? pkg.dependencies : {}),
153
+ [CORE_PKG]: dep,
154
+ };
138
155
  return pkg;
139
156
  }
140
157
 
158
+ // The external npm deps a single module declares in its module.json `dependencies` (ADR 0138).
159
+ // I/O (reads the core-package module manifest); fail-soft → {} when absent/unreadable.
160
+ function moduleDeclaredDeps(moduleKey, { coreRoot = resolveCoreRoot(), fsImpl = fs } = {}) {
161
+ try {
162
+ const mj = JSON.parse(fsImpl.readFileSync(path.join(coreRoot, 'modules', moduleKey, 'module.json'), 'utf8'));
163
+ return isObj(mj.dependencies) ? mj.dependencies : {};
164
+ } catch {
165
+ return {};
166
+ }
167
+ }
168
+
169
+ // Merge the declared deps of every ENABLED module (modulesConfig = { modules: { key: bool } })
170
+ // into one object. PURE — `readDeps(key)` is injected (defaults to moduleDeclaredDeps) so this is
171
+ // testable with no disk. A later-enabled module's range wins on a key collision (rare).
172
+ function enabledModuleDeps(modulesConfig, { readDeps = (k) => moduleDeclaredDeps(k) } = {}) {
173
+ const enabled = isObj(modulesConfig) && isObj(modulesConfig.modules) ? modulesConfig.modules : {};
174
+ const out = {};
175
+ for (const [key, on] of Object.entries(enabled)) {
176
+ if (on === true) Object.assign(out, readDeps(key));
177
+ }
178
+ return out;
179
+ }
180
+
141
181
  // ---------------------------------------------------------------------------
142
182
  // Pure builders (exported for tests — no I/O, no network)
143
183
  // ---------------------------------------------------------------------------
@@ -258,7 +298,7 @@ function buildHierarchyConfig(spec) {
258
298
  }
259
299
 
260
300
  // PURE — layer the pinned core dependency into a parsed package.json (ADR 0121
261
- // §Decision 2 / task 2042). NEVER clobbers: if `@cloudbongos/core` is already a dep
301
+ // §Decision 2 / task 2042). NEVER clobbers: if `@bongos/core` is already a dep
262
302
  // (in dependencies OR devDependencies) it returns the object untouched with
263
303
  // action:'present'; if there is no package.json at all it MINTS a minimal one
264
304
  // (action:'created'); otherwise it ADDS the dep, preserving every existing field and
@@ -466,7 +506,7 @@ async function interview({ adopt = false } = {}) {
466
506
  let provisioning;
467
507
  const wantProv = (await ask(rl, ' Provision production hosting now? (y/N)', 'N')).toLowerCase();
468
508
  if (wantProv === 'y' || wantProv === 'yes') {
469
- const shape = (await ask(rl, ' Hosting shape? (co-tenant = share an existing box, $0 / dedicated = its own droplet / standalone = its own repo + pinned @cloudbongos/core, ADR 0108)', 'co-tenant')).toLowerCase();
509
+ const shape = (await ask(rl, ' Hosting shape? (co-tenant = share an existing box, $0 / dedicated = its own droplet / standalone = its own repo + pinned @bongos/core, ADR 0108)', 'co-tenant')).toLowerCase();
470
510
  const domain = await ask(rl, ' Domain you own — bring-your-own (blank = none yet)?');
471
511
  const targetRef = await ask(rl, ' Target credential ref? (which control-plane creds the runner should use; optional)');
472
512
  let tier = '';
@@ -518,7 +558,11 @@ function writeConfigs(spec, { dir, force, dryRun, coreDep, log = console.log })
518
558
  const pkgName = slugify((spec.identity && spec.identity.productName) || '', 'cloudbongos-instance');
519
559
  let existingPkg = null;
520
560
  if (fs.existsSync(pkgPath)) { try { existingPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch { existingPkg = null; } }
521
- const pkg = buildInstancePackageJson(existingPkg, { name: pkgName, coreDep });
561
+ // Compose the deps of the ENABLED modules (ADR 0138 / task 2160) so an instance that turns a
562
+ // bundled module on gets that module's npm deps (e.g. discord → discord.js) in its package.json,
563
+ // rather than crashing at boot on a dep @bongos/core deliberately doesn't carry (rehearsal F2).
564
+ const moduleDeps = enabledModuleDeps(modules);
565
+ const pkg = buildInstancePackageJson(existingPkg, { name: pkgName, coreDep, moduleDeps });
522
566
  const pinNote = pkg.dependencies[CORE_PKG];
523
567
 
524
568
  if (dryRun) {
@@ -579,7 +623,7 @@ function layerConfigs(spec, { dir, dryRun, log = console.log }) {
579
623
  }
580
624
 
581
625
  // `--adopt` package.json layering (ADR 0121 §Decision 2 / task 2042): add the
582
- // pinned @cloudbongos/core dependency to the owner's EXISTING package.json without
626
+ // pinned @bongos/core dependency to the owner's EXISTING package.json without
583
627
  // touching any other field, or MINT a minimal one when the repo has none. When the
584
628
  // dep is already present the file is left byte-untouched (no reserialize). An
585
629
  // unparseable package.json is left alone with a report — adopt never risks
@@ -785,10 +829,15 @@ async function main(argv) {
785
829
  const adopt = hasFlag('--adopt', args); // brownfield: LAYER onto an existing repo (ADR 0121)
786
830
  const accept = hasFlag('--accept', args) || hasFlag('--yes', args) || hasFlag('-y', args); // adopt pre-flight accept-gate (§Decision 5)
787
831
  const vendorCore = arg('--vendor-core', args); // greenfield: vendor + file:-pin a core .tgz, then generate the lockfile (ADR 0108 gap 1/4, task 2053)
832
+ const registryCore = hasFlag('--registry-core', args); // greenfield: caret pin + env-fed .npmrc → `npm ci` pulls the PRIVATE core from the registry (ADR 0134, task 2090)
788
833
 
789
834
  if (adopt && force) {
790
835
  console.error('init: --force is a greenfield-only overwrite; --adopt never overwrites, so --force is ignored.');
791
836
  }
837
+ if (vendorCore && registryCore) {
838
+ console.error('init: --vendor-core and --registry-core are mutually exclusive — the vendored tarball and the registry pull are two ways to resolve the same core dependency. Pick one.');
839
+ return 1;
840
+ }
792
841
 
793
842
  let raw;
794
843
  if (fromPath) {
@@ -841,7 +890,7 @@ async function main(argv) {
841
890
  console.log(`\nScaffolding "${spec.identity.productName}"${dryRun ? ' (dry run — nothing will be written or created)' : ''}:`);
842
891
  writeConfigs(spec, { dir, force, dryRun, coreDep });
843
892
 
844
- // Vendor the pinned @cloudbongos/core tarball + generate the lockfile so the scaffold is
893
+ // Vendor the pinned @bongos/core tarball + generate the lockfile so the scaffold is
845
894
  // `npm ci --omit=dev`-installable by the provisioner (ADR 0108 gap 1/4, task 2053). The
846
895
  // `npm install` here is the ONLY heavy step — it CREATES package-lock.json from the file:
847
896
  // tarball; the provisioner later runs `npm ci` against that committed lockfile.
@@ -849,14 +898,42 @@ async function main(argv) {
849
898
  if (dryRun) {
850
899
  console.log(`\n[dry-run] would vendor ${vendorPlan.tgz} → ${vendorPlan.vendorRel} and run \`npm install\` to generate package-lock.json`);
851
900
  } else {
852
- console.log('\nVendoring the pinned @cloudbongos/core + generating the lockfile:');
901
+ console.log('\nVendoring the pinned @bongos/core + generating the lockfile:');
853
902
  upgrade.vendorTarball({ instanceDir: dir, fromTgz: vendorPlan.tgz, targetVersion: vendorPlan.version });
854
903
  console.log(` ✓ vendored ${vendorPlan.vendorRel}`);
855
904
  const inst = upgrade.npmInstall(dir);
856
905
  if (!inst.ok) {
857
906
  console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — package-lock.json not generated; run \`npm install\` in ${dir} by hand before provisioning.`);
858
907
  } else {
859
- console.log(' ✓ npm install → package-lock.json generated (node_modules/@cloudbongos/core installed)');
908
+ console.log(' ✓ npm install → package-lock.json generated (node_modules/@bongos/core installed)');
909
+ }
910
+ }
911
+ }
912
+
913
+ // Registry-install path (ADR 0134, task 2090): keep the caret pin writeConfigs wrote
914
+ // and add an env-fed .npmrc so `npm ci` pulls the PRIVATE @bongos/core from the
915
+ // registry. With NPM_TOKEN present we also generate package-lock.json (a reproducible
916
+ // `npm ci` later); without a token we still write the caret pin + .npmrc so the
917
+ // scaffold is registry-shaped, and say plainly that the private package 404s until a
918
+ // token is provided — the expected tokenless outcome the ADR calls to verify.
919
+ if (registryCore) {
920
+ if (dryRun) {
921
+ console.log(`\n[dry-run] would write .npmrc (env-fed @bongos auth) and ${hasNpmToken()
922
+ ? 'run `npm install` to generate package-lock.json from the registry'
923
+ : 'SKIP lockfile generation (no NPM_TOKEN — the private core 404s until one is provided)'}`);
924
+ } else {
925
+ console.log('\nWiring the registry-install path (ADR 0134):');
926
+ writeNpmrc(dir, { log: console.log });
927
+ if (hasNpmToken()) {
928
+ const inst = upgrade.npmInstall(dir);
929
+ if (!inst.ok) {
930
+ console.error(` ! npm install failed (${inst.reason || 'exit ' + inst.code}) — confirm NPM_TOKEN reaches the private @bongos org; package-lock.json not generated.`);
931
+ } else {
932
+ console.log(' ✓ npm install → package-lock.json generated (pulled @bongos/core from the registry)');
933
+ }
934
+ } else {
935
+ console.log(' • no NPM_TOKEN in the environment — wrote the caret pin + .npmrc but skipped lockfile generation.');
936
+ console.log(' Provide a read-only automation token (paid `bongos` org) as NPM_TOKEN before `npm ci`; without it the private package 404s cleanly.');
860
937
  }
861
938
  }
862
939
  }
@@ -868,6 +945,23 @@ async function main(argv) {
868
945
  console.log('\nMaterializing the .claude/ methodology surface:');
869
946
  materializeClaude({ coreRoot: resolveCoreRoot(), instanceDir: dir, dryRun, log: console.log });
870
947
 
948
+ // Regenerate the per-instance API artifacts (OpenAPI spec + api-reference + typed
949
+ // client) from the PINNED core's routes so this fresh consumer's COMMITTED artifacts
950
+ // match its core from day one — not the core package's build-time snapshot (task 2141).
951
+ // They ship as CORE starters (task 2078 — the hall/docs must render before first regen)
952
+ // yet are per-instance GENERATED; `bongos upgrade` does the same on a bump, this closes
953
+ // the gap at standup. Best-effort: a hiccup warns, never fails init — the instance-root
954
+ // drift guard (fitness `gen-api-docs --check` + the api_client byte-identity test)
955
+ // catches any lag on the consumer's first CI.
956
+ if (dryRun) {
957
+ console.log('\n[dry-run] would regenerate the API artifacts (OpenAPI spec + reference + typed client) from the pinned core');
958
+ } else {
959
+ console.log('\nRegenerating the API artifacts from the pinned core (OpenAPI spec + reference + typed client):');
960
+ const regen = upgrade.regenerateApiArtifacts({ instanceDir: dir, coreRoot: resolveCoreRoot() });
961
+ if (regen.ok) console.log(` ✓ regenerated (${regen.ran.join(', ')}) → docs/api/ + docs/api-reference.md + clients/bongos-client/`);
962
+ else console.error(` ! API-artifact regen skipped (${regen.reason}) — regenerate in ${dir} by hand; the instance-root fitness gate flags the lag otherwise.`);
963
+ }
964
+
871
965
  let seedResult = {};
872
966
  if (!noSeed) seedResult = (await seedViaApi(spec, { dryRun })) || {};
873
967
  else console.log(' (--no-seed: skipped version/task seeding)');
@@ -893,6 +987,8 @@ module.exports = {
893
987
  KNOWN_MODULES, HOSTING_SHAPES, CORE_PKG,
894
988
  // greenfield installability (ADR 0108 gap 1/4, task 2053)
895
989
  buildInstancePackageJson, coreVersionSafe, INSTANCE_START_SCRIPT, INSTANCE_MIGRATE_SCRIPT,
990
+ // enabled-module dep composition (ADR 0138 / task 2160)
991
+ moduleDeclaredDeps, enabledModuleDeps,
896
992
  };
897
993
 
898
994
  if (require.main === module) {