@bongos/core 1.19.478 → 1.19.479

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 (34) hide show
  1. package/.bongos-core.json +69 -34
  2. package/.claude/skills/design/SKILL.md +6 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -0
  5. package/clients/bongos-client/index.cjs +4 -0
  6. package/clients/bongos-client/index.d.ts +4 -0
  7. package/clients/bongos-client/index.mjs +4 -0
  8. package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +36 -0
  9. package/docs/adr/README.md +1 -0
  10. package/docs/api/openapi.json +80 -1
  11. package/docs/api-reference.md +5 -3
  12. package/docs/copy-inventory.md +6 -6
  13. package/docs/copy-registry.json +6 -6
  14. package/docs/file-map.md +1 -1
  15. package/docs/module-api-changelog.md +2 -0
  16. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  17. package/modules/economy/credits.js +78 -0
  18. package/modules/economy/module.json +1 -1
  19. package/modules/economy/reward.js +4 -0
  20. package/modules/economy/routes/credits.js +151 -0
  21. package/modules/hall-ui/public/md-reader.js +24 -1
  22. package/modules/ui-design/module.json +1 -1
  23. package/modules/ui-design/skills/README.md +6 -2
  24. package/modules/ui-design/skills/gpt-taste/SKILL.md +110 -0
  25. package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +110 -0
  26. package/modules/ui-design/skills/minimalist-ui/SKILL.md +103 -0
  27. package/modules/ui-design/skills/stitch-design-taste/SKILL.md +91 -0
  28. package/package-lock.json +2 -2
  29. package/package.json +1 -1
  30. package/src/module-api.js +1 -1
  31. package/tests/credit_ledger_read.mjs +347 -0
  32. package/tests/hall_reading_world.mjs +80 -0
  33. package/tests/own_scope_guard.mjs +24 -2
  34. package/tests/ui_design_skills.mjs +56 -1
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Bongos API reference
4
4
 
5
- > **Generated from the live route files** — the route file is authoritative. 339 endpoints across 54 route files.
5
+ > **Generated from the live route files** — the route file is authoritative. 341 endpoints across 55 route files.
6
6
  > Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
7
7
 
8
8
  Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
@@ -119,10 +119,11 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
119
119
  |---|---|---|---|---|
120
120
  | GET | `/api/bongos/bug-attachments/:name` | `any-builder` | — | GET /api/bongos/bug-attachments/:name — stream one stored image. |
121
121
 
122
- ## `builders` (10)
122
+ ## `builders` (11)
123
123
 
124
124
  | Method | Path | Rank | Body | Description |
125
125
  |---|---|---|---|---|
126
+ | GET | `/api/bongos/builders/:builderId/credits` | `metic+archon` | — | GET /builders/:builderId/credits — another builder's ledger. |
126
127
  | GET | `/api/bongos/builders/:id/active-claims` | `metic+archon` | — | GET /builders/:id/active-claims — V3.R14 (task 238). |
127
128
  | GET | `/api/bongos/builders/:id/activity` | `any-builder` | — | GET /builders/:id/activity — the profile's PER-CRAFT activity log (task 1003281, goal 1000072, criterion legibility-activity-by-role). |
128
129
  | PATCH | `/api/bongos/builders/:id/budget` | `metic+archon` | `monthly_budget_usd` | PATCH /builders/:id/budget — set or clear a builder's monthly spend ceiling (V3.R84 task 302, criterion C3). |
@@ -345,13 +346,14 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
345
346
  | POST | `/api/bongos/llm-cache/lookup` | `metic+archon` | `cache_key`, `tokens_saved_est` | POST /llm-cache/lookup — hit/miss + atomic hit_count bump. |
346
347
  | POST | `/api/bongos/llm-cache/store` | `metic+archon` | `spec`, `result_stdout`, `result_cost_usd` | POST /llm-cache/store — upsert a freshly-produced result. |
347
348
 
348
- ## `me` (30)
349
+ ## `me` (31)
349
350
 
350
351
  | Method | Path | Rank | Body | Description |
351
352
  |---|---|---|---|---|
