@bongos/core 1.19.1074 → 1.19.1075

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.bongos-core.json +122 -122
  2. package/bin/bongos.js +1 -3
  3. package/docs/api/openapi.json +4 -4
  4. package/docs/module-api-changelog.md +2 -0
  5. package/modules/copy-desk/routes/copy-desk.js +1 -1
  6. package/modules/discord/ship-broadcast.js +3 -3
  7. package/modules/economy/credits.js +2 -2
  8. package/modules/ideas/projection.js +1 -1
  9. package/modules/lifecycle/db-ship.js +4 -4
  10. package/modules/lifecycle/github-push.js +12 -14
  11. package/modules/lifecycle/publish-reconciler.js +10 -10
  12. package/modules/lifecycle/routes/tasks.js +8 -8
  13. package/modules/lifecycle/ship-card.js +1 -1
  14. package/modules/lifecycle/task-visuals.js +2 -2
  15. package/modules/memory/routes/memory.js +2 -2
  16. package/modules/platform-identity/routes/sso.js +3 -2
  17. package/modules/provisioning/capacity.js +1 -1
  18. package/modules/provisioning/provisioning.js +4 -3
  19. package/modules/provisioning/routes/provisioning.js +4 -4
  20. package/modules/sessions/db.js +1 -1
  21. package/package-lock.json +2 -2
  22. package/package.json +1 -1
  23. package/release-notes.json +6 -0
  24. package/scripts/gds/artifact-staleness.js +2 -2
  25. package/scripts/gds/backfill-task-rewards.js +5 -5
  26. package/scripts/gds/claude-materialize.js +2 -2
  27. package/scripts/gds/cli-lib.js +7 -7
  28. package/scripts/gds/codemod-rename-src-gds.js +1 -1
  29. package/scripts/gds/context-pack.js +5 -7
  30. package/scripts/gds/doctor.js +3 -3
  31. package/scripts/gds/fitness-checks-packaging.js +4 -4
  32. package/scripts/gds/fitness-ratchets.js +1 -1
  33. package/scripts/gds/gen-api-client.js +4 -4
  34. package/scripts/gds/gen-api-docs.js +1 -1
  35. package/scripts/gds/gen-repo-map.js +13 -14
  36. package/scripts/gds/gen-session-index.js +8 -8
  37. package/scripts/gds/leak-scan-allowlist.js +1 -1
  38. package/scripts/gds/plain-cards.js +1 -1
  39. package/scripts/gds/push-path-brief.js +10 -10
  40. package/scripts/gds/rename-history-check.js +1 -1
  41. package/scripts/gds/session-digest-build.js +2 -2
  42. package/scripts/gds/ship-deploy-target.js +13 -13
  43. package/scripts/gds/ship-flow.js +1 -1
  44. package/scripts/gds/ship-land.js +6 -6
  45. package/scripts/gds/ship-merge.js +4 -4
  46. package/scripts/gds/ship-preflight-steps.js +4 -4
  47. package/scripts/gds/ship-regen.js +9 -12
  48. package/scripts/gds/ship.js +9 -9
  49. package/scripts/gds/skill-preflight.js +9 -8
  50. package/src/bongos/api-prefix.js +4 -4
  51. package/src/bongos/platform-visibility-gate.js +1 -1
  52. package/src/bongos/routes/me.js +1 -1
  53. package/src/bongos/routes/security.js +3 -3
  54. package/src/bongos/routes.js +2 -2
  55. package/src/bongos/serve-internal.js +5 -6
  56. package/src/bongos-downloads.js +1 -6
  57. package/src/branding.js +2 -2
  58. package/src/build-info.js +4 -6
  59. package/src/instance-config.js +2 -2
  60. package/src/module-api.js +3 -3
  61. package/src/module-loader/manifest-schema.js +1 -1
  62. package/tests/api_alias_caller_ratchet.mjs +1 -1
  63. package/tests/api_alias_exceptions.mjs +1 -1
  64. package/tests/api_client.mjs +1 -1
  65. package/tests/api_path_404.mjs +1 -1
  66. package/tests/backfill_task_rewards.mjs +1 -1
  67. package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
  68. package/tests/cli_sessions.mjs +1 -1
  69. package/tests/context_pack.mjs +13 -13
  70. package/tests/copy_inventory.mjs +7 -7
  71. package/tests/credit_grant.mjs +2 -2
  72. package/tests/criterion_suggest.mjs +1 -1
  73. package/tests/design_tokens_sync.mjs +3 -2
  74. package/tests/fitness_ratchets.mjs +2 -2
  75. package/tests/github_push_land_proof.mjs +1 -1
  76. package/tests/goal_advisory.mjs +3 -3
  77. package/tests/goal_suggest.mjs +3 -3
  78. package/tests/government_abuse_matrix.mjs +4 -3
  79. package/tests/hall_audit.mjs +1 -1
  80. package/tests/helpers.mjs +1 -1
  81. package/tests/host_topology_skips.mjs +2 -2
  82. package/tests/migration_namespace.mjs +6 -6
  83. package/tests/module_api.mjs +2 -2
  84. package/tests/module_cli.mjs +5 -5
  85. package/tests/module_manifest.mjs +13 -13
  86. package/tests/module_route_rank.mjs +2 -2
  87. package/tests/modules_route_wiring.mjs +1 -1
  88. package/tests/onboarding_route_signals.mjs +1 -1
  89. package/tests/paste_token_session_store.mjs +1 -1
  90. package/tests/platform_visibility_gate.mjs +1 -1
  91. package/tests/project_modules_ui.mjs +17 -17
  92. package/tests/projects_hub_module_picker.mjs +75 -74
  93. package/tests/provisioning_capacity.mjs +1 -1
  94. package/tests/provisioning_recommendations.mjs +2 -1
  95. package/tests/publish_branch_route.mjs +1 -1
  96. package/tests/publish_manifest.mjs +1 -1
  97. package/tests/publish_reconciler.mjs +8 -8
  98. package/tests/push_path_brief.mjs +10 -9
  99. package/tests/regrade_eligibility.mjs +1 -1
  100. package/tests/rename_history_restraint.mjs +1 -1
  101. package/tests/repo_map.mjs +9 -9
  102. package/tests/seam_wiring_guard.mjs +11 -11
  103. package/tests/search_isolation.mjs +3 -3
  104. package/tests/session_records.mjs +1 -1
  105. package/tests/session_rename_fallback.mjs +1 -1
  106. package/tests/session_start_freshness.mjs +3 -3
  107. package/tests/session_token_hash_db.mjs +1 -1
  108. package/tests/ship_card.mjs +1 -1
  109. package/tests/ship_ci_deploy.mjs +17 -17
  110. package/tests/ship_premerge.mjs +1 -1
  111. package/tests/ship_resume.mjs +1 -1
  112. package/tests/skill_preflight.mjs +2 -2
  113. package/tests/skip_is_not_pass.mjs +1 -1
  114. package/tests/terms_acceptance.mjs +1 -1
