@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.
- package/.bongos-core.json +363 -312
- 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/CLAUDE.md +11 -238
- package/GOVERNANCE.md +1 -1
- package/README.md +1 -1
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +4 -4
- package/clients/bongos-client/bongos-client.global.js +14 -1
- package/clients/bongos-client/index.cjs +14 -1
- package/clients/bongos-client/index.d.ts +19 -2
- package/clients/bongos-client/index.mjs +11 -1
- package/clients/bongos-client/package.json +1 -1
- 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/0133-github-app-manifest-onboarding-signin.md +2 -0
- package/docs/adr/0134-private-first-npm-distribution.md +20 -6
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
- 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 +14 -10
- package/docs/api/openapi.json +315 -6
- package/docs/api-reference.md +10 -5
- package/docs/architecture.md +3 -3
- 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 +20 -19
- package/docs/handoff-template.md +2 -2
- package/docs/modules-contract.md +8 -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/instance-deploy.md +4 -4
- package/docs/recipes/instance-first-run.md +51 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/docs/recipes/packaging-the-core.md +5 -5
- package/docs/recipes/private-npm-distribution.md +39 -12
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/staging-environment.md +1 -1
- package/docs/recipes/upgrading-the-core.md +63 -8
- package/docs/research/cursor-competitive-analysis.md +1 -1
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
- package/migrations/core_191_module_submissions.sql +45 -0
- package/modules/autonomy/CLAUDE.md +11 -45
- package/modules/builder-settings/CLAUDE.md +11 -54
- package/modules/dev-box/CLAUDE.md +11 -34
- package/modules/discord/CLAUDE.md +11 -46
- package/modules/discord/module.json +2 -1
- package/modules/economy/CLAUDE.md +11 -44
- package/modules/economy/achievements.js +9 -1
- package/modules/economy/credits.js +10 -1
- package/modules/grading/CLAUDE.md +11 -38
- package/modules/hall-ui/CLAUDE.md +11 -57
- package/modules/hall-ui/public/builders.js +71 -4
- package/modules/hall-ui/public/goal-inbox.js +191 -0
- package/modules/hall-ui/public/goals-page.js +227 -22
- package/modules/hall-ui/public/goals.html +16 -0
- package/modules/hall-ui/public/hall-widgets.js +14 -1
- package/modules/hall-ui/public/index.html +3 -0
- package/modules/hall-ui/public/new-project-wizard.js +6 -4
- package/modules/hall-ui/public/style.css +43 -0
- package/modules/ideas/CLAUDE.md +11 -33
- package/modules/lifecycle/CLAUDE.md +11 -59
- package/modules/lifecycle/routes/goals.js +80 -0
- package/modules/memory/CLAUDE.md +11 -36
- package/modules/onboarding/CLAUDE.md +11 -51
- 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/CLAUDE.md +11 -40
- package/modules/security/secret-scrub.js +10 -0
- package/modules/sessions/CLAUDE.md +11 -35
- package/modules/status-ui/CLAUDE.md +11 -43
- package/package-lock.json +2 -2
- package/package.json +7 -2
- package/public-landing/create.html +58 -12
- package/public-landing/index.html +40 -21
- package/scripts/discord/README.md +1 -1
- package/scripts/gds/CLAUDE.md +11 -140
- package/scripts/gds/claim.js +98 -48
- package/scripts/gds/claude-materialize.js +174 -20
- package/scripts/gds/claude-md-template.js +84 -0
- package/scripts/gds/cli-lib.js +112 -2
- package/scripts/gds/cutover-codemod.js +241 -0
- package/scripts/gds/fitness.js +54 -0
- package/scripts/gds/gen-api-client.js +10 -10
- package/scripts/gds/init.js +112 -16
- package/scripts/gds/lint-infra-exec.js +70 -0
- package/scripts/gds/lint-workflow-exec.js +63 -0
- package/scripts/gds/migration-namespace.js +1 -1
- package/scripts/gds/mirror-lineage-check.js +14 -5
- package/scripts/gds/mirror-redact.js +8 -0
- package/scripts/gds/module.js +118 -4
- package/scripts/gds/npmrc.js +93 -0
- package/scripts/gds/onboard.js +17 -24
- package/scripts/gds/package-core.js +74 -17
- package/scripts/gds/provision.js +102 -17
- 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/seed-adopt-tasks.js +1 -1
- package/scripts/gds/status.js +6 -1
- package/scripts/gds/task.js +166 -0
- package/scripts/gds/update-channel.js +186 -0
- package/scripts/gds/upgrade.js +172 -25
- package/src/bongos/CLAUDE.md +11 -60
- package/src/bongos/auth.js +24 -0
- package/src/bongos/module-submissions.js +56 -0
- package/src/bongos/route-rank-check.js +6 -0
- package/src/bongos/routes/CLAUDE.md +11 -39
- package/src/bongos/routes/auth.js +6 -1
- package/src/bongos/routes/modules.js +123 -21
- package/src/bongos/serve-internal.js +10 -1
- package/src/branding.js +1 -0
- package/src/instance-config.js +1 -1
- package/src/module-api.js +24 -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 +125 -0
- package/tests/consumer_layout_boot.mjs +302 -0
- package/tests/cutover_codemod.mjs +184 -0
- package/tests/device_flow_start.mjs +96 -0
- package/tests/first_run_engine_check.mjs +79 -0
- package/tests/goal_membership_requests.mjs +33 -3
- package/tests/hook_core_resolve.mjs +125 -0
- package/tests/hook_user_agents.mjs +62 -0
- package/tests/infra_exec_paths.mjs +104 -0
- package/tests/init.mjs +106 -6
- package/tests/instance_mismatch_guard.mjs +101 -0
- package/tests/mirror_lineage_proof.mjs +14 -0
- package/tests/module_cli.mjs +130 -2
- package/tests/module_loader.mjs +42 -0
- package/tests/module_manifest.mjs +19 -0
- package/tests/module_submissions.mjs +135 -0
- package/tests/npmrc.mjs +105 -0
- package/tests/onboard.mjs +62 -0
- package/tests/package_core.mjs +76 -22
- package/tests/platform_boot.mjs +5 -2
- package/tests/provision.mjs +107 -8
- package/tests/provisioning_github_app_routes.mjs +44 -2
- 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/served_paths_shipped.mjs +2 -2
- package/tests/task_cli.mjs +99 -0
- package/tests/update_channel.mjs +186 -0
- package/tests/upgrade.mjs +220 -25
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/gds/routes/modules.js — the hall "Modules" tab backend (ADR 0107 §9,
|
|
2
|
-
// task 1776).
|
|
2
|
+
// task 1776). Four jobs, matching the ADR:
|
|
3
3
|
//
|
|
4
4
|
// GET /modules list every on-disk module: enabled state,
|
|
5
5
|
// attribution (author/origin/license/maintainer),
|
|
@@ -9,37 +9,72 @@
|
|
|
9
9
|
// and reload the live process (§6: default-off
|
|
10
10
|
// respected — this only ever flips an existing
|
|
11
11
|
// module's flag, never invents a new key).
|
|
12
|
-
// POST /modules/:key/submit the "submit for review" front door (§1)
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
12
|
+
// POST /modules/:key/submit the "submit for review" front door (§1) — files
|
|
13
|
+
// a packaged submission into the upstreaming
|
|
14
|
+
// review queue (task 1770). Re-runs the §4
|
|
15
|
+
// pre-check + requires a DCO sign-off against
|
|
16
|
+
// THIS box's own on-disk copy of the module (the
|
|
17
|
+
// authoritative check — `bongos module submit`
|
|
18
|
+
// is a thin CLI client of this same route).
|
|
19
|
+
// GET /modules/submissions list queued submissions (task 1771 reads this).
|
|
16
20
|
//
|
|
17
21
|
// Read is open to any signed-in builder (same gate as the atlas/primer pages);
|
|
18
|
-
//
|
|
19
|
-
// it changes running behavior for the whole instance
|
|
22
|
+
// every write (enable/disable, submit) is metic+archon, same rank as
|
|
23
|
+
// /backup/trigger — it changes running behavior for the whole instance (or
|
|
24
|
+
// files something into a shared review queue), not just the caller.
|
|
20
25
|
//
|
|
21
|
-
// Persistence note:
|
|
22
|
-
// in-process cache, so the effect is immediate on this box.
|
|
23
|
-
// hand-edit of that file, surviving the next `git reset --hard`
|
|
24
|
-
// the change committed — this route does not auto-commit (an
|
|
25
|
-
// auto-pushing to main bypasses the ship/grade/deploy pipeline,
|
|
26
|
-
// project's trust boundary treats as a bigger risk than a toggle
|
|
27
|
-
// human/agent to land it properly). Captured as a follow-up
|
|
28
|
-
// (DB-backed) module enablement that survives a deploy with no
|
|
26
|
+
// Persistence note: /modules/:key/enable writes config/modules.json on disk
|
|
27
|
+
// and reloads the in-process cache, so the effect is immediate on this box.
|
|
28
|
+
// Like any other hand-edit of that file, surviving the next `git reset --hard`
|
|
29
|
+
// deploy needs the change committed — this route does not auto-commit (an
|
|
30
|
+
// HTTP handler auto-pushing to main bypasses the ship/grade/deploy pipeline,
|
|
31
|
+
// which the project's trust boundary treats as a bigger risk than a toggle
|
|
32
|
+
// that needs a human/agent to land it properly). Captured as a follow-up
|
|
33
|
+
// idea: durable (DB-backed) module enablement that survives a deploy with no
|
|
34
|
+
// extra step.
|
|
35
|
+
//
|
|
36
|
+
// Why the review queue is a table in THIS instance's own DB (not a live
|
|
37
|
+
// cross-repo call to the extracted core repo): see
|
|
38
|
+
// docs/adr/0135-module-upstream-submission-interim-queue.md.
|
|
29
39
|
|
|
30
40
|
const express = require('express');
|
|
31
41
|
const fs = require('node:fs');
|
|
32
42
|
const path = require('node:path');
|
|
33
43
|
const auth = require('../auth');
|
|
34
|
-
const { asyncHandler, validateOrRespond } = require('./_helpers');
|
|
44
|
+
const { asyncHandler, validateOrRespond, LIMITS } = require('./_helpers');
|
|
35
45
|
const loader = require('../../module-loader/loader');
|
|
36
46
|
const { deriveProvenance } = require('../../module-loader/provenance');
|
|
37
47
|
const modulesLib = require('../../modules');
|
|
48
|
+
// scripts/gds/ is a sibling CORE-package dir (both resolve under
|
|
49
|
+
// resolveCoreRoot() — see scripts/gds/CLAUDE.md), so requiring its pure fs
|
|
50
|
+
// logic from here is in-boundary. module.js never statically requires the CLI
|
|
51
|
+
// session/fetch layer (cli-lib.js) — only cmdSubmit lazy-requires it, and only
|
|
52
|
+
// when actually invoked from the CLI — so this stays safe in the always-on
|
|
53
|
+
// server process.
|
|
54
|
+
const moduleCli = require('../../../scripts/gds/module');
|
|
55
|
+
const moduleSubmissions = require('../module-submissions');
|
|
56
|
+
|
|
57
|
+
// A packaged submission embeds every file's content (base64) — cap the total
|
|
58
|
+
// so a runaway/misbuilt module can't bloat the review-queue table. Generous
|
|
59
|
+
// headroom over what a real feature module carries (routes + migrations + UI
|
|
60
|
+
// + a CLAUDE.md are typically tens of KB); mirrors the security_docs content
|
|
61
|
+
// cap (SECURITY_DOC_CONTENT_MAX) in routes/security.js.
|
|
62
|
+
const SUBMISSION_MAX_BYTES = 5_000_000;
|
|
38
63
|
|
|
39
64
|
function ownProjectName() {
|
|
40
65
|
try { return require('../../branding').branding().identity.productName; } catch { return null; }
|
|
41
66
|
}
|
|
42
67
|
|
|
68
|
+
// "owner/name", e.g. "lars589/amazonprimea" — the provenance a submission
|
|
69
|
+
// carries (ADR 0107 §2: attribution/provenance travels WITH the module as a
|
|
70
|
+
// portable record, not a cross-GDS link). null if the branding pack has none.
|
|
71
|
+
function sourceRepoLabel() {
|
|
72
|
+
try {
|
|
73
|
+
const repo = require('../../branding').branding().repo;
|
|
74
|
+
return repo && repo.name ? `${repo.owner || '?'}/${repo.name}` : null;
|
|
75
|
+
} catch { return null; }
|
|
76
|
+
}
|
|
77
|
+
|
|
43
78
|
function readInstanceConfig() {
|
|
44
79
|
try { return JSON.parse(fs.readFileSync(modulesLib.INSTANCE_PATH, 'utf8')); }
|
|
45
80
|
catch (e) { if (e.code === 'ENOENT') return { modules: {} }; throw e; }
|
|
@@ -78,7 +113,7 @@ module.exports = function buildModulesRouter() {
|
|
|
78
113
|
modules: all,
|
|
79
114
|
own_built: all.filter((m) => m.provenance === 'own').length,
|
|
80
115
|
core_loaded: all.filter((m) => m.provenance === 'core').length,
|
|
81
|
-
submit_pipeline_live:
|
|
116
|
+
submit_pipeline_live: true, // task 1770 shipped — POST /modules/:key/submit is real
|
|
82
117
|
});
|
|
83
118
|
}));
|
|
84
119
|
|
|
@@ -108,7 +143,17 @@ module.exports = function buildModulesRouter() {
|
|
|
108
143
|
res.json({ ok: true, key, enabled, note: 'Live on this box now. Persisting past the next deploy needs config/modules.json committed.' });
|
|
109
144
|
}));
|
|
110
145
|
|
|
111
|
-
//
|
|
146
|
+
// POST /api/gds/modules/:key/submit — ADR 0107 §1 upstreaming submission.
|
|
147
|
+
//
|
|
148
|
+
// task 1770. Body: { signed_by?: "Name <email>" }. Re-runs the §4
|
|
149
|
+
// AGPL/publish pre-check + requires a DCO-style sign-off against THIS box's
|
|
150
|
+
// OWN on-disk copy of modules/:key (the authoritative check — a local
|
|
151
|
+
// `bongos module check` pass on a builder's box is a courtesy, not proof, if
|
|
152
|
+
// the module's changes aren't committed + shipped + deployed here yet).
|
|
153
|
+
// signed_by records a NEW sign-off only if none is on file; an existing
|
|
154
|
+
// .upstream-signoff.json is reused as-is (not overwritten).
|
|
155
|
+
//
|
|
156
|
+
// rank: metic+archon — files into a shared review queue, not just a toggle for the caller.
|
|
112
157
|
router.post('/modules/:key/submit', auth.requireBuilder, auth.requireRank('metic', 'archon'),
|
|
113
158
|
asyncHandler('POST /modules/:key/submit', async (req, res) => {
|
|
114
159
|
const { key } = req.params;
|
|
@@ -116,10 +161,67 @@ module.exports = function buildModulesRouter() {
|
|
|
116
161
|
if (!known.find((m) => m.key === key)) {
|
|
117
162
|
return res.fail('not_found', { status: 404, message: `No on-disk module "${key}".` });
|
|
118
163
|
}
|
|
119
|
-
res
|
|
120
|
-
|
|
121
|
-
|
|
164
|
+
if (validateOrRespond(req, res, { signed_by: { type: 'string', maxLength: LIMITS.TITLE } })) return;
|
|
165
|
+
|
|
166
|
+
const precheck = moduleCli.checkModulePublishability(key);
|
|
167
|
+
if (!precheck.ok) {
|
|
168
|
+
return res.fail('precheck_failed', {
|
|
169
|
+
status: 422,
|
|
170
|
+
message: `"${key}" is not clear to submit upstream (ADR 0107 §4 AGPL/publish pre-check failed).`,
|
|
171
|
+
findings: precheck.findings,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let signOff = moduleCli.readSignOff(key);
|
|
176
|
+
const signedBy = req.body && typeof req.body.signed_by === 'string' ? req.body.signed_by.trim() : '';
|
|
177
|
+
if (!signOff && signedBy) {
|
|
178
|
+
moduleCli.recordSignOff(key, signedBy);
|
|
179
|
+
signOff = moduleCli.readSignOff(key);
|
|
180
|
+
}
|
|
181
|
+
if (!signOff) {
|
|
182
|
+
return res.fail('sign_off_required', {
|
|
183
|
+
status: 422,
|
|
184
|
+
message: `No DCO-style sign-off on file for "${key}". Pass "signed_by": "Name <email>" to affirm + record it.`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const pkg = moduleCli.packageModuleForSubmission(key);
|
|
189
|
+
if (pkg.totalBytes > SUBMISSION_MAX_BYTES) {
|
|
190
|
+
return res.fail('submission_too_large', {
|
|
191
|
+
status: 413,
|
|
192
|
+
message: `"${key}" is ${pkg.totalBytes} bytes, over the ${SUBMISSION_MAX_BYTES}-byte upstreaming cap.`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const submission = await moduleSubmissions.createModuleSubmission({
|
|
197
|
+
moduleKey: key,
|
|
198
|
+
submittedBy: req.builder.id,
|
|
199
|
+
manifest: pkg.manifest,
|
|
200
|
+
files: pkg.files,
|
|
201
|
+
signOff,
|
|
202
|
+
precheckPassedAt: new Date(),
|
|
203
|
+
sourceRepo: sourceRepoLabel(),
|
|
122
204
|
});
|
|
205
|
+
|
|
206
|
+
console.log(`[gds] module "${key}" submitted upstream by builder ${req.builder.id} — submission ${submission.id}`);
|
|
207
|
+
res.status(201).json({ ok: true, submission });
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
// GET /api/gds/modules/submissions?status=&module_key= — the upstreaming
|
|
211
|
+
// review queue (task 1771 reads this). Metadata only — no file contents (a
|
|
212
|
+
// future GET .../:id can add a with-files fetch if the review UI needs it).
|
|
213
|
+
//
|
|
214
|
+
// rank: metic+archon — the review queue, same rank as filing into it.
|
|
215
|
+
router.get('/modules/submissions', auth.requireBuilder, auth.requireRank('metic', 'archon'),
|
|
216
|
+
asyncHandler('GET /modules/submissions', async (req, res) => {
|
|
217
|
+
const status = req.query.status ? String(req.query.status) : undefined;
|
|
218
|
+
if (status && !['pending', 'accepted', 'rejected'].includes(status)) {
|
|
219
|
+
return res.fail('bad_status', { status: 400, message: 'status must be one of pending|accepted|rejected.' });
|
|
220
|
+
}
|
|
221
|
+
const moduleKey = req.query.module_key ? String(req.query.module_key) : undefined;
|
|
222
|
+
const submissions = await moduleSubmissions.listModuleSubmissions({ status, moduleKey });
|
|
223
|
+
res.set('Cache-Control', 'no-store');
|
|
224
|
+
res.json({ submissions });
|
|
123
225
|
}));
|
|
124
226
|
|
|
125
227
|
return router;
|
|
@@ -318,7 +318,16 @@ function mountInternalSurfaces(app) {
|
|
|
318
318
|
// confirmation can commit-PIN (deployedCommit === mergeCommit) rather than only
|
|
319
319
|
// checking liveness via /healthz. Low-sensitivity (a public commit sha).
|
|
320
320
|
app.get('/version', (_req, res) => {
|
|
321
|
-
|
|
321
|
+
// coreVersion — the @bongos/core doorway version this process is running
|
|
322
|
+
// (src/module-api CORE_VERSION), read lazily to avoid a load-time cycle
|
|
323
|
+
// (module-api requires build-info at load, so a top-level require here
|
|
324
|
+
// would be circular). Additive: lets fleet/upgrade tooling confirm which
|
|
325
|
+
// core an instance actually materialized after a `bongos upgrade`,
|
|
326
|
+
// alongside the deployed git commit below. (Added in 1.17.2, task 2148 —
|
|
327
|
+
// the throwaway `updateCanary` proof marker was removed in phase E.)
|
|
328
|
+
let coreVersion = null;
|
|
329
|
+
try { coreVersion = require('../module-api').CORE_VERSION; } catch { /* fail-soft: version unknown */ }
|
|
330
|
+
res.json({ ...buildInfo(), coreVersion });
|
|
322
331
|
});
|
|
323
332
|
|
|
324
333
|
// Generated typed client for classic <script> web pages (task 1996 / R09).
|
package/src/branding.js
CHANGED
|
@@ -42,6 +42,7 @@ const ENV_OVERRIDES = [
|
|
|
42
42
|
['BUILDERS_ORIGIN', ['domains', 'buildersOrigin']],
|
|
43
43
|
['STATUS_ORIGIN', ['domains', 'statusOrigin']],
|
|
44
44
|
['OAUTH_ORIGIN', ['domains', 'oauthOrigin']],
|
|
45
|
+
['PROVISIONING_ORIGIN', ['domains', 'provisioningOrigin']],
|
|
45
46
|
['COOKIE_DOMAIN', ['domains', 'cookieDomain']],
|
|
46
47
|
];
|
|
47
48
|
|
package/src/instance-config.js
CHANGED
|
@@ -172,7 +172,7 @@ function resolveInstanceRoot({ env = process.env } = {}) {
|
|
|
172
172
|
// is written/read. A consumer that installs the core as an npm dep points at its
|
|
173
173
|
// own repo (via <PREFIX>_INSTANCE_ROOT / bongos.config.json — e.g. `bongos
|
|
174
174
|
// upgrade` sets it), so the docs+client regenerate INTO the instance, not into
|
|
175
|
-
// node_modules/@
|
|
175
|
+
// node_modules/@bongos/core. In a single checkout no pointer is set, so this
|
|
176
176
|
// is resolveCoreRoot() — BYTE-IDENTICAL to today (ADR 0108). Deliberately NOT the
|
|
177
177
|
// cwd fallback: a generated-doc write must be deterministic.
|
|
178
178
|
function resolveDocsRoot(opts = {}) {
|
package/src/module-api.js
CHANGED
|
@@ -176,8 +176,31 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
176
176
|
// the same reason validateOrRespond/LIMITS are on the doorway. Additive
|
|
177
177
|
// (new exports, no export removed or changed) → MINOR; every `^1.x`
|
|
178
178
|
// module keeps working, and a module using them is satisfied by 1.17.0.
|
|
179
|
+
// 1.17.1 — task 2124 (republish after task 2121): the FIRST PATCH bump on this
|
|
180
|
+
// surface. No doorway export is added, changed, or removed — the exported
|
|
181
|
+
// surface is byte-identical to 1.17.0. What changed is the PUBLISHED ARTIFACT's
|
|
182
|
+
// content: (a) task 2121 taught package-core to replace every CLAUDE.md/AGENTS.md
|
|
183
|
+
// in the @bongos/core tarball with a tiny brand-free stub, so a consumer instance's
|
|
184
|
+
// agent no longer auto-loads the platform's own project memory from
|
|
185
|
+
// node_modules/@bongos/core/ (the 2026-07-08 demo audit leak); (b) the bundled code
|
|
186
|
+
// carries the task-2126 clearer device_flow_disabled error surfacing. A backwards-
|
|
187
|
+
// compatible artifact fix with no API change is exactly semver PATCH. The bump is
|
|
188
|
+
// REQUIRED because 1.17.0 was already published to npm and task 2121 shipped
|
|
189
|
+
// without one, so `bongos package-core --publish` on 1.17.0 would be rejected as a
|
|
190
|
+
// re-publish. Every `^1.x` / `^1.17` module pin is satisfied by 1.17.1.
|
|
191
|
+
// 1.17.2 — task 2148 (canary upgrade-proof on demo.cloudbongos.com): a controlled
|
|
192
|
+
// PATCH bump used to round-trip the real `bongos upgrade` channel on the live
|
|
193
|
+
// demo instance with an OBSERVABLE code marker (the channel shipped in 1.16.0
|
|
194
|
+
// and was proven on emersonian-circles by task 1976, but never on demo with a
|
|
195
|
+
// served marker). No doorway export is added, changed, or removed — the exported
|
|
196
|
+
// surface is byte-identical to 1.17.1. What changed: the served /version payload
|
|
197
|
+
// now carries `coreVersion` (the running doorway version — a PERMANENT additive
|
|
198
|
+
// read so upgrade/fleet tooling can confirm which core an instance materialized),
|
|
199
|
+
// plus a THROWAWAY `updateCanary` marker removed in the phase-E follow-up. A
|
|
200
|
+
// backwards-compatible additive change with no API removal is exactly semver
|
|
201
|
+
// PATCH. Every `^1.x` / `^1.17` module pin is satisfied by 1.17.2.
|
|
179
202
|
// ---------------------------------------------------------------------------
|
|
180
|
-
const CORE_VERSION = '1.17.
|
|
203
|
+
const CORE_VERSION = '1.17.2'; // see the changelog above — 1.17.2 is the task-2148 canary upgrade-proof bump (adds /version coreVersion; no doorway export change)
|
|
181
204
|
|
|
182
205
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
183
206
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -19,11 +19,42 @@ const fs = require('node:fs');
|
|
|
19
19
|
const path = require('node:path');
|
|
20
20
|
const { validateManifest } = require('./manifest-schema');
|
|
21
21
|
const { satisfies } = require('./semver');
|
|
22
|
-
const { resolveCoreRoot } = require('../instance-config');
|
|
22
|
+
const { resolveCoreRoot, resolveInstanceRoot } = require('../instance-config');
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
24
|
+
// Collapse roots that resolve to the SAME absolute path, preserving order.
|
|
25
|
+
// Normalizes via path.resolve so a cwd that arrives with a trailing '.'/'//'
|
|
26
|
+
// segment still dedupes against the core root. Returns the resolved (canonical)
|
|
27
|
+
// paths. (Exported for the loader tests.)
|
|
28
|
+
function dedupeRoots(roots) {
|
|
29
|
+
const out = [];
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
for (const r of roots) {
|
|
32
|
+
const key = path.resolve(r);
|
|
33
|
+
if (seen.has(key)) continue;
|
|
34
|
+
seen.add(key);
|
|
35
|
+
out.push(key);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ADR 0108 §1 + task 2133 (R89-prereq): discovery must span TWO roots — the core
|
|
41
|
+
// PACKAGE's own modules/ (resolveCoreRoot()) AND the consumer/instance's own
|
|
42
|
+
// modules/ (resolveInstanceRoot()). Post-split the host modules (game /
|
|
43
|
+
// art-pipeline / provisioning) live at the instance root and are EXCLUDED from
|
|
44
|
+
// the @bongos/core package, so a core-only scan boots a split instance with none
|
|
45
|
+
// of its host modules.
|
|
46
|
+
//
|
|
47
|
+
// DEDUP BY RESOLVED PATH IS MANDATORY. In a single checkout resolveInstanceRoot()
|
|
48
|
+
// falls back to process.cwd(), which equals resolveCoreRoot() (the repo root), so
|
|
49
|
+
// the two candidates are the SAME directory. discoverModules() treats a repeated
|
|
50
|
+
// module KEY as a hard ERROR (not a skip), so scanning one root twice would turn
|
|
51
|
+
// every module into a "duplicate module key" error. dedupeRoots() collapses the
|
|
52
|
+
// coincident roots, so a single checkout scans ONE root (byte-identical to
|
|
53
|
+
// before) while a diverged split scans BOTH.
|
|
54
|
+
const DEFAULT_ROOTS = dedupeRoots([
|
|
55
|
+
path.join(resolveCoreRoot(), 'modules'),
|
|
56
|
+
path.join(resolveInstanceRoot(), 'modules'),
|
|
57
|
+
]);
|
|
27
58
|
|
|
28
59
|
// Lazy — avoids an eval-time require cycle (modules.js → loader → module-api →
|
|
29
60
|
// auth → …). By call time the graph is loaded; falls back to the known initial
|
|
@@ -263,6 +294,7 @@ module.exports = {
|
|
|
263
294
|
stopModulePollers,
|
|
264
295
|
loaderSnapshot,
|
|
265
296
|
coreVersion,
|
|
297
|
+
dedupeRoots,
|
|
266
298
|
DEFAULT_ROOTS,
|
|
267
299
|
_reset,
|
|
268
300
|
};
|
|
@@ -30,7 +30,11 @@
|
|
|
30
30
|
// },
|
|
31
31
|
// "provides": ["reward"], // OPTIONAL. seam PORTS this module registers a provider for.
|
|
32
32
|
// "consumes": ["grade"], // OPTIONAL. seam ports this module resolves (required capabilities).
|
|
33
|
-
// "prerequisites": { "modules": ["economy"] } // OPTIONAL. other modules that must be enabled.
|
|
33
|
+
// "prerequisites": { "modules": ["economy"] }, // OPTIONAL. other modules that must be enabled.
|
|
34
|
+
// "dependencies": { "discord.js": "^14.26.4" } // OPTIONAL. external npm deps this module needs at
|
|
35
|
+
// // runtime. Core stays lean (express+pg, ADR 0108); a bundled
|
|
36
|
+
// // module that needs more declares it HERE, and the consumer
|
|
37
|
+
// // composes the deps of the modules it ENABLES (ADR 0138).
|
|
34
38
|
// }
|
|
35
39
|
//
|
|
36
40
|
// validateManifest(obj) -> { valid, errors[], manifest } — pure, no I/O.
|
|
@@ -126,6 +130,17 @@ function validateManifest(obj) {
|
|
|
126
130
|
}
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
// --- optional runtime npm dependencies (ADR 0138 / task 2160) ---
|
|
134
|
+
// A bundled module's own external npm deps. @bongos/core stays lean (express+pg, ADR 0108);
|
|
135
|
+
// a module that needs more (e.g. discord.js) declares it here, and the consumer composes the
|
|
136
|
+
// deps of the modules it ENABLES (scripts/gds/init.js). Shape: { "<npm-name>": "<semver-range>" }.
|
|
137
|
+
if ('dependencies' in obj) {
|
|
138
|
+
const d = obj.dependencies;
|
|
139
|
+
if (!isObj(d) || !Object.entries(d).every(([k, v]) => isStr(k) && isStr(v))) {
|
|
140
|
+
E('dependencies: must be an object of { "<npm-name>": "<semver-range>" } string pairs');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
129
144
|
return { valid: errors.length === 0, errors, manifest: errors.length === 0 ? obj : null };
|
|
130
145
|
}
|
|
131
146
|
|
package/tests/branding.mjs
CHANGED
|
@@ -52,6 +52,17 @@ test('env override is ignored when empty', () => {
|
|
|
52
52
|
assert.equal(b.domains.publicOrigin, 'https://amazonprimea.com');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
test('provisioningOrigin: optional, passes through config, env-overridable (task 2101)', () => {
|
|
56
|
+
// Not required — unset resolves to undefined, and callers fall back to publicOrigin.
|
|
57
|
+
assert.equal(resolveBranding({ neutral: NEUTRAL, instance: OTB }).domains.provisioningOrigin, undefined);
|
|
58
|
+
// Set in the instance pack, it survives the deep-merge.
|
|
59
|
+
const inst = resolveBranding({ neutral: NEUTRAL, instance: { ...OTB, domains: { ...OTB.domains, provisioningOrigin: 'https://get.cloudbongos.com' } } });
|
|
60
|
+
assert.equal(inst.domains.provisioningOrigin, 'https://get.cloudbongos.com');
|
|
61
|
+
// And <PREFIX>_PROVISIONING_ORIGIN beats config (same precedence as the other origins).
|
|
62
|
+
const env = resolveBranding({ neutral: NEUTRAL, instance: OTB, env: { OTB_PROVISIONING_ORIGIN: 'https://get.cloudbongos.com' } });
|
|
63
|
+
assert.equal(env.domains.provisioningOrigin, 'https://get.cloudbongos.com');
|
|
64
|
+
});
|
|
65
|
+
|
|
55
66
|
test('missing required field throws', () => {
|
|
56
67
|
assert.throws(() => resolveBranding({ neutral: { identity: { productName: 'X' } } }), /missing required field/);
|
|
57
68
|
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// tests/claim_error_surface.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1476 — a failed /builder-claim must read as GUIDANCE, not a raw object
|
|
4
|
+
// dump. The API returns the R01 error envelope (ADR 0116):
|
|
5
|
+
// { error: { code, message, details } }
|
|
6
|
+
// so claim.js's old `r.data.error === 'CODE'` string branches all silently
|
|
7
|
+
// missed (error is now an object) and the refusal printed as `[object Object]`.
|
|
8
|
+
// This exercises the pure formatClaimFailure() surface DB-free, over BOTH the
|
|
9
|
+
// current envelope shape and the legacy flat shape.
|
|
10
|
+
//
|
|
11
|
+
// Run: node tests/claim_error_surface.mjs
|
|
12
|
+
|
|
13
|
+
import { strict as assert } from 'node:assert';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const { formatClaimFailure } = require('../scripts/gds/claim.js');
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
function t(name, fn) {
|
|
22
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
23
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const envelope = (code, details = {}, message = code, status = 409) => ({
|
|
27
|
+
status,
|
|
28
|
+
data: { error: { code, message, details } },
|
|
29
|
+
});
|
|
30
|
+
const text = (r) => r.lines.join('\n');
|
|
31
|
+
|
|
32
|
+
// The regression guard: no refusal may ever render a raw object.
|
|
33
|
+
t('never dumps [object Object] for any known code', () => {
|
|
34
|
+
const codes = [
|
|
35
|
+
'stage_required', 'session_already_has_active_claim', 'session_claim_check_failed',
|
|
36
|
+
'newcomer_must_ship_first', 'ALREADY_CLAIMED', 'MODULE_CONFLICT',
|
|
37
|
+
'TOUCHES_CONFLICT', 'TASK_NOT_READY', 'TASK_NOT_FOUND', 'DEPS_NOT_SHIPPED',
|
|
38
|
+
'some_unmapped_future_code',
|
|
39
|
+
];
|
|
40
|
+
for (const c of codes) {
|
|
41
|
+
const out = text(formatClaimFailure(envelope(c, { active_task_id: 5 }, 'msg'), 12));
|
|
42
|
+
assert.ok(!out.includes('[object Object]'), `${c} produced a raw object dump`);
|
|
43
|
+
assert.ok(out.trim().length > 0, `${c} produced no output`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// The core 1476 case: one-claim-per-session refusal is guidance + teaches parallel.
|
|
48
|
+
t('session_already_has_active_claim: names the in-flight task and teaches parallelism', () => {
|
|
49
|
+
const out = text(formatClaimFailure(
|
|
50
|
+
envelope('session_already_has_active_claim', {
|
|
51
|
+
active_task_id: 42, active_task_title: 'Foo', what_to_do: 'ship 42 first',
|
|
52
|
+
}), 99));
|
|
53
|
+
assert.match(out, /#42/, 'should name the in-flight task id');
|
|
54
|
+
assert.match(out, /Foo/, 'should name the in-flight task title');
|
|
55
|
+
assert.match(out, /ship 42 first/, 'should relay the server what_to_do');
|
|
56
|
+
assert.match(out, /PARALLEL/i, 'should teach how to parallelize');
|
|
57
|
+
assert.match(out, /worktree|bulk-claim/i, 'should point at separate worktree or bulk-claim');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
t('session_claim_check_failed shares the same guidance branch', () => {
|
|
61
|
+
const out = text(formatClaimFailure(envelope('session_claim_check_failed', {}), 3));
|
|
62
|
+
assert.match(out, /active claim/i);
|
|
63
|
+
assert.match(out, /PARALLEL/i);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Envelope + legacy flat shape must BOTH resolve the code + fields.
|
|
67
|
+
t('TASK_NOT_READY: envelope shape reads details.currentStatus + hint', () => {
|
|
68
|
+
const out = text(formatClaimFailure(
|
|
69
|
+
envelope('TASK_NOT_READY', { currentStatus: 'backlog', hint: 'promote it first' }), 7));
|
|
70
|
+
assert.match(out, /not ready/i);
|
|
71
|
+
assert.match(out, /backlog/);
|
|
72
|
+
assert.match(out, /promote it first/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
t('TASK_NOT_READY: legacy flat shape still resolves', () => {
|
|
76
|
+
const flat = { status: 409, data: { error: 'TASK_NOT_READY', currentStatus: 'blocked' } };
|
|
77
|
+
const out = text(formatClaimFailure(flat, 7));
|
|
78
|
+
assert.match(out, /not ready/i);
|
|
79
|
+
assert.match(out, /blocked/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
t('DEPS_NOT_SHIPPED: lists each blocking dependency', () => {
|
|
83
|
+
const out = text(formatClaimFailure(
|
|
84
|
+
envelope('DEPS_NOT_SHIPPED', { deps: [{ id: 10, status: 'ready', title: 'Dep A' }] }), 8));
|
|
85
|
+
assert.match(out, /#10/);
|
|
86
|
+
assert.match(out, /Dep A/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
t('MODULE_CONFLICT + TOUCHES_CONFLICT: list conflicting task titles', () => {
|
|
90
|
+
const mc = text(formatClaimFailure(
|
|
91
|
+
envelope('MODULE_CONFLICT', { module_key: 'economy', conflictTaskTitles: ['Bar'] }), 4));
|
|
92
|
+
assert.match(mc, /economy/);
|
|
93
|
+
assert.match(mc, /Bar/);
|
|
94
|
+
const tc = text(formatClaimFailure(
|
|
95
|
+
envelope('TOUCHES_CONFLICT', { conflictTaskTitles: ['Baz'] }), 4));
|
|
96
|
+
assert.match(tc, /Baz/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
t('unknown code: fallback carries code + message, exit 1', () => {
|
|
100
|
+
const r = formatClaimFailure(envelope('weird_thing', {}, 'boom', 500), 1);
|
|
101
|
+
assert.equal(r.exit, 1);
|
|
102
|
+
assert.match(text(r), /weird_thing/);
|
|
103
|
+
assert.match(text(r), /boom/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(`\nclaim_error_surface: ${passed} passed, ${failed} failed`);
|
|
107
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -108,6 +108,26 @@ t('materializeClaude: copies + rewrites skills into a fresh instance dir (no lef
|
|
|
108
108
|
rmSync(dir, { recursive: true, force: true });
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
t('materializeClaude: materializes scheduled-tasks — bodies→bongos, companions keep _core-resolve (task 2138)', () => {
|
|
112
|
+
const dir = mkdtempSync(join(tmpdir(), 'w3-rt-'));
|
|
113
|
+
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: dir, dryRun: false });
|
|
114
|
+
assert.ok(res.scheduledTasks > 0, 'at least one scheduled-task file materialized');
|
|
115
|
+
const rt = join(dir, '.claude', 'scheduled-tasks');
|
|
116
|
+
assert.ok(existsSync(rt), '.claude/scheduled-tasks created in the instance');
|
|
117
|
+
// A prose/judgment body's core invocation is bongosified so a consumer-layout model can run it.
|
|
118
|
+
const stale = readFileSync(join(rt, 'stale-claim-release', 'SKILL.md'), 'utf8');
|
|
119
|
+
assert.ok(stale.includes('bongos exec scripts/gds/pulse.js'), 'body core-invocation rewritten to bongos exec');
|
|
120
|
+
assert.ok(!stale.includes('node scripts/gds/pulse.js'), 'no bare relative node scripts/gds left in the body');
|
|
121
|
+
// A deterministic routine's `.claude/...` companion-step command is NOT a core invocation → preserved.
|
|
122
|
+
const dd = readFileSync(join(rt, 'diagram-drift', 'SKILL.md'), 'utf8');
|
|
123
|
+
assert.ok(dd.includes('node .claude/scheduled-tasks/diagram-drift/check.js'), 'companion-path step command preserved');
|
|
124
|
+
// Companion .js resolves its core require()s via the materialized _core-resolve.js sibling (task 2139 pattern).
|
|
125
|
+
const check = readFileSync(join(rt, 'diagram-drift', 'check.js'), 'utf8');
|
|
126
|
+
assert.ok(check.includes("require('../../hooks/_core-resolve.js')"), 'companion resolves core via _core-resolve');
|
|
127
|
+
assert.ok(!check.includes("require('../../../scripts/gds/"), 'no bare ../../../scripts/gds require left in the companion');
|
|
128
|
+
rmSync(dir, { recursive: true, force: true });
|
|
129
|
+
});
|
|
130
|
+
|
|
111
131
|
t('materializeClaude: SELF-COPY guard — target === core root is a no-op (byte-identical for the live instance)', () => {
|
|
112
132
|
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: ROOT, dryRun: false });
|
|
113
133
|
assert.equal(res.skipped, true, 'materializing onto the core root must skip');
|
|
@@ -144,5 +164,110 @@ t('copyTreeTransformed: overwrite defaults true → greenfield still clobbers (r
|
|
|
144
164
|
rmSync(dest, { recursive: true, force: true });
|
|
145
165
|
});
|
|
146
166
|
|
|
167
|
+
// ---- instance rebrand (task 2103a) -----------------------------------------
|
|
168
|
+
const OTB = { domains: { publicOrigin: 'https://amazonprimea.com', buildersOrigin: 'https://builders.amazonprimea.com', statusOrigin: 'https://status.amazonprimea.com' }, configDir: 'otb', envPrefix: 'OTB' };
|
|
169
|
+
|
|
170
|
+
t('buildRebrandRules: full target → founder origin/config-dir/prefix all rewritten', () => {
|
|
171
|
+
const to = { domains: { publicOrigin: 'https://demo.cloudbongos.com', buildersOrigin: 'https://demo.cloudbongos.com', statusOrigin: 'https://demo.cloudbongos.com' }, envPrefix: 'CLOUDBONGOS' };
|
|
172
|
+
const out = m.rebrandText('b="https://amazonprimea.com" h="builders.amazonprimea.com" ~/.config/otb OTB_API_BASE [otb]', m.buildRebrandRules(OTB, to));
|
|
173
|
+
assert.ok(!out.includes('amazonprimea'), `no founder host survives: ${out}`);
|
|
174
|
+
assert.ok(out.includes('https://demo.cloudbongos.com'), 'public origin rewritten');
|
|
175
|
+
assert.ok(out.includes('~/.config/cloudbongos') && out.includes('CLOUDBONGOS_API_BASE') && out.includes('[cloudbongos]'), 'config-dir + prefix + log tag rewritten');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
t('buildRebrandRules: partial target (only publicOrigin) → subdomains fall back to target public host, not builders.<target>', () => {
|
|
179
|
+
const out = m.rebrandText('builders.amazonprimea.com', m.buildRebrandRules(OTB, { domains: { publicOrigin: 'https://acme.example.org' }, envPrefix: 'ACME' }));
|
|
180
|
+
assert.equal(out, 'acme.example.org', 'no builders.acme.example.org mangling');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
t('buildRebrandRules: source === target is an empty ruleset → rebrandText is a no-op', () => {
|
|
184
|
+
assert.equal(m.buildRebrandRules(OTB, OTB).length, 0);
|
|
185
|
+
const s = 'https://amazonprimea.com ~/.config/otb';
|
|
186
|
+
assert.equal(m.rebrandText(s, m.buildRebrandRules(OTB, OTB)), s);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ---- skill filtering by disabled module (task 2103b) -----------------------
|
|
190
|
+
t('disabledModuleSkills: only skills of DISABLED modules are collected (default + explicit override)', () => {
|
|
191
|
+
const core = mkdtempSync(join(tmpdir(), 'rb-core-'));
|
|
192
|
+
const inst = mkdtempSync(join(tmpdir(), 'rb-inst-'));
|
|
193
|
+
const mod = (key, def, skills) => { const d = join(core, 'modules', key); mkdirSync(d, { recursive: true }); writeFileSync(join(d, 'module.json'), JSON.stringify({ key, default: def, contributes: { skills } })); };
|
|
194
|
+
mod('foo', false, ['a']); // default off, but ENABLED in config → keep
|
|
195
|
+
mod('bar', true, ['b']); // default on, not overridden → keep
|
|
196
|
+
mod('baz', false, ['c']); // default off, not overridden → SKIP
|
|
197
|
+
mod('qux', true, ['d']); // default on, but DISABLED in config → SKIP
|
|
198
|
+
mkdirSync(join(inst, 'config'), { recursive: true });
|
|
199
|
+
writeFileSync(join(inst, 'config', 'modules.json'), JSON.stringify({ modules: { foo: true, qux: false } }));
|
|
200
|
+
const skip = m.disabledModuleSkills({ coreRoot: core, instanceDir: inst });
|
|
201
|
+
assert.deepEqual([...skip].sort(), ['c', 'd']);
|
|
202
|
+
rmSync(core, { recursive: true, force: true }); rmSync(inst, { recursive: true, force: true });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ---- materializeClaude end-to-end: rebrand + skill-exclude (from the REAL repo) --
|
|
206
|
+
t('materializeClaude: copied hooks carry NO founder origin/config-dir; a disabled-module skill is excluded', () => {
|
|
207
|
+
const dir = mkdtempSync(join(tmpdir(), 'rb-e2e-'));
|
|
208
|
+
// Target instance: demo-branded, art-pipeline OFF (so its otb-* skills must be dropped).
|
|
209
|
+
mkdirSync(join(dir, 'config'), { recursive: true });
|
|
210
|
+
writeFileSync(join(dir, 'config', 'branding.json'), JSON.stringify({ domains: { publicOrigin: 'https://demo.cloudbongos.com', buildersOrigin: 'https://demo.cloudbongos.com', statusOrigin: 'https://demo.cloudbongos.com', oauthOrigin: 'https://demo.cloudbongos.com' }, configDir: 'cloudbongos', envPrefix: 'CLOUDBONGOS' }));
|
|
211
|
+
writeFileSync(join(dir, 'config', 'modules.json'), JSON.stringify({ modules: { 'art-pipeline': false } }));
|
|
212
|
+
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: dir, dryRun: false });
|
|
213
|
+
assert.ok(res.rebrandRules > 0, 'rebrand rules were built from the OTB→demo branding delta');
|
|
214
|
+
|
|
215
|
+
// (a) no copied hook may still name the founder origin or config dir.
|
|
216
|
+
const offenders = [];
|
|
217
|
+
const hooksDir = join(dir, '.claude', 'hooks');
|
|
218
|
+
if (existsSync(hooksDir)) {
|
|
219
|
+
const walk = (d) => { for (const n of readdirSync(d, { withFileTypes: true })) { const p = join(d, n.name); if (n.isDirectory()) walk(p); else { const txt = readFileSync(p, 'utf8'); if (txt.includes('amazonprimea.com') || txt.includes('.config/otb') || txt.includes('/etc/otb')) offenders.push(n.name); } } };
|
|
220
|
+
walk(hooksDir);
|
|
221
|
+
}
|
|
222
|
+
assert.deepEqual(offenders, [], `founder origin/config-dir survived in: ${offenders.join(', ')}`);
|
|
223
|
+
|
|
224
|
+
// (b) art-pipeline is off → its otb-* skills are excluded; a core skill remains.
|
|
225
|
+
assert.ok(res.excludedSkills.includes('otb-tile-generate'), `art-pipeline skill excluded (got ${res.excludedSkills.join(',')})`);
|
|
226
|
+
assert.ok(!existsSync(join(dir, '.claude', 'skills', 'otb-tile-generate')), 'excluded skill not written');
|
|
227
|
+
assert.ok(existsSync(join(dir, '.claude', 'skills', 'builder-ship')), 'a core skill is still materialized');
|
|
228
|
+
rmSync(dir, { recursive: true, force: true });
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ---- CLAUDE.md scaffold (task 2104) ----------------------------------------
|
|
232
|
+
const tmpl = require('../scripts/gds/claude-md-template.js');
|
|
233
|
+
|
|
234
|
+
t('renderInstanceClaudeMd: brand-substituted — names the instance + its hall/currency, no founder literal', () => {
|
|
235
|
+
const md = tmpl.renderInstanceClaudeMd({ identity: { productName: 'Acme', worldName: 'Acmeland', company: { name: 'Widgets Inc' } }, domains: { publicOrigin: 'https://acme.example.org', buildersOrigin: 'https://builders.acme.example.org', statusOrigin: 'https://status.acme.example.org' }, currency: { label: 'coins' } });
|
|
236
|
+
assert.ok(md.startsWith('# Acme — Project Memory'), 'titled with the product name');
|
|
237
|
+
assert.ok(md.includes('the Acmeland world') && md.includes('by Widgets Inc'), 'world + company woven in');
|
|
238
|
+
assert.ok(md.includes('https://builders.acme.example.org'), 'hall origin present');
|
|
239
|
+
assert.ok(md.includes('coins'), 'currency label present');
|
|
240
|
+
assert.ok(!md.toLowerCase().includes('amazonprimea') && !md.includes('~/.config/otb'), 'no founder literal in the starter');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
t('renderInstanceClaudeMd: degrades cleanly on a minimal branding (no world dup, defaults currency)', () => {
|
|
244
|
+
const md = tmpl.renderInstanceClaudeMd({ identity: { productName: 'Solo', worldName: 'Solo' } });
|
|
245
|
+
assert.ok(md.startsWith('# Solo — Project Memory'));
|
|
246
|
+
assert.ok(!md.includes('the Solo world'), 'world == product is not duplicated');
|
|
247
|
+
assert.ok(md.includes('credits'), 'currency defaults to credits');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
t('materializeClaude: writes a brand-substituted CLAUDE.md when the instance has none', () => {
|
|
251
|
+
const dir = mkdtempSync(join(tmpdir(), 'cm-md-'));
|
|
252
|
+
mkdirSync(join(dir, 'config'), { recursive: true });
|
|
253
|
+
writeFileSync(join(dir, 'config', 'branding.json'), JSON.stringify({ identity: { productName: 'Freshie' }, domains: { publicOrigin: 'https://freshie.example.com' }, configDir: 'freshie', envPrefix: 'FRESHIE' }));
|
|
254
|
+
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: dir, dryRun: false });
|
|
255
|
+
assert.equal(res.claudeMd, true, 'reported writing CLAUDE.md');
|
|
256
|
+
assert.ok(readFileSync(join(dir, 'CLAUDE.md'), 'utf8').startsWith('# Freshie — Project Memory'), 'written + brand-substituted');
|
|
257
|
+
rmSync(dir, { recursive: true, force: true });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
t('materializeClaude: NEVER clobbers an existing CLAUDE.md (init / adopt / upgrade safe)', () => {
|
|
261
|
+
const dir = mkdtempSync(join(tmpdir(), 'cm-md2-'));
|
|
262
|
+
mkdirSync(join(dir, 'config'), { recursive: true });
|
|
263
|
+
writeFileSync(join(dir, 'config', 'branding.json'), JSON.stringify({ identity: { productName: 'Freshie' }, domains: { publicOrigin: 'https://freshie.example.com' } }));
|
|
264
|
+
const owner = '# My own CLAUDE.md — do not touch\n';
|
|
265
|
+
writeFileSync(join(dir, 'CLAUDE.md'), owner);
|
|
266
|
+
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: dir, dryRun: false });
|
|
267
|
+
assert.equal(res.claudeMd, false, 'did not (re)write');
|
|
268
|
+
assert.equal(readFileSync(join(dir, 'CLAUDE.md'), 'utf8'), owner, 'existing CLAUDE.md left byte-untouched');
|
|
269
|
+
rmSync(dir, { recursive: true, force: true });
|
|
270
|
+
});
|
|
271
|
+
|
|
147
272
|
console.log(`\nclaude_materialize: ${passed} passed, ${failed} failed`);
|
|
148
273
|
process.exit(failed ? 1 : 0);
|