@bongos/core 1.19.305 → 1.19.307
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 +41 -21
- package/.claude/skills/builder-ship/SKILL.md +21 -9
- package/.claude/skills/fix-task/SKILL.md +1 -1
- package/.claude/skills/otb-tile-generate/SKILL.md +1 -1
- package/.claude/skills/paint/SKILL.md +1 -1
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/figma-design-loop.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/cli-lib.js +53 -0
- package/scripts/gds/doc-cli-guard.js +245 -0
- package/scripts/gds/fitness.js +1 -1
- package/scripts/gds/ship-honesty.js +150 -0
- package/scripts/gds/ship.js +23 -32
- package/src/module-api.js +5 -1
- package/tests/doc_cli_guard.mjs +195 -0
- package/tests/route_rank_check.mjs +117 -9
- package/tests/ship_cannot_lie.mjs +168 -0
- package/tests/ship_smoke_gate.mjs +9 -3
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.307",
|
|
6
|
+
"core_contract": "1.19.307",
|
|
7
|
+
"source_commit": "b8d2ed227fd6cb0b948128e23f0daf0070b50bcc",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-23T20:16:34.186Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 322,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1418,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1762,
|
|
20
|
+
"tree_sha256": "2ce3db5cb1a893df39826469894f19d4acdee021e5729859eaa8d429933be04d",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
{
|
|
98
98
|
"path": ".claude/skills/builder-ship/SKILL.md",
|
|
99
99
|
"mode": "0000644",
|
|
100
|
-
"sha256": "
|
|
100
|
+
"sha256": "78fcb68ce3ff4b1028904acb9d4d0a37dadd8377108fefef248feef010e4c8b1"
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"path": ".claude/skills/builder-stage/SKILL.md",
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
{
|
|
143
143
|
"path": ".claude/skills/fix-task/SKILL.md",
|
|
144
144
|
"mode": "0000644",
|
|
145
|
-
"sha256": "
|
|
145
|
+
"sha256": "59b29fcdf54fe51e85191d1a66be237ef35432b76b26a10ec67e46bf08b4d1d3"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"path": ".claude/skills/goal-create/SKILL.md",
|
|
@@ -212,12 +212,12 @@
|
|
|
212
212
|
{
|
|
213
213
|
"path": ".claude/skills/otb-tile-generate/SKILL.md",
|
|
214
214
|
"mode": "0000644",
|
|
215
|
-
"sha256": "
|
|
215
|
+
"sha256": "53bfcf6c531c0aeefc1696b470ba6a746f4490501ab092766f42c501f06d453d"
|
|
216
216
|
},
|
|
217
217
|
{
|
|
218
218
|
"path": ".claude/skills/paint/SKILL.md",
|
|
219
219
|
"mode": "0000644",
|
|
220
|
-
"sha256": "
|
|
220
|
+
"sha256": "54af43e9417eaa369d9f166e0bbc8567c4540e064504e67ef32186fe31b2f766"
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
223
|
"path": ".claude/skills/planning-session/SKILL.md",
|
|
@@ -1662,7 +1662,7 @@
|
|
|
1662
1662
|
{
|
|
1663
1663
|
"path": "docs/recipes/claude-design-loop.md",
|
|
1664
1664
|
"mode": "0000644",
|
|
1665
|
-
"sha256": "
|
|
1665
|
+
"sha256": "389dbbbfb7c941315731d533e300b5d43f40a8aac866c5a1c74c32d5be212a22"
|
|
1666
1666
|
},
|
|
1667
1667
|
{
|
|
1668
1668
|
"path": "docs/recipes/core-release-pipeline.md",
|
|
@@ -1687,7 +1687,7 @@
|
|
|
1687
1687
|
{
|
|
1688
1688
|
"path": "docs/recipes/figma-design-loop.md",
|
|
1689
1689
|
"mode": "0000644",
|
|
1690
|
-
"sha256": "
|
|
1690
|
+
"sha256": "6bfee82bddc07a5743316e310dda1e6cb7266fa7805fc1eed67bc8cc17980b46"
|
|
1691
1691
|
},
|
|
1692
1692
|
{
|
|
1693
1693
|
"path": "docs/recipes/gds-db-backup.md",
|
|
@@ -4577,12 +4577,12 @@
|
|
|
4577
4577
|
{
|
|
4578
4578
|
"path": "package-lock.json",
|
|
4579
4579
|
"mode": "0000644",
|
|
4580
|
-
"sha256": "
|
|
4580
|
+
"sha256": "4815e8ce78e80ac2fff7dad9f77bef508e2f0090459c67e59722b65af3e27294"
|
|
4581
4581
|
},
|
|
4582
4582
|
{
|
|
4583
4583
|
"path": "package.json",
|
|
4584
4584
|
"mode": "0000644",
|
|
4585
|
-
"sha256": "
|
|
4585
|
+
"sha256": "bdeee91142d9d7f3930c99134be76d03f28329222adcf293deaa8f8354ab2cd8"
|
|
4586
4586
|
},
|
|
4587
4587
|
{
|
|
4588
4588
|
"path": "public-docs/index.html",
|
|
@@ -5082,7 +5082,7 @@
|
|
|
5082
5082
|
{
|
|
5083
5083
|
"path": "scripts/gds/cli-lib.js",
|
|
5084
5084
|
"mode": "0000644",
|
|
5085
|
-
"sha256": "
|
|
5085
|
+
"sha256": "5aa658b099b2acda06cdb3aecfe905da7de79f2b12c844faca091ff061454017"
|
|
5086
5086
|
},
|
|
5087
5087
|
{
|
|
5088
5088
|
"path": "scripts/gds/code.js",
|
|
@@ -5189,6 +5189,11 @@
|
|
|
5189
5189
|
"mode": "0000644",
|
|
5190
5190
|
"sha256": "7e279cd4cb8fb0458b8b3be7b0d694e7afcc96eec926abf68b8ccd6afb65429f"
|
|
5191
5191
|
},
|
|
5192
|
+
{
|
|
5193
|
+
"path": "scripts/gds/doc-cli-guard.js",
|
|
5194
|
+
"mode": "0000644",
|
|
5195
|
+
"sha256": "e7feb497a7b568273d1a2c16c2a65036c60017d163d792d05d3c43addcfd7d8f"
|
|
5196
|
+
},
|
|
5192
5197
|
{
|
|
5193
5198
|
"path": "scripts/gds/docs-entropy.js",
|
|
5194
5199
|
"mode": "0000644",
|
|
@@ -5227,7 +5232,7 @@
|
|
|
5227
5232
|
{
|
|
5228
5233
|
"path": "scripts/gds/fitness.js",
|
|
5229
5234
|
"mode": "0000644",
|
|
5230
|
-
"sha256": "
|
|
5235
|
+
"sha256": "71f7b70fb6b7b2251c5a335b90faf0b63b51c614829a247679b6f25209549264"
|
|
5231
5236
|
},
|
|
5232
5237
|
{
|
|
5233
5238
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -5779,10 +5784,15 @@
|
|
|
5779
5784
|
"mode": "0000644",
|
|
5780
5785
|
"sha256": "087def1396283b19cb8a6deea4cc7f362d7a2f164d496a587220f0a48568aae6"
|
|
5781
5786
|
},
|
|
5787
|
+
{
|
|
5788
|
+
"path": "scripts/gds/ship-honesty.js",
|
|
5789
|
+
"mode": "0000644",
|
|
5790
|
+
"sha256": "32827178a46cc01c36c422e1adef433be99492e8444a8affbe51e5e8c1e8ce9f"
|
|
5791
|
+
},
|
|
5782
5792
|
{
|
|
5783
5793
|
"path": "scripts/gds/ship.js",
|
|
5784
5794
|
"mode": "0000644",
|
|
5785
|
-
"sha256": "
|
|
5795
|
+
"sha256": "f3005175abc0ba20e958381df6aee02402267e9956da466386656dd4e86ad8cf"
|
|
5786
5796
|
},
|
|
5787
5797
|
{
|
|
5788
5798
|
"path": "scripts/gds/smoke-batch-claim.sh",
|
|
@@ -6237,7 +6247,7 @@
|
|
|
6237
6247
|
{
|
|
6238
6248
|
"path": "src/module-api.js",
|
|
6239
6249
|
"mode": "0000644",
|
|
6240
|
-
"sha256": "
|
|
6250
|
+
"sha256": "90436d6db6e32424c48549f61ad48a3a77f5c7cac1ae16d5559b7db76fee7b4a"
|
|
6241
6251
|
},
|
|
6242
6252
|
{
|
|
6243
6253
|
"path": "src/module-loader/catalog.js",
|
|
@@ -7019,6 +7029,11 @@
|
|
|
7019
7029
|
"mode": "0000644",
|
|
7020
7030
|
"sha256": "d9fd04cc3f2fcb3b4a9945c1e743c44445a216a10648fa619f74716b381468f3"
|
|
7021
7031
|
},
|
|
7032
|
+
{
|
|
7033
|
+
"path": "tests/doc_cli_guard.mjs",
|
|
7034
|
+
"mode": "0000644",
|
|
7035
|
+
"sha256": "4e68c67489daa51167b8d738a36f7c0187016b342e8a57731d14241563262878"
|
|
7036
|
+
},
|
|
7022
7037
|
{
|
|
7023
7038
|
"path": "tests/docs_entropy.mjs",
|
|
7024
7039
|
"mode": "0000644",
|
|
@@ -8272,7 +8287,7 @@
|
|
|
8272
8287
|
{
|
|
8273
8288
|
"path": "tests/route_rank_check.mjs",
|
|
8274
8289
|
"mode": "0000644",
|
|
8275
|
-
"sha256": "
|
|
8290
|
+
"sha256": "bdc58224050392ece492ae8e8f12f62a8934d86e3993e0df5b8cb0aac80602e8"
|
|
8276
8291
|
},
|
|
8277
8292
|
{
|
|
8278
8293
|
"path": "tests/route_validation.mjs",
|
|
@@ -8494,6 +8509,11 @@
|
|
|
8494
8509
|
"mode": "0000644",
|
|
8495
8510
|
"sha256": "afdb09a3ee072fea82390a187c12c42d8249fce0ce84644a4b8dff6a643dd009"
|
|
8496
8511
|
},
|
|
8512
|
+
{
|
|
8513
|
+
"path": "tests/ship_cannot_lie.mjs",
|
|
8514
|
+
"mode": "0000644",
|
|
8515
|
+
"sha256": "82f6f951cc50c34860545be0f309e18a796ebb5baa712ccb341e1fd0dd72a233"
|
|
8516
|
+
},
|
|
8497
8517
|
{
|
|
8498
8518
|
"path": "tests/ship_card.mjs",
|
|
8499
8519
|
"mode": "0000644",
|
|
@@ -8542,7 +8562,7 @@
|
|
|
8542
8562
|
{
|
|
8543
8563
|
"path": "tests/ship_smoke_gate.mjs",
|
|
8544
8564
|
"mode": "0000644",
|
|
8545
|
-
"sha256": "
|
|
8565
|
+
"sha256": "6ce7bd5d2a8910e201a9869c8a3f22ef210fd4400fbd14777d8d15d13252a97e"
|
|
8546
8566
|
},
|
|
8547
8567
|
{
|
|
8548
8568
|
"path": "tests/ship_upload_skip.mjs",
|
|
@@ -7,7 +7,7 @@ You are shipping a task on behalf of the current builder. Shipping is the moment
|
|
|
7
7
|
|
|
8
8
|
## What this skill does
|
|
9
9
|
|
|
10
|
-
Runs `node scripts/gds/ship.js <task-id> --notes
|
|
10
|
+
Runs `node scripts/gds/ship.js <task-id> --notes-file notes.md --summary-file summary.txt`.
|
|
11
11
|
|
|
12
12
|
The CLI drives a **three-state lifecycle** with a subagent grader between states (Phase 5, 2026-05-10):
|
|
13
13
|
|
|
@@ -34,7 +34,7 @@ The CLI drives a **three-state lifecycle** with a subagent grader between states
|
|
|
34
34
|
|
|
35
35
|
**When you see that block, position the sandbox link prominently for the builder.** `ship.js` also emits the deterministic lifecycle card for the `review` stage (an `[otb-card-html] <path>` marker) — if an in-feed visualization tool is available, render that card (its primary action is **Open in sandbox ↗**, the URL); it's the cleanest way to make the link impossible to miss. Either way, surface the URL as its own standalone, clickable line in chat — a clear callout, NOT buried inside a paragraph of command output — and say in one line what to look at. This is the moment the change is ready to review; the link must be impossible to miss. Let the builder open it, review, and suggest changes; iterate if needed. **Only once the builder explicitly approves, re-run with `node scripts/gds/ship.js <id> --approved`** — that records their sign-off as a durable `sandbox_review` fact on the task (who, when, and the content stamp of exactly what was reviewed). Never pass `--approved` yourself without a real human go; the pause + sign-off IS the feature. `--skip-stage` is the escape hatch (use sparingly — the gate exists so unreviewed game changes don't ride straight to prod). Laptop sessions and non-game diffs are unaffected.
|
|
36
36
|
|
|
37
|
-
2. **Compose the handoff notes (`--notes`)** following the structure in `docs/handoff-template.md`:
|
|
37
|
+
2. **Compose the handoff notes (`--notes-file`)** following the structure in `docs/handoff-template.md`:
|
|
38
38
|
- What shipped (with confidence tags: verified-prod | verified-smoke | implemented-not-verified)
|
|
39
39
|
- What's broken or unverified
|
|
40
40
|
- Manual checks the next session should run first
|
|
@@ -52,16 +52,28 @@ The CLI drives a **three-state lifecycle** with a subagent grader between states
|
|
|
52
52
|
- "Sessions can claim work atomically; merge conflicts can't happen across worktrees."
|
|
53
53
|
No jargon, no file paths, no commit hashes.
|
|
54
54
|
|
|
55
|
-
4. **Run
|
|
55
|
+
4. **Run** — write the prose to files and pass the file (task 1003168):
|
|
56
56
|
```bash
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
...handoff markdown...
|
|
60
|
-
EOF
|
|
61
|
-
)" \
|
|
62
|
-
--summary "the one-liner"
|
|
57
|
+
# write notes.md and summary.txt with your editor/Write tool first
|
|
58
|
+
node scripts/gds/ship.js <task-id> --notes-file notes.md --summary-file summary.txt
|
|
63
59
|
```
|
|
64
60
|
|
|
61
|
+
**Use the `-file` forms.** Handoff notes are technical prose, so they contain
|
|
62
|
+
backticks — and a backtick in an inline `--notes "..."` is command-substituted
|
|
63
|
+
by the shell *before* node runs: the span executes, its output replaces it, and
|
|
64
|
+
the rest of the argument list is truncated. That is not hypothetical; it shipped
|
|
65
|
+
a task with **empty notes and no summary**, so the ledger held nothing about a
|
|
66
|
+
session that had happened (learning 1000197). A file argument is the one form
|
|
67
|
+
the shell cannot rewrite. `-` reads stdin if you'd rather pipe.
|
|
68
|
+
|
|
69
|
+
Inline `--notes "..."` / `--summary "..."` still work for short prose with no
|
|
70
|
+
backticks, `$`, or newlines. The older documented form —
|
|
71
|
+
`--notes "$(cat <<'EOF' … EOF)"` — is *also* shell-safe, because the quoted
|
|
72
|
+
heredoc delimiter suppresses expansion; it is simply easy to get subtly wrong,
|
|
73
|
+
and when you do the failure is silent. Passing both `--notes` and
|
|
74
|
+
`--notes-file` is refused rather than resolved by precedence, so nothing you
|
|
75
|
+
wrote is dropped without a word.
|
|
76
|
+
|
|
65
77
|
**Never merge a task branch to main before running `ship.js`.** `ship.js` diffs the claim branch against `main`; an already-merged branch yields an empty diff, and the only exits are `--allow-empty` (dead-ends the task at `completed`, requiring an Archon-only confirm) or reverting the merge. Ship from the branch, then let `ship.js` land it. (This trap was hit on tasks 1002417 and 1002419, producing blockers 1000081/1000082 and an ~8.5-hour stall.)
|
|
66
78
|
|
|
67
79
|
5. **Render the completion card — it IS the summary; do NOT write a prose recap (task 1270).**
|
|
@@ -39,7 +39,7 @@ Runs the recurring "claim, fix, and ship task NNNN" macro end to end. Its own va
|
|
|
39
39
|
|
|
40
40
|
6. **Ship from the claim branch** — see `/builder-ship` for handoff notes, the value summary, and the completion-card rendering contract. In short:
|
|
41
41
|
```
|
|
42
|
-
node scripts/gds/ship.js <id> --notes
|
|
42
|
+
node scripts/gds/ship.js <id> --notes-file notes.md --summary-file summary.txt
|
|
43
43
|
```
|
|
44
44
|
**Never merge the claim branch into `main` before running `ship.js`.** `ship.js` diffs the branch against `main`; a pre-merged branch yields an empty diff, and the only way out is `--allow-empty` (dead-ends the task at `completed`, needing an Archon-only confirm) or reverting the merge. Ship from the branch and let `ship.js` land it — the same trap `/builder-ship` calls out.
|
|
45
45
|
|
|
@@ -30,7 +30,7 @@ Runs `modules/art-pipeline/pipeline/orchestrator.py` for the requested tiles. Th
|
|
|
30
30
|
|
|
31
31
|
**View it on your sandbox without placing it in-game** (#1262): every tile you've generated — native + zoomed, with its score and critique — is served at **`https://sandbox-<login>.example.com/art`** (locally: `http://localhost:<port>/art`). This is the generated-art *gallery* (`modules/art-pipeline/generated/preview.html`), so you can show the builder what they made even when the tile isn't placed in the world yet. The atlas-rebuild in-game step below is only needed once you actually want it *in the playable world*.
|
|
32
32
|
|
|
33
|
-
6. **See it in-game on your sandbox** (#1067): run `node scripts/gds/art-stage.js` — it rebuilds the atlas the client loads, surfaces it on your sandbox live preview (`sandbox-<login>.example.com`, ADR 0044), and prints the URL + affected tiles. With the auto-reload (task 1065) an already-open tab refreshes itself. (The family/refresh runs also accept `--stage` to do this automatically: `family_orchestrator.py <id> --publish --stage`, `refresh_all.py --stage`.) Only a genuinely NEW base-tile *type* still needs a `public/game/world/tilePalette.js` mapping edit; an updated existing tile just needs the atlas rebuild above.
|
|
33
|
+
6. **See it in-game on your sandbox** (#1067): run `node scripts/gds/art-stage.js` (**ships with the `art-pipeline` host module, not the portable core** — absent from a core-only checkout, where this step doesn't apply) — it rebuilds the atlas the client loads, surfaces it on your sandbox live preview (`sandbox-<login>.example.com`, ADR 0044), and prints the URL + affected tiles. With the auto-reload (task 1065) an already-open tab refreshes itself. (The family/refresh runs also accept `--stage` to do this automatically: `family_orchestrator.py <id> --publish --stage`, `refresh_all.py --stage`.) Only a genuinely NEW base-tile *type* still needs a `public/game/world/tilePalette.js` mapping edit; an updated existing tile just needs the atlas rebuild above.
|
|
34
34
|
|
|
35
35
|
## Constraints
|
|
36
36
|
|
|
@@ -40,7 +40,7 @@ Ground every asset in the locked theme so nothing drifts:
|
|
|
40
40
|
1. **Establish the visual target first, not in prose.** Confirm the subject (an existing vocab id, or a new in-world prop/plant/animal/accessory) and, if helpful, show the reference cluster it should sit beside. One or two lines, then move to making.
|
|
41
41
|
2. **Generate via the pipeline — never freelance.** Drive `/otb-tile-generate` (which runs `modules/art-pipeline/pipeline/orchestrator.py`: generate → post-process to 32×32 → score against rubric → retry up to 5 tiers → save to `modules/art-pipeline/generated/tiles/<id>.png`, logging cost). Check the budget first (`python3 modules/art-pipeline/pipeline/cost_ledger.py status`; hard stop at $80) — but report it as one line.
|
|
42
42
|
3. **SHOW the result.** Display the final accepted PNG and its rubric score. The gallery serves every tile (native + zoomed, with score + critique) at `sandbox-<login>.example.com/art` — point them there and/or render the image inline. **Default visibility: show the final accepted asset and any failures. Show the intermediate retry attempts only if the artist asks ("show me the attempts").**
|
|
43
|
-
4. **See it in the world.** When they want it placed, run `node scripts/gds/art-stage.js` — it rebuilds the atlas and surfaces the asset live in the game on their sandbox (auto-reloads an open tab). Screenshot/point at the in-world view. (A genuinely new base-tile *type* also needs a `public/game/world/tilePalette.js` mapping edit; an updated existing tile just needs the rebuild.)
|
|
43
|
+
4. **See it in the world.** When they want it placed, run `node scripts/gds/art-stage.js` (**ships with the `art-pipeline` host module, not the portable core** — on a core-only checkout the file is absent and this step doesn't apply) — it rebuilds the atlas and surfaces the asset live in the game on their sandbox (auto-reloads an open tab). Screenshot/point at the in-world view. (A genuinely new base-tile *type* also needs a `public/game/world/tilePalette.js` mapping edit; an updated existing tile just needs the rebuild.)
|
|
44
44
|
5. **Feedback is visual → a durable rule.** When the artist reacts ("too saturated", "trees too modern", "weather the marble"), don't just tweak once — run `/otb-feedback-capture` to write the note into `modules/art-pipeline/template/style_guide.md` as a dated rule, then regenerate and **show the new result**. The loop is see → react → see again, and the style guide gets smarter each pass.
|
|
45
45
|
6. **Use `/otb-design-review`** when you want a rubric-grounded second opinion on whether an asset truly hits the bar — surface its numbers, not a paragraph.
|
|
46
46
|
|
|
@@ -113,7 +113,7 @@ The adapter writes files to `src/ui/<surfaceId>/` and updates `config/design-sou
|
|
|
113
113
|
```bash
|
|
114
114
|
git add src/ui/hall-ui/ config/design-sources.json
|
|
115
115
|
git commit -m "ui(hall-ui): Claude Design export (task 1234)"
|
|
116
|
-
node scripts/gds/ship.js 1234 --notes
|
|
116
|
+
node scripts/gds/ship.js 1234 --notes-file notes.md --summary-file summary.txt
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
---
|
|
@@ -98,7 +98,7 @@ The adapter translates frames/text/solid-fill rectangles into `.js` + `.css` fil
|
|
|
98
98
|
```bash
|
|
99
99
|
git add src/ui/hall-ui/ config/design-sources.json
|
|
100
100
|
git commit -m "ui(hall-ui): Figma export (task 1234)"
|
|
101
|
-
node scripts/gds/ship.js 1234 --notes
|
|
101
|
+
node scripts/gds/ship.js 1234 --notes-file notes.md --summary-file summary.txt
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
---
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.307",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.307",
|
|
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.
|
|
3
|
+
"version": "1.19.307",
|
|
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",
|
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -266,6 +266,58 @@ function stripBom(s) {
|
|
|
266
266
|
return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
// argText(name, args, io) — resolve prose passed EITHER inline (`--notes "..."`)
|
|
270
|
+
// or from a file (`--notes-file path`, or `-` for stdin). Returns
|
|
271
|
+
// { value, source, error }: `value` is a string ('' when neither flag is
|
|
272
|
+
// present), `source` is 'inline' | 'file' | 'stdin' | 'none', and `error` is a
|
|
273
|
+
// caller-printable string when the request was refused or unreadable.
|
|
274
|
+
//
|
|
275
|
+
// WHY THIS EXISTS (learning 1000197). Prose handed to a CLI inline goes through
|
|
276
|
+
// the SHELL first. A handoff note containing backticks — which technical prose
|
|
277
|
+
// constantly does — is command-substituted by bash before node ever runs: the
|
|
278
|
+
// backticked span executes, its output replaces it, and the rest of the argument
|
|
279
|
+
// list is truncated. That is not a theoretical hazard; it silently shipped a task
|
|
280
|
+
// with EMPTY notes and no summary, so the ledger recorded nothing about a session
|
|
281
|
+
// that had happened. A file argument is the only form the shell cannot rewrite.
|
|
282
|
+
//
|
|
283
|
+
// BOTH-GIVEN IS AN ERROR, never a silent precedence. Picking one would mean the
|
|
284
|
+
// caller's other input vanished without a word — the same class of quiet loss
|
|
285
|
+
// this helper exists to end (the capture.js --spark/--full refusal precedent).
|
|
286
|
+
function argText(name, args = process.argv.slice(2), io = {}) {
|
|
287
|
+
const readFile = io.readFile || ((f) => require('fs').readFileSync(f, 'utf8'));
|
|
288
|
+
const readStdin = io.readStdin || (() => require('fs').readFileSync(0, 'utf8'));
|
|
289
|
+
const fileFlag = `${name}-file`;
|
|
290
|
+
const inline = arg(name, args);
|
|
291
|
+
// arg() returns undefined (not null) for a flag present as the LAST token, so
|
|
292
|
+
// presence must be tested separately — otherwise `--notes-file` with no path
|
|
293
|
+
// slips past the null check and resolves to an empty string, which is the very
|
|
294
|
+
// silent-empty outcome this helper exists to prevent.
|
|
295
|
+
const filePresent = args.includes(fileFlag) || args.some((a) => a.startsWith(`${fileFlag}=`));
|
|
296
|
+
const file = filePresent ? arg(fileFlag, args) : null;
|
|
297
|
+
|
|
298
|
+
if (inline != null && filePresent) {
|
|
299
|
+
return {
|
|
300
|
+
value: '', source: 'none',
|
|
301
|
+
error: `${name} and ${fileFlag} were both given. Pass one — ${fileFlag} is the safe form for prose `
|
|
302
|
+
+ '(a shell rewrites backticks in an inline value before node sees it). Refusing rather than '
|
|
303
|
+
+ 'silently picking one, so nothing you wrote is dropped without a word.',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (filePresent) {
|
|
307
|
+
if (file == null || file === '' || file.startsWith('--')) {
|
|
308
|
+
return { value: '', source: 'none', error: `${fileFlag} needs a path (or - for stdin).` };
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const raw = file === '-' ? readStdin() : readFile(file);
|
|
312
|
+
return { value: stripBom(String(raw)), source: file === '-' ? 'stdin' : 'file', error: null };
|
|
313
|
+
} catch (err) {
|
|
314
|
+
return { value: '', source: 'none', error: `could not read ${fileFlag} ${file}: ${err.message}` };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (inline != null) return { value: inline, source: 'inline', error: null };
|
|
318
|
+
return { value: '', source: 'none', error: null };
|
|
319
|
+
}
|
|
320
|
+
|
|
269
321
|
// resolveQuiet(args, env) — true when api.js's stderr status banner should be
|
|
270
322
|
// suppressed: the --quiet flag or the GDS_QUIET env var. Both default to the
|
|
271
323
|
// live process, matching hasFlag()'s own default-args contract. Default OFF —
|
|
@@ -853,6 +905,7 @@ module.exports = {
|
|
|
853
905
|
loadSessionSync,
|
|
854
906
|
saveSession,
|
|
855
907
|
arg,
|
|
908
|
+
argText,
|
|
856
909
|
hasFlag,
|
|
857
910
|
stripBom,
|
|
858
911
|
repairMsysMangledPath,
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/doc-cli-guard.js — a doc that NAMES a CLI invocation must match the
|
|
3
|
+
// real CLI (task 1003165 / G01, goal 1000077; criterion guards-doc-names-a-command).
|
|
4
|
+
//
|
|
5
|
+
// WHY. Task 1002931 shipped a skill instructing `capture.js --full --fields-file`
|
|
6
|
+
// while those flags lived on an unlanded branch. Its test passed, because the test
|
|
7
|
+
// pinned the skill's PROSE — it asserted the file said the words, never that the
|
|
8
|
+
// words worked. So the skill confidently told agents to run a command that
|
|
9
|
+
// silently ignored both flags and filed the wrong thing. That is the whole class:
|
|
10
|
+
// AN UNRECOGNISED FLAG IS NOT AN ERROR. `arg('--x')`/`hasFlag('--x')` return null
|
|
11
|
+
// and the command proceeds, so a doc can name a flag that does nothing and every
|
|
12
|
+
// test stays green (learning 1000198). It happened TWICE in one session: the
|
|
13
|
+
// second time /workspace ran an older capture.js and the same documented flags
|
|
14
|
+
// were dropped on the floor again.
|
|
15
|
+
//
|
|
16
|
+
// WHAT IT CHECKS, deliberately narrow: for every `node scripts/gds/<x>.js` in a
|
|
17
|
+
// tracked .md, (a) the script exists, and (b) each `--flag` written next to it
|
|
18
|
+
// appears in that script's source. Nothing else — this is not a prose linter.
|
|
19
|
+
//
|
|
20
|
+
// STATIC ONLY, NEVER EXECUTED. The reason matters: running every documented CLI
|
|
21
|
+
// would be slow, would need a session, and a command that blocks on a prompt
|
|
22
|
+
// would hang CI forever — the exact failure R151's TTY gate exists to prevent.
|
|
23
|
+
//
|
|
24
|
+
// It lives in its OWN FILE rather than in fitness.js because fitness.js is already
|
|
25
|
+
// over the size ratchet (config/fitness-baselines.json) — the control-manifest.js
|
|
26
|
+
// precedent. fitness.js requires it in CHECKS.
|
|
27
|
+
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
const { execFileSync } = require('node:child_process');
|
|
33
|
+
|
|
34
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Check 22 — a doc that NAMES a CLI invocation must match the real CLI
|
|
38
|
+
// (task 1003165 / G01, goal 1000077).
|
|
39
|
+
//
|
|
40
|
+
// WHY. Task 1002931 shipped a skill instructing `capture.js --full --fields-file`
|
|
41
|
+
// while those flags lived on an unlanded branch. Its test passed, because the test
|
|
42
|
+
// pinned the skill's PROSE — it asserted the file said the words, never that the
|
|
43
|
+
// words worked. So the skill confidently told agents to run a command that
|
|
44
|
+
// silently ignored both flags and filed the wrong thing. That is the whole class:
|
|
45
|
+
// AN UNRECOGNISED FLAG IS NOT AN ERROR. `arg('--x')`/`hasFlag('--x')` just return
|
|
46
|
+
// null and the command proceeds, so a doc can name a flag that does nothing and
|
|
47
|
+
// every test stays green (learning 1000198).
|
|
48
|
+
//
|
|
49
|
+
// It happened TWICE in one session: the second time, /workspace ran an older
|
|
50
|
+
// capture.js and the same documented flags were dropped on the floor again.
|
|
51
|
+
//
|
|
52
|
+
// WHAT IT CHECKS, deliberately narrow: for every `node scripts/gds/<x>.js` in a
|
|
53
|
+
// tracked .md, (a) the script exists, and (b) each `--flag` written next to it is
|
|
54
|
+
// parsed by that script. Nothing else — this is not a linter for prose.
|
|
55
|
+
//
|
|
56
|
+
// STATIC ONLY, NEVER EXECUTED. The task says so and the reason matters: running
|
|
57
|
+
// every documented CLI from a fitness check would be slow, would need a session,
|
|
58
|
+
// and a command that blocks on a prompt would hang CI forever — the exact failure
|
|
59
|
+
// R151's TTY gate exists to prevent.
|
|
60
|
+
// Key is `<doc path>::<token>`, where token is either the script path (the whole
|
|
61
|
+
// invocation is tolerated) or `--flag` (just that flag). Every entry carries its
|
|
62
|
+
// reason on the same line: an unexplained entry is a silent hole, and the point of
|
|
63
|
+
// this check is that a doc's claims are checkable.
|
|
64
|
+
const DOC_CLI_ALLOW = new Map([
|
|
65
|
+
['.claude/skills/paint/SKILL.md::scripts/gds/art-stage.js',
|
|
66
|
+
'art-stage.js ships with the art-pipeline HOST module, which publish-manifest.js excludes from core — absent from a core-only checkout by design; the skill says so inline.'],
|
|
67
|
+
['.claude/skills/otb-tile-generate/SKILL.md::scripts/gds/art-stage.js',
|
|
68
|
+
'same as paint/SKILL.md — the art staging CLI travels with the excluded art-pipeline module, not the portable core.'],
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// `node scripts/gds/foo.js` … up to the end of the line/segment, so the flags that
|
|
72
|
+
// follow travel with the script that owns them.
|
|
73
|
+
const DOC_CLI_RE = /node\s+(scripts\/gds\/[A-Za-z0-9._-]+\.js)([^\n`]*)/g;
|
|
74
|
+
// A long flag only. Short flags and bare words are ignored: too many false
|
|
75
|
+
// positives from prose, and the failure this guards is long-flag drift.
|
|
76
|
+
const DOC_FLAG_RE = /(?<![\w-])--([A-Za-z][A-Za-z0-9-]*)/g;
|
|
77
|
+
|
|
78
|
+
// Flags every CLI inherits from shared arg plumbing rather than naming itself.
|
|
79
|
+
const UNIVERSAL_FLAGS = new Set(['quiet', 'help', 'dry-run', 'json']);
|
|
80
|
+
|
|
81
|
+
// Does `script` know about `--flag` AT ALL?
|
|
82
|
+
//
|
|
83
|
+
// This deliberately asks the WEAK question — does the literal appear anywhere in
|
|
84
|
+
// the source — and not the strong one ("is it wired into arg parsing"). The first
|
|
85
|
+
// draft of this check asked the strong question, matching `arg('--x')` and
|
|
86
|
+
// `hasFlag('--x')`, and produced FIVE false positives out of nine hits on its
|
|
87
|
+
// first real run: triage.js parses via `argv.indexOf('--development')`,
|
|
88
|
+
// provision.js via `hasFlag(args, '--apply')`, oauth-secret.js via
|
|
89
|
+
// `argVal(args, '--client-id')`, status.js via `a === '--widget'`, and
|
|
90
|
+
// validate-design.js via `ARGS.has('--strict')`. Every one of those is correct
|
|
91
|
+
// code that the strong check called broken.
|
|
92
|
+
//
|
|
93
|
+
// The set of ways to read an argv in JavaScript is open-ended, so a check that
|
|
94
|
+
// enumerates them will keep growing false positives forever — and a fitness check
|
|
95
|
+
// that cries wolf gets muted, which costs more than the drift it was catching.
|
|
96
|
+
// The weak question is ONE-DIRECTIONAL and therefore sound: a script whose source
|
|
97
|
+
// never contains the string `--full` cannot possibly honour `--full`, whatever
|
|
98
|
+
// idiom it uses. That is exactly the failure this exists for (learning 1000198:
|
|
99
|
+
// /workspace ran a capture.js from before those flags existed, and both were
|
|
100
|
+
// silently dropped). A stale usage comment naming a removed flag slips through —
|
|
101
|
+
// accepted, deliberately: guessing which mentions are "real" is how the strong
|
|
102
|
+
// version went wrong.
|
|
103
|
+
function scriptMentionsFlag(src, flag) {
|
|
104
|
+
const f = flag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
105
|
+
// Trailing guard so `--client` does not match `--client-id`.
|
|
106
|
+
return new RegExp(`--${f}(?![\\w-])`).test(src);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The scan CORE. IO is injected — `readDoc(path)` and `readScript(path)` both
|
|
110
|
+
// return source or null — so a test can exercise a DRIFTED doc without writing
|
|
111
|
+
// a broken file into the tree (the control-manifest.js `evaluate` precedent).
|
|
112
|
+
// A check that cannot be shown to fail is not evidence of anything, which is
|
|
113
|
+
// precisely how task 1002931's prose-pinning test let the bad skill through.
|
|
114
|
+
function scanDocs(docs, { readDoc, readScript, allow = DOC_CLI_ALLOW } = {}) {
|
|
115
|
+
const violations = [];
|
|
116
|
+
const warnings = [];
|
|
117
|
+
let invocations = 0;
|
|
118
|
+
const srcCache = new Map();
|
|
119
|
+
const script = (rel) => {
|
|
120
|
+
if (!srcCache.has(rel)) srcCache.set(rel, readScript(rel));
|
|
121
|
+
return srcCache.get(rel);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
for (const doc of docs) {
|
|
125
|
+
const text = readDoc(doc);
|
|
126
|
+
if (text === null) continue;
|
|
127
|
+
DOC_CLI_RE.lastIndex = 0;
|
|
128
|
+
let m;
|
|
129
|
+
while ((m = DOC_CLI_RE.exec(text))) {
|
|
130
|
+
const scriptRel = m[1];
|
|
131
|
+
const tail = m[2] || '';
|
|
132
|
+
invocations += 1;
|
|
133
|
+
const src = script(scriptRel);
|
|
134
|
+
if (src === null) {
|
|
135
|
+
if (!allow.has(`${doc}::${scriptRel}`)) {
|
|
136
|
+
violations.push(
|
|
137
|
+
`${doc} → \`node ${scriptRel}\` names a script that does not exist. `
|
|
138
|
+
+ 'An agent following this doc gets MODULE_NOT_FOUND. Fix the path, or allowlist it in DOC_CLI_ALLOW with a reason.'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// A whole-invocation allowlist covers its flags too — the script isn't here to check them against.
|
|
144
|
+
if (allow.has(`${doc}::${scriptRel}`)) continue;
|
|
145
|
+
DOC_FLAG_RE.lastIndex = 0;
|
|
146
|
+
let f;
|
|
147
|
+
while ((f = DOC_FLAG_RE.exec(tail))) {
|
|
148
|
+
const flag = f[1];
|
|
149
|
+
if (UNIVERSAL_FLAGS.has(flag)) continue;
|
|
150
|
+
if (allow.has(`${doc}::--${flag}`)) continue;
|
|
151
|
+
if (scriptMentionsFlag(src, flag)) {
|
|
152
|
+
// The criterion asks that the flag be PARSED, not merely present. The
|
|
153
|
+
// hard check can only prove presence (see scriptMentionsFlag), so the
|
|
154
|
+
// residual gap — a flag surviving in a usage comment after the code that
|
|
155
|
+
// read it was deleted — is reported as a WARNING, not a failure. Warning
|
|
156
|
+
// rather than failing is the deliberate choice: a usage banner is the
|
|
157
|
+
// ONLY place some scripts name a flag they genuinely handle via a helper,
|
|
158
|
+
// so failing here would resurrect the false positives that sank draft 1.
|
|
159
|
+
if (!scriptMentionsFlag(stripLineComments(src), flag)) {
|
|
160
|
+
warnings.push(
|
|
161
|
+
`${doc} → \`node ${scriptRel} --${flag}\`: ${scriptRel} names \`--${flag}\` only in comments/usage text, `
|
|
162
|
+
+ 'never in code — it may be a flag whose implementation was deleted. Verify it is still handled.'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
violations.push(
|
|
167
|
+
`${doc} → \`node ${scriptRel} --${flag}\`: ${scriptRel} never mentions \`--${flag}\` anywhere in its source, so it cannot honour it. `
|
|
168
|
+
+ 'An unrecognised flag is silently IGNORED, not an error — so the documented command does the wrong thing while every test stays green. '
|
|
169
|
+
+ 'Add the flag, fix the doc, or allowlist it in DOC_CLI_ALLOW with a reason.'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { violations, warnings, invocations };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Drop `//` line comments and `/* */` blocks — enough to tell "named in code" from
|
|
179
|
+
// "named only in a usage banner". Not a JS parser, and it does not need to be: a
|
|
180
|
+
// false split here downgrades a warning, never a failure.
|
|
181
|
+
function stripLineComments(src) {
|
|
182
|
+
return String(src)
|
|
183
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
184
|
+
.split('\n')
|
|
185
|
+
.map((l) => l.replace(/(^|\s)\/\/.*$/, '$1'))
|
|
186
|
+
.join('\n');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Tracked markdown, via an argv ARRAY and never a shell string: `git ls-files
|
|
190
|
+
// '*.md'` as a string goes through cmd.exe on Windows, where the quotes are
|
|
191
|
+
// literal, git matches nothing, and the scan silently reports on zero files
|
|
192
|
+
// (task 1002526).
|
|
193
|
+
function trackedMarkdown() {
|
|
194
|
+
const out = execFileSync('git', ['ls-files', '*.md'], {
|
|
195
|
+
cwd: REPO_ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
196
|
+
});
|
|
197
|
+
return String(out || '').split('\n').map((f) => f.trim()).filter(Boolean);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const readFileOrNull = (rel) => {
|
|
201
|
+
try { return fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8'); } catch { return null; }
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const NAME = 'a doc that names a CLI matches the real CLI (G01)';
|
|
205
|
+
|
|
206
|
+
function checkDocNamesRealCli({ files = null } = {}) {
|
|
207
|
+
const docs = files || trackedMarkdown();
|
|
208
|
+
// A lint that reports on nothing is worse than one that fails (the
|
|
209
|
+
// docs-entropy precedent) — this repo tracks hundreds of .md files, so an
|
|
210
|
+
// empty enumeration is a broken scan, not a clean result.
|
|
211
|
+
if (!docs.length) {
|
|
212
|
+
return {
|
|
213
|
+
name: NAME, ok: false, hardFail: true,
|
|
214
|
+
violations: ['scan defect — enumerated 0 tracked markdown files; a broken enumeration, not a clean result.'],
|
|
215
|
+
warnings: [], note: 'static scan of documented `node scripts/gds/*.js` invocations.',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { violations, warnings, invocations } = scanDocs(docs, {
|
|
220
|
+
readDoc: readFileOrNull, readScript: readFileOrNull,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
name: NAME,
|
|
225
|
+
ok: violations.length === 0,
|
|
226
|
+
hardFail: violations.length > 0,
|
|
227
|
+
violations,
|
|
228
|
+
warnings,
|
|
229
|
+
note: `${invocations} documented \`node scripts/gds/*.js\` invocation(s) across ${docs.length} tracked .md file(s): the script exists and every long flag it is written with appears in that script's source. Static — never executes a CLI (a documented command that blocks on a prompt would hang CI). ${DOC_CLI_ALLOW.size} allowlisted.`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function main() {
|
|
234
|
+
const r = checkDocNamesRealCli();
|
|
235
|
+
for (const v of r.violations) console.error(`\u2717 ${v}`);
|
|
236
|
+
console.log(`${r.hardFail ? 'FAIL' : 'PASS'} ${r.note}`);
|
|
237
|
+
process.exit(r.hardFail ? 1 : 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (require.main === module) main();
|
|
241
|
+
|
|
242
|
+
module.exports = {
|
|
243
|
+
checkDocNamesRealCli, scanDocs, trackedMarkdown, scriptMentionsFlag, stripLineComments,
|
|
244
|
+
DOC_CLI_ALLOW, DOC_CLI_RE, DOC_FLAG_RE, UNIVERSAL_FLAGS,
|
|
245
|
+
};
|