package/bin/bongos.js CHANGED
@@ -12,9 +12,7 @@
12
12
  // scripts/gds/cli-lib.js.
13
13
  //
14
14
  // Phases 2-4 extend this same CLI with a one-line installer + standalone binary
15
- // and instance-agnostic `login`/`init`/`upgrade`. (They also added `shell`/`code`,
16
- // VS Code Remote-SSH into a per-builder dev box; the dev box was retired and
17
- // those verbs removed with it — task 1003889.)
15
+ // and instance-agnostic `login`/`init`/`upgrade`.
18
16
 
19
17
  'use strict';
20
18
 
@@ -7,7 +7,7 @@
7
7
  "name": "AGPL-3.0-or-later",
8
8
  "url": "https://www.gnu.org/licenses/agpl-3.0.html"
9
9
  },
10
- "description": "The Bongos API — the build-methodology backend\n(builders, tasks, claims, credits, grading, memory, dev boxes).\n\n**API version:** `v1` — served at `/api/bongos/v1`. The\nunversioned `/api/bongos` and legacy `/api/gds` are permanent aliases\npinned to this version; pin to `/api/bongos/v1` for a stable\ncontract (breaking changes ship a new `/vN`, ADR 0117). This is the\nCONTRACT version, distinct from the package build in `info.version`.\n\n**Base path:** `/api/bongos` (every path below is relative to it).\n\n**Auth:** a builder session — the `gds_session` cookie (web) or\n`Authorization: Bearer <token>` (CLI). **Ranks** gate writes and are\nenforced server-side per request, never cached (ADR 0016):\n`public` < `any-builder` < `metic+archon` < `archon`.\nEach operation carries its required rank in `x-rank`.\n\nGenerated from the live route files by `scripts/gds/gen-api-docs.js` —\ndo not hand-edit; it regenerates on every ship."
10
+ "description": "The Bongos API — the build-methodology backend\n(builders, tasks, claims, credits, grading, memory).\n\n**API version:** `v1` — served at `/api/bongos/v1`. The\nunversioned `/api/bongos` and legacy `/api/gds` are permanent aliases\npinned to this version; pin to `/api/bongos/v1` for a stable\ncontract (breaking changes ship a new `/vN`, ADR 0117). This is the\nCONTRACT version, distinct from the package build in `info.version`.\n\n**Base path:** `/api/bongos` (every path below is relative to it).\n\n**Auth:** a builder session — the `gds_session` cookie (web) or\n`Authorization: Bearer <token>` (CLI). **Ranks** gate writes and are\nenforced server-side per request, never cached (ADR 0016):\n`public` < `any-builder` < `metic+archon` < `archon`.\nEach operation carries its required rank in `x-rank`.\n\nGenerated from the live route files by `scripts/gds/gen-api-docs.js` —\ndo not hand-edit; it regenerates on every ship."
11
11
  },
12
12
  "x-api-version": "v1",
13
13
  "servers": [
@@ -13659,7 +13659,7 @@
13659
13659
  "provisioning"
13660
13660
  ],
13661
13661
  "summary": "GET /provisioning/fleet",
13662
- "description": "GET /provisioning/fleet — the full instance roster across all owners. rank: archon (exposes every owner's instance + cost; the /boxes precedent).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `provisioning.fleet.manage` (all required).",
13662
+ "description": "GET /provisioning/fleet — the full instance roster across all owners. rank: archon (exposes every owner's instance + cost).\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `provisioning.fleet.manage` (all required).",
13663
13663
  "x-rank": "metic+archon",
13664
13664
  "x-source": "modules/provisioning/routes/provisioning.js",
13665
13665
  "x-permissions": [
@@ -13700,7 +13700,7 @@
13700
13700
  "provisioning"
13701
13701
  ],
13702
13702
  "summary": "GET /provisioning/fleet/cost-ledger",
13703
- "description": "GET /provisioning/fleet/cost-ledger — per-instance monthly cost to us + estimated cost to its owner, with totals. rank: archon — cost oversight across the fleet (the /boxes/cost-ledger precedent). Declared among the archon routes (archon-first) for correct auditor classification.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `provisioning.fleet.manage` (all required).",
13703
+ "description": "GET /provisioning/fleet/cost-ledger — per-instance monthly cost to us + estimated cost to its owner, with totals. rank: archon — cost oversight across the fleet. Declared among the archon routes (archon-first) for correct auditor classification.\n\n**Rank:** `metic+archon` — Metic or Archon rank (review/triage powers).\n\n**Permissions:** `provisioning.fleet.manage` (all required).",
13704
13704
  "x-rank": "metic+archon",
13705
13705
  "x-source": "modules/provisioning/routes/provisioning.js",
13706
13706
  "x-permissions": [
@@ -19293,7 +19293,7 @@
19293
19293
  "tasks"
19294
19294
  ],
19295
19295
  "summary": "GET /tasks/:id/publish-status",
19296
- "description": "GET /tasks/:id/publish-status?branch=<branch> — poll the PR + prod-deploy state for a server-mediated publish so ship.js can confirm the land WITHOUT gh on the box. Ownership-gated like the POST. Derives state live from GitHub. `branch` is optional (task 1003764): without it the server finds the task's own PR (both publish paths title it \"task <id>: …\") and the answer names the `branch` it used and its `branch_source`, so the check agents are sent to instead of git probes never refuses a live publish for want of a branch name.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
19296
+ "description": "GET /tasks/:id/publish-status?branch=<branch> — poll the PR + prod-deploy state for a server-mediated publish so ship.js can confirm the land WITHOUT gh on the checkout. Ownership-gated like the POST. Derives state live from GitHub. `branch` is optional (task 1003764): without it the server finds the task's own PR (both publish paths title it \"task <id>: …\") and the answer names the `branch` it used and its `branch_source`, so the check agents are sent to instead of git probes never refuses a live publish for want of a branch name.\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
19297
19297
  "x-rank": "any-builder",