352
353
  | GET | `/api/bongos/me` | `any-builder` | — | PMS-V2 task 188: response now also carries `unlocked` (server-joined achievement metadata) + `locked_with_progress` (server-computed huma… |
353
354
  | GET | `/api/bongos/me/achievements` | `any-builder` | — | The builder's unlocked achievements (joined to achievement metadata). |
354
355
  | POST | `/api/bongos/me/anthropic-link` | `any-builder` | `anthropic_email`, `anthropic_user_id` | V3.R10 (task 235), criterion C3 — link the authed builder's Anthropic account. |
356
+ | GET | `/api/bongos/me/credits` | `any-builder` | — | GET /me/credits — the caller's own ledger. |
355
357
  | GET | `/api/bongos/me/cross-project` | `any-builder` | — | the caller's OWN cross-project record + privacy flags (own data). |
356
358
  | PATCH | `/api/bongos/me/cross-project` | `any-builder` | `hide_stats`, `account_visibility`, `recruiter_discoverable` | flip the caller's OWN privacy flags (C2's self-serve opt-out plus the task-1002966 pair; own data, keyed on the authenticated github_id —… |
357
359
  | PATCH | `/api/bongos/me/disciplines` | `any-builder` | `preferred_disciplines` | Self-service edit of the authenticated builder's preferred disciplines (migrations 027 + 086 / V3.R80). |
@@ -84,11 +84,11 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
84
84
  |---|---|---|---|
85
85
  | `a8813c34d20d` | certain | &ldquo;Host denied&rdquo; | `modules/hall-ui/public/box-panel.js:160` |
86
86
  | `a8813c34d20d` | certain | &ldquo;Host denied&rdquo; | `modules/hall-ui/public/box-panel.js:340` |
87
- | `b585019e3e1d` | certain | ' lines collapse into one | `modules/hall-ui/public/md-reader.js:163` |
88
- | `44e29d37ddf6` | certain | '); out = out.replace(/(^\|[^*])\*([^*]+)\*(?!\*)/g, '$1 | `modules/hall-ui/public/md-reader.js:117` |
89
- | `66cea7cefa4e` | certain | '; for (const row of bodyRows) { html += ' | `modules/hall-ui/public/md-reader.js:194` |
90
- | `53e4ac514458` | certain | '; out.push(html); continue; } // Lists (unordered '- ' or ordered '1. '); consecutive items collapse. if (/^( | `modules/hall-ui/public/md-reader.js:198` |
91
- | `87fa099f5068` | certain | '; } html += ' | `modules/hall-ui/public/md-reader.js:198` |
87
+ | `b585019e3e1d` | certain | ' lines collapse into one | `modules/hall-ui/public/md-reader.js:186` |
88
+ | `44e29d37ddf6` | certain | '); out = out.replace(/(^\|[^*])\*([^*]+)\*(?!\*)/g, '$1 | `modules/hall-ui/public/md-reader.js:140` |
89
+ | `66cea7cefa4e` | certain | '; for (const row of bodyRows) { html += ' | `modules/hall-ui/public/md-reader.js:217` |
90
+ | `53e4ac514458` | certain | '; out.push(html); continue; } // Lists (unordered '- ' or ordered '1. '); consecutive items collapse. if (/^( | `modules/hall-ui/public/md-reader.js:221` |
91
+ | `87fa099f5068` | certain | '; } html += ' | `modules/hall-ui/public/md-reader.js:221` |
92
92
  | `cb327c50b079` | certain | 's dev box (state: {…}). Any uncommitted work on that box is lost. The box is destroyed; the builder can open | `modules/hall-ui/public/harbor.js:125` |
93
93
  | `544794f1ec8f` | certain | (later in | `modules/hall-ui/public/index.html:9` |
94
94
  | `3ac2eb36ba97` | certain | (no sections) | `modules/hall-ui/public/drachmae.js:128` |
@@ -1001,7 +1001,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1001
1001
  | `3c148c7b9539` | certain | Your work runs on a dev box we host for you — there's almost nothing to install. Choose how to connect: | `modules/hall-ui/public/hall-render.js:653` |
1002
1002
  | `137b07b42203` | certain | Zoom in | `modules/hall-ui/public/diagram-viewer.js:98` |
1003
1003
  | `0d0bfa651b36` | certain | Zoom out | `modules/hall-ui/public/diagram-viewer.js:96` |
1004
- | `cc361cc7333a` | certain | \s?/.test(raw)) { const quoteLines = []; while (i | `modules/hall-ui/public/md-reader.js:163` |
1004
+ | `cc361cc7333a` | certain | \s?/.test(raw)) { const quoteLines = []; while (i | `modules/hall-ui/public/md-reader.js:186` |
1005
1005
  | `44e6e402db94` | certain | account, then open | `modules/hall-ui/public/hall-render.js:653` |
1006
1006
  | `ae43396446ed` | certain | account. | `modules/hall-ui/public/hall-render.js:653` |
1007
1007
  | `9612c983451f` | certain | achieved · | `modules/hall-ui/public/roadmap.js:263` |
@@ -1631,7 +1631,7 @@
1631
1631
  "surface": "builders-hall",
1632
1632
  "text": "' lines collapse into one",
1633
1633
  "file": "modules/hall-ui/public/md-reader.js",
1634
- "line": 163,
1634
+ "line": 186,
1635
1635
  "origin": "js-markup",
1636
1636
  "confidence": "certain"
1637
1637
  },
@@ -1640,7 +1640,7 @@
1640
1640
  "surface": "builders-hall",
1641
1641
  "text": "'); out = out.replace(/(^|[^*])\\*([^*]+)\\*(?!\\*)/g, '$1",
1642
1642
  "file": "modules/hall-ui/public/md-reader.js",
1643
- "line": 117,
1643
+ "line": 140,
1644
1644
  "origin": "js-markup",
1645
1645
  "confidence": "certain"
1646
1646
  },
@@ -1649,7 +1649,7 @@
1649
1649
  "surface": "builders-hall",
1650
1650
  "text": "'; for (const row of bodyRows) { html += '",
1651
1651
  "file": "modules/hall-ui/public/md-reader.js",
1652
- "line": 194,
1652
+ "line": 217,
1653
1653
  "origin": "js-markup",
1654
1654
  "confidence": "certain"
1655
1655
  },
@@ -1658,7 +1658,7 @@
1658
1658
  "surface": "builders-hall",
1659
1659
  "text": "'; out.push(html); continue; } // Lists (unordered '- ' or ordered '1. '); consecutive items collapse. if (/^(-|\\d+\\.)\\s+/.test(raw)) { const ordered = /^\\d+\\.\\s+/.test(raw); const items = []; while (i",
1660
1660
  "file": "modules/hall-ui/public/md-reader.js",
1661
- "line": 198,
1661
+ "line": 221,
1662
1662
  "origin": "js-markup",
1663
1663
  "confidence": "certain"
1664
1664
  },
@@ -1667,7 +1667,7 @@
1667
1667
  "surface": "builders-hall",
1668
1668
  "text": "'; } html += '",
1669
1669
  "file": "modules/hall-ui/public/md-reader.js",
1670
- "line": 198,
1670
+ "line": 221,
1671
1671
  "origin": "js-markup",
1672
1672
  "confidence": "certain"
1673
1673
  },
@@ -9884,7 +9884,7 @@
9884
9884
  "surface": "builders-hall",
9885
9885
  "text": "\\s?/.test(raw)) { const quoteLines = []; while (i",
9886
9886
  "file": "modules/hall-ui/public/md-reader.js",
9887
- "line": 163,
9887
+ "line": 186,
9888
9888
  "origin": "js-markup",
9889
9889
  "confidence": "certain"
9890
9890
  },
package/docs/file-map.md CHANGED
@@ -133,7 +133,7 @@
133
133
  │ ├── hall-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R85, default-on): the authenticated builders' hall at builders.<apex>, moved from public-builders/ (~13k LOC, the largest). NO routes/db/port — declares contributes.webSurfaces:[{host:"builders.",dir:"public"}] for discovery, but UNLIKE status-ui keeps its DEDICATED serving block in serve-internal (the /builders shim + canonical redirects + citizen/Archon page gates + asset-stamping + hall-widget injection), partitioned out of the generic web-surface loop and served from BUILDERS_DIR. CLAUDE.md, module.json, public/{index.html,builders.js,style.css,work,watch,harbor,gate,pair,settings,ranks,sessions,primer,diagrams,atlas,...}.{html,js},cursors/,chime.wav. Three ARCHETYPE sheets sit beside the per-page ones: `record.css` (the task + idea records, task 1003305), `room.css` (the READING ROOMS — the primer + the diagrams index, task 1003306: the reading measure and rhythm, the table of contents, the progress hairline and the framed figure diagram-viewer.js upgrades on both pages) and `oversight.css` (the OVERSIGHT family — government, watch, harbor, gate, task 1003469: the row, the figure band inside a panel, the group head, the inline form, the grant toggle and the shared confirm modal; named oversight because the vocabulary wall bans "governance" on the government surface). A page sheet then holds only what one page has and the others do not — `primer.css` the acknowledge gate, `diagrams.css` the legend strip, `gate.css` the hold reasons and file chips, `watch.css` the severities, the report reader and the grade trend, `people.css` the directory's identity cell (task 1003470 made the legacy door `/scouting` ship the same body and script as `/people`; task 1003440 then deleted the door — `scouting.html` and `scouting.states.json` are gone and the URL 301s to `/people`).
