@bongos/core 1.19.545 → 1.19.546

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 (28) hide show
  1. package/.bongos-core.json +32 -32
  2. package/docs/copy-inventory.md +5 -5
  3. package/docs/copy-registry.json +7 -7
  4. package/docs/module-api-changelog.md +2 -0
  5. package/modules/hall-ui/public/hall-kit.js +76 -41
  6. package/modules/hall-ui/public/profile-charts.js +8 -2
  7. package/modules/ui-design/skills/brandkit/SKILL.md +5 -1
  8. package/modules/ui-design/skills/design-taste-frontend/SKILL.md +5 -1
  9. package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +5 -1
  10. package/modules/ui-design/skills/gpt-taste/SKILL.md +5 -1
  11. package/modules/ui-design/skills/high-end-visual-design/SKILL.md +5 -1
  12. package/modules/ui-design/skills/image-to-code/SKILL.md +5 -1
  13. package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +5 -1
  14. package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +5 -1
  15. package/modules/ui-design/skills/impeccable/SKILL.md +5 -1
  16. package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +5 -1
  17. package/modules/ui-design/skills/minimalist-ui/SKILL.md +5 -1
  18. package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +5 -1
  19. package/modules/ui-design/skills/stitch-design-taste/SKILL.md +5 -1
  20. package/modules/ui-design/skills/style/SKILL.md +5 -1
  21. package/package-lock.json +2 -2
  22. package/package.json +1 -1
  23. package/scripts/gds/fitness.js +10 -3
  24. package/scripts/gds/skill-lint.js +43 -9
  25. package/src/module-api.js +1 -1
  26. package/tests/hall_kit_charts.mjs +59 -0
  27. package/tests/skill_lint.mjs +51 -2
  28. package/tests/ui_design_skills.mjs +12 -2
package/.bongos-core.json CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.545",
6
- "core_contract": "1.19.545",
7
- "source_commit": "5c4f4e2a15f8c6f9861c15036d1c064fe8136a67",
5
+ "core_version": "1.19.546",
6
+ "core_contract": "1.19.546",
7
+ "source_commit": "979e1675fe919e685e74158c7420564adda4d461",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-04T17:49:07.724Z",
9
+ "built_at": "2026-09-04T18:10:33.995Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 441,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2477,
20
- "tree_sha256": "9502351a44d0d93a8a696a9d0a399703688efc0805e47ca4dc3a0027eb37df04",
20
+ "tree_sha256": "b5685b70ee82098100e1443399d51bf40ee47c0f7dd5487af8f73d92d81f1f1c",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -1797,12 +1797,12 @@
1797
1797
  {
1798
1798
  "path": "docs/copy-inventory.md",
1799
1799
  "mode": "0000644",
1800
- "sha256": "6178d1bd3c18b85943d5d48a55b38b4663f1de01788e6c47603a41d017fbeb12"
1800
+ "sha256": "f031d9706b2afc0f579684ec7d451d38b9c8ffd88f7fdb29e3b720a306f80c2f"
1801
1801
  },
1802
1802
  {
1803
1803
  "path": "docs/copy-registry.json",
1804
1804
  "mode": "0000644",
1805
- "sha256": "f087ebec28680cbb56319e404bf06eb18957a606c08296641de4c88db72801cf"
1805
+ "sha256": "aff8da11e90581e6ca7c9410d4524bf22d8bce6fe8f58bae9ebd6f1de744a2eb"
1806
1806
  },