19298
19298
  "x-source": "modules/lifecycle/routes/tasks.js",
19299
19299
  "parameters": [
@@ -2635,5 +2635,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2635
2635
  landed since 1.19.1072 with no explicit bump. run 36485068639. (task 1002620)
2636
2636
  1.19.1074 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2637
2637
  landed since 1.19.1073 with no explicit bump. run 36510198162. (task 1002620)
2638
+ 1.19.1075 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2639
+ landed since 1.19.1074 with no explicit bump. run 36512197709. (task 1002620)
2638
2640
  ---------------------------------------------------------------------------
2639
2641
  ```
@@ -66,7 +66,7 @@ const { waitingRoundOrFail, refuseDecision, nextWaiting } = require('../page-dec
66
66
  const { validateOrRespond } = api;
67
67
 
68
68
  // The doorway's namespaced logger, not console.error (task 1003114). Same seam
69
- // builder-settings / dev-box / ideas / government already use: a module's log
69
+ // builder-settings / ideas / government already use: a module's log
70
70
  // lines come out structured and attributable, and an error object logged as
71
71
  // `{ err }` keeps its stack instead of being stringified into one line.
72
72
  const log = api.logger('copy-desk');
@@ -8,9 +8,9 @@
8
8
  // either path. ship.js re-exports it so its existing regression test is unchanged.
9
9
  //
10
10
  // postShipBroadcast is the SERVER-SIDE poster (task 1045). On the server-mediated
11
- // publish path a dev box cannot post the broadcast — it holds no webhook secret
12
- // (ADR 0053 "box holds nothing"), which is exactly why a box ship was silently
13
- // skipping the public announcement. So when the GDS server confirms the deploy
11
+ // publish path the uploading checkout cannot post the broadcast — it holds no
12
+ // webhook secret, which is exactly why such a ship was silently skipping the
13
+ // public announcement. So when the GDS server confirms the deploy
14
14
  // landed (publish-status reconcile, confirmed→shipped), the server — which holds
15
15
  // the webhook secrets and just deployed — fires the broadcast instead. Best-effort:
16
16
  // it never throws, so a Discord hiccup can never undo a ship.
@@ -919,7 +919,7 @@ async function awardIdeaGovernancePassCredit(client, {
919
919
  // column the route's empty-session guard checks). Used ONLY as a
920
920
  // fallback: when modelUsage prices to $0 but the session carried
921
921
  // real tokens, the reward is priced from totalTokens at the opus
922
- // rate so the dev-box/CI agent path (which historically uploaded
922
+ // rate so the CI agent path (which historically uploaded
923
923
  // empty/unpriceable model_usage → $0 reward; ADR 0097) still pays
924
924
  // the real cost-plus amount. This is NOT a fabricated dollar value
925
925
  // — it prices REAL token counts via the same locked PRICES table.
@@ -941,7 +941,7 @@ async function awardIdeaGovernancePassCredit(client, {
941
941
  // what actually paid.
942
942
  //
943
943
  // Cost-plus zero-out fallback (task 1676). priceModelUsage returns $0 when
944
- // model_usage is empty or carries no token counts — the exact dev-box/CI symptom
944
+ // model_usage is empty or carries no token counts — the exact CI-agent symptom
945
945
  // ADR 0097 records ("upload no priced model usage"). When that happens but the
946
946
  // session DID carry real tokens, price the total at the opus rate so the reward
947
947
  // reflects the real spend instead of silently rounding to 0. The blend assumes a
@@ -50,7 +50,7 @@ const IDEA_COLS = IDEA_COL_NAMES.join(', ');
50
50
  //
51
51
  // THE DOOR IS READ OFF THE SESSION, not claimed in the body. A web cookie is the
52
52
  // hall (the ideas form, the sky's composer), a device-flow token is the CLI
53
- // (capture.js, spark.js), anything else (a box token, a program on the port) is
53
+ // (capture.js, spark.js), anything else (a program on the port) is
54
54
  // the API — so a filer cannot say they came through a door they did not. The one
55
55
  // value a body may ADD is 'planning': a planning session's capture is a CLI
56
56
  // filing no session can tell apart, so capture.js --via planning names it. The
@@ -224,17 +224,17 @@ async function resolveClaim({ claimId, builderId, outcome, notesMd, valueSummary
224
224
  }
225
225
 
226
226
  // Task #1048 (ADR 0046 follow-up): record the builder's sandbox self-approval
227
- // as a durable fact on the task. When a dev-box builder reviews a staged
227
+ // as a durable fact on the task. When a builder reviews a staged
228
228
  // game change on their sandbox and approves shipping it (TTY 'y', or an
229
229
  // explicit --approved re-run on the dominant non-TTY path), ship.js threads
230
230
  // { stamp, url, method } through this resolve call. We stamp `by` + `at`
231
231
  // SERVER-SIDE (never trust the client for identity or time) and persist the
232
232
  // blob to tasks.sandbox_review. Only on a 'shipped' resolve carrying a
233
- // payload — laptop ships, non-game diffs, quick-tunnel boxes, --skip-stage,
233
+ // payload — laptop ships, non-game diffs, --skip-stage,
234
234
  // and un-approved re-runs send nothing, leaving the column NULL.
235
235
  if (outcome === 'shipped' && sandboxReview && typeof sandboxReview === 'object' && sandboxReview.stamp) {
236
236
  // Sanitize the client-supplied fields (defense in depth): the builder's
237
- // box controls stamp/url/method, so clamp lengths, whitelist `method`, and
237
+ // machine controls stamp/url/method, so clamp lengths, whitelist `method`, and
238
238
  // accept only an https:// URL. This keeps a forged/oversized value — or a
239
239
  // future stored-XSS string like a `javascript:` URL rendered in the hall —
240
240
  // out of the persisted record. `by`/`at` stay SERVER-stamped (never
@@ -1098,7 +1098,7 @@ async function confirmTask({ taskId, builderId }, deps = {}) {
1098
1098
  // first-class way to KILL a task that is no longer relevant. The only routes to
1099
1099
  // status='abandoned' were offboarding a builder (offboardBuilder, which seals
1100
1100
  // each open claim) and a raw SQL UPDATE — so stale/moot tasks sat in 'ready' as
1101
- // claimable noise (the box-teardown chores #819/#822/#827 and the dup tasks
1101
+ // claimable noise (the teardown chores #819/#822/#827 and the dup tasks
1102
1102
  // #907/#926 that other sessions had already shipped, all retired 2026-06-13).
1103
1103
  // This is the missing transition.
1104
1104
  //
@@ -1,18 +1,16 @@
1
1
  // GDS server-mediated branch publish (ADR 0055 / task 1025).
2
2
  //
3
3
  // WHY THIS EXISTS
4
- // A builder on a cloud dev box cannot publish in `ci` deploy mode: the box has
5
- // NO GitHub push credential by design (credential-less HTTPS origin, no gh, no
6
- // GH_TOKEN — infra/builder-box-cloud-init.yaml scrubs the clone token), and its
7
- // baked GDS session is box-scoped (deny-by-default, #919/ADR 0053). CI self-
4
+ // A checkout with NO GitHub push credential by design (credential-less HTTPS
5
+ // origin, no gh, no GH_TOKEN) cannot publish in `ci` deploy mode. CI self-
8
6
  // deploy (#859/ADR 0042) fixed only the LAST leg (Actions deploys main->prod);
9
- // the box still can't get its branch onto GitHub to open the PR.
7
+ // such a checkout still can't get its branch onto GitHub to open the PR.
10
8
  //
11
- // THE FIX (owner-chosen: "the box holds nothing"). The box uploads its branch as
12
- // a THIN git bundle to the GDS over a RE-AUTHED (non-box) cli session; the server
9
+ // THE FIX (owner-chosen: "the checkout holds nothing"). The checkout uploads its
10
+ // branch as a THIN git bundle to the GDS over a RE-AUTHED cli session; the server
13
11
  // pushes the branch to GitHub + opens the PR + enables auto-merge using a SERVER-
14
- // side push credential. The box never holds a GitHub push credential, so the #919
15
- // model is preserved. The credential is scoped to a single repo (Contents:write +
12
+ // side push credential. The checkout never holds a GitHub push credential, so the
13
+ // #919 model is preserved. The credential is scoped to a single repo (Contents:write +
16
14
  // Pull-requests:write + Actions:read) and CANNOT push to branch-protected `main`
17
15
  // nor bypass required checks — a leaked server credential can only land code on a
18
16
  // feature branch behind the same checks every PR faces.
@@ -30,8 +28,8 @@
30
28
  // This module is the server seam: push a bundle, open/find the PR, enable auto-
31
29
  // merge, report status. All git runs through execFile arg-arrays (shell:false,
32
30
  // the #681 injection discipline); the credentialed URL is built in-process and
33
- // NEVER persisted to a remote nor echoed on an error path (the box-source-fetch.sh
34
- // "one-shot URL, scrub the credential" pattern). The GitHub API calls are raw
31
+ // NEVER persisted to a remote nor echoed on an error path (the "one-shot URL,
32
+ // scrub the credential" pattern). The GitHub API calls are raw
35
33
  // global fetch (no Octokit — fetch is used throughout auth.js), header shape
36
34
  // mirrors auth.js fetchGithubUser.
37
35
 
@@ -574,7 +572,7 @@ async function enableAutoMergeDetailed({ nodeId }, deps = {}) {
574
572
  }
575
573
 
576
574
  // getPublishStatus — report the PR + prod-deploy state so ship.js can poll
577
- // WITHOUT gh on the box. Returns:
575
+ // WITHOUT gh on the checkout. Returns:
578
576
  // { pr_state, merged, merge_sha, deploy_status, deploy_probe } where
579
577
  // pr_state ∈ open|closed|none,
580
578
  // deploy_status ∈ pending|success|none|unconfigured|unavailable
@@ -811,7 +809,7 @@ function prReferencesTask(status, taskId) {
811
809
 
812
810
  // task 1002645 — locate a task's PR by the "task <id>: …" TITLE convention, for when
813
811
  // the reconciler cannot derive the branch. The sweep derives claude/<worktree_name>
814
- // (the dev-box convention), but a laptop/wave ship pushes its own branch name
812
+ // (the conventional name), but a laptop/wave ship pushes its own branch name
815
813
  // (worktree-<worktree_name>), so the derived branch finds no PR and the sweep's whole
816
814
  // PR path — the mergeIfGreen nudge (the ONLY green-PR merger where native auto-merge
817
815
  // is repo-disallowed), conflict heal, land proof — never engages; the task strands at
@@ -972,7 +970,7 @@ function mergeBlockReason(s) {
972
970
  const bad = s.checks.filter(_BAD_CHECK);
973
971
  if (bad.length) return `failing_check:${bad[0].name}`;
974
972
  // Red, and the failing check is one of the nulled nodes: say the list is blind
975
- // rather than guess "review or pending", which sent a box retrying (task 1003988).
973
+ // rather than guess "review or pending", which sent a ship retrying (task 1003988).
976
974
  if (s.checks_unreadable && ['FAILURE', 'ERROR'].includes(s.rollup_state)) return 'failing_check_unreadable';
977
975
  return 'blocked_required_review_or_pending_check';
978
976
  }
@@ -4,18 +4,18 @@
4
4
  // "strands at confirmed" failure class.
5
5
  //
6
6
  // WHY THIS EXISTS
7
- // A server-mediated publish (ADR 0055) is ASYNCHRONOUS: a dev box uploads its
7
+ // A server-mediated publish (ADR 0055) is ASYNCHRONOUS: a checkout uploads its
8
8
  // branch, the server pushes + opens the PR + arms auto-merge, then GitHub runs
9
- // the required checks, merges, and Actions deploys to prod. The box's ship.js
9
+ // the required checks, merges, and Actions deploys to prod. The uploader's ship.js
10
10
  // only POLLS for ~9 min (pollServerPublish) and then EXITS, printing "it flips to
11
11
  // shipped on its own". But the confirmed→shipped transition lived in exactly ONE
12
12
  // place — the GET /tasks/:id/publish-status reconcile-on-read (routes/tasks.js).
13
13
  // That flip can ONLY happen while a client keeps issuing that GET. Real CI (check
14
- // queue + merge queue + deploy) routinely finishes AFTER the box has stopped
14
+ // queue + merge queue + deploy) routinely finishes AFTER the client has stopped
15
15
  // polling, so nothing ever re-checks and the task row freezes at 'confirmed'
16
16
  // forever — even though prod is live. Every prior point-fix (1025/1027/1037/1041/
17
17
  // 1045/1046/1049/1050/1052/1068) compensated for a symptom of this one gap:
18
- // "the box must stay alive and keep polling to finish its OWN ship."
18
+ // "the client must stay alive and keep polling to finish its OWN ship."
19
19
  //
20
20
  // THE FIX. Move the last mile to the SERVER, where it belongs. A periodic sweep
21
21
  // (this module) finds every 'confirmed' task, derives its branch from the latest
@@ -23,7 +23,7 @@
23
23
  // succeeded?), and on a confirmed land runs the EXACT db.shipTask +
24
24
  // ship-broadcast.postShipBroadcast the publish-status route already runs. The land
25
25
  // no longer depends on the shipper's CLI being alive — it works for EVERY builder,
26
- // on a box, a laptop, or a desktop session, and retroactively heals strands left
26
+ // on a laptop or a desktop session, and retroactively heals strands left
27
27
  // by a session that exited early. Idempotent: shipTask throws TASK_NOT_CONFIRMED
28
28
  // if a client poll (or a prior sweep) already flipped it, so a sweep racing the
29
29
  // route never double-ships nor double-broadcasts.
@@ -333,7 +333,7 @@ async function runSweep(deps = {}) {
333
333
 
334
334
  // task 1002645 — the per-branch PR path (merge nudge → conflict/stale heal →
335
335
  // publish-status), extracted so it can run TWICE for one task: first on the
336
- // derived claude/<worktree_name> branch (the dev-box convention), then — when
336
+ // derived claude/<worktree_name> branch (the conventional name), then — when
337
337
  // that finds no PR — on the REAL branch adopted from the task's own PR
338
338
  // (findTaskPullRequest). Mutates the sweep counters via closure; returns
339
339
  // { status, prNotLanded }.
@@ -605,7 +605,7 @@ async function runSweep(deps = {}) {
605
605
  }
606
606
  }
607
607
 
608
- // task 1002645 — the claude/<worktree_name> derivation is the DEV-BOX convention;
608
+ // task 1002645 — the claude/<worktree_name> derivation is only ONE convention;
609
609
  // a laptop/wave ship pushes its own branch name (worktree-<worktree_name>), so the
610
610
  // derived branch finds no PR and the merge nudge / conflict heal / land proof all
611
611
  // sat idle while the task stranded at 'confirmed' (wave PRs 34/35/36 — native
@@ -768,8 +768,8 @@ async function runSweep(deps = {}) {
768
768
  logFn(
769
769
  `[publish-reconciler] task ${r.task_id} (${landNote}) landed → shipped`
770
770
  );
771
- // The box that published could not post the Discord broadcast (no webhook
772
- // secret — ADR 0053). The server holds the secrets, so it fires it here,
771
+ // The checkout that published cannot post the Discord broadcast (no webhook
772
+ // secret). The server holds the secrets, so it fires it here,
773
773
  // exactly ONCE on the confirmed→shipped transition. Best-effort: never let
774
774
  // a broadcast hiccup turn a successful reconcile into a failure.
775
775
  try {
@@ -827,7 +827,7 @@ async function runSweep(deps = {}) {
827
827
 
828
828
  // task 1366 — server-side session-cost reconcile, piggybacked on the same 5-min
829
829
  // sweep. System 1's client cost hook silently skips the MAJORITY of sessions (a
830
- // cloud box / cowork VM / overnight agent runs with no GDS token, so the
830
+ // cowork VM / overnight agent runs with no GDS token, so the
831
831
  // SessionEnd hook quiet-skips), so a server pass re-prices the model_usage we
832
832
  // ALREADY stored on session_records into a cost_log row — keyed on the SAME
833
833
  // (source, source_ref) the client would use, so logCost's ON CONFLICT DO NOTHING
@@ -624,7 +624,7 @@ module.exports = function buildTasksRouter() {
624
624
  // confirmed→shipped flip and throws TASK_NOT_CONFIRMED on a repeat, so
625
625
  // whichever entry point wins the transition is the only one that reaches
626
626
  // this line — the emit fires exactly ONCE per ship across all three
627
- // paths. The server is thus the SINGLE ship broadcaster (the box/laptop
627
+ // paths. The server is thus the SINGLE ship broadcaster (the laptop
628
628
  // local post in scripts/gds/ship.js was removed as a duplicate, task
629
629
  // 1749). Best-effort: emitAsync isolates listener errors so a broadcast
630
630
  // hiccup can never turn a successful ship into a 500.
@@ -1108,9 +1108,9 @@ module.exports = function buildTasksRouter() {
1108
1108
  // ==========================================================================
1109
1109
  // Server-mediated branch publish (ADR 0055 / task 1025)
1110
1110
  // --------------------------------------------------------------------------
1111
- // A builder on a cloud dev box has NO GitHub push credential (#919/ADR 0053
1112
- // box-scope + credential-less origin), so in `ci` deploy mode ship.js cannot
1113
- // `git push` + `gh pr create`. Instead the box uploads its branch as a THIN
1111
+ // A checkout with NO GitHub push credential (credential-less origin, #919)
1112
+ // cannot `git push` + `gh pr create`, so in `ci` deploy mode ship.js uploads
1113
+ // its branch as a THIN
1114
1114
  // git bundle here; the server pushes it + opens the PR + enables auto-merge
1115
1115
  // using its own push credential — a GitHub App installation token (preferred,
1116
1116
  // task 1028) or the GITHUB_PUSH_TOKEN PAT fallback; single repo, Contents:write
@@ -1255,7 +1255,7 @@ module.exports = function buildTasksRouter() {
1255
1255
 
1256
1256
  // GET /tasks/:id/publish-status?branch=<branch> — poll the PR + prod-deploy
1257
1257
  // state for a server-mediated publish so ship.js can confirm the land WITHOUT
1258
- // gh on the box. Ownership-gated like the POST. Derives state live from GitHub.
1258
+ // gh on the checkout. Ownership-gated like the POST. Derives state live from GitHub.
1259
1259
  // `branch` is optional (task 1003764): without it the server finds the task's own PR
1260
1260
  // (both publish paths title it "task <id>: …") and the answer names the `branch` it
1261
1261
  // used and its `branch_source`, so the check agents are sent to instead of git probes
@@ -1302,9 +1302,9 @@ module.exports = function buildTasksRouter() {
1302
1302
  try {
1303
1303
  const shipped = await db.shipTask({ taskId: id, builderId: req.builder.id, landAssurance: RECONCILE_PROOF }); // ADR 0184
1304
1304
  reconciled = true;
1305
- // task 1045: the box that published can't post the Discord broadcast —
1306
- // it holds no webhook secret (ADR 0053 "box holds nothing"), so a box
1307
- // ship was silently skipping the public announcement. The server holds
1305
+ // task 1045: the checkout that published can't post the Discord
1306
+ // broadcast — it holds no webhook secret, so such a ship was silently
1307
+ // skipping the public announcement. The server holds
1308
1308
  // the secrets and just confirmed the deploy, so it fires the broadcast
1309
1309
  // here — exactly ONCE, on the confirmed→shipped transition. shipTask
1310
1310
  // throws TASK_NOT_CONFIRMED on a repeat poll, so this never re-posts.
@@ -9,7 +9,7 @@
9
9
  //
10
10
  // ONE generator, two render paths, both PURE:
11
11
  // renderMonospaceCard(model) — alignment-proof left-rule text. The CLIs ALWAYS
12
- // print this, so headless / cron / dev-box runs and feeds without the in-feed
12
+ // print this, so headless / cron runs and feeds without the in-feed
13
13
  // visualization tool still get a consistent summary.
14
14
  // renderCardHtml(model) — the populated, branded HTML the skills render in
15
15
  // the chat feed via the visualization tool (show_widget). Brand tokens come
@@ -181,8 +181,8 @@ function isPubliclyViewable(task) {
181
181
  // The CANONICAL prefix (src/bongos/api-prefix.js) is hard-coded rather than
182
182
  // imported: this lib is deliberately core-free so it unit-tests with a Buffer and
183
183
  // a temp dir, and the doorway does not publish the prefix. It is safe to hard-code
184
- // because BOTH prefixes are permanent — '/api/gds' is a forever alias for shipped
185
- // Dev Box binaries and the off-box status mirror — so a URL stored under either
184
+ // because BOTH prefixes are permanent — '/api/gds' is a forever alias for older
185
+ // CLIs and the off-box status mirror — so a URL stored under either
186
186
  // keeps resolving, and every renderer's guard accepts both.
187
187
  const SERVE_PREFIX = '/api/bongos/task-visuals';
188
188
  function urlForName(name) {
@@ -32,8 +32,8 @@
32
32
  //
33
33
  // RENDER / STORED-XSS (#575, audit 2026-06-19) — memory content is stored RAW
34
34
  // and is NEVER emitted as HTML. Every endpoint here returns it as JSON
35
- // (res.json — content as a string field), and the only consumers are the CLI /
36
- // dev-box memory sync (it writes the strings to the local filesystem) and the
35
+ // (res.json — content as a string field), and the only consumers are the CLI
36
+ // memory sync (it writes the strings to the local filesystem) and the
37
37
  // Archon-only forensic reads (also JSON). An audit of every web surface
38
38
  // (modules/hall-ui/public/, modules/status-ui/public/) found NO code path that interpolates
39
39
  // memory content into the DOM: there is no markdown→HTML renderer in the client
@@ -249,8 +249,9 @@ module.exports = function ssoRoutes() {
249
249
  //
250
250
  // resolveSession is the RAW resolver (so we can redirect rather than 401), so it
251
251
  // does NOT apply requireBuilder's deny gates — we must. sessionUsableForFederation
252
- // rejects a deactivated account, a dev-box-scoped token (outside the ADR 0016
253
- // trust boundary), and service principals: none may mint a federated identity code.
252
+ // rejects a deactivated account, a session of an undeclared source (outside
253
+ // the ADR 0016 trust boundary), and service principals: none may mint a
254
+ // federated identity code.
254
255
  let session;
255
256
  try {
256
257
  session = await resolveSession(req);
@@ -137,7 +137,7 @@ async function countPlaced(db, shapes, { excludeId = null } = {}) {
137
137
  }
138
138
 
139
139
  // readLocalMeminfo — THIS host's /proc/meminfo, for the surfaces that run on the
140
- // control plane itself. Null (not a throw) where it does not exist, e.g. a dev box
140
+ // control plane itself. Null (not a throw) where it does not exist, e.g. a laptop
141
141
  // that is not Linux — the summary then reads 'default' and says so.
142
142
  function readLocalMeminfo() {
143
143
  try { return fs.readFileSync('/proc/meminfo', 'utf8'); } catch { return null; }
@@ -351,8 +351,9 @@ function defaultDomainForSlug(slug, apex) {
351
351
  }
352
352
 
353
353
  // Labels the platform itself occupies (or has planned) directly under the instance
354
- // apex, plus the dev-box hostname prefixes. The runner's DNS write is an UPSERT,
355
- // so a project claiming one of these would overwrite platform records — refuse at
354
+ // apex, plus the `term-` / `sandbox-` hostname prefixes older per-builder machines
355
+ // took. The runner's DNS write is an UPSERT, so a project claiming one of these
356
+ // would overwrite platform records — refuse at
356
357
  // the API layer instead (task 1002704; the guard applies to auto-assigned AND
357
358
  // bring-your-own hostnames, on create and on attach). Anything deeper than one
358
359
  // label under the apex is also refused: it breaks the wildcard-cert rule (one
@@ -1133,7 +1134,7 @@ async function setInstanceStatus(db, id, status, patch = {}) {
1133
1134
 
1134
1135
  // Enqueue a provision/teardown intent. Idempotent: the partial unique index
1135
1136
  // ux_provisioning_intents_open makes a second open enqueue collide (23505); we
1136
- // then return the already-open intent with created:false (mirrors boxes.enqueueIntent).
1137
+ // then return the already-open intent with created:false.
1137
1138
  async function enqueueIntent(db, instanceId, action, requestedBy, targetVersion = null) {
1138
1139
  await require('./control-plane-guard').assertIntentAllowed(db, instanceId, action); // ADR 0293 D2; required HERE because this file is at the size ratchet
1139
1140
  try {
@@ -2,7 +2,7 @@
2
2
  // surface (ADR 0111 §1/§2, task #1942). Instance hosting + domains: the web-tier
3
3
  // REQUEST + STATE surface for standing up + operating a Cloud Bongos instance.
4
4
  //
5
- // THE TRUST BOUNDARY (ADR 0111 §2 / ADR 0016 / the dev-box /box precedent). The
5
+ // THE TRUST BOUNDARY (ADR 0111 §2 / ADR 0016). The
6
6
  // internet-facing web process NEVER calls a cloud API and holds NO provider tokens.
7
7
  // A mutation route only ENQUEUES an intent (provision | teardown) into
8
8
  // provisioning_intents and returns; the control-plane runner (scripts/gds/
@@ -27,7 +27,7 @@
27
27
  // task 1003581 — see the handler)
28
28
  // POST /provisioning/instances/:id/teardown — request teardown of one's own
29
29
  // GET /provisioning/instances/:id/invite-suggestions — who is worth inviting (R28, ADR 0251)
30
- // Archon-scoped (requireRank('archon') — the fleet, the /boxes precedent):
30
+ // Archon-scoped (requireRank('archon') — the fleet):
31
31
  // GET /provisioning/fleet — the full instance roster + the box's slot `capacity` (ADR 0285)
32
32
  // GET /provisioning/fleet/cost-ledger — per-instance cost to us + to its owner, with totals
33
33
  // POST /provisioning/instances/:id/force-teardown — tear down ANY instance
@@ -204,7 +204,7 @@ module.exports = function provisioningRoutes() {
204
204
  // it. Archon-first keeps every route classifying to its true gate.
205
205
 
206
206
  // GET /provisioning/fleet — the full instance roster across all owners.
207
- // rank: archon (exposes every owner's instance + cost; the /boxes precedent).
207
+ // rank: archon (exposes every owner's instance + cost).
208
208
  router.get('/provisioning/fleet', auth.requireBuilder, auth.requirePermission('provisioning.fleet.manage'), async (req, res) => {
209
209
  try {
210
210
  const status = req.query.status || undefined;
@@ -217,7 +217,7 @@ module.exports = function provisioningRoutes() {
217
217
  });
218
218
 
219
219
  // GET /provisioning/fleet/cost-ledger — per-instance monthly cost to us + estimated cost to its owner, with totals.
220
- // rank: archon — cost oversight across the fleet (the /boxes/cost-ledger precedent).
220
+ // rank: archon — cost oversight across the fleet.
221
221
  // Declared among the archon routes (archon-first) for correct auditor classification.
222
222
  router.get('/provisioning/fleet/cost-ledger', auth.requireBuilder, auth.requirePermission('provisioning.fleet.manage'), async (req, res) => {
223
223
  try {
@@ -187,7 +187,7 @@ async function syncSessionDrachmaeEarnedTx(client, builderId, sessionId) {
187
187
  // safe, and lets a later, costlier ship in the same session book its increment.
188
188
  // task 1676: pass totalTokens so the reward can fall back to pricing the
189
189
  // session's real token total when modelUsage is empty/unpriceable — the
190
- // dev-box/CI agent path that historically paid $0 cost-plus (ADR 0097).
190
+ // CI agent path that historically paid $0 cost-plus (ADR 0097).
191
191
  async function awardSessionTokenRewardTx(client, { builderId, sessionId, modelUsage, totalTokens }) {
192
192
  const reward = seams.resolveOptional('reward');
193
193
  if (!reward) return { drachmae: 0, trueCostUsd: 0, awarded: false, unknownModels: [], deferred: false };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1074",
3
+ "version": "1.19.1075",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.1074",
9
+ "version": "1.19.1075",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1074",
3
+ "version": "1.19.1075",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -7821,5 +7821,11 @@
7821
7821
  "id": "1003781",
7822
7822
  "text": "Every module now has its own version number, shown in the module list and the hall's Modules tab — the first step toward publishing and updating modules through the store."
7823
7823
  }
7824
+ ],
7825
+ "1.19.1075": [
7826
+ {
7827
+ "id": "1003897",
7828
+ "text": "Cleanup: the code, tests and settings no longer describe the dev box as if it still existed, and seven code-quality limits are tightened to today's better numbers. Nothing changes for builders."
7829
+ }
7824
7830
  ]
7825
7831
  }
@@ -32,8 +32,8 @@
32
32
  //
33
33
  // ── WHERE THIS MAY RUN — read before scheduling it anywhere new ───────────────
34
34
  //
35
- // ONLY where the artifacts are SUPPOSED to already exist: a deployed instance, or a
36
- // dev box after its source-fetch regen. NEVER in CI. A fresh `git clone` legitimately
35
+ // ONLY where the artifacts are SUPPOSED to already exist: a deployed instance after its
36
+ // source-fetch regen. NEVER in CI. A fresh `git clone` legitimately
37
37
  // has no `docs/repo-map.md` — it is gitignored, so it is not in the repository at all
38
38
  // — and this check would report it absent on every single run. A monitor that is red
39
39
  // every run teaches its reader to ignore it, which is the exact failure ADR 0110's
@@ -4,12 +4,12 @@
4
4
  // scripts/gds/backfill-task-rewards.js — retroactive drachmae backfill for
5
5
  // shipped tasks that paid 0 reward (task 1672, ADR 0097).
6
6
  //
7
- // THE GAP. A task's `credits_reward` defaults to 0; many early/dev-box/CI ships
7
+ // THE GAP. A task's `credits_reward` defaults to 0; many early and CI ships
8
8
  // never had an author-estimated reward set, so the kind-multiplied task-confirm
9
9
  // credit (modules/economy/credits.js insertMultipliedConfirmCredit) computed
10
10
  // `0 × multiplier = 0` and minted nothing. The cost-plus *session* reward
11
11
  // (reason='session.token_reward', ADR 0054) was supposed to backstop those, but
12
- // it pays ~0 for dev-box/CI agent ships (no priced model usage uploaded), so
12
+ // it pays ~0 for CI agent ships (no priced model usage uploaded), so
13
13
  // those builders earned nothing for real shipped work. This script lets an
14
14
  // Archon retroactively pay a *fair, reviewed* per-task amount — once, safely.
15
15
  //
@@ -133,7 +133,7 @@ function planForBuilder(builderId, taskRewards, costPlusAlreadyPaid, alreadyBack
133
133
  // =========================================================================
134
134
 
135
135
  function loadPool() {
136
- // Lazy + guarded: a builder box has no DATABASE_URL / no peer-auth Postgres,
136
+ // Lazy + guarded: a builder machine has no DATABASE_URL / no peer-auth Postgres,
137
137
  // so --help and --dry-run must not crash here. We only reach for the pool when
138
138
  // we actually need to read/write the DB, and we report a clean message if the
139
139
  // connection is unavailable rather than throwing a raw stack.
@@ -230,7 +230,7 @@ async function buildPlan(pool, mapping, defaultRaw) {
230
230
  // session-level (task_id NULL), so we net at the BUILDER level — the audit
231
231
  // question is "did the cost-plus stream already compensate this builder for
232
232
  // the work these tasks represent". The dry-run surfaces this number so the
233
- // Archon can see whether it actually fired (it pays ~0 for dev-box/CI ships).
233
+ // Archon can see whether it actually fired (it pays ~0 for CI ships).
234
234
  const { rows: costPlusRows } = await pool.query(
235
235
  `SELECT builder_id, COALESCE(SUM(delta), 0)::int AS paid
236
236
  FROM credit_log
@@ -334,7 +334,7 @@ async function main() {
334
334
  try {
335
335
  pool = loadPool();
336
336
  } catch (err) {
337
- console.error('No DB connection available (expected on a builder box with no prod credentials).');
337
+ console.error('No DB connection available (expected on a builder machine with no prod credentials).');
338
338
  console.error('This script is inert here — run --help for usage. Detail:', err.message);
339
339
  process.exit(2);
340
340
  }
@@ -352,7 +352,7 @@ const STARTER_COMMANDS = [
352
352
  // `git diff|log|show` rule, also on purpose: the harness already runs their
353
353
  // read-only forms unprompted, and a prefix rule would switch off its flag check
354
354
  // (`--output=<file>` writes anywhere, `diff --no-index` reads any file).
355
- // tests/box_ship_permissions.mjs pins this list to the core's, rule for rule.
355
+ // tests/cli_allowlist_permissions.mjs pins this list to the core's, rule for rule.
356
356
  'bongos start:*',
357
357
  'bongos status:*',
358
358
  'bongos recall:*',
@@ -381,7 +381,7 @@ const STARTER_COMMANDS = [
381
381
  ];
382
382
  // One rule per command PER SHELL TOOL (task 1004014): the shell tool is `PowerShell` on a Windows
383
383
  // laptop, and a `Bash(...)` rule says nothing about its commands. The tools are the ones the guards
384
- // see (`.claude/hooks/shell-tools.js`) — held equal by tests/box_ship_permissions.mjs, not a require.
384
+ // see (`.claude/hooks/shell-tools.js`) — held equal by tests/cli_allowlist_permissions.mjs, not a require.
385
385
  const STARTER_SHELL_TOOLS = ['Bash', 'PowerShell'];
386
386
  const STARTER_ALLOW = STARTER_SHELL_TOOLS.flatMap((tool) => STARTER_COMMANDS.map((command) => `${tool}(${command})`));
387
387
 
@@ -24,12 +24,12 @@ const ic = require('../../src/instance-config');
24
24
  // namespaced instance, still resolves (R61 / task 1200). os.homedir() keeps it
25
25
  // per-user on any machine.
26
26
  //
27
- // WHY THE OLD NAMES STAY READABLE. This file is on disk on every builder machine
28
- // and every live dev box. Renaming it with no fallback signs all of them out at
29
- // once and every /builder-* command dies with 'no Bongos session'. So a read that
30
- // resolves through a superseded name MIGRATES forward (see loadSession), and the
31
- // old file is deliberately LEFT IN PLACE — a builder may roll back to an older
32
- // CLI, which can only see the old name. Part 4 (task 1003706) deletes it behind a
27
+ // WHY THE OLD NAMES STAY READABLE. This file is on disk on every builder machine.
28
+ // Renaming it with no fallback signs all of them out at once and every /builder-*
29
+ // command dies with 'no Bongos session'. So a read that resolves through a
30
+ // superseded name MIGRATES forward (see loadSession), and the old file is
31
+ // deliberately LEFT IN PLACE — a builder may roll back to an older CLI, which
32
+ // can only see the old name. Part 4 (task 1003706) deletes it behind a
33
33
  // test of its absence. (task 1003704)
34
34
  //
35
35
  // Order is FILENAME-major, not directory-major: the newest name found in ANY read
@@ -330,7 +330,7 @@ async function writeSessionToStore(session) {
330
330
  // upgrading would still lose it.
331
331
  // 2. store the incoming one under its own host.
332
332
  // 3. write the ACTIVE pointer exactly where it has always gone, so every existing reader —
333
- // in-repo skills, hooks, the dev box — behaves identically.
333
+ // in-repo skills and hooks — behaves identically.
334
334
  async function saveSession(session) {
335
335
  const dir = path.dirname(SESSION_PATH);
336
336
  await fsp.mkdir(dir, { recursive: true });
@@ -12,7 +12,7 @@
12
12
  // never clobbered:
13
13
  // src/gds/... -> src/bongos/... (directory refs)
14
14
  // "src/gds" -> "src/bongos" (bare, at a boundary)
15
- // src/gds-client -> src/gds-client (UNCHANGED: devbox-app/src/gds-client.js)
15
+ // src/gds-client -> src/gds-client (UNCHANGED: a look-alike file name)
16
16
  // src/gdsx -> src/gdsx (UNCHANGED here: prefix-edge fixture)
17
17
  //
18
18
  // Regex: /src\/gds(?![A-Za-z0-9_-])/g -- trailing guard only. No LEADING guard: