@bongos/core 1.19.968 → 1.19.970
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 +55 -30
- package/.claude/skills/builder-cost/SKILL.md +5 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/parallel-goal-waves.md +7 -0
- package/modules/economy/cost-report.js +66 -0
- package/modules/economy/cost-summary-shape.js +13 -1
- package/modules/economy/cost.js +14 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/abandon.js +8 -4
- package/scripts/gds/cli-lib.js +51 -0
- package/scripts/gds/cost-report.js +63 -0
- package/scripts/gds/demote.js +9 -4
- package/scripts/gds/doctor.js +16 -1
- package/scripts/gds/install-git-hooks.js +72 -5
- package/scripts/gds/login.js +6 -2
- package/scripts/gds/provision-repo.js +36 -3
- package/scripts/gds/release.js +11 -3
- package/scripts/gds/search-ingest-md.js +1 -1
- package/scripts/gds/setup.js +2 -2
- package/scripts/gds/start.js +7 -3
- package/src/module-api.js +1 -1
- package/tests/cli_err_code.mjs +148 -0
- package/tests/cost_report.mjs +83 -0
- package/tests/cost_summary_shape.mjs +17 -1
- package/tests/merge_driver_register.mjs +150 -0
- package/tests/plain_cards.mjs +5 -1
- package/tests/provision.mjs +105 -0
- package/tests/search_ingest.mjs +40 -4
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.970",
|
|
6
|
+
"core_contract": "1.19.970",
|
|
7
|
+
"source_commit": "6fcd89630051b6a8a27ab995131716370ed9082e",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-23T01:02:05.515Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 515,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2370,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2911,
|
|
20
|
+
"tree_sha256": "60a4632de03ed24bd98cbfe694a93acfed70e02c72838da91213a9c68ad14813",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
{
|
|
58
58
|
"path": ".claude/skills/builder-cost/SKILL.md",
|
|
59
59
|
"mode": "0000644",
|
|
60
|
-
"sha256": "
|
|
60
|
+
"sha256": "0f9b196e81b99a2313f6e1a0c0f091caaaf4b11b0d669076509ca60e3d10434a"
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"path": ".claude/skills/builder-end/SKILL.md",
|
|
@@ -2697,7 +2697,7 @@
|
|
|
2697
2697
|
{
|
|
2698
2698
|
"path": "docs/module-api-changelog.md",
|
|
2699
2699
|
"mode": "0000644",
|
|
2700
|
-
"sha256": "
|
|
2700
|
+
"sha256": "e842c06ae274c61f5f38063af7b5eb5da4798954df2b9e3e42c1929352dd9a3f"
|
|
2701
2701
|
},
|
|
2702
2702
|
{
|
|
2703
2703
|
"path": "docs/modules-contract.md",
|
|
@@ -2892,7 +2892,7 @@
|
|
|
2892
2892
|
{
|
|
2893
2893
|
"path": "docs/recipes/parallel-goal-waves.md",
|
|
2894
2894
|
"mode": "0000644",
|
|
2895
|
-
"sha256": "
|
|
2895
|
+
"sha256": "4879badb2ab0678098d3f5cfa1002a6b4b50e8a691a5d4afc17b533803fbe262"
|
|
2896
2896
|
},
|
|
2897
2897
|
{
|
|
2898
2898
|
"path": "docs/recipes/private-npm-distribution.md",
|
|
@@ -4244,15 +4244,20 @@
|
|
|
4244
4244
|
"mode": "0000644",
|
|
4245
4245
|
"sha256": "fefd4908bd22aa3b747a7c8b4146e5a7e53731fe92612db105ea3512ae34300c"
|
|
4246
4246
|
},
|
|
4247
|
+
{
|
|
4248
|
+
"path": "modules/economy/cost-report.js",
|
|
4249
|
+
"mode": "0000644",
|
|
4250
|
+
"sha256": "484448390d675c3884cbee5d15405e1e7167bbc111f8461e06890750b7f68be4"
|
|
4251
|
+
},
|
|
4247
4252
|
{
|
|
4248
4253
|
"path": "modules/economy/cost-summary-shape.js",
|
|
4249
4254
|
"mode": "0000644",
|
|
4250
|
-
"sha256": "
|
|
4255
|
+
"sha256": "449251df02a484bc865c3844d0a85beffe5e3be920db0f09833f962df3e8b3d4"
|
|
4251
4256
|
},
|
|
4252
4257
|
{
|
|
4253
4258
|
"path": "modules/economy/cost.js",
|
|
4254
4259
|
"mode": "0000644",
|
|
4255
|
-
"sha256": "
|
|
4260
|
+
"sha256": "ce3ce114f5315c78a2feb810bce09cde84941765629f0e5a10a44aced2c34210"
|
|
4256
4261
|
},
|
|
4257
4262
|
{
|
|
4258
4263
|
"path": "modules/economy/credits.js",
|
|
@@ -8102,12 +8107,12 @@
|
|
|
8102
8107
|
{
|
|
8103
8108
|
"path": "package-lock.json",
|
|
8104
8109
|
"mode": "0000644",
|
|
8105
|
-
"sha256": "
|
|
8110
|
+
"sha256": "b398350d28d5e528b46daafbadd4b19ddd9e9423a0c234aaabd27dc1718d6864"
|
|
8106
8111
|
},
|
|
8107
8112
|
{
|
|
8108
8113
|
"path": "package.json",
|
|
8109
8114
|
"mode": "0000644",
|
|
8110
|
-
"sha256": "
|
|
8115
|
+
"sha256": "03e808eb0d1d2cb9be5d0ee13830a79fd5b0752ee5ff7f0a17993df3dc147903"
|
|
8111
8116
|
},
|
|
8112
8117
|
{
|
|
8113
8118
|
"path": "public-docs/index.html",
|
|
@@ -8157,7 +8162,7 @@
|
|
|
8157
8162
|
{
|
|
8158
8163
|
"path": "scripts/gds/abandon.js",
|
|
8159
8164
|
"mode": "0000644",
|
|
8160
|
-
"sha256": "
|
|
8165
|
+
"sha256": "a1246dd85fe90e105561d4069bd65a79fc1b516addbbeea22c69ce3fcf1e19ab"
|
|
8161
8166
|
},
|
|
8162
8167
|
{
|
|
8163
8168
|
"path": "scripts/gds/accessibility-replay.js",
|
|
@@ -8467,7 +8472,7 @@
|
|
|
8467
8472
|
{
|
|
8468
8473
|
"path": "scripts/gds/cli-lib.js",
|
|
8469
8474
|
"mode": "0000644",
|
|
8470
|
-
"sha256": "
|
|
8475
|
+
"sha256": "013a3ff00e01912efabe65d58d5c2cbff93bd61ad052e645d03c034aff203ddb"
|
|
8471
8476
|
},
|
|
8472
8477
|
{
|
|
8473
8478
|
"path": "scripts/gds/client-baseurl-guard.js",
|
|
@@ -8509,6 +8514,11 @@
|
|
|
8509
8514
|
"mode": "0000644",
|
|
8510
8515
|
"sha256": "55ac5440a1c5481766198d6a89cef601fde816024b592b1e77f535c465a2df47"
|
|
8511
8516
|
},
|
|
8517
|
+
{
|
|
8518
|
+
"path": "scripts/gds/cost-report.js",
|
|
8519
|
+
"mode": "0000644",
|
|
8520
|
+
"sha256": "988d9e5dec9ed4bd35a3aea25fc841a832a2a251ee6eefa7fb0d86f7ee5b7b2e"
|
|
8521
|
+
},
|
|
8512
8522
|
{
|
|
8513
8523
|
"path": "scripts/gds/cost.js",
|
|
8514
8524
|
"mode": "0000644",
|
|
@@ -8532,7 +8542,7 @@
|
|
|
8532
8542
|
{
|
|
8533
8543
|
"path": "scripts/gds/demote.js",
|
|
8534
8544
|
"mode": "0000644",
|
|
8535
|
-
"sha256": "
|
|
8545
|
+
"sha256": "3b80acbc43ce96e07f11e6531c35499bc9a5bf8e4103d57e37c83169afdf0087"
|
|
8536
8546
|
},
|
|
8537
8547
|
{
|
|
8538
8548
|
"path": "scripts/gds/deploy-divergence.js",
|
|
@@ -8607,7 +8617,7 @@
|
|
|
8607
8617
|
{
|
|
8608
8618
|
"path": "scripts/gds/doctor.js",
|
|
8609
8619
|
"mode": "0000644",
|
|
8610
|
-
"sha256": "
|
|
8620
|
+
"sha256": "be0f4da3a9885bdc555f720d853923b226ae993a6a965a934c728032a0575988"
|
|
8611
8621
|
},
|
|
8612
8622
|
{
|
|
8613
8623
|
"path": "scripts/gds/enroll-control-plane.js",
|
|
@@ -8832,7 +8842,7 @@
|
|
|
8832
8842
|
{
|
|
8833
8843
|
"path": "scripts/gds/install-git-hooks.js",
|
|
8834
8844
|
"mode": "0000644",
|
|
8835
|
-
"sha256": "
|
|
8845
|
+
"sha256": "6234ca799f99e5a16622043b0f43d42624efe5fb6862d74e4b8c2b7603cf8bef"
|
|
8836
8846
|
},
|
|
8837
8847
|
{
|
|
8838
8848
|
"path": "scripts/gds/instance-apex.js",
|
|
@@ -8902,7 +8912,7 @@
|
|
|
8902
8912
|
{
|
|
8903
8913
|
"path": "scripts/gds/login.js",
|
|
8904
8914
|
"mode": "0000644",
|
|
8905
|
-
"sha256": "
|
|
8915
|
+
"sha256": "0d719e43955fdbcc40aee500438db5b31c6c006490e90d99caadf31b215196f7"
|
|
8906
8916
|
},
|
|
8907
8917
|
{
|
|
8908
8918
|
"path": "scripts/gds/main-audit.js",
|
|
@@ -9077,7 +9087,7 @@
|
|
|
9077
9087
|
{
|
|
9078
9088
|
"path": "scripts/gds/provision-repo.js",
|
|
9079
9089
|
"mode": "0000644",
|
|
9080
|
-
"sha256": "
|
|
9090
|
+
"sha256": "6e025cfa37165ff491f736f31bb97fa439e3e6cece9eaeeb4edcd6698e9868c0"
|
|
9081
9091
|
},
|
|
9082
9092
|
{
|
|
9083
9093
|
"path": "scripts/gds/provision-teardown.js",
|
|
@@ -9152,7 +9162,7 @@
|
|
|
9152
9162
|
{
|
|
9153
9163
|
"path": "scripts/gds/release.js",
|
|
9154
9164
|
"mode": "0000644",
|
|
9155
|
-
"sha256": "
|
|
9165
|
+
"sha256": "5c260296937727b4445f4df0b851ba174334b7680b6cad6243cc94b73d3934c4"
|
|
9156
9166
|
},
|
|
9157
9167
|
{
|
|
9158
9168
|
"path": "scripts/gds/rename-goals.js",
|
|
@@ -9237,7 +9247,7 @@
|
|
|
9237
9247
|
{
|
|
9238
9248
|
"path": "scripts/gds/search-ingest-md.js",
|
|
9239
9249
|
"mode": "0000644",
|
|
9240
|
-
"sha256": "
|
|
9250
|
+
"sha256": "7638bd1d2160ef1aa5b386dd555084cb33e1af02bcd9e2f55967cb32a8e0bba5"
|
|
9241
9251
|
},
|
|
9242
9252
|
{
|
|
9243
9253
|
"path": "scripts/gds/security-baseline-check.sh",
|
|
@@ -9417,7 +9427,7 @@
|
|
|
9417
9427
|
{
|
|
9418
9428
|
"path": "scripts/gds/setup.js",
|
|
9419
9429
|
"mode": "0000644",
|
|
9420
|
-
"sha256": "
|
|
9430
|
+
"sha256": "156e63c837a5b3bd57ac3f76c4770ef73ad469d27a6f03f8b8a617b57f4020d0"
|
|
9421
9431
|
},
|
|
9422
9432
|
{
|
|
9423
9433
|
"path": "scripts/gds/ship-check.js",
|
|
@@ -9592,7 +9602,7 @@
|
|
|
9592
9602
|
{
|
|
9593
9603
|
"path": "scripts/gds/start.js",
|
|
9594
9604
|
"mode": "0000644",
|
|
9595
|
-
"sha256": "
|
|
9605
|
+
"sha256": "36c814dcf1b233f2279f1a7b06a337dea0d1799119acbfac004af88c6e1a56d1"
|
|
9596
9606
|
},
|
|
9597
9607
|
{
|
|
9598
9608
|
"path": "scripts/gds/status.js",
|
|
@@ -10112,7 +10122,7 @@
|
|
|
10112
10122
|
{
|
|
10113
10123
|
"path": "src/module-api.js",
|
|
10114
10124
|
"mode": "0000644",
|
|
10115
|
-
"sha256": "
|
|
10125
|
+
"sha256": "a16e899a5c50b65af3e0976a27e72e2f1e50b7cfc8a8acafc64b19cebae1eb93"
|
|
10116
10126
|
},
|
|
10117
10127
|
{
|
|
10118
10128
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10904,6 +10914,11 @@
|
|
|
10904
10914
|
"mode": "0000644",
|
|
10905
10915
|
"sha256": "3e3e17428a6c8f50ce0512cfacc4e18e0b6a2c506d185fa5f2adf21c9feffe9f"
|
|
10906
10916
|
},
|
|
10917
|
+
{
|
|
10918
|
+
"path": "tests/cli_err_code.mjs",
|
|
10919
|
+
"mode": "0000644",
|
|
10920
|
+
"sha256": "ec9d2cc2054545b3fc886a9b7168ca851acf4cfb7937fab6e065e8fd7ed5eeb4"
|
|
10921
|
+
},
|
|
10907
10922
|
{
|
|
10908
10923
|
"path": "tests/cli_exit_no_abort.mjs",
|
|
10909
10924
|
"mode": "0000644",
|
|
@@ -11049,10 +11064,15 @@
|
|
|
11049
11064
|
"mode": "0000644",
|
|
11050
11065
|
"sha256": "8356250d8fc753afb2960d867f81586531053afd15f4e66da1a1b7b62b6b5db7"
|
|
11051
11066
|
},
|
|
11067
|
+
{
|
|
11068
|
+
"path": "tests/cost_report.mjs",
|
|
11069
|
+
"mode": "0000644",
|
|
11070
|
+
"sha256": "cb69ea9b53e37f8590bb1cd2e594df1a63fa07f452ad7b0bf7a607507f8e2c82"
|
|
11071
|
+
},
|
|
11052
11072
|
{
|
|
11053
11073
|
"path": "tests/cost_summary_shape.mjs",
|
|
11054
11074
|
"mode": "0000644",
|
|
11055
|
-
"sha256": "
|
|
11075
|
+
"sha256": "50f195ef2060df92016b58f541a28e7492dd95ad4da8bd0781bdc07007193338"
|
|
11056
11076
|
},
|
|
11057
11077
|
{
|
|
11058
11078
|
"path": "tests/credit_grant.mjs",
|
|
@@ -12624,6 +12644,11 @@
|
|
|
12624
12644
|
"mode": "0000644",
|
|
12625
12645
|
"sha256": "d05b6e354ac08cf5512a62b76b213df44ab1ef8639a1ba635319e9b5e8511a5c"
|
|
12626
12646
|
},
|
|
12647
|
+
{
|
|
12648
|
+
"path": "tests/merge_driver_register.mjs",
|
|
12649
|
+
"mode": "0000644",
|
|
12650
|
+
"sha256": "c75880064b52b6edb2689232eb1e6401567618f46b16581140685ccceee2d41c"
|
|
12651
|
+
},
|
|
12627
12652
|
{
|
|
12628
12653
|
"path": "tests/merge_lease.mjs",
|
|
12629
12654
|
"mode": "0000644",
|
|
@@ -12952,7 +12977,7 @@
|
|
|
12952
12977
|
{
|
|
12953
12978
|
"path": "tests/plain_cards.mjs",
|
|
12954
12979
|
"mode": "0000644",
|
|
12955
|
-
"sha256": "
|
|
12980
|
+
"sha256": "90310dd37d97dff67feaed377c2db4efdf906902f5c0fa8f49ee721941c2b272"
|
|
12956
12981
|
},
|
|
12957
12982
|
{
|
|
12958
12983
|
"path": "tests/planning_session_skill.mjs",
|
|
@@ -13182,7 +13207,7 @@
|
|
|
13182
13207
|
{
|
|
13183
13208
|
"path": "tests/provision.mjs",
|
|
13184
13209
|
"mode": "0000644",
|
|
13185
|
-
"sha256": "
|
|
13210
|
+
"sha256": "58dd9185b37e9f5654564a5c700e51995b303f26af9eaae3b4b4d3ee29351864"
|
|
13186
13211
|
},
|
|
13187
13212
|
{
|
|
13188
13213
|
"path": "tests/provision_account_identity.mjs",
|
|
@@ -13652,7 +13677,7 @@
|
|
|
13652
13677
|
{
|
|
13653
13678
|
"path": "tests/search_ingest.mjs",
|
|
13654
13679
|
"mode": "0000644",
|
|
13655
|
-
"sha256": "
|
|
13680
|
+
"sha256": "dd92de1e35d82fc4ea82c2f5cb99b506153e5386e4668846cba87e0f8d135d97"
|
|
13656
13681
|
},
|
|
13657
13682
|
{
|
|
13658
13683
|
"path": "tests/search_ingest_db.mjs",
|
|
@@ -42,7 +42,11 @@ The CLI hits `POST /api/bongos/cost`, which inserts a `cost_log` row.
|
|
|
42
42
|
- **Don't double-count.** Before logging an art-pipeline cost manually, verify it isn't already in the DB from the one-time cost-ledger backfill (those rows use `source: 'art-cost-ledger'`).
|
|
43
43
|
- **Don't log without a description for `other` category.**
|
|
44
44
|
|
|
45
|
+
## Reading the ledger back
|
|
46
|
+
|
|
47
|
+
To see what has been spent rather than add to it, run `node scripts/gds/cost-report.js` — total spend plus totals by month, category and source. The endpoint it reads is public, so no rank is needed, but the session still supplies the instance URL. `--source X`, `--category Y` and `--days N` scope the breakdowns (the header total stays project-wide); `--json` prints the raw payload. The newest month is labelled partial — it is spend to date, not a month's worth.
|
|
48
|
+
|
|
45
49
|
## Files this skill touches
|
|
46
50
|
|
|
47
51
|
- Reads: `~/.config/otb/gds-session.json`
|
|
48
|
-
- Calls: `POST /api/bongos/cost`
|
|
52
|
+
- Calls: `POST /api/bongos/cost` (logging), `GET /public/cost-summary` (the read-out, via `scripts/gds/cost-report.js`)
|
|
@@ -2410,5 +2410,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2410
2410
|
landed since 1.19.966 with no explicit bump. run 35795807836. (task 1002620)
|
|
2411
2411
|
1.19.968 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2412
2412
|
landed since 1.19.967 with no explicit bump. run 35797263467. (task 1002620)
|
|
2413
|
+
1.19.969 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2414
|
+
landed since 1.19.968 with no explicit bump. run 35798488315. (task 1002620)
|
|
2415
|
+
1.19.970 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2416
|
+
landed since 1.19.969 with no explicit bump. run 35804568036. (task 1002620)
|
|
2413
2417
|
---------------------------------------------------------------------------
|
|
2414
2418
|
```
|
|
@@ -105,6 +105,11 @@ One file will become the contention point. Here it was the module's nested `CLAU
|
|
|
105
105
|
**Generated files: always resolve by regenerating, never by picking a side.**
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
+
# --theirs only CLEARS THE CONFLICT MARKERS so git will accept the path as resolved
|
|
109
|
+
# (git add refuses a still-conflicted path). It is not a choice of content: the
|
|
110
|
+
# generator on the next line overwrites the file from source, so whichever side you
|
|
111
|
+
# took is discarded. --ours works identically here. This is the one place the "never
|
|
112
|
+
# pick a side" rule above does not apply, because nothing is being picked.
|
|
108
113
|
git checkout --theirs <file>
|
|
109
114
|
node scripts/gds/gen-repo-map.js # or gen-api-docs.js, gen-diagrams.js
|
|
110
115
|
git add <file> && git commit --no-edit
|
|
@@ -112,6 +117,8 @@ git add <file> && git commit --no-edit
|
|
|
112
117
|
|
|
113
118
|
Picking a side leaves the artefact inconsistent with source, and the freshness fitness check fails on the *next* task instead of yours. The generated symbol blocks and the OpenAPI spec both bit us this way.
|
|
114
119
|
|
|
120
|
+
**If the generator is not the last thing you run, you have picked a side.** That is the test to apply, not the flag you typed.
|
|
121
|
+
|
|
115
122
|
> If `ship.js` says *"auto-merge: bailed; task stays at confirmed"*, don't reach for `/merge-mode` first. Merge `origin/main` into the **task's own branch**, resolve there, re-run `fitness.js`, then re-run `ship.js <id>` — it resumes from `confirmed`. `/merge-mode` is an operator procedure that mutates the main worktree and assumes a bash/SSH environment.
|
|
116
123
|
|
|
117
124
|
---
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// modules/economy/cost-report.js — the spend read-out's text rendering (task
|
|
2
|
+
// 1001621: "what did this cost, by month, category and source", the figures
|
|
3
|
+
// hosting ROI is sized against). Pure (no DB, no I/O) so the CLI
|
|
4
|
+
// (scripts/gds/cost-report.js) stays a thin fetch-and-print shell and the
|
|
5
|
+
// wording is unit-testable DB-free (tests/cost_report.mjs). Reads a
|
|
6
|
+
// db.costSummary() payload / the GET /public/cost-summary response as-is.
|
|
7
|
+
|
|
8
|
+
function usd(n) {
|
|
9
|
+
return `$${(Number(n) || 0).toFixed(2)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A dimension the server did not send is NOT the same as a dimension with no
|
|
13
|
+
// rows: the CLI reads a LIVE instance, which may predate the key. Say which,
|
|
14
|
+
// so an old server never reads as "nothing was spent".
|
|
15
|
+
function section(label, rows, render) {
|
|
16
|
+
if (!Array.isArray(rows)) return [` ${label}: unavailable (this server predates the rollup)`];
|
|
17
|
+
if (!rows.length) return [` ${label}: no spend recorded`];
|
|
18
|
+
return [` ${label}:`, ...rows.map((r) => ` - ${render(r)}`)];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The newest month is spend-to-date, not a month's worth, so an unlabelled row
|
|
22
|
+
// reads as a collapse in spend at the top of every month — in the one figure a
|
|
23
|
+
// hosting bill is sized against. Compared in UTC: a boundary mismatch with the
|
|
24
|
+
// server's timezone only drops the label for a few hours, which under-claims
|
|
25
|
+
// rather than calling a partial month complete.
|
|
26
|
+
function monthLabel(row, now) {
|
|
27
|
+
const current = now.toISOString().slice(0, 7);
|
|
28
|
+
const partial = row.month === current ? ' (partial — month to date)' : '';
|
|
29
|
+
return `${row.month}: ${usd(row.total_usd)}${partial}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function filterLabel(filters) {
|
|
33
|
+
const f = filters || {};
|
|
34
|
+
const parts = [];
|
|
35
|
+
if (f.source) parts.push(`source=${f.source}`);
|
|
36
|
+
if (f.category) parts.push(`category=${f.category}`);
|
|
37
|
+
if (f.days) parts.push(`last ${f.days} days`);
|
|
38
|
+
return parts.join(' · ');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Render a spend rollup — total, then totals by month, category and source —
|
|
43
|
+
* from a cost-summary payload, for the `cost-report` CLI.
|
|
44
|
+
* @param {object} summary - db.costSummary() / GET /public/cost-summary.
|
|
45
|
+
* @param {object} [opts] - { now } — the clock, injectable so the partial-month
|
|
46
|
+
* label is testable.
|
|
47
|
+
* @returns {string} the printable report.
|
|
48
|
+
*/
|
|
49
|
+
function formatCostReport(summary, { now = new Date() } = {}) {
|
|
50
|
+
const s = summary || {};
|
|
51
|
+
const lines = ['Spend rollup — cost_log'];
|
|
52
|
+
// The header total stays project-wide whatever the filter: costSummary scopes
|
|
53
|
+
// only the breakdowns, so labelling it is what keeps the two figures honest.
|
|
54
|
+
lines.push(` total spend: ${usd(s.total_usd)} (project-wide, all time)`);
|
|
55
|
+
const label = filterLabel(s.applied_filters);
|
|
56
|
+
if (label) {
|
|
57
|
+
lines.push(` filter: ${label}`);
|
|
58
|
+
lines.push(` under filter: ${usd(s.filtered_total_usd)}`);
|
|
59
|
+
}
|
|
60
|
+
lines.push(...section('by month', s.by_month, (r) => monthLabel(r, now)));
|
|
61
|
+
lines.push(...section('by category', s.by_category, (r) => `${r.category}: ${usd(r.total_usd)}`));
|
|
62
|
+
lines.push(...section('by source', s.by_source, (r) => `${r.source}: ${usd(r.total_usd)}`));
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { formatCostReport };
|
|
@@ -87,4 +87,16 @@ function shapeCostCalibration(rows) {
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
// by_month: cost_log rows grouped by calendar month (task 1001621), the axis the
|
|
91
|
+
// daily series cannot answer — "what does a month of this cost" is the figure
|
|
92
|
+
// hosting ROI is sized against. The month label is formatted in SQL (to_char),
|
|
93
|
+
// not here, so a driver Date + a UTC-shifted server can never move a row into
|
|
94
|
+
// the neighbouring month. Rows arrive pre-sorted oldest-first by SQL.
|
|
95
|
+
function shapeByMonth(rows) {
|
|
96
|
+
return (rows || []).map((r) => ({
|
|
97
|
+
month: String(r.month),
|
|
98
|
+
total_usd: round2(r.total_usd),
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { shapeBySource, shapeByTier, shapeSavings, shapeCostCalibration, shapeByMonth };
|
package/modules/economy/cost.js
CHANGED
|
@@ -327,6 +327,7 @@ async function costSummary(filters = {}) {
|
|
|
327
327
|
{ rows: filteredTotalRows },
|
|
328
328
|
{ rows: sourceOptRows },
|
|
329
329
|
{ rows: categoryOptRows },
|
|
330
|
+
{ rows: byMonthRows },
|
|
330
331
|
] = await Promise.all([
|
|
331
332
|
pool.query(`SELECT COALESCE(SUM(amount_usd), 0) AS total_usd FROM cost_log`),
|
|
332
333
|
pool.query(
|
|
@@ -423,6 +424,16 @@ async function costSummary(filters = {}) {
|
|
|
423
424
|
pool.query(
|
|
424
425
|
`SELECT DISTINCT category FROM cost_log WHERE category IS NOT NULL ORDER BY category`
|
|
425
426
|
),
|
|
427
|
+
// task 1001621: by_month — the axis `daily` cannot answer. A month of spend
|
|
428
|
+
// is what a hosting bill is compared against, and the day series is
|
|
429
|
+
// deliberately project-wide, so it cannot be re-bucketed per slice. This one
|
|
430
|
+
// honors the same filters as the other breakdowns. to_char formats the label
|
|
431
|
+
// in SQL so no driver Date/timezone can shift a row into the next month.
|
|
432
|
+
pool.query(
|
|
433
|
+
`SELECT to_char(recorded_at, 'YYYY-MM') AS month, COALESCE(SUM(amount_usd), 0) AS total_usd
|
|
434
|
+
FROM cost_log ${whereCl} GROUP BY month ORDER BY month`,
|
|
435
|
+
params
|
|
436
|
+
),
|
|
426
437
|
]);
|
|
427
438
|
|
|
428
439
|
// savings — the counterfactual ledger (cost_baseline, migration 124). Guarded:
|
|
@@ -474,6 +485,9 @@ async function costSummary(filters = {}) {
|
|
|
474
485
|
daily_credits: valueHistory.map((r) => ({ day: r.day, credits: Number(r.credits) })),
|
|
475
486
|
// System 1 / task 1345 — additive dimensions (existing keys keep shape).
|
|
476
487
|
by_source: costSummaryShape.shapeBySource(bySourceRows),
|
|
488
|
+
// task 1001621 — spend per calendar month, under the same filters as the
|
|
489
|
+
// other breakdowns. Additive; existing keys keep their shape.
|
|
490
|
+
by_month: costSummaryShape.shapeByMonth(byMonthRows),
|
|
477
491
|
by_tier: costSummaryShape.shapeByTier(byTierRows[0]),
|
|
478
492
|
savings: costSummaryShape.shapeSavings(savingsRows),
|
|
479
493
|
// task 572 / spend-audit F6 — cost-per-task & cost-per-credit medians.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.970",
|
|
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.970",
|
|
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.970",
|
|
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/abandon.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// the ledger stays complete. Retired the wrong task? POST /tasks/<id>/promote
|
|
18
18
|
// restores it to backlog at the same rank (db.restoreTask) — no Archon SQL needed.
|
|
19
19
|
|
|
20
|
-
const { cliClient, requireSession, arg } = require('./cli-lib');
|
|
20
|
+
const { errCode, cliClient, requireSession, arg } = require('./cli-lib');
|
|
21
21
|
// task 1003153 — the live-status set, shared with the dead-edge detector so
|
|
22
22
|
// "who did this cut strand" means the same thing in both places.
|
|
23
23
|
const { LIVE_STATUSES } = require('../../modules/lifecycle/dead-deps.js');
|
|
@@ -68,12 +68,16 @@ function commitsAheadOfMain(branch) {
|
|
|
68
68
|
const api = await cliClient();
|
|
69
69
|
const r = await api.tasks.postTasksIdAbandon({ id: taskId, body: { reason } });
|
|
70
70
|
if (!r.ok) {
|
|
71
|
-
|
|
71
|
+
// ADR 0116 nests the slug at error.code; a flat `r.data.error === '…'` compares an
|
|
72
|
+
// object to a string and never matches, so every branch below was unreachable
|
|
73
|
+
// against a live server (task 1003658).
|
|
74
|
+
const code = errCode(r.data);
|
|
75
|
+
if (code === 'cannot_abandon_shipped') {
|
|
72
76
|
console.error(`Task #${taskId} is shipped — shipped work is immutable history and cannot be abandoned.`);
|
|
73
|
-
} else if (
|
|
77
|
+
} else if (code === 'task_has_active_claim') {
|
|
74
78
|
console.error(`Task #${taskId} has an active claim. Release it first:`);
|
|
75
79
|
console.error(` node scripts/gds/release.js ${taskId}`);
|
|
76
|
-
} else if (
|
|
80
|
+
} else if (code === 'task_not_found') {
|
|
77
81
|
console.error(`Task #${taskId} not found.`);
|
|
78
82
|
} else if (r.status === 403) {
|
|
79
83
|
console.error('Abandoning a task needs Metic+ rank (ADR 0152).');
|
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -1098,7 +1098,58 @@ function cardFlagsFromMe(me) {
|
|
|
1098
1098
|
return { level: null, showCard: true, showWidget: enabled, expanded: false };
|
|
1099
1099
|
}
|
|
1100
1100
|
|
|
1101
|
+
// ---------------------------------------------------------------------------
|
|
1102
|
+
// errCode — read the error slug out of an ADR 0116 response envelope.
|
|
1103
|
+
// ---------------------------------------------------------------------------
|
|
1104
|
+
// ADR 0116 standardised every failure on a NESTED envelope:
|
|
1105
|
+
//
|
|
1106
|
+
// { "error": { "code": "task_not_found", "message": "…", "details": … } }
|
|
1107
|
+
//
|
|
1108
|
+
// so the obvious-looking `r.data.error === 'task_not_found'` compares an OBJECT to a
|
|
1109
|
+
// STRING and is never true. That is not a cosmetic bug: each such comparison guards a
|
|
1110
|
+
// helpful, specific message, so against a live server every one of those branches is
|
|
1111
|
+
// unreachable and the user falls through to the raw `(409): [object Object]` dump —
|
|
1112
|
+
// the failure looks like a tooling crash rather than the clear refusal the server sent.
|
|
1113
|
+
//
|
|
1114
|
+
// Verified live 2026-09-22 against cloudbongos.com:
|
|
1115
|
+
// POST /api/bongos/tasks/1003846/abandon
|
|
1116
|
+
// -> 409 {"error":{"code":"cannot_abandon_shipped","message":"shipped tasks are immutable history"}}
|
|
1117
|
+
//
|
|
1118
|
+
// The flat-string fallback is deliberate, not defensive padding: some older routes and
|
|
1119
|
+
// every non-envelope transport error (a proxy's plain-text body, a pre-0116 instance an
|
|
1120
|
+
// upgraded CLI is talking to) still surface `error` as a bare string. Reading both is
|
|
1121
|
+
// what makes one helper safe to use everywhere.
|
|
1122
|
+
//
|
|
1123
|
+
// Fixed in ship.js under task 1002789; extracted here under task 1003658 so the next
|
|
1124
|
+
// caller cannot reintroduce the same comparison. Pure — tests/cli_err_code.mjs.
|
|
1125
|
+
function errCode(data) {
|
|
1126
|
+
const e = data && data.error;
|
|
1127
|
+
if (typeof e === 'string') return e;
|
|
1128
|
+
if (e && typeof e === 'object' && typeof e.code === 'string') return e.code;
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// errDetails — the structured payload beside the slug, same envelope.
|
|
1133
|
+
//
|
|
1134
|
+
// This matters as much as errCode and is easy to miss: the sibling fields moved WITH
|
|
1135
|
+
// the code. demote.js printed `r.data.current_status` beside its message; ADR 0116 put
|
|
1136
|
+
// that at `error.details.current_status`, so a fix that changed only the comparison
|
|
1137
|
+
// would have restored the branch and then printed "it is 'undefined', not 'ready'" —
|
|
1138
|
+
// a half-fix that looks like a whole one. Verified live 2026-09-22:
|
|
1139
|
+
//
|
|
1140
|
+
// POST /api/bongos/tasks/1003846/demote
|
|
1141
|
+
// -> 409 {"error":{"code":"cannot_demote","details":{"current_status":"shipped"}}}
|
|
1142
|
+
//
|
|
1143
|
+
// Falls back to the top-level body so a pre-0116 flat response still resolves.
|
|
1144
|
+
function errDetails(data) {
|
|
1145
|
+
const e = data && data.error;
|
|
1146
|
+
if (e && typeof e === 'object' && e.details && typeof e.details === 'object') return e.details;
|
|
1147
|
+
return (data && typeof data === 'object') ? data : {};
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1101
1150
|
module.exports = {
|
|
1151
|
+
errCode,
|
|
1152
|
+
errDetails,
|
|
1102
1153
|
API_BASE,
|
|
1103
1154
|
detectWorktreeName,
|
|
1104
1155
|
hallBase,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Read the spend rollup: total spend, and totals by month, category and source.
|
|
3
|
+
//
|
|
4
|
+
// The read half of cost.js, which only LOGS spend — task 1001621 (ADR 0092
|
|
5
|
+
// finding): nothing rolled it up where a person could size hosting against real
|
|
6
|
+
// numbers. The endpoint it reads is public, so no RANK is needed — but the base
|
|
7
|
+
// URL still comes from your CLI session (or <PREFIX>_API_BASE), which is what
|
|
8
|
+
// says WHICH instance to ask.
|
|
9
|
+
//
|
|
10
|
+
// Usage:
|
|
11
|
+
// node scripts/gds/cost-report.js [--source X] [--category Y] [--days N] [--json]
|
|
12
|
+
//
|
|
13
|
+
// The filters scope the BREAKDOWNS only — the header total stays project-wide,
|
|
14
|
+
// which is how the server's treasury filter already behaves.
|
|
15
|
+
|
|
16
|
+
const { cliClient, arg, readSessionToken } = require('./cli-lib');
|
|
17
|
+
// A CLI may require a module directly (the backfill-cost.js / cost-classify
|
|
18
|
+
// precedent, BV1.R77 / ADR 0093 §1-2). The rendering is pure and lives there so
|
|
19
|
+
// it is testable without a server (tests/cost_report.mjs).
|
|
20
|
+
const { formatCostReport } = require('../../modules/economy/cost-report');
|
|
21
|
+
|
|
22
|
+
// arg() hands back whatever follows the flag, so `--source --json` would filter
|
|
23
|
+
// on the literal '--json' and silently report $0 — a wrong answer, not an error.
|
|
24
|
+
function value(name, args) {
|
|
25
|
+
const v = arg(name, args);
|
|
26
|
+
if (v == null) return null;
|
|
27
|
+
if (String(v).startsWith('--')) {
|
|
28
|
+
console.error(`${name} needs a value (got '${v}')`);
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
return v;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
(async () => {
|
|
35
|
+
const args = process.argv.slice(2);
|
|
36
|
+
const query = {};
|
|
37
|
+
const source = value('--source', args);
|
|
38
|
+
const category = value('--category', args);
|
|
39
|
+
const days = value('--days', args);
|
|
40
|
+
if (source) query.source = source;
|
|
41
|
+
if (category) query.category = category;
|
|
42
|
+
if (days) query.days = days;
|
|
43
|
+
|
|
44
|
+
const api = await cliClient();
|
|
45
|
+
const r = await api.public.getPublicCostSummary({ query });
|
|
46
|
+
if (!r.ok) {
|
|
47
|
+
console.error(`cost report failed (HTTP ${r.status}):`, r.data);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
console.log(args.includes('--json') ? JSON.stringify(r.data, null, 2) : formatCostReport(r.data));
|
|
51
|
+
})().catch((err) => {
|
|
52
|
+
// A session-less or offline run lands here, and a raw fetch stack says nothing
|
|
53
|
+
// about WHICH instance was tried. readSessionToken() returns null when not
|
|
54
|
+
// signed in, which is itself the answer in that case.
|
|
55
|
+
const session = readSessionToken();
|
|
56
|
+
const msg = (err && err.message) || String(err);
|
|
57
|
+
console.error(`cost-report failed (instance: ${session ? session.apiBase : 'no session — falling back to this checkout\'s default'}).`);
|
|
58
|
+
console.error(` cause: ${msg}`);
|
|
59
|
+
if (/fetch failed|ECONNREFUSED|ENOTFOUND|EAI_AGAIN/.test(msg)) {
|
|
60
|
+
console.error(' The instance could not be reached. Sign in with `node scripts/gds/setup.js`, or name one with <PREFIX>_API_BASE.');
|
|
61
|
+
}
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
package/scripts/gds/demote.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// prod. Strictly ready -> backlog — the server refuses any other status (a
|
|
10
10
|
// claimed/active task, or a later lifecycle state). Metic+ only (mirrors promote).
|
|
11
11
|
|
|
12
|
-
const { cliClient, requireSession } = require('./cli-lib');
|
|
12
|
+
const { cliClient, errCode, errDetails, requireSession } = require('./cli-lib');
|
|
13
13
|
|
|
14
14
|
(async () => {
|
|
15
15
|
await requireSession();
|
|
@@ -23,10 +23,15 @@ const { cliClient, requireSession } = require('./cli-lib');
|
|
|
23
23
|
const api = await cliClient();
|
|
24
24
|
const r = await api.tasks.postTasksIdDemote({ id: taskId });
|
|
25
25
|
if (!r.ok) {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// ADR 0116 nests BOTH the slug and its payload — see the note in cli-lib errCode
|
|
27
|
+
// (task 1003658). current_status moved to error.details with the code, so reading
|
|
28
|
+
// it off the top level printed 'undefined' even once the branch was reachable.
|
|
29
|
+
const code = errCode(r.data);
|
|
30
|
+
const details = errDetails(r.data);
|
|
31
|
+
if (code === 'cannot_demote') {
|
|
32
|
+
console.error(`Cannot demote task #${taskId}: it is '${details.current_status}', not 'ready'.`);
|
|
28
33
|
console.error("demote only moves a task ready -> backlog (the inverse of promote).");
|
|
29
|
-
} else if (
|
|
34
|
+
} else if (code === 'task_not_found') {
|
|
30
35
|
console.error(`No task #${taskId}.`);
|
|
31
36
|
} else {
|
|
32
37
|
console.error(`demote failed (${r.status}):`, r.data);
|