1807
1807
  {
1808
1808
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2647,7 +2647,7 @@
2647
2647
  {
2648
2648
  "path": "docs/module-api-changelog.md",
2649
2649
  "mode": "0000644",
2650
- "sha256": "d2b0d54eaf8523656fba5441fd370608fcfcf3281ba41c2ec404305790850ffc"
2650
+ "sha256": "f93c244f3ed83f67a8ccf326a784d9a9049d83d72c2d05f6b06f12b3377481fe"
2651
2651
  },
2652
2652
  {
2653
2653
  "path": "docs/modules-contract.md",
@@ -4802,7 +4802,7 @@
4802
4802
  {
4803
4803
  "path": "modules/hall-ui/public/hall-kit.js",
4804
4804
  "mode": "0000644",
4805
- "sha256": "402344a4e8c5c8a9e983c5e341dc2ea8ab40cab4d5512715b62bd7736060ab74"
4805
+ "sha256": "f153e550a362c0da63c307e93eb135966e0ca74f7bffbbfebd44af98b1a1ece3"
4806
4806
  },
4807
4807
  {
4808
4808
  "path": "modules/hall-ui/public/hall-render.js",
@@ -5002,7 +5002,7 @@
5002
5002
  {
5003
5003
  "path": "modules/hall-ui/public/profile-charts.js",
5004
5004
  "mode": "0000644",
5005
- "sha256": "8a630558026a76af240eb4b10ad11086fca7005386015574574b6b754a89322c"
5005
+ "sha256": "8ec23ddc97bca27b13c27d18e378476d639545c1c772bae7ae4b113d4b2fcdea"
5006
5006
  },
5007
5007
  {
5008
5008
  "path": "modules/hall-ui/public/profile-connections.js",
@@ -7182,47 +7182,47 @@
7182
7182
  {
7183
7183
  "path": "modules/ui-design/skills/brandkit/SKILL.md",
7184
7184
  "mode": "0000644",
7185
- "sha256": "7b5cd9e896dcdb79a4386c863a03c628b6187fe81a0380ac50ea5dabc91fa468"
7185
+ "sha256": "b17be59c55cc536ed4dd4182564ca926ca886113ad8ce3ba5025f0caad689b28"
7186
7186
  },
7187
7187
  {
7188
7188
  "path": "modules/ui-design/skills/design-taste-frontend-v1/SKILL.md",
7189
7189
  "mode": "0000644",
7190
- "sha256": "5e461056c3e6fa4775cc6734b55bf6d5c050f01e0ee7a52378a9f4ab5501da28"
7190
+ "sha256": "7f342307efb46a82fa29faa1708b8f67daef63a4992dc0edb0e80398cd95fb81"
7191
7191
  },
7192
7192
  {
7193
7193
  "path": "modules/ui-design/skills/design-taste-frontend/SKILL.md",
7194
7194
  "mode": "0000644",
7195
- "sha256": "ee07bdde8e388b2863431673390d4bff74ee1296920ebcb07b529060eb7f8959"
7195
+ "sha256": "e1df9e0e7fa46b06147404cdf87673477cac42977da559064892c478bfb2ec37"
7196
7196
  },
7197
7197
  {
7198
7198
  "path": "modules/ui-design/skills/gpt-taste/SKILL.md",
7199
7199
  "mode": "0000644",
7200
- "sha256": "3e2e4802407df6763db372c27f3deaaf8f9b6233778668ddeaff65dec147ac7c"
7200
+ "sha256": "7c22d993cf7e42114f1c234e1321299b340c0e7fabd09f19b408f771de211fb5"
7201
7201
  },
7202
7202
  {
7203
7203
  "path": "modules/ui-design/skills/high-end-visual-design/SKILL.md",
7204
7204
  "mode": "0000644",
7205
- "sha256": "eeb33e0a2d0a227d23b67c5e6d32c5757d8d5d7249a1a6d0fe3c67d8bd95fc91"
7205
+ "sha256": "2bf36058d7db8c619b89b4e7fc97e303e99d2123cc2ea0f241174ab3c54b7d00"
7206
7206
  },
7207
7207
  {
7208
7208
  "path": "modules/ui-design/skills/image-to-code/SKILL.md",
7209
7209
  "mode": "0000644",
7210
- "sha256": "9c07a2328416874d69897bada333f955a93ef165eb128f073fb3bf40baef2d1e"
7210
+ "sha256": "7a85f43a0455549a5434485f5803569693308bd713debbfe045f676d29dcdae0"
7211
7211
  },
7212
7212
  {
7213
7213
  "path": "modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md",
7214
7214
  "mode": "0000644",
7215
- "sha256": "664f64d1f16afb10d3027fcd7dc50031e0f090f75cf5ee72258adc78e2e2ff73"
7215
+ "sha256": "5de59ad0e46c1efa1d52c8148ad39c26f797bc7fa888839236fa49e97a30d382"
7216
7216
  },
7217
7217
  {
7218
7218
  "path": "modules/ui-design/skills/imagegen-frontend-web/SKILL.md",
7219
7219
  "mode": "0000644",
7220
- "sha256": "75586cc997b927c7ca5ef7f3be574f4d81bd9238b01c9b66b1a5f8c587acaa14"
7220
+ "sha256": "c096c4c7a74b5613c7012f4b0dc3b1baca5e59819dd2f2b218fbe57e2ecba8a5"
7221
7221
  },
7222
7222
  {
7223
7223
  "path": "modules/ui-design/skills/impeccable/SKILL.md",
7224
7224
  "mode": "0000644",
7225
- "sha256": "ec144ae6ee19f59743e42e035f0a0fefc342bfd292353be73d50419d9df98491"
7225
+ "sha256": "285e595744c3d52b8a96e32f8986a82a08dd83fee172b852b5d8b14c808ae95e"
7226
7226
  },
7227
7227
  {
7228
7228
  "path": "modules/ui-design/skills/impeccable/reference/adapt.md",
@@ -7317,12 +7317,12 @@
7317
7317
  {
7318
7318
  "path": "modules/ui-design/skills/industrial-brutalist-ui/SKILL.md",
7319
7319
  "mode": "0000644",
7320
- "sha256": "4d781aa002138bdad572dfcae0dc6f1175fe070ee37fa8f3ef9a0f6af08e039b"
7320
+ "sha256": "86e9497cbfe20def445fcf4a53429755f31517f19a0e07dc3a73f99d13ec9b4f"
7321
7321
  },
7322
7322
  {
7323
7323
  "path": "modules/ui-design/skills/minimalist-ui/SKILL.md",
7324
7324
  "mode": "0000644",
7325
- "sha256": "b7b22a1ab8aa148ac4fc8573d7088c6ff207beb53f628009e7cfc363c6bbd2c2"
7325
+ "sha256": "fa1d7b7a801d6640bc49b4f762d48ff063f0cb01dc93d188a916a2b357c60542"
7326
7326
  },
7327
7327
  {
7328
7328
  "path": "modules/ui-design/skills/policy.json",
@@ -7332,17 +7332,17 @@
7332
7332
  {
7333
7333
  "path": "modules/ui-design/skills/redesign-existing-projects/SKILL.md",
7334
7334
  "mode": "0000644",
7335
- "sha256": "22e8f7ea0d206b3f4b2243b986a4dcd10df4ba0198aa063e76ef4d2788fec55d"
7335
+ "sha256": "b58f23bda03283aea06430601dd73f654509d3404bdd31322c8214582fee7eb1"
7336
7336
  },
7337
7337
  {
7338
7338
  "path": "modules/ui-design/skills/stitch-design-taste/SKILL.md",
7339
7339
  "mode": "0000644",
7340
- "sha256": "f8c6c38ccb5fea529e86731005e8bbf231a9131c890010a29129c06195f48945"
7340
+ "sha256": "6fbf6fa566e576e2d8b55aad5cfee97d75f316263669d0eefd1eb1b8e691ea0f"
7341
7341
  },
7342
7342
  {
7343
7343
  "path": "modules/ui-design/skills/style/SKILL.md",
7344
7344
  "mode": "0000644",
7345
- "sha256": "7853e7f17cd39caeae5107959905397dcc634aac0af2a7e017f357c8d36e45e5"
7345
+ "sha256": "5383fe2edfc30bd17325797405b2e0809b3f76ae49b0cc94622557b2aac8a30d"
7346
7346
  },
7347
7347
  {
7348
7348
  "path": "modules/ui-design/styles/README.md",
@@ -7452,12 +7452,12 @@
7452
7452
  {
7453
7453
  "path": "package-lock.json",
7454
7454
  "mode": "0000644",
7455
- "sha256": "c56d9508b46348664d7d32a7d213eeb6261e34879903149c73def19d545a9921"
7455
+ "sha256": "d19f98e5a6379aae00b038e27ad4051484dfe060b053b67bef5577e892e623ec"
7456
7456
  },
7457
7457
  {
7458
7458
  "path": "package.json",
7459
7459
  "mode": "0000644",
7460
- "sha256": "07a22aa815528b99f6db3a1fb72caf6171e457c97efed837f1e4c5b0c796520f"
7460
+ "sha256": "97a3bc18d404c2c95f273adcefacee1b34c6ed9445229622681f3a34a0bfbd72"
7461
7461
  },
7462
7462
  {
7463
7463
  "path": "public-docs/index.html",
@@ -7892,7 +7892,7 @@
7892
7892
  {
7893
7893
  "path": "scripts/gds/fitness.js",
7894
7894
  "mode": "0000644",
7895
- "sha256": "172f890f2a4297d287008f0ba11bc04fc801ea21a52728855967b178e2875373"
7895
+ "sha256": "c825842ed0e4bf34fa9fa7bc7e711967d1aa0efdde4132253c51231c6ccbd22e"
7896
7896
  },
7897
7897
  {
7898
7898
  "path": "scripts/gds/gate-review.js",
@@ -8612,7 +8612,7 @@
8612
8612
  {
8613
8613
  "path": "scripts/gds/skill-lint.js",
8614
8614
  "mode": "0000644",
8615
- "sha256": "cacae521e5580e66c74128142772abe93d6742e8438417bd4e5a19d48d611c9f"
8615
+ "sha256": "c68e39e195cf38a7ec09d601f616dd44f83e2ea698705f41e30a08eef4308fba"
8616
8616
  },
8617
8617
  {
8618
8618
  "path": "scripts/gds/skill-preflight.js",
@@ -9142,7 +9142,7 @@
9142
9142
  {
9143
9143
  "path": "src/module-api.js",
9144
9144
  "mode": "0000644",
9145
- "sha256": "54d926a5e17a476efbde6fe6786af692b26ea841a09c9c49c45e43654c13790b"
9145
+ "sha256": "83cdf243b987aea59adf18f939bc808239053d6f2fa39339bd17001be9bcf501"
9146
9146
  },
9147
9147
  {
9148
9148
  "path": "src/module-loader/catalog.js",
@@ -10582,7 +10582,7 @@
10582
10582
  {
10583
10583
  "path": "tests/hall_kit_charts.mjs",
10584
10584
  "mode": "0000644",
10585
- "sha256": "b3c95d32e4c2402699070c08fa4eaa9f460cd4d2b813f8b15e39d29fc5aa5b4d"
10585
+ "sha256": "5654f4366ddcfcb61848a119a2dba3827f3fe19c0151005558a803c2e09db573"
10586
10586
  },
10587
10587
  {
10588
10588
  "path": "tests/hall_nav.mjs",
@@ -12067,7 +12067,7 @@
12067
12067
  {
12068
12068
  "path": "tests/skill_lint.mjs",
12069
12069
  "mode": "0000644",
12070
- "sha256": "da75748d8733b16c479686a350f4e648e85ccc171ce633b4561315bd6d7073bb"
12070
+ "sha256": "bb11de1a32e4d3504d9b5b4f649a1117364f0d5368ca96cf2a720d6dbbfbd040"
12071
12071
  },
12072
12072
  {
12073
12073
  "path": "tests/skill_preflight.mjs",
@@ -12307,7 +12307,7 @@
12307
12307
  {
12308
12308
  "path": "tests/ui_design_skills.mjs",
12309
12309
  "mode": "0000644",
12310
- "sha256": "f12817db3dd3486446cfcbe1701a06e19b7d1c18e9a6046f7861a667268fd3d4"
12310
+ "sha256": "e8ae559aa743e4ea3377f1bf57fd33cb5efdb96ba7c67ab57964915814e7e212"
12311
12311
  },
12312
12312
  {
12313
12313
  "path": "tests/ui_design_styles.mjs",
@@ -557,7 +557,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
557
557
  | `7a239a17afc8` | certain | Loading your preferences… | `modules/hall-ui/public/settings.html:32` |
558
558
  | `04df1bd30129` | certain | Loading your rank… | `modules/hall-ui/public/ranks.html:30` |
559
559
  | `c27711f9eb4d` | certain | Locked until you reach the end of the article above. | `modules/hall-ui/public/primer.html:103` |
560
- | `4017ee030bc2` | certain | Lookback window | `modules/hall-ui/public/profile-charts.js:425` |
560
+ | `4017ee030bc2` | certain | Lookback window | `modules/hall-ui/public/profile-charts.js:431` |
561
561
  | `a93ca2841b96` | certain | Mac (Intel) | `modules/hall-ui/public/hall-render.js:750` |
562
562
  | `06b4ede3fb58` | certain | Machine principals | `modules/hall-ui/public/government.html:124` |
563
563
  | `6b3d660e0e31` | certain | Map view | `modules/hall-ui/public/atlas.html:46` |
@@ -1062,7 +1062,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1062
1062
  | `b6dceaace783` | certain | Your profile page setting applies now — private already answers "not found" everywhere your profile is read. R | `modules/hall-ui/public/settings.html:119` |
1063
1063
  | `f39746e3d66b` | certain | Your rendering preference could not be loaded. Reload the page to try again. | `modules/hall-ui/public/settings.js:554` |
1064
1064
  | `b2dfd7120052` | certain | Your request is sent ✓ | `modules/hall-ui/public/hall-render.js:381` |
1065
- | `b410b052f000` | certain | Your shipping year | `modules/hall-ui/public/profile-charts.js:279` |
1065
+ | `b410b052f000` | certain | Your shipping year | `modules/hall-ui/public/profile-charts.js:285` |
1066
1066
  | `c16addf957d0` | certain | Your track record across every project you build on, shown on your public profile. Hiding it takes effect imme | `modules/hall-ui/public/settings.html:96` |
1067
1067
  | `f6b12676169b` | certain | Your vote | `modules/hall-ui/public/hall-widgets.js:170` |
1068
1068
  | `fe94ff29a2cc` | certain | Your wandering level could not be loaded. Reload the page to try again. | `modules/hall-ui/public/settings.js:486` |
@@ -1072,7 +1072,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1072
1072
  | `137b07b42203` | certain | Zoom in | `modules/hall-ui/public/diagram-viewer.js:98` |
1073
1073
  | `0d0bfa651b36` | certain | Zoom out | `modules/hall-ui/public/diagram-viewer.js:96` |
1074
1074
  | `cc361cc7333a` | certain | \s?/.test(raw)) { const quoteLines = []; while (i | `modules/hall-ui/public/md-reader.js:189` |
1075
- | `a02616e47d11` | certain | a year of qualifying ships, in your timezone \u00b7 only you see this one | `modules/hall-ui/public/profile-charts.js:279` |
1075
+ | `a02616e47d11` | certain | a year of qualifying ships, in your timezone \u00b7 only you see this one | `modules/hall-ui/public/profile-charts.js:285` |
1076
1076
  | `44e6e402db94` | certain | account, then open | `modules/hall-ui/public/hall-render.js:750` |
1077
1077
  | `ae43396446ed` | certain | account. | `modules/hall-ui/public/hall-render.js:750` |
1078
1078
  | `9612c983451f` | certain | achieved · | `modules/hall-ui/public/roadmap.js:275` |
@@ -1189,8 +1189,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1189
1189
  | `9bec9eb4829e` | certain | scope: {…} | `modules/hall-ui/public/goals-render.js:785` |
1190
1190
  | `a6f55d7aa6f7` | certain | shipped (live) | `modules/hall-ui/public/roadmap.js:285` |
1191
1191
  | `026ba1289d4d` | certain | shipped · | `modules/hall-ui/public/goals-render.js:707` |
1192
- | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:493` |
1193
- | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:505` |
1192
+ | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:499` |
1193
+ | `ec6d8c900bc8` | certain | sign in | `modules/hall-ui/public/profile-charts.js:511` |
1194
1194
  | `0215b2cdca8c` | certain | submit for review | `modules/hall-ui/public/modules.js:132` |
1195
1195
  | `da30b952a60b` | certain | system / unattributed | `modules/hall-ui/public/goals-page.js:249` |
1196
1196
  | `da30b952a60b` | certain | system / unattributed | `modules/hall-ui/public/work.js:498` |
@@ -1553,8 +1553,8 @@
1553
1553
  "text": "sign in",
1554
1554
  "occurrences": 2,
1555
1555
  "at": [
1556
- "modules/hall-ui/public/profile-charts.js:493",
1557
- "modules/hall-ui/public/profile-charts.js:505"
1556
+ "modules/hall-ui/public/profile-charts.js:499",
1557
+ "modules/hall-ui/public/profile-charts.js:511"
1558
1558
  ]
1559
1559
  },
1560
1560
  {
@@ -6009,7 +6009,7 @@
6009
6009
  "surface": "builders-hall",
6010
6010
  "text": "Lookback window",
6011
6011
  "file": "modules/hall-ui/public/profile-charts.js",
6012
- "line": 425,
6012
+ "line": 431,
6013
6013
  "origin": "js-markup",
6014
6014
  "confidence": "certain"
6015
6015
  },
@@ -10554,7 +10554,7 @@
10554
10554
  "surface": "builders-hall",
10555
10555
  "text": "Your shipping year",
10556
10556
  "file": "modules/hall-ui/public/profile-charts.js",
10557
- "line": 279,
10557
+ "line": 285,
10558
10558
  "origin": "js-markup",
10559
10559
  "confidence": "certain"
10560
10560
  },
@@ -10644,7 +10644,7 @@
10644
10644
  "surface": "builders-hall",
10645
10645
  "text": "a year of qualifying ships, in your timezone \\u00b7 only you see this one",
10646
10646
  "file": "modules/hall-ui/public/profile-charts.js",
10647
- "line": 279,
10647
+ "line": 285,
10648
10648
  "origin": "js-markup",
10649
10649
  "confidence": "certain"
10650
10650
  },
@@ -11697,7 +11697,7 @@
11697
11697
  "surface": "builders-hall",
11698
11698
  "text": "sign in",
11699
11699
  "file": "modules/hall-ui/public/profile-charts.js",
11700
- "line": 493,
11700
+ "line": 499,
11701
11701
  "origin": "js-markup",
11702
11702
  "confidence": "certain"
11703
11703
  },
@@ -11706,7 +11706,7 @@
11706
11706
  "surface": "builders-hall",
11707
11707
  "text": "sign in",
11708
11708
  "file": "modules/hall-ui/public/profile-charts.js",
11709
- "line": 505,
11709
+ "line": 511,
11710
11710
  "origin": "js-markup",
11711
11711
  "confidence": "certain"
11712
11712
  },
@@ -1539,5 +1539,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1539
1539
  landed since 1.19.543 with no explicit bump. run 33899781074. (task 1002620)
1540
1540
  1.19.545 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1541
1541
  landed since 1.19.544 with no explicit bump. run 33902527608. (task 1002620)
1542
+ 1.19.546 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1543
+ landed since 1.19.545 with no explicit bump. run 33904396127. (task 1002620)
1542
1544
  ---------------------------------------------------------------------------
1543
1545
  ```
@@ -1033,69 +1033,100 @@
1033
1033
  + '</div>';
1034
1034
  }
1035
1035
 
1036
- // calendarGridHtml(days, opts) — a year of ship-days as a contribution calendar:
1037
- // one cell per calendar day, BANDED by count rather than scaled, its reading a
1038
- // per-cell title (plus an aria summary on the whole grid), not a crosshair. The
1039
- // hall's THIRD chart family (task 1003572, criterion legibility-series-are-drawn):
1040
- // OTBKit's other two shapes are a horizontal bar series and a plot on a time axis;
1041
- // a 2-D year grid is neither, so it earns its own primitive rather than a hand-roll
1042
- // at the page (modules/hall-ui/CLAUDE.md; tests/hall_audit.mjs section 6b).
1036
+ // The calendar's date arithmetic, shared by the window and the grid.
1043
1037
  //
1044
- // THE READING, decided and written down (the task asked which): a NON-picture reader
1045
- // gets the exact figure two ways — each cell carries a `title` ("2026-09-03 · 3
1046
- // ships" / "… · no ships"), and the grid carries an aria-label summarising the
1047
- // window (active days + total ships). No table twin: unlike a line, a calendar's
1048
- // value IS its position, so a per-cell title reads the same fact the picture shows,
1049
- // and a 366-row table beneath would be longer than the thing it explains.
1050
- //
1051
- // `days`: [{ local_date:'YYYY-MM-DD', count }] — SPARSE (only days that had ships),
1052
- // already resolved in the BUILDER's timezone server-side (GET /me). Empty → '' so
1053
- // the caller renders its own empty state, never an empty grid.
1038
+ // THE TIMEZONE TRAP: local_date is already the builder's own day, so the grid is built
1039
+ // by ORDINAL arithmetic on the 'YYYY-MM-DD' parts (Date.UTC on the parsed integers,
1040
+ // floored to an epoch day) — never `new Date('2026-09-03')`, which is UTC-midnight and
1041
+ // would shift a cell a day west of Greenwich. Same epoch-day floor cumulateByDay uses,
1042
+ // so no cell moves relative to the other charts. calDow is Sunday-indexed: epoch day 0
1043
+ // (1970-01-01) is a Thursday, hence the +4.
1044
+ const calYmdToOrd = (ymd) => {
1045
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
1046
+ if (!m) return null;
1047
+ return Math.floor(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) / 86400000);
1048
+ };
1049
+ const calOrdToYmd = (ord) => {
1050
+ const dt = new Date(ord * 86400000);
1051
+ return `${dt.getUTCFullYear()}-${String(dt.getUTCMonth() + 1).padStart(2, '0')}-${String(dt.getUTCDate()).padStart(2, '0')}`;
1052
+ };
1053
+ const calDow = (o) => (((o + 4) % 7) + 7) % 7;
1054
+
1055
+ // calendarWindow(days, opts) — the DRAWN window and what falls inside it, resolved
1056
+ // once so the grid and whatever caption sits beside it can never disagree (task
1057
+ // 1003583). Two bugs came from computing this twice: the aria summary tallied every
1058
+ // row handed in rather than the cells drawn, and profile-charts.js counted
1059
+ // `days.filter(d => d.count > 0)` over the whole bounded array — so "N days shipped
1060
+ // this year" could name a day sitting outside the grid's first partial week.
1054
1061
  //
1055
- // THE TIMEZONE TRAP the task names: local_date is already the builder's own day, so
1056
- // the grid is built by ORDINAL arithmetic on the 'YYYY-MM-DD' parts (Date.UTC on the
1057
- // parsed integers, floored to an epoch day) — never `new Date('2026-09-03')`, which
1058
- // is UTC-midnight and would shift a cell a day west of Greenwich. Same epoch-day
1059
- // floor cumulateByDay uses, so no cell moves relative to the other charts.
1060
- function calendarGridHtml(days, opts = {}) {
1062
+ // Returns null when there is nothing real to draw, so callers keep their own empty
1063
+ // state rather than rendering an empty grid.
1064
+ function calendarWindow(days, opts = {}) {
1061
1065
  const list = (days || []).filter((d) => d && typeof d.local_date === 'string');
1062
- const ymdToOrd = (ymd) => {
1063
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
1064
- if (!m) return null;
1065
- return Math.floor(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) / 86400000);
1066
- };
1067
- const ordToYmd = (ord) => {
1068
- const dt = new Date(ord * 86400000);
1069
- return `${dt.getUTCFullYear()}-${String(dt.getUTCMonth() + 1).padStart(2, '0')}-${String(dt.getUTCDate()).padStart(2, '0')}`;
1070
- };
1071
1066
  const counts = new Map();
1072
1067
  let maxOrd = null;
1073
- let activeDays = 0;
1074
- let totalShips = 0;
1075
1068
  for (const d of list) {
1076
- const o = ymdToOrd(d.local_date);
1069
+ const o = calYmdToOrd(d.local_date);
1077
1070
  if (o == null) continue;
1078
1071
  const c = Math.max(0, Math.round(Number(d.count) || 0));
1079
1072
  if (c <= 0) continue;
1080
1073
  counts.set(o, (counts.get(o) || 0) + c);
1081
1074
  if (maxOrd == null || o > maxOrd) maxOrd = o;
1082
1075
  }
1083
- if (maxOrd == null) return ''; // nothing real to draw → caller's empty state
1084
- for (const [, c] of counts) { activeDays += 1; totalShips += c; }
1076
+ if (maxOrd == null) return null;
1085
1077
 
1086
1078
  // The grid ENDS at `today` (the builder's, when given) so a quiet recent week still
1087
1079
  // reads as time that passed, not a truncated chart; falls back to the last ship day.
1088
- const todayOrd = (opts.today && ymdToOrd(opts.today)) || maxOrd;
1080
+ const todayOrd = (opts.today && calYmdToOrd(opts.today)) || maxOrd;
1089
1081
  const endOrd = Math.max(todayOrd, maxOrd);
1090
1082
  const weeks = Math.max(1, Math.min(53, Math.round(Number(opts.weeks)) || 53));
1091
1083
 
1092
1084
  // Columns are weeks, rows Sun..Sat. Epoch day 0 (1970-01-01) is a Thursday, so the
1093
1085
  // Sunday-indexed weekday is (ord + 4) mod 7. The last column is the week holding
1094
1086
  // endOrd; walk back `weeks` Sundays to the first cell.
1095
- const dow = (o) => (((o + 4) % 7) + 7) % 7;
1096
- const endSun = endOrd - dow(endOrd);
1087
+ const endSun = endOrd - calDow(endOrd);
1097
1088
  const startSun = endSun - (weeks - 1) * 7;
1098
1089
 
1090
+ // Tallied over the DRAWN range only — [startSun, endOrd] — not over everything
1091
+ // handed in. A day the caller supplied that falls before the first column is real
1092
+ // history, but it is not on this picture, so it is not in this picture's summary.
1093
+ let activeDays = 0;
1094
+ let totalShips = 0;
1095
+ for (const [o, c] of counts) {
1096
+ if (o < startSun || o > endOrd) continue;
1097
+ activeDays += 1;
1098
+ totalShips += c;
1099
+ }
1100
+ return { counts, startSun, endOrd, weeks, activeDays, totalShips };
1101
+ }
1102
+
1103
+ // calendarGridHtml(days, opts) — a year of ship-days as a contribution calendar:
1104
+ // one cell per calendar day, BANDED by count rather than scaled, its reading a
1105
+ // per-cell title (plus an aria summary on the whole grid), not a crosshair. The
1106
+ // hall's THIRD chart family (task 1003572, criterion legibility-series-are-drawn):
1107
+ // OTBKit's other two shapes are a horizontal bar series and a plot on a time axis;
1108
+ // a 2-D year grid is neither, so it earns its own primitive rather than a hand-roll
1109
+ // at the page (modules/hall-ui/CLAUDE.md; tests/hall_audit.mjs section 6b).
1110
+ //
1111
+ // THE READING, decided and written down (the task asked which): a NON-picture reader
1112
+ // gets the exact figure two ways — each cell carries a `title` ("2026-09-03 · 3
1113
+ // ships" / "… · no ships"), and the grid carries an aria-label summarising the
1114
+ // window (active days + total ships). No table twin: unlike a line, a calendar's
1115
+ // value IS its position, so a per-cell title reads the same fact the picture shows,
1116
+ // and a 366-row table beneath would be longer than the thing it explains.
1117
+ //
1118
+ // `days`: [{ local_date:'YYYY-MM-DD', count }] — SPARSE (only days that had ships),
1119
+ // already resolved in the BUILDER's timezone server-side (GET /me). Empty → '' so
1120
+ // the caller renders its own empty state, never an empty grid.
1121
+ //
1122
+ // The window it draws, and the tallies its summary quotes, come from calendarWindow —
1123
+ // see the timezone trap documented there.
1124
+ function calendarGridHtml(days, opts = {}) {
1125
+ const win = calendarWindow(days, opts);
1126
+ if (!win) return ''; // nothing real to draw → caller's empty state
1127
+ const { counts, startSun, endOrd, weeks, activeDays, totalShips } = win;
1128
+ const ordToYmd = calOrdToYmd;
1129
+
1099
1130
  // level 0 = empty; 1..5 map to the anti-gaming daily cap (streak.js caps a day at
1100
1131
  // 5), so the darkest band means "hit the day's max", not "over some threshold".
1101
1132
  const level = (c) => (c <= 0 ? 0 : Math.min(5, c));
@@ -1112,8 +1143,11 @@
1112
1143
  }
1113
1144
  }
1114
1145
 
1146
+ // The window is DESCRIBED, not assumed: a caller passing weeks:6 used to be handed
1147
+ // an aria label saying "over the last year" (task 1003583).
1148
+ const span = weeks === 53 ? 'the last year' : `the last ${weeks} week${weeks === 1 ? '' : 's'}`;
1115
1149
  const summary = opts.ariaLabel
1116
- || `Ships per day over the last year: ${totalShips} ship${totalShips === 1 ? '' : 's'} across ${activeDays} day${activeDays === 1 ? '' : 's'}.`;
1150
+ || `Ships per day over ${span}: ${totalShips} ship${totalShips === 1 ? '' : 's'} across ${activeDays} day${activeDays === 1 ? '' : 's'}.`;
1117
1151
  // The legend is aria-hidden: it decorates the bands, it carries no fact the cell
1118
1152
  // titles and the summary do not already state in words.
1119
1153
  const legend = '<div class="kit-cal__key" aria-hidden="true"><span class="kit-cal__key-label">less</span>'
@@ -1141,6 +1175,7 @@
1141
1175
  sparklineHtml, barSeriesHtml, chartCardHtml, linePoints, cumulate, cumulateByDay,
1142
1176
  // the year-grid contribution calendar (task 1003572) — the hall's third chart family
1143
1177
  calendarGridHtml,
1178
+ calendarWindow,
1144
1179
  // plotted charts: the axis frame + the two series shapes that ride it
1145
1180
  plotSvgHtml, plotValueLabelHtml, timeStepSeriesHtml, timeStepGeometry, columnSeriesHtml,
1146
1181
  // plotBox is exported so a caller mapping pointer x back into viewBox
@@ -267,9 +267,15 @@
267
267
  // module doc's honest-exit rule; DONE WHEN #4).
268
268
  function calendarChart({ id, streak }) {
269
269
  const days = (streak && Array.isArray(streak.ship_days)) ? streak.ship_days : [];
270
- const grid = window.OTBKit.calendarGridHtml(days, { today: streak && streak.today });
270
+ const opts = { today: streak && streak.today };
271
+ const grid = window.OTBKit.calendarGridHtml(days, opts);
271
272
  if (!grid) return null;
272
- const active = days.filter((d) => Number(d.count) > 0).length;
273
+ // The caption counts what the GRID draws, not what the server sent (task 1003583).
274
+ // `days` is bounded to 371 days server-side while the grid spans 53 weeks back to a
275
+ // Sunday, so filtering `days` here could name a day the picture does not contain —
276
+ // and the number sits directly under that picture. One window, read twice.
277
+ const win = window.OTBKit.calendarWindow(days, opts);
278
+ const active = win ? win.activeDays : 0;
273
279
  const cur = Number(streak && streak.current) || 0;
274
280
  const lng = Number(streak && streak.longest) || 0;
275
281
  // The scalars in words, in the .pchart__read slot the other figures use for their
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: brandkit
3
- description: The identity-system method for ANY Cloud Bongos instance, under the owner's hero-only rule: infers the brand strategy before anything is made (category, audience, function, promise, cultural position, the one symbolic metaphor, what to avoid), holds the logo standard and the five concept methods, and lands the system as the instance's own materials: the mark drawn as SVG in code, the colour system as the branding pack, the type specimen as the pack's faces, the applications as the platform's real surfaces rendered by the kit, and the image direction as a text-free hero-object family with a sidecar beside every plate. The nine-panel board becomes a contact sheet composed from those parts, never a generated image with text in it. Triggers on "brand kit", "identity system", "logo system", "brand guidelines board", "the visual world for a new instance", or when cloudbongos init asks what an instance's mark and material are. Never generates a logo, a wordmark, a board with text, or a mockup with UI. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
3
+ description: >-
4
+ The identity-system method for any instance, under the owner's hero-only rule. Infers the brand strategy
5
+ first, holds the logo standard and the five concept methods, and lands the system as the instance's own
6
+ materials — the mark as SVG, the colours as the pack, the type as its faces. Triggers on "brand kit",
7
+ "identity system", "logo system". Never generates a logo, wordmark or UI mockup.
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: design-taste-frontend
3
- description: The default taste skill for a landing page, a portfolio, a marketing surface or a redesign of one, on ANY Cloud Bongos instance. Reads the brief and says the design read in one line, sets three dials (variance, motion, density) from named presets, locks one world for the whole page, keeps the hero and the layout disciplined, rations decoration, and ends on a pre-flight the kit measures. Triggers on "design a landing page", "make this not look templated", "taste pass", "anti-slop", "this looks like every AI site", or when /design hands off a marketing surface. Not for dashboards, data tables, wizards, code editors, native mobile or realtime collaboration (those go to /design and the hall's archetypes). Rebuilt in-house from the taste-skill rebuild spec (task 1003325, ADR 0198); the instance's world is the palette and images are hero plates only.
3
+ description: >-
4
+ The default taste skill for a landing page, portfolio or marketing surface on any Cloud Bongos instance.
5
+ Says the design read in one line, sets three dials (variance, motion, density), locks one world for the
6
+ whole page, and ends on a pre-flight the kit measures. Triggers on "design a landing page", "make this not
7
+ look templated", "taste pass", "anti-slop". Not for dashboards or native mobile.
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: design-taste-frontend-v1
3
- description: The earlier generation of the taste skill, preserved under its own name so a task or a builder that names it gets exactly this behaviour while the default (design-taste-frontend) evolves separately. A fixed baseline triple of dials adapted in conversation, stack conventions for a served surface, typography and colour rules read against the instance's pack, the layout bans, full interaction cycles, motion rules inside the Kill Switch, performance guardrails, a named vocabulary of premium patterns and a tile-grid archetype set. Triggers on "design-taste-frontend-v1", "use the v1 taste skill", "the fixed dials", or "the pattern vocabulary". Rebuilt in-house from the taste-skill rebuild spec (task 1003325, ADR 0198); nothing here auto-updates from v2.
3
+ description: >-
4
+ The earlier generation of the taste skill, kept under its own name so a task or builder that asks for it
5
+ gets exactly this behaviour while the default (design-taste-frontend) evolves separately. A fixed triple of
6
+ dials, the layout bans, full interaction cycles, and a named vocabulary of premium patterns. Triggers on
7
+ "design-taste-frontend-v1", "the fixed dials", "the pattern vocabulary".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: gpt-taste
3
- description: Anti-default enforcement for a persuasive scrolling surface when the last attempt came out looking like every other one. A written plan block before any markup whose selection is derived deterministically from the route (a hero architecture, three component architectures, two CSS motion paradigms, checked against what sibling surfaces already used), the four-stage attention-interest-desire-action structure, an ultra-wide hero container that holds the headline to two or three lines, a dense tile grid with zero empty cells, real scroll-driven motion inside the Kill Switch, and a measured contrast sweep. Triggers on "it looks generic", "award-tier", "stop making the same page", "anti-default". Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198).
3
+ description: >-
4
+ Anti-default enforcement for a persuasive scrolling surface when the last attempt came out looking like
5
+ every other one. A written plan block before any markup, derived deterministically from the route; the
6
+ four-stage attention-interest-desire-action structure; an ultra-wide hero; a dense tile grid with zero empty
7
+ cells. Triggers on "it looks generic", "award-tier", "anti-default".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: high-end-visual-design
3
- description: Makes an interface read as an expensive agency build, inside the instance's world. One texture archetype and one layout archetype per project (never the same pair twice), the container anatomy (a shell and a core with a hairline ring and concentric radii, where the world allows a container at all), the pill button with its nested arrow disc, the spatial rhythm, the motion choreography inside the Kill Switch, the hover physics, the scroll entry, the mobile collapse per archetype and the performance rules, then a pre-output checklist. Triggers on "make it feel premium", "agency-grade", "this looks cheap", "expensive", "high-end", "the polish pass". Rebuilt in-house from the taste-skill rebuild spec (task 1003325, ADR 0198); the fonts are the pack's, the palette is the world's, images are hero plates only.
3
+ description: >-
4
+ Makes an interface read as an expensive agency build, inside the instance's world. One texture archetype and
5
+ one layout archetype per project, the container anatomy, the pill button, the spatial rhythm, the motion
6
+ choreography inside the Kill Switch, the mobile collapse, then a pre-output checklist. Triggers on "make it
7
+ feel premium", "agency-grade", "this looks cheap", "the polish pass".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: image-to-code
3
- description: The analyse-then-implement loop for visually important web work on ANY Cloud Bongos instance, under the owner's hero-only rule: the design source of truth for the UI is the owner's reference images and the instance's world, read with a structured per-image analysis and extracted into a full spec before a line is written; the only image this skill generates is the hero plate the composition has a slot for, and the sections are built from the world's templates. Implements as a faithful translation rather than an interpretation, names design drift as the failure mode, holds the hero and clutter rules, and ends on a clarity checklist the kit measures. Triggers on "build this from the reference", "recreate these images in code", "implement the mock faithfully", "the owner sent screenshots to build from", or when a claimed ui task carries reference images. Never generates a section, a comp or a screen. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
3
+ description: >-
4
+ The analyse-then-implement loop for visually important web work, under the owner's hero-only rule. The
5
+ design source of truth is the owner's reference images and the instance's world, read per image and
6
+ extracted into a full spec before a line is written. Triggers on "build this from the reference", "implement
7
+ the mock faithfully". Never generates a section, comp or screen.
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: imagegen-frontend-mobile
3
- description: The plate-making skill for the narrow end of a web surface on ANY Cloud Bongos instance, under the owner's hero-only rule: it produces text-free portrait plates and hero objects for the 390 and 320 compositions the kit renders, the masthead above the words and the field behind them, sized for the fold and clear of the regions a phone reserves. Never a screen, never a phone mockup, never a flow of screens as images, and never code: a screen concept is the surface's states file rendered by the kit at 390, and a native app target declines to /design. Locks one material, light and camera across the family from the world's DESIGN.md, keeps the first view to one focal point, puts a scrim under any text that sits over a plate, and lands the pair convention with a sidecar beside every file. Triggers on "a plate for the mobile hero", "the masthead at 390", "the hero object for the phone collapse", "a portrait plate", or when adapt or design-taste-frontend names a narrow-width slot with no plate. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
3
+ description: >-
4
+ The plate-making skill for the narrow end of a web surface, under the owner's hero-only rule. Text-free
5
+ portrait plates and hero objects for the 390 and 320 compositions the kit renders, sized for the fold and
6
+ clear of what a phone reserves, with a sidecar beside every file. Triggers on "a plate for the mobile hero",
7
+ "the masthead at 390". Never a screen, a phone mockup or code.
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: imagegen-frontend-web
3
- description: The plate-making skill for a web surface on ANY Cloud Bongos instance, under the owner's hero-only rule: it produces text-free hero plates, hero objects and atmosphere backdrops for the slots a composition already has, never one image per section, never a screen, never a UI comp, and it writes no page code. Commits to the plate list out loud, chooses a composition anchor per plate that is not the text-left image-right default, locks one material, one light and one camera across the family, matches every plate tonally to the instance's pack, runs the generation through the instance's art pipeline key, and lands each plate as the pair convention (a black-ground plate for dark, an alpha cutout for light) with a provenance sidecar beside every file. Triggers on "make a hero plate", "we need a hero object for", "an atmosphere backdrop for the landing", "the project orbs need a new material", or when design-taste-frontend or /design names a plate slot with no plate. Ported in-house from the taste-skill rebuild spec (tasks 1003325 and 1003328, ADR 0198) with the hero-only rewrite the upstream never had.
3
+ description: >-
4
+ The plate-making skill for a web surface, under the owner's hero-only rule. Text-free hero plates, hero
5
+ objects and atmosphere backdrops for the slots a composition already has — never one per section, never a
6
+ screen, never a UI comp, and no page code. One material, light and camera across the family, landed with a
7
+ provenance sidecar. Triggers on "make a hero plate", "an atmosphere backdrop".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: impeccable
3
- description: The design-craft method for ANY surface on ANY Cloud Bongos instance, invoked as one skill plus an optional sub-command and an optional target ("impeccable audit /projects", "impeccable polish the hall's ranks page", "impeccable harden the wizard"). Sixteen sub-command playbooks (shape, audit, critique, polish, harden, animate, typeset, colorize, layout, adapt, distill, clarify, onboard, optimize, document, extract) plus the two directions (bolder, quieter), exactly one loaded per request; a bare description with no sub-command is ordinary design work routed to the closest playbook; no argument prints a menu built from live project signals and never auto-runs anything. Carries the craft floor (loaded right before UI is edited, never recited to the user) and the bounded finish (build fully, one render round through the kit, one batch of fixes, at most one confirming round, stop). Triggers on "impeccable", "critique this page", "audit the UI", "polish the whole path", "make it survive long text", "animate this", "tighten the copy", "document the design system", "extract the repeated pattern". Rebuilt in-house from the impeccable rebuild spec (task 1003326, ADR 0198) with no hooks, no bypass agent, no telemetry and no live server; the live loop is the playwright plugin and the headless loop is the module's kit. Web surfaces only; a native mobile target declines to /design.
3
+ description: >-
4
+ The design-craft method for any surface, invoked as one skill plus an optional sub-command and target
5
+ ("impeccable audit /projects"). Sixteen playbooks — shape, audit, critique, polish, harden, animate,
6
+ typeset, colorize, layout, adapt, distill, clarify, onboard, optimize, document, extract — one per request.
7
+ Triggers on "impeccable", "critique this page", "audit the UI". Web surfaces only.
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: industrial-brutalist-ui
3
- description: Raw mechanical interfaces that read like a technical document, inside the instance's world. One archetype per project (Swiss industrial print or dark tactical terminal, never mixed), massive uppercase clamped display against fixed tracked-out mono metadata, the pack's terracotta as the single hazard accent and its sea as one terminal green, strict grid anchoring with the hairline as a one-pixel grid gap, zero corner rounding, bracket and crosshair framing glyphs, and static halftone, scanline and noise degradation inside the Kill Switch. Triggers on "brutalist", "industrial", "terminal", "make it raw", "like a technical document". Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198).
3
+ description: >-
4
+ Raw mechanical interfaces that read like a technical document, inside the instance's world. One archetype
5
+ per project (Swiss industrial print or dark tactical terminal, never mixed), massive uppercase display
6
+ against tracked-out mono metadata, strict grid anchoring, zero corner rounding, halftone and scanline
7
+ degradation inside the Kill Switch. Triggers on "brutalist", "industrial", "make it raw".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: minimalist-ui
3
- description: Quiet editorial product interfaces built out of type and white space, inside the instance's world. Macro whitespace first, a capped measure, one display job and one body job, exactly one hairline weight for every border in the sheet, washed accents measured against their own ink, flat tile cards with no shadow, box-less accordions, keycap shortcuts, one depth treatment per band, entry motion inside the Kill Switch. Triggers on "make it minimal", "clean and editorial", "quiet", "document-like", "this feels busy". Declines dense surfaces like dashboards and ledgers. Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198); the palette is the pack and the nearest library look is expedition.
3
+ description: >-
4
+ Quiet editorial product interfaces built out of type and white space, inside the instance's world. Macro
5
+ whitespace first, a capped measure, one display job and one body job, exactly one hairline weight for every
6
+ border, flat tile cards with no shadow, entry motion inside the Kill Switch. Triggers on "make it minimal",
7
+ "clean and editorial", "this feels busy". Declines dense surfaces.
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: redesign-existing-projects
3
- description: Upgrades an existing site or app in place, in whatever framework and styling method it already uses, without breaking it. A fixed three-step sequence (scan the stack, diagnose against a written audit, apply targeted fixes), the seven-group audit (typography, colour and surface, layout, interaction, content, code and omissions) with the instance's world as the answer key, a catalogue of upgrade techniques marked CSS-only or not available, and a fixed impact-per-risk fix order. Triggers on "redesign this", "upgrade the existing page", "make the current X premium", "audit this UI", "this page looks generic, fix it". Rebuilt in-house from the taste-skill rebuild spec (task 1003325, ADR 0198); keeps the stack, never migrates frameworks, images are hero plates only.
3
+ description: >-
4
+ Upgrades an existing site or app in place, in whatever framework and styling method it already uses, without
5
+ breaking it. Three fixed steps — scan the stack, diagnose against a written audit, apply targeted fixes —
6
+ with the instance's world as the answer key and a fixed impact-per-risk fix order. Triggers on "redesign
7
+ this", "upgrade the existing page", "audit this UI".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: stitch-design-taste
3
- description: Writes a design contract document for an external screen-generating tool such as Google Stitch, in the platform's own frontmatter shape. Four dials (creativity, density, variance, motion intent) with named bands and stated defaults, then eight fixed sections (atmosphere, colour roles, typography, components, layout, responsive rules, motion intent, banned patterns) written as descriptive language paired with exact values. The thirteen colours are transcribed from the instance's pack and diffed, never invented; motion is an intent brief inside the Kill Switch; the screens that come back are a reference, not a source of truth. Triggers on "write a design brief for Stitch", "a design contract document", "a prompt-to-screen tool". Rebuilt in-house from the taste-skill rebuild spec (task 1003327, ADR 0198).
3
+ description: >-
4
+ Writes a design contract document for an external screen-generating tool such as Google Stitch, in the
5
+ platform's own frontmatter shape. Four dials with named bands and stated defaults, then eight fixed sections
6
+ pairing descriptive language with exact values. The thirteen colours are transcribed from the instance's
7
+ pack, never invented. Triggers on "a design brief for Stitch".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  name: style
3
- description: Runs a bounded session in which an owner authors their OWN look for the instance, as a branding pack inside the fifteen-token contract, and proves it clean before it lands. Four steps and no more. SEARCH reads the instance's world and the style library, renders the nearest two entries so the owner looks rather than imagines, and asks two or three questions about feel. CREATE drafts modules/ui-design/styles/<name>/ in the library's contract shape (pack.json, DESIGN.md, mock.html), never a fork of a sheet and never a sixteenth token. PROVE retunes the thirteen against contrast.js, the styles suite, check-mock and the kit's renders until every named pair is green, showing the shots each round. ADOPT hands the owner the one-step switch and files the look into the catalogue. Triggers on "/style", "make a look", "author a style", "I want our own theme", "can we have a different look", "add a style to the library".
3
+ description: >-
4
+ Runs a bounded session in which an owner authors their own look for the instance, as a branding pack inside
5
+ the fifteen-token contract, and proves it clean before it lands. Four steps and no more — SEARCH, CREATE,
6
+ PROVE, ADOPT. Triggers on "/style", "make a look", "author a style", "I want our own theme", "add a style to
7
+ the library".
4
8
  ---
5
9
 
6
10
  <!-- BEGIN PLATFORM PREAMBLE · ui-design module · one block, byte-identical in every skill the module ships (vendored or rebuilt in-house) so an upstream refresh is a three-way merge; the source is modules/ui-design/skills/PREAMBLE.md and tests/ui_design_skills.mjs fails on drift -->
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.545",
3
+ "version": "1.19.546",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.545",
9
+ "version": "1.19.546",
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.545",
3
+ "version": "1.19.546",
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",
@@ -1311,7 +1311,11 @@ function checkErrorEnvelope() {
1311
1311
  // truncated EVERY field, the skill matched on its body text, and nothing in CI noticed. A parse
1312
1312
  // error is a hard fail; description length and the listing budget are advisory (the listing is
1313
1313
  // resident in every session, ~1% of context, and gets truncated past ~8k chars). The check reads
1314
- // the instance's .claude/skills/ and is a quiet pass when a checkout carries none (published core).
1314
+ // every skill Claude Code can load — the instance's .claude/skills/ AND the module-owned
1315
+ // modules/*/skills/ that claude-materialize.js copies into it (task 1003587: scanning only the
1316
+ // tracked root meant 14 ui-design skills went unlinted until the untracked copy existed, so four
1317
+ // unparseable frontmatters shipped and fitness only went red on a checkout that had materialized).
1318
+ // A quiet pass when a checkout carries none (published core).
1315
1319
  function checkSkillFrontmatter() {
1316
1320
  const violations = [];
1317
1321
  const warnings = [];
@@ -1329,7 +1333,10 @@ function checkSkillFrontmatter() {
1329
1333
  }
1330
1334
  if (long.length) {
1331
1335
  long.sort((a, b) => b.n - a.n);
1332
- warnings.push(`${long.length} description(s) over ${lint.DESC_WARN_CHARS} chars (longest: ${long.slice(0, 4).map((l) => `${l.file.split('/')[2]} ${l.n}`).join(', ')}) — every char is resident in every session; run node scripts/gds/skill-lint.js.`);
1336
+ // The skill's own name is its PARENT dir — a fixed path index would read "skills"
1337
+ // for a module-owned path (modules/<key>/skills/<name>/SKILL.md).
1338
+ const skillName = (f) => f.split('/').slice(-2, -1)[0] || f;
1339
+ warnings.push(`${long.length} description(s) over ${lint.DESC_WARN_CHARS} chars (longest: ${long.slice(0, 4).map((l) => `${skillName(l.file)} ${l.n}`).join(', ')}) — every char is resident in every session; run node scripts/gds/skill-lint.js.`);
1333
1340
  }
1334
1341
  if (report.overBudget) warnings.push(`the skill listing is ${report.listingChars} chars (~${estTokens(report.listingChars)} tokens) against a ~${report.budgetChars}-char budget — Claude Code truncates entries past it and routing degrades.`);
1335
1342
  return {
@@ -1338,7 +1345,7 @@ function checkSkillFrontmatter() {
1338
1345
  hardFail: violations.length > 0,
1339
1346
  violations,
1340
1347
  warnings,
1341
- note: `${report.results.length} SKILL.md file(s) under .claude/skills/ parse as YAML (name + description present, no ": " / " #" in a plain scalar); length + listing budget advisory.`,
1348
+ note: `${report.results.length} SKILL.md file(s) under .claude/skills/ + modules/*/skills/ parse as YAML (name + description present, no ": " / " #" in a plain scalar); length + listing budget advisory.`,
1342
1349
  };
1343
1350
  }
1344
1351
 
@@ -23,7 +23,9 @@
23
23
  // verdict as authoritative over this one.
24
24
  //
25
25
  // Usage:
26
- // node scripts/gds/skill-lint.js lint every .claude/skills/*/SKILL.md; exit 1 on any error
26
+ // node scripts/gds/skill-lint.js lint every skill Claude Code can load — .claude/skills/*
27
+ // plus the module-owned modules/*/skills/* that materialize
28
+ // into it; exit 1 on any error
27
29
  // node scripts/gds/skill-lint.js <file...> lint specific files
28
30
  // node scripts/gds/skill-lint.js --hook PostToolUse mode: read the hook JSON on stdin, lint the
29
31
  // edited file if it is a SKILL.md, print findings, exit 0
@@ -108,14 +110,40 @@ function lintSkillFile(text, { file = 'SKILL.md' } = {}) {
108
110
  return res;
109
111
  }
110
112
 
111
- // Every SKILL.md under .claude/skills/ (one level deep).
112
- function listSkillFiles(root = REPO_ROOT) {
113
- const dir = path.join(root, '.claude', 'skills');
113
+ // Every SKILL.md in one skills root (one level deep, plus a `vendor/` tier).
114
+ function skillsIn(dir) {
114
115
  if (!fs.existsSync(dir)) return [];
115
- return fs.readdirSync(dir, { withFileTypes: true })
116
- .filter((d) => d.isDirectory())
117
- .map((d) => path.join(dir, d.name, 'SKILL.md'))
118
- .filter((f) => fs.existsSync(f));
116
+ const out = [];
117
+ for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
118
+ if (!d.isDirectory()) continue;
119
+ const own = path.join(dir, d.name, 'SKILL.md');
120
+ if (fs.existsSync(own)) { out.push(own); continue; }
121
+ if (d.name === 'vendor') out.push(...skillsIn(path.join(dir, d.name)));
122
+ }
123
+ return out;
124
+ }
125
+
126
+ // Every skill Claude Code can end up loading — the tracked `.claude/skills/` PLUS the
127
+ // module-owned `modules/<key>/skills/`, because claude-materialize.js copies those into
128
+ // `.claude/skills/` as untracked copies (task 1003330) and a defect in one only shows up
129
+ // after materialization. Scanning only the tracked root left 14 ui-design skills unlinted
130
+ // until the copy existed, which is exactly how four unparseable frontmatters shipped
131
+ // (task 1003587). A name already present in `.claude/skills/` wins and its module copy is
132
+ // skipped — the same precedence the materializer applies — so nothing is counted twice.
133
+ function listSkillFiles(root = REPO_ROOT) {
134
+ const core = skillsIn(path.join(root, '.claude', 'skills'));
135
+ const seen = new Set(core.map((f) => path.basename(path.dirname(f))));
136
+ const modulesDir = path.join(root, 'modules');
137
+ const owned = [];
138
+ if (fs.existsSync(modulesDir)) {
139
+ for (const d of fs.readdirSync(modulesDir, { withFileTypes: true })) {
140
+ if (!d.isDirectory()) continue;
141
+ for (const f of skillsIn(path.join(modulesDir, d.name, 'skills'))) {
142
+ if (!seen.has(path.basename(path.dirname(f)))) owned.push(f);
143
+ }
144
+ }
145
+ }
146
+ return [...core, ...owned];
119
147
  }
120
148
 
121
149
  // Lint a set of files; returns per-file results plus the listing total vs budget.
@@ -125,7 +153,13 @@ function lintSkills(files) {
125
153
  return { results, listingChars, budgetChars: LISTING_BUDGET_CHARS, overBudget: listingChars > LISTING_BUDGET_CHARS };
126
154
  }
127
155
 
128
- function isSkillMd(p) { return /[\\/]\.claude[\\/]skills[\\/][^\\/]+[\\/]SKILL\.md$/.test(String(p || '')); }
156
+ // The PostToolUse hook's gate. It must match a module-owned SKILL.md too — that is the
157
+ // file a builder actually edits; the `.claude/skills/` copy is generated (task 1003587).
158
+ function isSkillMd(p) {
159
+ const s = String(p || '');
160
+ return /[\\/]\.claude[\\/]skills[\\/](?:vendor[\\/])?[^\\/]+[\\/]SKILL\.md$/.test(s)
161
+ || /[\\/]modules[\\/][^\\/]+[\\/]skills[\\/](?:vendor[\\/])?[^\\/]+[\\/]SKILL\.md$/.test(s);
162
+ }
129
163
 
130
164
  function format(report, { relTo = REPO_ROOT } = {}) {
131
165
  const out = [];
package/src/module-api.js CHANGED
@@ -49,7 +49,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
49
49
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
50
50
  // the entry to that file. Look for a version's history there, not here.
51
51
  // ---------------------------------------------------------------------------
52
- const CORE_VERSION = '1.19.545'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
52
+ const CORE_VERSION = '1.19.546'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
53
53
 
54
54
  // A namespaced logger so a module's log lines are attributable + consistent.
55
55
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -623,3 +623,62 @@ test('CAL: the grid gets its own horizontal strip — it never widens the docume
623
623
  const html = kit.calendarGridHtml([{ local_date: '2026-09-03', count: 1 }], { today: '2026-09-03' });
624
624
  assert.match(html, /kit-cal__scroll/, 'the wide grid scrolls inside its own strip');
625
625
  });
626
+
627
+ // --- task 1003583: the summary and the caption must describe the DRAWN window ------
628
+
629
+ test('CAL: a narrowed window is described as weeks, not as "the last year"', () => {
630
+ // The bug: the default aria summary hardcoded "over the last year" whatever
631
+ // opts.weeks said, so a caller asking for six weeks was handed a label describing
632
+ // fifty-three.
633
+ const html = kit.calendarGridHtml([{ local_date: '2026-09-03', count: 2 }], { today: '2026-09-03', weeks: 6 });
634
+ assert.match(html, /over the last 6 weeks/, 'the aria summary still claims a year');
635
+ assert.doesNotMatch(html, /over the last year/);
636
+ });
637
+
638
+ test('CAL: a one-week window says "week", and the full window still says "the last year"', () => {
639
+ const one = kit.calendarGridHtml([{ local_date: '2026-09-03', count: 1 }], { today: '2026-09-03', weeks: 1 });
640
+ assert.match(one, /over the last 1 week:/, 'singular grammar');
641
+ const full = kit.calendarGridHtml([{ local_date: '2026-09-03', count: 1 }], { today: '2026-09-03', weeks: 53 });
642
+ assert.match(full, /over the last year/, 'the 53-week window is a year and should read like one');
643
+ });
644
+
645
+ test('CAL: the summary counts only the days the grid actually draws', () => {
646
+ // 2026-09-03 is a Thursday, so a 2-week grid starts at Sunday 2026-08-23. A ship on
647
+ // 2026-08-01 is real history the caller supplied, but it is off this picture, and it
648
+ // used to be counted in this picture's summary anyway.
649
+ const days = [
650
+ { local_date: '2026-08-01', count: 4 }, // before the first column
651
+ { local_date: '2026-08-30', count: 2 }, // inside
652
+ { local_date: '2026-09-03', count: 1 }, // inside
653
+ ];
654
+ const html = kit.calendarGridHtml(days, { today: '2026-09-03', weeks: 2 });
655
+ assert.match(html, /3 ships across 2 days/, 'the out-of-window day leaked into the summary');
656
+ });
657
+
658
+ test('CAL: calendarWindow is the one source of truth the caller reads too', () => {
659
+ // profile-charts.js used to recompute "N days shipped" with its own filter over the
660
+ // whole array; it now reads activeDays from here, so the caption and the picture
661
+ // cannot disagree.
662
+ const days = [
663
+ { local_date: '2026-08-01', count: 4 },
664
+ { local_date: '2026-08-30', count: 2 },
665
+ { local_date: '2026-09-03', count: 1 },
666
+ ];
667
+ const opts = { today: '2026-09-03', weeks: 2 };
668
+ const win = kit.calendarWindow(days, opts);
669
+ assert.equal(win.activeDays, 2);
670
+ assert.equal(win.totalShips, 3);
671
+ assert.equal(win.weeks, 2);
672
+ // The window's own bounds are what the grid iterates, so the naive filter the caller
673
+ // used to run disagrees with it — which is exactly the bug.
674
+ assert.notEqual(days.filter((d) => d.count > 0).length, win.activeDays);
675
+ const html = kit.calendarGridHtml(days, opts);
676
+ assert.match(html, new RegExp(`across ${win.activeDays} days`), 'grid and window disagree');
677
+ });
678
+
679
+ test('CAL: calendarWindow returns null on nothing to draw, matching the grid empty state', () => {
680
+ assert.equal(kit.calendarWindow([], {}), null);
681
+ assert.equal(kit.calendarWindow(null, {}), null);
682
+ assert.equal(kit.calendarWindow([{ local_date: '2026-09-03', count: 0 }], {}), null,
683
+ 'a zero-count day is not a ship day');
684
+ });
@@ -3,6 +3,8 @@
3
3
  import { strict as assert } from 'node:assert';
4
4
  import { test } from 'node:test';
5
5
  import { createRequire } from 'node:module';
6
+ import fs from 'node:fs';
7
+ import os from 'node:os';
6
8
  import path from 'node:path';
7
9
  import { fileURLToPath } from 'node:url';
8
10
 
@@ -58,11 +60,58 @@ test('no frontmatter at all is an error', () => {
58
60
  assert.equal(run('# Title\n\nBody.\n').errors.length, 1);
59
61
  });
60
62
 
61
- test('the hook path filter matches only .claude/skills/<name>/SKILL.md', () => {
63
+ test('the hook path filter matches a SKILL.md in either home — .claude/skills/ or a module (task 1003587)', () => {
62
64
  assert.ok(lint.isSkillMd('C:\\repo\\.claude\\skills\\dev\\SKILL.md'));
63
65
  assert.ok(lint.isSkillMd('/repo/.claude/skills/dev/SKILL.md'));
66
+ // The module copy is the file a builder EDITS; .claude/skills/ holds the generated
67
+ // copy, so a hook that only watched the tracked root never saw the real edit.
68
+ assert.ok(lint.isSkillMd('/repo/modules/ui-design/skills/dev/SKILL.md'));
69
+ assert.ok(lint.isSkillMd('C:\\repo\\modules\\ui-design\\skills\\vendor\\dev\\SKILL.md'));
64
70
  assert.ok(!lint.isSkillMd('/repo/.claude/skills/dev/notes.md'));
65
- assert.ok(!lint.isSkillMd('/repo/modules/ui-design/skills/dev/SKILL.md'));
71
+ assert.ok(!lint.isSkillMd('/repo/modules/ui-design/skills/dev/notes.md'));
72
+ assert.ok(!lint.isSkillMd('/repo/docs/skills/dev/SKILL.md'));
73
+ });
74
+
75
+ // A fixture root, because a real dev checkout may or may not have materialized its
76
+ // module skills into .claude/skills/ (claude-materialize.js, task 1003330) and the
77
+ // lister's contract must hold either way.
78
+ function fixtureRoot(files) {
79
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-lint-'));
80
+ for (const [rel, body] of Object.entries(files)) {
81
+ const abs = path.join(root, rel);
82
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
83
+ fs.writeFileSync(abs, body);
84
+ }
85
+ return root;
86
+ }
87
+
88
+ test('the lister reaches module-owned skills, and a core copy of the same name wins (task 1003587)', () => {
89
+ const skill = fm('>-\n A description long enough for the harness to route on, written as a block scalar.');
90
+ const root = fixtureRoot({
91
+ '.claude/skills/only-core/SKILL.md': skill,
92
+ '.claude/skills/both/SKILL.md': skill,
93
+ 'modules/ui-design/skills/both/SKILL.md': skill,
94
+ 'modules/ui-design/skills/only-module/SKILL.md': skill,
95
+ 'modules/ui-design/skills/vendor/vendored/SKILL.md': skill,
96
+ 'modules/ui-design/skills/README.md': '# not a skill\n',
97
+ });
98
+ const files = lint.listSkillFiles(root).map((f) => path.relative(root, f).replace(/\\/g, '/'));
99
+ assert.deepEqual(files.sort(), [
100
+ '.claude/skills/both/SKILL.md',
101
+ '.claude/skills/only-core/SKILL.md',
102
+ 'modules/ui-design/skills/only-module/SKILL.md',
103
+ 'modules/ui-design/skills/vendor/vendored/SKILL.md',
104
+ ], 'a module skill is linted at its source; a name already in .claude/skills/ is not counted twice');
105
+ fs.rmSync(root, { recursive: true, force: true });
106
+ });
107
+
108
+ test('every module-owned skill this repo ships parses too — the 14 that did not on 2026-09-04 (task 1003587)', () => {
109
+ const dir = path.join(ROOT, 'modules', 'ui-design', 'skills');
110
+ const files = lint.listSkillFiles(ROOT).filter((f) => f.startsWith(dir) || fs.existsSync(path.join(dir, path.basename(path.dirname(f)), 'SKILL.md')));
111
+ assert.ok(files.length >= 14, `the ui-design module ships 14+ skills; found ${files.length}`);
112
+ const report = lint.lintSkills(files);
113
+ const broken = report.results.filter((r) => r.errors.length).map((r) => path.relative(ROOT, r.file) + ': ' + r.errors.join('; '));
114
+ assert.deepEqual(broken, [], broken.join('\n'));
66
115
  });
67
116
 
68
117
  test('every committed skill parses (the ten that did not on 2026-09-03 must stay fixed)', () => {
@@ -34,14 +34,24 @@ const policy = readJson(path.join(SKILLS, 'policy.json'));
34
34
  const manifest = readJson(path.join(MODULE, 'module.json'));
35
35
  const materialize = require(path.join(ROOT, 'scripts', 'gds', 'claude-materialize.js'));
36
36
 
37
- // Minimal YAML-frontmatter reader: `key: value` lines between the first two `---` fences.
37
+ // Minimal YAML-frontmatter reader: `key: value` lines between the first two `---`
38
+ // fences, plus folded block scalars (`key: >-` then the text indented), which is the
39
+ // only safe way to write a description carrying a colon-space (task 1003587) — a plain
40
+ // scalar with ": " in it makes Claude Code's YAML reader drop EVERY field silently.
38
41
  function frontmatter(text) {
39
42
  const m = text.match(/^---\n([\s\S]*?)\n---\n/);
40
43
  const out = {};
41
44
  if (!m) return out;
45
+ let block = null; // the key whose folded value the indented lines below belong to
42
46
  for (const line of m[1].split('\n')) {
43
47
  const kv = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
44
- if (kv) out[kv[1]] = kv[2].replace(/^["']|["']$/g, '').trim();
48
+ if (kv) {
49
+ const v = kv[2].trim();
50
+ block = /^[>|][+-]?\d*$/.test(v) ? kv[1] : null;
51
+ out[kv[1]] = block ? '' : v.replace(/^["']|["']$/g, '').trim();
52
+ continue;
53
+ }
54
+ if (block && /^\s+\S/.test(line)) out[block] = (out[block] ? out[block] + ' ' : '') + line.trim();
45
55
  }
46
56
  return out;
47
57
  }