@bongos/core 1.17.1 → 1.17.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +521 -281
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +2 -0
- package/.claude/skills/builder-exit/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +3 -3
- package/.claude/skills/builder-setup/SKILL.md +2 -2
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/dev/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +2 -2
- package/.claude/skills/ideate/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/new-project/SKILL.md +3 -0
- package/.claude/skills/paint/SKILL.md +1 -1
- package/GOVERNANCE.md +1 -1
- package/README.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +4 -2
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0001-stack-choice-single-vps.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
- package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
- package/docs/adr/0057-container-cost-ledger.md +3 -3
- package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
- package/docs/adr/0096-require-reward-before-workable.md +9 -9
- package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
- package/docs/adr/0100-two-repo-instance-model.md +1 -1
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
- package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
- package/docs/adr/README.md +9 -6
- package/docs/api/openapi.json +65 -26
- package/docs/api-reference.md +3 -2
- package/docs/architecture.md +2 -2
- package/docs/branding-contract.md +5 -4
- package/docs/canonical-permissions.md +1 -1
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +18 -17
- package/docs/handoff-template.md +2 -2
- package/docs/modules-contract.md +6 -1
- package/docs/onboarding/diagrams/README.md +7 -7
- package/docs/onboarding/drachmae-and-karma.md +18 -18
- package/docs/onboarding/primer.md +9 -9
- package/docs/recipes/audit-replay.md +2 -2
- package/docs/recipes/builder-box-lifecycle.md +2 -2
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/ops-gotchas.md +1 -1
- package/docs/recipes/private-npm-distribution.md +17 -0
- package/docs/recipes/staging-environment.md +1 -1
- package/docs/recipes/upgrading-the-core.md +55 -0
- package/docs/research/cursor-competitive-analysis.md +1 -1
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
- package/modules/discord/module.json +2 -1
- package/modules/economy/achievements.js +9 -1
- package/modules/economy/credits.js +10 -1
- package/modules/hall-ui/public/builders.js +137 -620
- package/modules/hall-ui/public/goal-inbox.js +232 -0
- package/modules/hall-ui/public/goals-page.js +4 -1
- package/modules/hall-ui/public/goals.html +8 -0
- package/modules/hall-ui/public/hall-kit.js +474 -0
- package/modules/hall-ui/public/hall-widgets.js +14 -5
- package/modules/hall-ui/public/index.html +3 -0
- package/modules/hall-ui/public/instances-panel.js +24 -3
- package/modules/hall-ui/public/new-project-wizard.js +7 -5
- package/modules/hall-ui/public/style.css +168 -0
- package/modules/hall-ui/public/work.css +194 -0
- package/modules/hall-ui/public/work.html +46 -374
- package/modules/hall-ui/public/work.js +137 -102
- package/modules/lifecycle/criterion-suggest.js +80 -0
- package/modules/lifecycle/db.js +42 -5
- package/modules/lifecycle/routes/goals.js +26 -0
- package/modules/lifecycle/routes/tasks.js +21 -0
- package/modules/provisioning/CLAUDE.md +15 -0
- package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
- package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
- package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
- package/modules/provisioning/module.json +17 -0
- package/modules/provisioning/onboard-plan.js +37 -0
- package/modules/provisioning/provisioning.js +539 -0
- package/modules/provisioning/rate-limit.js +35 -0
- package/modules/provisioning/routes/provisioning.js +607 -0
- package/modules/provisioning/tests/provisioning.mjs +232 -0
- package/modules/provisioning/ui/.gitkeep +0 -0
- package/modules/security/secret-scrub.js +10 -0
- package/package-lock.json +2 -2
- package/package.json +7 -2
- package/scripts/discord/README.md +1 -1
- package/scripts/gds/claim.js +98 -48
- package/scripts/gds/claude-materialize.js +30 -9
- package/scripts/gds/cutover-codemod.js +241 -0
- package/scripts/gds/fitness.js +54 -0
- package/scripts/gds/init.js +59 -3
- package/scripts/gds/lint-infra-exec.js +70 -0
- package/scripts/gds/lint-workflow-exec.js +63 -0
- package/scripts/gds/mirror-lineage-check.js +14 -5
- package/scripts/gds/mirror-redact.js +8 -0
- package/scripts/gds/onboard.js +4 -0
- package/scripts/gds/package-core.js +18 -3
- package/scripts/gds/publish-manifest.js +60 -2
- package/scripts/gds/run-routine.js +51 -13
- package/scripts/gds/run-unit-tests.js +4 -0
- package/scripts/gds/update-channel.js +186 -0
- package/scripts/gds/upgrade.js +116 -9
- package/scripts/hall-preview/README.md +103 -0
- package/scripts/hall-preview/fixtures/access-requests.json +1 -0
- package/scripts/hall-preview/fixtures/achievements.json +76 -0
- package/scripts/hall-preview/fixtures/analytics__builder.json +251 -0
- package/scripts/hall-preview/fixtures/audit-log.json +1 -0
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +1 -0
- package/scripts/hall-preview/fixtures/builders__active-claims.json +1 -0
- package/scripts/hall-preview/fixtures/builders__directory.json +81 -0
- package/scripts/hall-preview/fixtures/builders__roster.json +1 -0
- package/scripts/hall-preview/fixtures/goals.json +502 -0
- package/scripts/hall-preview/fixtures/goals__25.json +96 -0
- package/scripts/hall-preview/fixtures/goals__26.json +67 -0
- package/scripts/hall-preview/fixtures/goals__3.json +103 -0
- package/scripts/hall-preview/fixtures/goals__35.json +49 -0
- package/scripts/hall-preview/fixtures/goals__conflicts.json +154 -0
- package/scripts/hall-preview/fixtures/goals__detail.json +96 -0
- package/scripts/hall-preview/fixtures/goals__inbox.json +5 -0
- package/scripts/hall-preview/fixtures/goals__requests.json +3 -0
- package/scripts/hall-preview/fixtures/grades__by-builder.json +1 -0
- package/scripts/hall-preview/fixtures/inbox.json +330 -0
- package/scripts/hall-preview/fixtures/instance.json +124 -0
- package/scripts/hall-preview/fixtures/leaderboard.json +184 -0
- package/scripts/hall-preview/fixtures/me.json +476 -0
- package/scripts/hall-preview/fixtures/me__recent-ships.json +304 -0
- package/scripts/hall-preview/fixtures/override-requests.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__instances.json +1 -0
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +516 -0
- package/scripts/hall-preview/fixtures/public__bounty-table.json +37 -0
- package/scripts/hall-preview/fixtures/public__cost-summary.json +742 -0
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +41 -0
- package/scripts/hall-preview/fixtures/public__grades.json +50 -0
- package/scripts/hall-preview/fixtures/public__progress.json +124 -0
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +426 -0
- package/scripts/hall-preview/fixtures/public__repo-health.json +160 -0
- package/scripts/hall-preview/fixtures/public__repo-info.json +5 -0
- package/scripts/hall-preview/fixtures/security__docs.json +1 -0
- package/scripts/hall-preview/fixtures/security__reports.json +1 -0
- package/scripts/hall-preview/fixtures/sessions__search.json +1 -0
- package/scripts/hall-preview/fixtures/tasks.json +2420 -0
- package/scripts/hall-preview/fixtures/tasks__claimable.json +2555 -0
- package/scripts/hall-preview/fixtures/tasks__detail.json +111 -0
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +330 -0
- package/scripts/hall-preview/fixtures/versions.json +89 -0
- package/scripts/hall-preview/refresh-fixtures.js +77 -0
- package/scripts/hall-preview/server.js +178 -0
- package/scripts/migrate.sh +29 -4
- package/src/bongos/auth.js +14 -1
- package/src/bongos/optimizer.js +37 -2
- package/src/bongos/serve-internal.js +10 -1
- package/src/branding.js +1 -0
- package/src/module-api.js +12 -1
- package/src/module-loader/loader.js +36 -4
- package/src/module-loader/manifest-schema.js +16 -1
- package/tests/branding.mjs +11 -0
- package/tests/claim_error_surface.mjs +107 -0
- package/tests/claude_materialize.mjs +20 -0
- package/tests/consumer_layout_boot.mjs +302 -0
- package/tests/criterion_suggest.mjs +106 -0
- package/tests/cutover_codemod.mjs +184 -0
- package/tests/device_flow_start.mjs +25 -2
- package/tests/hook_core_resolve.mjs +125 -0
- package/tests/infra_exec_paths.mjs +104 -0
- package/tests/init.mjs +47 -0
- package/tests/migrate_root_resolution.mjs +97 -0
- package/tests/mirror_lineage_proof.mjs +14 -0
- package/tests/module_loader.mjs +42 -0
- package/tests/module_manifest.mjs +19 -0
- package/tests/onboard.mjs +13 -0
- package/tests/package_core.mjs +8 -0
- package/tests/provisioning_github_app_routes.mjs +15 -0
- package/tests/publish_manifest.mjs +52 -1
- package/tests/run_routine.mjs +26 -1
- package/tests/run_routine_two_root.mjs +77 -0
- package/tests/scrubber_corpus.mjs +17 -0
- package/tests/update_channel.mjs +186 -0
- package/tests/upgrade.mjs +131 -0
- package/tests/workflow_exec_paths.mjs +98 -0
- package/docs/design/container-exterior-tiles.md +0 -49
- package/docs/design/container-exterior.md +0 -81
- package/docs/design/container-interior.md +0 -77
- package/docs/design/world-map-scroll.md +0 -41
- package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
- package/docs/recipes/cloudbongos-staging-gate.md +0 -139
- package/docs/recipes/cloudbongos-standup.md +0 -111
- package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
- package/docs/recipes/parallel-load-test.md +0 -32
- package/docs/recipes/pixel-art-pipeline.md +0 -411
- package/docs/recipes/staging-data.md +0 -50
- package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
- package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
- package/migrations/instance/110_medusa_planning_seed.sql +0 -70
- package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
- package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
- package/scripts/gds/art-stage.js +0 -152
- package/server.js +0 -212
- package/tests/art_key.mjs +0 -121
- package/tests/day_cycle.mjs +0 -197
- package/tests/snapshot_picker.mjs +0 -233
- package/tests/sprite_facing.mjs +0 -35
- package/tests/terrain_picker.mjs +0 -298
- package/tests/turn_in_place.mjs +0 -280
- 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
|
+
}
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -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
|
];
|
package/scripts/gds/init.js
CHANGED
|
@@ -133,7 +133,13 @@ const INSTANCE_MIGRATE_SCRIPT = `bash ${INSTANCE_CORE_PKG_DIR}/scripts/migrate.s
|
|
|
133
133
|
// standalone instance runs. Preserves any pre-existing fields + scripts (fills only what
|
|
134
134
|
// is missing); greenfield OWNS the core dep, so it is always (re)set to `coreDep`. Never
|
|
135
135
|
// spawns npm — generating the lockfile is a separate imperative step in main().
|
|
136
|
-
|
|
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 } = {}) {
|
|
137
143
|
const dep = coreDep || coreDepRange();
|
|
138
144
|
const pkg = isObj(existing) ? { ...existing } : {};
|
|
139
145
|
if (!pkg.name) pkg.name = name;
|
|
@@ -141,10 +147,37 @@ function buildInstancePackageJson(existing, { name = 'cloudbongos-instance', cor
|
|
|
141
147
|
pkg.scripts = { ...(isObj(pkg.scripts) ? pkg.scripts : {}) };
|
|
142
148
|
if (!pkg.scripts.start) pkg.scripts.start = INSTANCE_START_SCRIPT;
|
|
143
149
|
if (!pkg.scripts.migrate) pkg.scripts.migrate = INSTANCE_MIGRATE_SCRIPT;
|
|
144
|
-
pkg.dependencies = {
|
|
150
|
+
pkg.dependencies = {
|
|
151
|
+
...(isObj(moduleDeps) ? moduleDeps : {}),
|
|
152
|
+
...(isObj(pkg.dependencies) ? pkg.dependencies : {}),
|
|
153
|
+
[CORE_PKG]: dep,
|
|
154
|
+
};
|
|
145
155
|
return pkg;
|
|
146
156
|
}
|
|
147
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
|
+
|
|
148
181
|
// ---------------------------------------------------------------------------
|
|
149
182
|
// Pure builders (exported for tests — no I/O, no network)
|
|
150
183
|
// ---------------------------------------------------------------------------
|
|
@@ -525,7 +558,11 @@ function writeConfigs(spec, { dir, force, dryRun, coreDep, log = console.log })
|
|
|
525
558
|
const pkgName = slugify((spec.identity && spec.identity.productName) || '', 'cloudbongos-instance');
|
|
526
559
|
let existingPkg = null;
|
|
527
560
|
if (fs.existsSync(pkgPath)) { try { existingPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch { existingPkg = null; } }
|
|
528
|
-
|
|
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 });
|
|
529
566
|
const pinNote = pkg.dependencies[CORE_PKG];
|
|
530
567
|
|
|
531
568
|
if (dryRun) {
|
|
@@ -908,6 +945,23 @@ async function main(argv) {
|
|
|
908
945
|
console.log('\nMaterializing the .claude/ methodology surface:');
|
|
909
946
|
materializeClaude({ coreRoot: resolveCoreRoot(), instanceDir: dir, dryRun, log: console.log });
|
|
910
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
|
+
|
|
911
965
|
let seedResult = {};
|
|
912
966
|
if (!noSeed) seedResult = (await seedViaApi(spec, { dryRun })) || {};
|
|
913
967
|
else console.log(' (--no-seed: skipped version/task seeding)');
|
|
@@ -933,6 +987,8 @@ module.exports = {
|
|
|
933
987
|
KNOWN_MODULES, HOSTING_SHAPES, CORE_PKG,
|
|
934
988
|
// greenfield installability (ADR 0108 gap 1/4, task 2053)
|
|
935
989
|
buildInstancePackageJson, coreVersionSafe, INSTANCE_START_SCRIPT, INSTANCE_MIGRATE_SCRIPT,
|
|
990
|
+
// enabled-module dep composition (ADR 0138 / task 2160)
|
|
991
|
+
moduleDeclaredDeps, enabledModuleDeps,
|
|
936
992
|
};
|
|
937
993
|
|
|
938
994
|
if (require.main === module) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// lint-infra-exec.js — task 2137 (ADR 0108 configurable-root). The dry-run/lint that proves every
|
|
4
|
+
// host systemd unit running a CORE script still resolves AFTER the R89 split.
|
|
5
|
+
//
|
|
6
|
+
// The units (box-*, cohost-*, provision-*, search-index-db) keep living in the instance repo
|
|
7
|
+
// (infra/ is host topology) while scripts/gds/ moves into node_modules/@bongos/core, so each
|
|
8
|
+
// ExecStart runs through infra/core-node.sh, which resolves the script under the instance root OR
|
|
9
|
+
// its node_modules/@bongos/core (both layouts). "Resolves in a consumer layout" is provable from
|
|
10
|
+
// the package manifest: the referenced core script must be a PUBLISHABLE core file (isPublishable →
|
|
11
|
+
// it ships inside @bongos/core → present at node_modules/@bongos/core/<rel> post-install). We also
|
|
12
|
+
// assert it exists at the core root today (monolith resolution + a typo catch) and that NO unit
|
|
13
|
+
// still uses the bare repo-relative `node scripts/…` form (which resolves nothing post-split).
|
|
14
|
+
//
|
|
15
|
+
// Usage: node scripts/gds/lint-infra-exec.js (exit 0 = every unit resolves; 1 = violation)
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { isPublishable } = require('./publish-manifest');
|
|
20
|
+
const { resolveInstanceRoot, resolveCoreRoot } = require('../../src/instance-config');
|
|
21
|
+
|
|
22
|
+
const LAUNCHER = 'infra/core-node.sh';
|
|
23
|
+
// Repointed form: ExecStart=/usr/bin/env bash infra/core-node.sh <core-rel> [args]
|
|
24
|
+
const LAUNCHER_EXEC = /^ExecStart=.*\binfra\/core-node\.sh\s+(\S+)/;
|
|
25
|
+
// Old bare form: ExecStart=<node> scripts/… — must no longer appear on a core-script unit.
|
|
26
|
+
const BARE_EXEC = /^ExecStart=\S*node\s+(scripts\/\S+)/;
|
|
27
|
+
|
|
28
|
+
// Pure-ish (reads the fs): classify every infra/*.service ExecStart. Returns
|
|
29
|
+
// { ok, checked:[{unit,rel}], violations:[{unit,rel,why}] }. Roots are injectable for tests.
|
|
30
|
+
function lintInfraExec({ instanceRoot, coreRoot } = {}) {
|
|
31
|
+
const iRoot = instanceRoot || resolveInstanceRoot();
|
|
32
|
+
const cRoot = coreRoot || resolveCoreRoot();
|
|
33
|
+
const dir = path.join(iRoot, 'infra');
|
|
34
|
+
const checked = [];
|
|
35
|
+
const violations = [];
|
|
36
|
+
const files = fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => f.endsWith('.service')).sort() : [];
|
|
37
|
+
for (const f of files) {
|
|
38
|
+
for (const raw of fs.readFileSync(path.join(dir, f), 'utf8').split('\n')) {
|
|
39
|
+
const L = raw.trim();
|
|
40
|
+
if (!L.startsWith('ExecStart=')) continue;
|
|
41
|
+
const viaLauncher = LAUNCHER_EXEC.exec(L);
|
|
42
|
+
if (viaLauncher) {
|
|
43
|
+
const rel = viaLauncher[1];
|
|
44
|
+
if (!isPublishable(rel)) {
|
|
45
|
+
violations.push({ unit: f, rel, why: 'not a publishable core file — ABSENT from node_modules/@bongos/core in a consumer layout' });
|
|
46
|
+
} else if (!fs.existsSync(path.join(cRoot, rel))) {
|
|
47
|
+
violations.push({ unit: f, rel, why: `does not exist at the core root (${cRoot})` });
|
|
48
|
+
} else {
|
|
49
|
+
checked.push({ unit: f, rel });
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const bare = BARE_EXEC.exec(L);
|
|
54
|
+
if (bare && bare[1].startsWith('scripts/')) {
|
|
55
|
+
violations.push({ unit: f, rel: bare[1], why: `bare repo-relative ExecStart — route it through ${LAUNCHER}` });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { ok: violations.length === 0, checked, violations };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (require.main === module) {
|
|
63
|
+
const r = lintInfraExec();
|
|
64
|
+
for (const c of r.checked) console.log(` ✓ ${c.unit.padEnd(38)} ${c.rel} (publishable → resolves in a consumer layout)`);
|
|
65
|
+
for (const v of r.violations) console.error(` ✗ ${v.unit.padEnd(38)} ${v.rel} — ${v.why}`);
|
|
66
|
+
console.log(`\nlint-infra-exec: ${r.checked.length} core-script unit(s) resolve; ${r.violations.length} violation(s)`);
|
|
67
|
+
process.exit(r.ok ? 0 : 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { lintInfraExec };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// lint-workflow-exec.js — task 2136 (ADR 0108 configurable-root). The dry-run/lint that proves
|
|
4
|
+
// every .github/workflows/*.yml invocation of a CORE script still resolves AFTER the R89 split.
|
|
5
|
+
//
|
|
6
|
+
// Workflows keep living in the instance repo (.github/ is host topology) while scripts/gds/* +
|
|
7
|
+
// scripts/migrate.sh move into @bongos/core, so each core invocation runs through the both-layouts
|
|
8
|
+
// resolver: infra/core-node.sh for node targets, infra/core-path.sh for bash targets. "Resolves in
|
|
9
|
+
// a consumer layout" is provable from the package manifest: the referenced core script must be a
|
|
10
|
+
// PUBLISHABLE core file (ships in @bongos/core → present at node_modules/@bongos/core post-install)
|
|
11
|
+
// AND exist at the core root today. A BARE `node scripts/gds/x.js` / `bash scripts/migrate.sh` (not
|
|
12
|
+
// routed through the resolver) is a violation — it resolves nothing from the instance root
|
|
13
|
+
// post-split. Host scripts (scripts/art/*) stay at the instance root, so they are NOT flagged.
|
|
14
|
+
//
|
|
15
|
+
// Usage: node scripts/gds/lint-workflow-exec.js (exit 0 = all routed + resolve; 1 = violation)
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { isPublishable } = require('./publish-manifest');
|
|
20
|
+
const { resolveInstanceRoot, resolveCoreRoot } = require('../../src/instance-config');
|
|
21
|
+
|
|
22
|
+
// Routed: `… core-node.sh <rel>` / `… core-path.sh <rel>` (rel = a scripts/… core path).
|
|
23
|
+
const ROUTED = /core-(?:node|path)\.sh\s+(scripts\/[^\s"')]+)/g;
|
|
24
|
+
// Bare core invocation the resolver should have wrapped. Core scripts = scripts/gds/*.{js,sh} +
|
|
25
|
+
// scripts/migrate.sh; host scripts/art/* deliberately excluded (they stay at the instance root).
|
|
26
|
+
const BARE = /(?:^|[^/\w])(?:node|bash)\s+(scripts\/(?:gds\/[^\s"']+\.(?:js|sh)|migrate\.sh))/;
|
|
27
|
+
|
|
28
|
+
function lintWorkflowExec({ instanceRoot, coreRoot } = {}) {
|
|
29
|
+
const iRoot = instanceRoot || resolveInstanceRoot();
|
|
30
|
+
const cRoot = coreRoot || resolveCoreRoot();
|
|
31
|
+
const dir = path.join(iRoot, '.github', 'workflows');
|
|
32
|
+
const checked = [];
|
|
33
|
+
const violations = [];
|
|
34
|
+
const files = fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => /\.ya?ml$/.test(f)).sort() : [];
|
|
35
|
+
for (const f of files) {
|
|
36
|
+
for (const raw of fs.readFileSync(path.join(dir, f), 'utf8').split('\n')) {
|
|
37
|
+
const line = raw.replace(/\s+$/, '');
|
|
38
|
+
if (line.trim().startsWith('#')) continue; // a YAML/shell comment — reference, not an invocation
|
|
39
|
+
let m;
|
|
40
|
+
ROUTED.lastIndex = 0;
|
|
41
|
+
while ((m = ROUTED.exec(line)) !== null) {
|
|
42
|
+
const rel = m[1];
|
|
43
|
+
if (!isPublishable(rel)) violations.push({ file: f, rel, why: 'routed but NOT a publishable core file — absent from @bongos/core in a consumer layout' });
|
|
44
|
+
else if (!fs.existsSync(path.join(cRoot, rel))) violations.push({ file: f, rel, why: `routed but does not exist at the core root (${rel})` });
|
|
45
|
+
else checked.push({ file: f, rel });
|
|
46
|
+
}
|
|
47
|
+
if (/core-(?:node|path)\.sh/.test(line)) continue; // routed line — the arg was handled above
|
|
48
|
+
const bare = BARE.exec(line);
|
|
49
|
+
if (bare) violations.push({ file: f, rel: bare[1], why: 'bare core invocation — route it through infra/core-node.sh (node) or infra/core-path.sh (bash)' });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { ok: violations.length === 0, checked, violations };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (require.main === module) {
|
|
56
|
+
const r = lintWorkflowExec();
|
|
57
|
+
for (const c of r.checked) console.log(` ✓ ${c.file.padEnd(26)} ${c.rel} (publishable → resolves in a consumer layout)`);
|
|
58
|
+
for (const v of r.violations) console.error(` ✗ ${v.file.padEnd(26)} ${v.rel} — ${v.why}`);
|
|
59
|
+
console.log(`\nlint-workflow-exec: ${r.checked.length} routed core invocation(s) resolve; ${r.violations.length} violation(s)`);
|
|
60
|
+
process.exit(r.ok ? 0 : 1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { lintWorkflowExec };
|
|
@@ -55,9 +55,11 @@ const { nextFree } = require(path.join(REPO_ROOT, 'scripts/gds/next-number.js'))
|
|
|
55
55
|
// deterministic regardless of file order.
|
|
56
56
|
const KNOWN_SECRET_SHAPES = SECRET_PATTERNS.map((re) => new RegExp(re.source, re.flags.replace('g', '')));
|
|
57
57
|
|
|
58
|
-
// Core migrations are migrations/NNN_slug.sql (3+ digit prefix)
|
|
59
|
-
// live under modules/<key>/migrations/ and
|
|
60
|
-
//
|
|
58
|
+
// Core migrations are migrations/NNN_slug.sql (3+ digit prefix) DIRECTLY under
|
|
59
|
+
// migrations/. Module migrations live under modules/<key>/migrations/ and instance
|
|
60
|
+
// SEEDS under migrations/instance/ (ADR 0105) — both numbered independently and
|
|
61
|
+
// excluded from the publishable mirror; the claim-time allocator only reads the
|
|
62
|
+
// top-level core dir, so only core numbering can diverge.
|
|
61
63
|
const MIGRATION_BASENAME_RE = /^(\d{3,})_.*\.sql$/;
|
|
62
64
|
|
|
63
65
|
// ---- pure helpers (exported for tests) ------------------------------------
|
|
@@ -67,8 +69,15 @@ function migrationNumbersFromPaths(paths) {
|
|
|
67
69
|
const out = new Set();
|
|
68
70
|
for (const p of paths || []) {
|
|
69
71
|
const s = String(p);
|
|
70
|
-
if (!s.startsWith('migrations/')) continue; //
|
|
71
|
-
|
|
72
|
+
if (!s.startsWith('migrations/')) continue; // the migrations dir …
|
|
73
|
+
// … and ONLY its top level. migrations/instance/NNN_*.sql are this instance's host
|
|
74
|
+
// seeds (ADR 0105), excluded from the publishable mirror (task 2140) — counting them
|
|
75
|
+
// here put every instance seed in the PRIVATE set but not the MIRROR set, a false
|
|
76
|
+
// private⇄mirror divergence that red-lined the unit gate (task 2152). A path with a
|
|
77
|
+
// further slash is a subdir, never a core migration.
|
|
78
|
+
const rest = s.slice('migrations/'.length);
|
|
79
|
+
if (rest.includes('/')) continue;
|
|
80
|
+
const m = MIGRATION_BASENAME_RE.exec(rest);
|
|
72
81
|
if (m) out.add(parseInt(m[1], 10));
|
|
73
82
|
}
|
|
74
83
|
return out;
|
|
@@ -171,6 +171,14 @@ function deriveInstanceLiterals(branding = {}, extra = {}) {
|
|
|
171
171
|
add(owners, branding.firstAdmin);
|
|
172
172
|
const identity = branding.identity || {};
|
|
173
173
|
add(tokens, identity.worldName);
|
|
174
|
+
// The PRODUCT name is instance identity too. It was leaking into the published core (the
|
|
175
|
+
// onboarding primer described this instance's product as the reader's own project) because
|
|
176
|
+
// only worldName was derived — the redactor never saw it AND findLeaks couldn't gate it.
|
|
177
|
+
// Deriving it here closes both in one place. (task 2153)
|
|
178
|
+
add(tokens, identity.productName);
|
|
179
|
+
// The reward-currency label is per-instance identity as well; a vanilla core must not ship
|
|
180
|
+
// another instance's currency name (the currency/karma onboarding doc carried it verbatim).
|
|
181
|
+
add(tokens, (branding.currency || {}).label);
|
|
174
182
|
// The fulfillment/parent company is instance identity too (a coined single word like
|
|
175
183
|
// "Acme"); redact its name + its site's registrable label.
|
|
176
184
|
const company = identity.company || {};
|
package/scripts/gds/onboard.js
CHANGED
|
@@ -163,6 +163,10 @@ async function run() {
|
|
|
163
163
|
log(' RECOMMENDED — one click: in the control-plane hall, open “Start a project” →');
|
|
164
164
|
log(' “Set up GitHub sign-in”. Approve the pre-filled GitHub App; the control plane');
|
|
165
165
|
log(' places the credentials automatically (no secret to paste). Then skip to ⑦.');
|
|
166
|
+
log(' ⚠ Terminal CLI only: the one-click App cannot enable Device Flow via the manifest,');
|
|
167
|
+
log(' but `bongos login` needs it. After approving, open the app (github.com → Settings');
|
|
168
|
+
log(' → Developer settings → GitHub Apps → your app), check "Enable Device Flow", save.');
|
|
169
|
+
log(' Web sign-in works without this.');
|
|
166
170
|
log(' — or, to create an OAuth app BY HAND —');
|
|
167
171
|
log(` ${oa.location}`);
|
|
168
172
|
log(' (Developer settings is under your ACCOUNT, not the repository.)');
|
|
@@ -240,13 +240,28 @@ function buildPackageJson({ version, sourceDeps, sourceOverrides }) {
|
|
|
240
240
|
main: 'src/platform-server.js',
|
|
241
241
|
bin: { bongos: 'bin/bongos.js' },
|
|
242
242
|
// Named exports a consumer imports directly, alongside the default entry
|
|
243
|
-
// (buildPlatformApp, from `main`)
|
|
244
|
-
//
|
|
245
|
-
// module-api doorway a
|
|
243
|
+
// (buildPlatformApp, from `main`). Two groups:
|
|
244
|
+
// • the platform surface — serve-internal (the shared host-routing surface
|
|
245
|
+
// both server.js and platform-server.js call) + the module-api doorway a
|
|
246
|
+
// module's routes/seams are authored against.
|
|
247
|
+
// • the GAME-HOST KIT (rehearsal F3, task 2161) — the core internals a
|
|
248
|
+
// game-bearing instance's own entrypoint (server.js, kept host per task
|
|
249
|
+
// 2134) wires directly: the API-404 shaping, the uptime poller, the module
|
|
250
|
+
// loader, the enabled-module gate, and the seam registry. A game-less
|
|
251
|
+
// instance boots via `.`/platform-server.js and never imports these; a game
|
|
252
|
+
// instance's server.js requires them, and the cutover codemod
|
|
253
|
+
// (scripts/gds/cutover-codemod.js) rewrites its relative `./src/*` requires
|
|
254
|
+
// to exactly these subpaths. Every subpath the codemod's CORE_INTERNAL_MAP
|
|
255
|
+
// targets MUST appear here or the rewritten require won't resolve.
|
|
246
256
|
exports: {
|
|
247
257
|
'.': './src/platform-server.js',
|
|
248
258
|
'./serve-internal': './src/bongos/serve-internal.js',
|
|
249
259
|
'./module-api': './src/module-api.js',
|
|
260
|
+
'./bongos/api-path-404': './src/bongos/api-path-404.js',
|
|
261
|
+
'./bongos/uptime-poller': './src/bongos/uptime-poller.js',
|
|
262
|
+
'./module-loader/loader': './src/module-loader/loader.js',
|
|
263
|
+
'./modules': './src/modules.js',
|
|
264
|
+
'./module-seams': './src/module-seams.js',
|
|
250
265
|
},
|
|
251
266
|
engines: { node: '>=22' },
|
|
252
267
|
// Pre-launch distribution posture (task 2089 / ADR 0134): publish PRIVATE by
|