134
134
  │ ├── lifecycle/ ← CORE-DOMAIN carve (BV1.R86, default-on): the build state machine — tasks · claims · versions · done-when · goals · dependencies · claim→ship→grade→credit · github-push + merge-lock + conflict-resolve + publish-reconciler. The LAST, most-coupled carve; registers the `lifecycle` kernel port (createTask, classifyTaskKind, tallyPeerVotes, work-tracking reads). CLAUDE.md, module.json, routes/{tasks,claims,versions,done-when,goals,dependencies,gate-approvals,analytics}.js, lifecycle.js, github-push.js, merge-lock.js, conflict-resolve.js, publish-reconciler.js, task-visuals.js + task-visual-db.js + routes/visuals.js (the optional ship-time visual, task 1003109 — the route file is NOT named task-*.js on purpose; see the module CLAUDE.md).
135
135
  │ ├── copy-desk/ ← CORE-DOMAIN module (task 1003113 / R02 of goal 1000074, default-on): the COPY half of the design contract — ADR 0081 gave design TOKENS a repo-owned source of truth, text had none. Owns the FLAG (anyone in any role marks a string or a whole surface as needing an artist's attention, with a reason required in BOTH the route and a CHECK constraint) and the QUEUE an artist reads it from. READS R01's committed registry (docs/copy-registry.json, generated by scripts/gds/copy-inventory.js) and never writes it. NO LIVE CMS is the hard non-goal: no column here could hold replacement text, and copy_no_cms.mjs pins that. Key is `copy-desk`, not `copy` — the branding contract already owns a `copy` block, so the bare word reds the "core carries no module code" fitness check. CLAUDE.md, module.json, registry.js, flags.js, routes/copy-desk.js, migrations/copy_desk_001_flags.sql, tests/{copy_flags,copy_queue,copy_no_cms}.mjs. Its hall surface lives with hall-ui (public/copy-desk.{html,js,css}), per the one-key-per-web-surface precedent.
136
- │ ├── ui-design/ ← CORE-DOMAIN module (task 1003324 / ADR 0197, default-on): the design capability every builder on every instance gets — ships METHOD, never a world. Absorbs ADR 0081's layer: adapters/{claude-design,figma}/index.js (moved from src/ui/adapters/), scripts/{design-sync,figma-design-sync,validate-design}.js (moved from scripts/gds/, which keeps thin shims), docs/design-contract.md (moved from docs/). Declaration-only: contributes.skills design (the /design playbook, new) + design-sync + figma-design-sync; no routes/port/migrations, so disabling it touches no served surface. CLAUDE.md separates the platform FLOORS (the Fifteen Rule, the dark twins, no page :root, the 24px floor, AA in both modes, the kill switch) from the instance's WORLD (its packs + DESIGN.md). kit/ (task 1003320) is the look-before-you-ship kit: lib.js (the floors, the states/probes contracts, the action + expectation vocabularies, the WCAG maths, the stub spawner), serve.js (any surface through the doorway's real transforms + fixtures/), render.js (states × 1440/390/320 × dark/light, audited), probe.js (PASS/FAIL contracts), check-mock.js (platform rules + the instance's DESIGN.md taste bans, then the detector), tells.js (task 1003329 / ADR 0223: the anti-pattern detector as two tiers — floors FAIL, craft tells WARN, kit-ignore waivers per file; the rendered twins live in lib.js factsInPage), contrast.js + png.js; a page's <page>.states.json / <page>.probes.json sit BESIDE the surface (modules/public-landing/public/, modules/hall-ui/public/). Recipe: docs/recipes/ui-look-before-you-ship.md. skills/ (task 1003321 / ADR 0198) is the vendoring policy: policy.json (licence allowlist, verdicts that may land, sidecar fields, preamble markers), PREAMBLE.md (the platform preamble every vendored SKILL.md opens with), README.md (the roster — both origins scanned dangerous on 2026-08-28, landed as rebuild specs; nothing vendored — and the landing recipe); a landable copy goes under skills/vendor/<name>/ with PROVENANCE.md + LICENSE and is materialised into an instance's .claude/skills/ by claude-materialize.js step 1b. skills/<name>/ (task 1003325 / ADR 0220) are the in-house rebuilds from the specs, first-party and counted: design-taste-frontend (the default taste skill), design-taste-frontend-v1 (the earlier generation, kept distinct), high-end-visual-design (the polish pass), redesign-existing-projects (the in-place upgrade); each opens with the same preamble and carries a "Rebuilt from" section instead of a sidecar. impeccable/ (task 1003326 / ADR 0221) is the design-craft method as ONE skill with sub-commands: SKILL.md (the router, the menu, the craft floor, the bounded finish) + reference/<sub-command>.md (eighteen playbooks, one loaded per request); no hooks, no bypass agent, no telemetry, no live server. image-to-code/, imagegen-frontend-web/, imagegen-frontend-mobile/, brandkit/ (task 1003328 / ADR 0222) are the image family under the hero-only rule: plates and hero objects only, the pair (JPEG dark plate + WebP alpha cutout) with a <file>.json sidecar beside each, through the instance's art pipeline key; the grove specimen's styles/grove/assets/ is the worked example. styles/ (task 1003323 / ADR 0219) is the style library: <name>/pack.json (the theme.ui overlay, exactly the fifteen) + DESIGN.md (the look's derived recipes, thirteen in lockstep with the pack) + mock.html (the specimen); chrome-world (pinned to the neutral pack), expedition, grove; STYLE=<name> on the kit lays a look over the instance's pack through GDS_BRANDING_FILE; tests/ui_design_styles.mjs runs the world/cosmos/hall token suites once per alternate (UI_DESIGN_PACK). config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
136
+ │ ├── ui-design/ ← CORE-DOMAIN module (task 1003324 / ADR 0197, default-on): the design capability every builder on every instance gets — ships METHOD, never a world. Absorbs ADR 0081's layer: adapters/{claude-design,figma}/index.js (moved from src/ui/adapters/), scripts/{design-sync,figma-design-sync,validate-design}.js (moved from scripts/gds/, which keeps thin shims), docs/design-contract.md (moved from docs/). Declaration-only: contributes.skills design (the /design playbook, new) + design-sync + figma-design-sync; no routes/port/migrations, so disabling it touches no served surface. CLAUDE.md separates the platform FLOORS (the Fifteen Rule, the dark twins, no page :root, the 24px floor, AA in both modes, the kill switch) from the instance's WORLD (its packs + DESIGN.md). kit/ (task 1003320) is the look-before-you-ship kit: lib.js (the floors, the states/probes contracts, the action + expectation vocabularies, the WCAG maths, the stub spawner), serve.js (any surface through the doorway's real transforms + fixtures/), render.js (states × 1440/390/320 × dark/light, audited), probe.js (PASS/FAIL contracts), check-mock.js (platform rules + the instance's DESIGN.md taste bans, then the detector), tells.js (task 1003329 / ADR 0223: the anti-pattern detector as two tiers — floors FAIL, craft tells WARN, kit-ignore waivers per file; the rendered twins live in lib.js factsInPage), contrast.js + png.js; a page's <page>.states.json / <page>.probes.json sit BESIDE the surface (modules/public-landing/public/, modules/hall-ui/public/). Recipe: docs/recipes/ui-look-before-you-ship.md. skills/ (task 1003321 / ADR 0198) is the vendoring policy: policy.json (licence allowlist, verdicts that may land, sidecar fields, preamble markers), PREAMBLE.md (the platform preamble every vendored SKILL.md opens with), README.md (the roster — both origins scanned dangerous on 2026-08-28, landed as rebuild specs; nothing vendored — and the landing recipe); a landable copy goes under skills/vendor/<name>/ with PROVENANCE.md + LICENSE and is materialised into an instance's .claude/skills/ by claude-materialize.js step 1b. skills/<name>/ (task 1003325 / ADR 0220) are the in-house rebuilds from the specs, first-party and counted: design-taste-frontend (the default taste skill), design-taste-frontend-v1 (the earlier generation, kept distinct), high-end-visual-design (the polish pass), redesign-existing-projects (the in-place upgrade); each opens with the same preamble and carries a "Rebuilt from" section instead of a sidecar. impeccable/ (task 1003326 / ADR 0221) is the design-craft method as ONE skill with sub-commands: SKILL.md (the router, the menu, the craft floor, the bounded finish) + reference/<sub-command>.md (eighteen playbooks, one loaded per request); no hooks, no bypass agent, no telemetry, no live server. minimalist-ui/, industrial-brutalist-ui/, gpt-taste/, stitch-design-taste/ (task 1003327 / ADR 0230) are the four looks, composition grammars with no colours of their own: the palette is the instance's pack and the texture is a named entry in styles/; a substrate is an archetype that still ships both modes, gpt-taste's selection is derived from the route rather than rolled, and stitch-design-taste writes a DESIGN.md-shaped contract document whose thirteen are transcribed from the pack. image-to-code/, imagegen-frontend-web/, imagegen-frontend-mobile/, brandkit/ (task 1003328 / ADR 0222) are the image family under the hero-only rule: plates and hero objects only, the pair (JPEG dark plate + WebP alpha cutout) with a <file>.json sidecar beside each, through the instance's art pipeline key; the grove specimen's styles/grove/assets/ is the worked example. styles/ (task 1003323 / ADR 0219) is the style library: <name>/pack.json (the theme.ui overlay, exactly the fifteen) + DESIGN.md (the look's derived recipes, thirteen in lockstep with the pack) + mock.html (the specimen); chrome-world (pinned to the neutral pack), expedition, grove; STYLE=<name> on the kit lays a look over the instance's pack through GDS_BRANDING_FILE; tests/ui_design_styles.mjs runs the world/cosmos/hall token suites once per alternate (UI_DESIGN_PACK). config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
137
137
  │ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
138
138
  │ ├── provisioning/ ← stands up + operates a Cloud Bongos instance's own infra (server/DB/domain/DNS/TLS) via the API (ADR 0111); web-tier request+state only, no cloud tokens — a separate control-plane runner drains the intents. Distinct from the `hosting` module (ADR 0092), which brokers compute workloads. CLAUDE.md, module.json.
139
139
  │ └── demo/ ← BV1.R52 acceptance-proof module: one rank-gated write route + one namespaced migration proving the loader mounts a scaffolded module with zero core edits. CLAUDE.md, module.json.
@@ -1393,5 +1393,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
1393
1393
  landed since 1.19.476 with no explicit bump. run 33662059275. (task 1002620)
1394
1394
  1.19.478 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1395
1395
  landed since 1.19.477 with no explicit bump. run 33664027105. (task 1002620)
1396
+ 1.19.479 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1397
+ landed since 1.19.478 with no explicit bump. run 33665076054. (task 1002620)
1396
1398
  ---------------------------------------------------------------------------
1397
1399
  ```
@@ -220,6 +220,8 @@ function createClient(opts = {}) {
220
220
  getBugAttachmentsName: (args) => request("GET", "/bug-attachments/{name}", { hasBody: false }, args),
221
221
  },
222
222
  "builders": {
223
+ // GET /builders/{builderId}/credits — rank: metic+archon — GET /builders/:builderId/credits
224
+ getBuildersBuilderIdCredits: (args) => request("GET", "/builders/{builderId}/credits", { hasBody: false }, args),
223
225
  // GET /builders/{id}/active-claims — rank: metic+archon — GET /builders/:id/active-claims
224
226
  getBuildersIdActiveClaims: (args) => request("GET", "/builders/{id}/active-claims", { hasBody: false }, args),
225
227
  // GET /builders/{id}/activity — rank: any-builder — GET /builders/:id/activity
@@ -502,6 +504,8 @@ function createClient(opts = {}) {
502
504
  getMeAchievements: (args) => request("GET", "/me/achievements", { hasBody: false }, args),
503
505
  // POST /me/anthropic-link — rank: any-builder — POST /me/anthropic-link
504
506
  postMeAnthropicLink: (args) => request("POST", "/me/anthropic-link", { hasBody: true }, args),
507
+ // GET /me/credits — rank: any-builder — GET /me/credits
508
+ getMeCredits: (args) => request("GET", "/me/credits", { hasBody: false }, args),
505
509
  // GET /me/cross-project — rank: any-builder — GET /me/cross-project
506
510
  getMeCrossProject: (args) => request("GET", "/me/cross-project", { hasBody: false }, args),
507
511
  // PATCH /me/cross-project — rank: any-builder — PATCH /me/cross-project
@@ -921,6 +921,81 @@ async function sumCreditsForTasks(builderId, taskIds) {
921
921
  return rows[0] ? rows[0].total : 0;
922
922
  }
923
923
 
924
+ // The three ideator streams (ADR 0172) each book under a reason key that EMBEDS
925
+ // the idea (and, for stream 1, the task) — that string IS the idempotency
926
+ // predicate, which is why it can't be a bare constant:
927
+ //
928
+ // stream 1 (trailing) idea.credit.task_ship:<ideaId>:<taskId>
929
+ // stream 2 (grade) idea.credit.full_pass:<ideaId>
930
+ // stream 3 (board room) idea.credit.governance_pass:<ideaId>
931
+ //
932
+ // So "show me everything stream 2 ever paid me" is a PREFIX question, never an
933
+ // equality one — an exact-match filter over `reason` can only ever return the
934
+ // single row for one idea. These are the prefixes `reasonPrefix` is meant to be
935
+ // given; exported so a reader never hand-builds the string and drifts from the
936
+ // writers above.
937
+ const IDEA_STREAM_REASON_PREFIXES = {
938
+ task_ship: 'idea.credit.task_ship:',
939
+ full_pass: 'idea.credit.full_pass:',
940
+ governance_pass: 'idea.credit.governance_pass:',
941
+ };
942
+
943
+ // creditLedger — the per-row read over credit_log for ONE builder. The ledger is
944
+ // append-only and builders.total_credits is only its cache, so this is the
945
+ // authoritative answer to "what was I actually paid, and for what".
946
+ //
947
+ // It exists because until now credit_log had NO read surface anywhere in the API
948
+ // (blocker 1000127): every other reader in the tree is an internal aggregate or
949
+ // an existence probe, so the ideator lane could not be audited without direct
950
+ // database access, and the hall had no surface that could show an author what an
951
+ // idea earned them.
952
+ //
953
+ // Filters are AND-ed. `reason` is exact — right for the unparameterized keys
954
+ // (task.shipped, session.token_reward, idea_promotion_bonus); `reasonPrefix` is a
955
+ // literal prefix match for the parameterized ones above. LIKE wildcards in the
956
+ // caller's input are escaped, so a `%` in a query string matches a literal `%`
957
+ // and cannot widen the scan. Newest-first; `total` is the UNPAGINATED count, so a
958
+ // caller can tell whether more rows exist.
959
+ async function creditLedger(builderId, { reason = null, reasonPrefix = null, taskId = null, since = null, limit = 50, offset = 0 } = {}) {
960
+ const id = Number(builderId);
961
+ if (!Number.isInteger(id) || id <= 0) return { rows: [], total: 0 };
962
+
963
+ const where = ['builder_id = $1'];
964
+ const params = [id];
965
+ if (reason) {
966
+ params.push(reason);
967
+ where.push(`reason = $${params.length}`);
968
+ }
969
+ if (reasonPrefix) {
970
+ params.push(reasonPrefix.replace(/([\\%_])/g, '\\$1') + '%');
971
+ where.push(`reason LIKE $${params.length} ESCAPE '\\'`);
972
+ }
973
+ if (taskId != null) {
974
+ params.push(Number(taskId));
975
+ where.push(`task_id = $${params.length}`);
976
+ }
977
+ if (since) {
978
+ params.push(since);
979
+ where.push(`recorded_at >= $${params.length}::timestamptz`);
980
+ }
981
+ const whereSql = where.join(' AND ');
982
+
983
+ // Count and page in one round trip. reward_basis is deliberately NOT selected:
984
+ // it is session top-up bookkeeping (core_196), not a ledger fact a reader needs,
985
+ // and exposing it would invite its being read as money.
986
+ const { rows } = await pool.query(
987
+ `SELECT id, delta, reason, description, task_id, session_id, recorded_at,
988
+ COUNT(*) OVER ()::int AS total_count
989
+ FROM credit_log
990
+ WHERE ${whereSql}
991
+ ORDER BY recorded_at DESC, id DESC
992
+ LIMIT $${params.length + 1} OFFSET $${params.length + 2}`,
993
+ [...params, limit, offset]
994
+ );
995
+ const total = rows.length > 0 ? Number(rows[0].total_count) : 0;
996
+ return { rows: rows.map(({ total_count, ...r }) => r), total };
997
+ }
998
+
924
999
  module.exports = {
925
1000
  leaderboard,
926
1001
  KIND_MULTIPLIERS,
@@ -953,4 +1028,7 @@ module.exports = {
953
1028
  netNegativeBonusAmount,
954
1029
  awardNetNegativeBonus,
955
1030
  sumCreditsForTasks,
1031
+ // the ledger read surface (task 1003484, blocker 1000127)
1032
+ creditLedger,
1033
+ IDEA_STREAM_REASON_PREFIXES,
956
1034
  };
@@ -5,7 +5,7 @@
5
5
  "coreVersion": "^1.7.0",
6
6
  "default": true,
7
7
  "maintenance": { "status": "core-maintained" },
8
- "contributes": { "routes": ["cost", "leaderboard", "reward"] },
8
+ "contributes": { "routes": ["cost", "credits", "leaderboard", "reward"] },
9
9
  "provides": ["reward"],
10
10
  "consumes": []
11
11
  }
@@ -73,6 +73,10 @@ module.exports = {
73
73
  getBuilderAchievements: (...a) => achievements.getBuilderAchievements(...a),
74
74
  getLockedAchievementsProgress: (...a) => achievements.getLockedAchievementsProgress(...a),
75
75
  sumCreditsForTasks: (...a) => credits.sumCreditsForTasks(...a),
76
+ // The per-row ledger read (task 1003484). On the port because the surface core
77
+ // most wants it for is a HALL view — "what did this idea earn me" — and a core
78
+ // route must reach economy through here, never by importing the module.
79
+ creditLedger: (...a) => credits.creditLedger(...a),
76
80
  computeStreak: (...a) => streak.computeStreak(...a),
77
81
  wouldExtendStreak: (...a) => streak.wouldExtendStreak(...a),
78
82
  };
@@ -0,0 +1,151 @@
1
+ // modules/economy/routes/credits.js — GET /me/credits + GET /builders/:builderId/credits:
2
+ // the credit ledger, readable at last (task 1003484, blocker 1000127).
3
+ //
4
+ // WHY. `credit_log` is the append-only record of every drachma this instance has
5
+ // ever paid, and `builders.total_credits` is only its cache — yet until this file
6
+ // there was NO read surface for it anywhere in the API. Every other reader in the
7
+ // tree is an internal aggregate or an existence probe. The consequences were not
8
+ // abstract: nobody could answer "was this ideator actually paid, and under which
9
+ // stream" without direct database access, the three ideator streams (ADR 0172)
10
+ // could not be audited from outside at all, and the builders' hall had no surface
11
+ // that could ever show an author what one of their ideas earned them.
12
+ //
13
+ // A balance you cannot itemise is a number you have to take on faith. This route
14
+ // is what turns it back into a ledger.
15
+ //
16
+ // The route is only the doorway; `../credits.js` owns `creditLedger` and every
17
+ // other credit_log query. Keep it that way — this file must never grow SQL.
18
+ //
19
+ // MODULE BOUNDARY: reaches core only through the published doorway
20
+ // `../../../src/module-api`; `../credits` is an intra-module sibling.
21
+
22
+ const express = require('express');
23
+ const api = require('../../../src/module-api');
24
+ const credits = require('../credits');
25
+
26
+ // Cap the page. The ledger is unbounded and grows forever, so an uncapped read is
27
+ // a slow query waiting to happen on the oldest, busiest builder.
28
+ const DEFAULT_LIMIT = 50;
29
+ const MAX_LIMIT = 200;
30
+ const MAX_REASON_LEN = 200;
31
+
32
+ // Pull the shared filter/paging shape out of the query string. Returns
33
+ // { opts } on success or { error } with a res.fail code — both routes read the
34
+ // SAME filters, and a filter that behaves differently depending on whose ledger
35
+ // you are reading is exactly the drift this shared parser exists to prevent.
36
+ function parseLedgerQuery(req) {
37
+ const q = req.query || {};
38
+ const { limit, offset } = api.parsePagination(req, { defaultLimit: DEFAULT_LIMIT, maxLimit: MAX_LIMIT });
39
+
40
+ for (const key of ['reason', 'reason_prefix']) {
41
+ const v = q[key];
42
+ if (v !== undefined && (typeof v !== 'string' || v.length > MAX_REASON_LEN)) {
43
+ return { error: 'bad_reason_filter' };
44
+ }
45
+ }
46
+
47
+ let taskId = null;
48
+ if (q.task_id !== undefined) {
49
+ const n = Number(q.task_id);
50
+ if (!Number.isInteger(n) || n <= 0) return { error: 'bad_task_id' };
51
+ taskId = n;
52
+ }
53
+
54
+ let since = null;
55
+ if (q.since !== undefined) {
56
+ // Validate here rather than letting Postgres raise: an unparseable timestamp
57
+ // would otherwise surface as a 500 on what is a plain bad request.
58
+ if (typeof q.since !== 'string' || Number.isNaN(Date.parse(q.since))) {
59
+ return { error: 'bad_since' };
60
+ }
61
+ since = q.since;
62
+ }
63
+
64
+ return {
65
+ opts: {
66
+ reason: q.reason || null,
67
+ reasonPrefix: q.reason_prefix || null,
68
+ taskId,
69
+ since,
70
+ limit,
71
+ offset,
72
+ },
73
+ };
74
+ }
75
+
76
+ // Shared responder — one shape for both routes, so a client (or the hall) can
77
+ // read either without branching. `streams` echoes the ideator-stream reason
78
+ // PREFIXES: the stream keys embed the idea id, so a caller that hand-built
79
+ // 'idea.credit.full_pass:' would silently drift from the writer. Serving them
80
+ // beside the rows means the audit filter is discoverable from the response.
81
+ async function respondWithLedger(res, builderId, opts) {
82
+ const { rows, total } = await credits.creditLedger(builderId, opts);
83
+ // A builder's own balance, and cheap to recompute — never cached.
84
+ res.set('Cache-Control', 'no-store');
85
+ res.json({
86
+ builder_id: builderId,
87
+ rows,
88
+ page: api.pageMeta(opts.limit, opts.offset, total),
89
+ streams: credits.IDEA_STREAM_REASON_PREFIXES,
90
+ });
91
+ }
92
+
93
+ module.exports = function buildCreditsRouter() {
94
+ const router = express.Router();
95
+
96
+ // Route ORDER is deliberate, and the same rule modules/dev-box/routes/box.js
97
+ // documents: the route-rank auditor classifies from a lookahead window that
98
+ // checks requireRank/requirePermission before requireBuilder, so the
99
+ // PRIVILEGED route is declared FIRST. Below the own-scoped one, its gate
100
+ // would bleed upward and mislabel that route as privileged.
101
+
102
+ // GET /builders/:builderId/credits — another builder's ledger.
103
+ //
104
+ // rank: metic — gated on the `builder.roster.read` ATOM, the same gate
105
+ // /builders/:id/active-claims carries, because this is the same act: reading
106
+ // another builder's record off the roster. ADR 0157 delegated that atom
107
+ // archon→metic. Note this exposes NO new secret — the leaderboard already
108
+ // publishes total_credits and /builders/:id/profile already publishes per-task
109
+ // awards, both unauthenticated. What this adds is the itemisation, which is
110
+ // precisely what makes the ledger auditable.
111
+ //
112
+ // The param is `:builderId`, not the `:id` its neighbours in
113
+ // src/bongos/routes/builders.js use, and deliberately: tests/own_scope_guard.mjs
114
+ // classifies a route as cross-owner by whether a path param NAMES a builder, and
115
+ // a bare `:id` reads as own-scoped. Since this file also mounts /me/credits it is
116
+ // held to that guard — so the explicit name is what lets the guard see this route
117
+ // for what it is and check that its gate is really there.
118
+ router.get('/builders/:builderId/credits', api.requireBuilder, api.requirePermission('builder.roster.read'), async (req, res) => {
119
+ const targetId = api.parseId(req, res, { param: 'builderId', code: 'bad_builder_id', positiveInt: true });
120
+ if (targetId === null) return;
121
+ const { opts, error } = parseLedgerQuery(req);
122
+ if (error) return res.fail(error, 400);
123
+ try {
124
+ const b = await api.getBuilderById(targetId);
125
+ if (!b) return res.fail('builder_not_found', 404);
126
+ await respondWithLedger(res, targetId, opts);
127
+ } catch (err) {
128
+ console.error('[gds] GET /builders/:builderId/credits', err);
129
+ res.fail('credit_ledger_read_failed', { status: 500, message: 'internal error' });
130
+ }
131
+ });
132
+
133
+ // GET /me/credits — the caller's own ledger.
134
+ //
135
+ // rank: any-builder — own-scoped by construction: the builder id comes from the
136
+ // authenticated session and is never read off the request, so there is no
137
+ // parameter here to tamper with. A builder may always read their own ledger;
138
+ // gating this any higher would mean a Xenos cannot see what they were paid.
139
+ router.get('/me/credits', api.requireBuilder, async (req, res) => {
140
+ const { opts, error } = parseLedgerQuery(req);
141
+ if (error) return res.fail(error, 400);
142
+ try {
143
+ await respondWithLedger(res, Number(req.builder.id), opts);
144
+ } catch (err) {
145
+ console.error('[gds] GET /me/credits', err);
146
+ res.fail('credit_ledger_read_failed', { status: 500, message: 'internal error' });
147
+ }
148
+ });
149
+
150
+ return router;
151
+ };
@@ -56,9 +56,28 @@
56
56
  return out.join('/') + (p.endsWith('/') ? '/' : '');
57
57
  }
58
58
 
59
+ // Which link targets the reader will write into an href (task 1003482).
60
+ // escapeHtml stops markup, not URI schemes: a `javascript:` target would run
61
+ // on click. Vouched for: http(s), mailto, tel, an in-page #fragment, and a
62
+ // repo-relative path (which the resolver rewrites onto the repo bases).
63
+ // Everything else — javascript:, data:, vbscript:, file:, any unlisted
64
+ // scheme, a protocol-relative //host — renders as text. Tab/newline and
65
+ // leading controls are dropped before the scheme is read, because a
66
+ // browser's URL parser drops them too (`java\tscript:` runs).
67
+ const ALLOWED_SCHEMES = ['http', 'https', 'mailto', 'tel'];
68
+ function isAllowedLinkHref(href) {
69
+ if (href == null) return false;
70
+ const probe = String(href).replace(/[\t\n\r]/g, '').replace(/^[\x00-\x20]+/, '');
71
+ if (probe.startsWith('#')) return true;
72
+ if (/^[\/\\]{2}/.test(probe)) return false;
73
+ const scheme = probe.match(/^([a-z][a-z0-9+.-]*):/i);
74
+ return !scheme || ALLOWED_SCHEMES.includes(scheme[1].toLowerCase());
75
+ }
76
+
59
77
  // Build a link resolver bound to where THIS doc lives in the repo. Relative
60
78
  // repo paths (../adr/…, diagrams/…) are rewritten to something that resolves
61
79
  // in the browser instead of 404-ing under /builders/*:
80
+ // - a target the allowlist refuses → null (renderInline drops the <a>)
62
81
  // - http(s):// or #anchor or mailto:/tel: → unchanged
63
82
  // - the diagrams dir → the in-hall diagrams route
64
83
  // - everything else → GitHub blob/tree (bases fetched
@@ -72,6 +91,7 @@
72
91
  const diagramsHref = (opts && opts.diagramsHref) || '/builders/diagrams';
73
92
  return function resolveLinkHref(href) {
74
93
  if (!href) return href;
94
+ if (!isAllowedLinkHref(href)) return null;
75
95
  if (/^https?:\/\//i.test(href)) return href;
76
96
  if (href.startsWith('#')) return href;
77
97
  if (href.startsWith('mailto:') || href.startsWith('tel:')) return href;
@@ -108,6 +128,9 @@
108
128
  out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => {
109
129
  const safeUrl = url.replace(/&amp;/g, '&').replace(/&#39;/g, "'");
110
130
  const resolved = resolve(safeUrl);
131
+ // The reader never writes an href it did not vouch for — checked on the
132
+ // RESOLVED value, so a page's own resolver cannot reopen the door.
133
+ if (resolved == null || !isAllowedLinkHref(resolved)) return text;
111
134
  let linkHost = '';
112
135
  try { linkHost = new URL(resolved, location.href).hostname; } catch (_) { /* relative */ }
113
136
  const instApex = location.hostname.split('.').slice(-2).join('.');
@@ -236,5 +259,5 @@
236
259
  return { html: out.join('\n'), headings };
237
260
  }
238
261
 
239
- window.OTBMdReader = { escapeHtml, slugify, normalizePath, makeResolveLinkHref, renderInline, splitTableRow, parseMarkdown };
262
+ window.OTBMdReader = { escapeHtml, slugify, normalizePath, isAllowedLinkHref, makeResolveLinkHref, renderInline, splitTableRow, parseMarkdown };
240
263
  })();
@@ -5,7 +5,7 @@
5
5
  "coreVersion": "^1.19.0",
6
6
  "default": true,
7
7
  "maintenance": { "status": "core-maintained" },
8
- "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"] },
8
+ "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable", "minimalist-ui", "industrial-brutalist-ui", "gpt-taste", "stitch-design-taste", "image-to-code", "imagegen-frontend-web", "imagegen-frontend-mobile", "brandkit"] },
9
9
  "provides": [],
10
10
  "consumes": []
11
11
  }
@@ -27,7 +27,7 @@ The eleven taste skills are single-file prose; the floor's one critical is a fal
27
27
 
28
28
  ## The in-house rebuilds (2026-09-01)
29
29
 
30
- A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` segment, so the ratchets count it; no `PROVENANCE.md` or `LICENSE`, because an in-house skill claims no upstream; the same PLATFORM PREAMBLE first after the frontmatter, byte-identical; then a **`## Rebuilt from`** section naming the origin the *spec* was derived from (documentation only), the scan verdict, the spec task, the statement that no upstream bytes were consulted or copied, and what changed in translation ([ADR 0220](../../../docs/adr/<redacted>.md)). Declared in `module.json` like any other; `claude-materialize.js` step 1b lands it through its own-dir branch, which no sidecar gate touches. `tests/ui_design_skills.mjs` pins all of it. A rebuild whose method is a family of sub-commands ships as **one** skill dir with a `reference/<sub-command>.md` playbook beside the `SKILL.md`, exactly one loaded per request; the preamble is pinned once, in the `SKILL.md`, and governs every playbook ([ADR 0221](../../../docs/adr/<redacted>.md)).
30
+ A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` segment, so the ratchets count it; no `PROVENANCE.md` or `LICENSE`, because an in-house skill claims no upstream; the same PLATFORM PREAMBLE first after the frontmatter, byte-identical; then a **`## Rebuilt from`** section naming the origin the *spec* was derived from (documentation only), the scan verdict, the spec task, the statement that no upstream bytes were consulted or copied, and what changed in translation ([ADR 0220](../../../docs/adr/<redacted>.md)). Declared in `module.json` like any other; `claude-materialize.js` step 1b lands it through its own-dir branch, which no sidecar gate touches. `tests/ui_design_skills.mjs` pins all of it. A rebuild whose method is a family of sub-commands ships as **one** skill dir with a `reference/<sub-command>.md` playbook beside the `SKILL.md`, exactly one loaded per request; the preamble is pinned once, in the `SKILL.md`, and governs every playbook ([ADR 0221](../../../docs/adr/<redacted>.md)). A rebuild whose upstream shipped a **look** carries a composition grammar and no colours of its own — the palette is the instance's pack and the texture is a named entry in [`../styles/`](../styles/README.md) ([ADR 0230](../../../docs/adr/<redacted>.md)).
31
31
 
32
32
  | Skill | Rebuilt from (spec) | Task | What it is |
33
33
  |---|---|---|---|
@@ -35,13 +35,17 @@ A rebuild spec becomes a **first-party skill** at `<name>/` — no `vendor/` seg
35
35
  | `design-taste-frontend-v1` | `Leonxlnx/taste-skill` `taste-skill-v1` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the earlier generation, kept distinct: a fixed baseline triple adapted in conversation, stack conventions for a served surface, the layout bans, full interaction cycles, the pattern vocabulary, the tile-grid archetypes |
36
36
  | `high-end-visual-design` | `Leonxlnx/taste-skill` `soft-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the agency-build polish pass: one texture archetype (the style library's looks) and one layout archetype per project, the nested container anatomy where the world allows a container, the pill with its nested arrow disc, rhythm, choreography and scroll entry inside the Kill Switch |
37
37
  | `redesign-existing-projects` | `Leonxlnx/taste-skill` `redesign-skill` | [task 1003325](https://cloudbongos.com/builders#/task/1003325) | the in-place upgrade: scan the stack, diagnose against the seven-group audit with the instance's world as the answer key, fix in impact-per-risk order, one group per commit; keeps the stack, never migrates frameworks |
38
+ | `minimalist-ui` | `Leonxlnx/taste-skill` `minimalist-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the quiet editorial look as a grammar and no colours: macro whitespace first, a capped measure, one display job and one body job, exactly one hairline weight in the sheet, washed accents measured against their own ink, flat tile cards, box-less accordions, keycaps, one depth treatment per band; declines dense surfaces |
39
+ | `industrial-brutalist-ui` | `Leonxlnx/taste-skill` `brutalist-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the technical-document look: one archetype per project (Swiss print or dark terminal, never mixed) with the other mode still complete, clamped uppercase display against fixed mono metadata, terracotta as the one hazard accent and sea as one terminal green, the hairline as a grid gap, zero rounding, static degradation |
40
+ | `gpt-taste` | `Leonxlnx/taste-skill` `gpt-tasteskill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the anti-default pass: a plan block before any markup with a selection derived from the route and bumped against siblings, the four-stage structure, the ultra-wide hero at two or three lines, a dense grid with zero empty cells, scroll-driven motion in CSS; pack-agnostic |
41
+ | `stitch-design-taste` | `Leonxlnx/taste-skill` `stitch-skill` | [task 1003327](https://cloudbongos.com/builders#/task/1003327) | the design contract document for an external screen-generating tool: four dials, eight fixed sections, descriptive language paired with exact values, the thirteen transcribed from the pack and diffed, motion as an intent brief inside the Kill Switch |
38
42
  | `impeccable` | `pbakaus/impeccable` @ `<redacted>` (skill v4.1.x) | [task 1003326](https://cloudbongos.com/builders#/task/1003326) | the design-craft method as one skill with sub-commands, one playbook loaded per request from `impeccable/reference/`: shape, critique, audit, polish, harden, onboard, animate, colorize, typeset, layout, adapt, distill, clarify, optimize, document, extract, bolder, quieter; the craft floor and the bounded finish live in its `SKILL.md`; the three persisted homes are the instance's own files; no hooks, no bypass agent, no telemetry, no live server (the playwright plugin and the kit are the loops) |
39
43
  | `image-to-code` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the analyse-then-implement loop with the owner's references and the world as the source of truth; the only image it may cause is the hero plate; a structured per-image analysis, the extraction spec, a faithful translation, drift named as the failure mode, a clarity checklist the kit measures |
40
44
  | `imagegen-frontend-web` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the plate-making skill: text-free hero plates, hero objects, backdrops and orbs for the slots a composition already has, a composition anchor per plate, one family per surface, the pack as the palette lock, the instance's art pipeline key, the pair-and-sidecar output contract; never a section image, a comp or a screen |
41
45
  | `imagegen-frontend-mobile` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | portrait plates for the 390 and 320 compositions, clear of the regions a phone reserves, the first view held to one focal point, a scrim under text over a plate; a screen is the states file rendered by the kit; no phone mockups, no flows of screens; native declines |
42
46
  | `brandkit` | `Leonxlnx/taste-skill` @ `<redacted>` | [task 1003328](https://cloudbongos.com/builders#/task/1003328) | the identity system with the strategy inferred first and the logo standard kept: the mark drawn as SVG, the colour system as the pack, the type specimen as the faces, the applications as the kit's renders, the image direction as a hero-object family, the board as a contact sheet; never a generated logo, board or mockup |
43
47
 
44
- What every rebuild does with the upstream's opinions, in the same words: the palette is the pack and a variant is a look from [`../styles/`](../styles/README.md); the design system is the instance's `DESIGN.md`; images are hero plates and hero objects by token and never a step; every motion rule lives inside the Kill Switch (springs, magnetic hover, pointer-tracked borders are named *not available*); the upstream's bans are defaults an instance's `DESIGN.md` overrides; the kit's renders are the pre-flight. The image family (task 1003328, [ADR 0222](../../../docs/adr/<redacted>.md)) carries the one rewrite the upstream never had: images are for text-free hero plates and hero objects only, so a screen is the surface itself, the mark is drawn, the board is a contact sheet, and what ships is the pair (a black-ground JPEG for dark, an alpha WebP cutout for light) with a provenance sidecar beside every file, through the instance's art pipeline key. Still specs, not skills: the detector (task 1003329), the four looks (1003327).
48
+ What every rebuild does with the upstream's opinions, in the same words: the palette is the pack and a variant is a look from [`../styles/`](../styles/README.md); the design system is the instance's `DESIGN.md`; images are hero plates and hero objects by token and never a step; every motion rule lives inside the Kill Switch (springs, magnetic hover, pointer-tracked borders are named *not available*); the upstream's bans are defaults an instance's `DESIGN.md` overrides; the kit's renders are the pre-flight. The image family (task 1003328, [ADR 0222](../../../docs/adr/<redacted>.md)) carries the one rewrite the upstream never had: images are for text-free hero plates and hero objects only, so a screen is the surface itself, the mark is drawn, the board is a contact sheet, and what ships is the pair (a black-ground JPEG for dark, an alpha WebP cutout for light) with a provenance sidecar beside every file, through the instance's art pipeline key. The four looks (task 1003327, [ADR 0230](../../../docs/adr/<redacted>.md)) carry the other rewrite: a look skill ships structure, rhythm, type roles, component anatomy, motion and bans and **no colours**, so a substrate becomes an archetype that still ships both modes, a prompt-seeded roll becomes a deterministic selection derived from the route, a design document transcribes the world instead of authoring one, and the upstream's seeded placeholder photography is dropped rather than softened. The whole scoped set of thirteen is now rebuilt in-house; nothing from either origin is still waiting on a spec.
45
49
 
46
50
  **In the core checkout itself**, where these skills are written, Claude Code reads only `.claude/skills/` at the repo root and the materialiser's self-copy guard leaves it empty of module skills. `node scripts/gds/claude-materialize.js --module-skills-only` lands every enabled module's skills there as untracked, self-ignored copies (a generated `.claude/skills/.module-skills.json` manifest and a `.gitignore` beside them; `--clean` removes them; the file map skips them by the manifest) so a builder can invoke them here too ([task 1003330](https://cloudbongos.com/builders#/task/1003330), [ADR 0224](../../../docs/adr/<redacted>.md)). Edit the module's `skills/<name>/`, never the copy, and re-run to refresh.
47
51