@bongos/core 1.19.393 → 1.19.395

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 (48) hide show
  1. package/.bongos-core.json +108 -58
  2. package/.claude/skills/design/SKILL.md +68 -0
  3. package/.claude/skills/design-sync/SKILL.md +2 -2
  4. package/.claude/skills/figma-design-sync/SKILL.md +3 -3
  5. package/config/design-tokens.neutral.json +1 -1
  6. package/config/design-tokens.schema.json +1 -1
  7. package/docs/adr/0197-the-ui-design-module.md +52 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/architecture.md +1 -1
  10. package/docs/copy-inventory.md +249 -236
  11. package/docs/copy-registry.json +543 -324
  12. package/docs/file-map.md +2 -0
  13. package/docs/modules-contract.md +1 -0
  14. package/docs/onboarding/slash-commands.md +1 -1
  15. package/docs/recipes/claude-design-loop.md +3 -3
  16. package/docs/recipes/figma-design-loop.md +4 -4
  17. package/modules/ui-design/CLAUDE.md +15 -0
  18. package/{src/ui → modules/ui-design}/adapters/claude-design/index.js +1 -1
  19. package/{src/ui → modules/ui-design}/adapters/figma/index.js +3 -3
  20. package/{docs → modules/ui-design/docs}/design-contract.md +12 -12
  21. package/modules/ui-design/kit/README.md +3 -0
  22. package/modules/ui-design/module.json +11 -0
  23. package/modules/ui-design/scripts/design-sync.js +65 -0
  24. package/modules/ui-design/scripts/figma-design-sync.js +64 -0
  25. package/modules/ui-design/scripts/validate-design.js +258 -0
  26. package/modules/ui-design/skills/README.md +3 -0
  27. package/modules/ui-design/styles/README.md +3 -0
  28. package/package-lock.json +2 -2
  29. package/package.json +1 -1
  30. package/public-landing/assets/cosmos.css +235 -103
  31. package/public-landing/index.html +228 -75
  32. package/public-landing/projects.html +256 -140
  33. package/scripts/gds/copy-inventory.js +7 -5
  34. package/scripts/gds/design-sync.js +9 -59
  35. package/scripts/gds/doc-cli-guard.js +11 -5
  36. package/scripts/gds/figma-design-sync.js +9 -59
  37. package/scripts/gds/fitness-checks-identity.js +5 -3
  38. package/scripts/gds/publish-manifest.js +1 -1
  39. package/scripts/gds/validate-design.js +7 -249
  40. package/src/bongos/module-scope-map.js +18 -0
  41. package/src/module-api.js +5 -1
  42. package/tests/design_tokens_sync.mjs +1 -1
  43. package/tests/doc_cli_guard.mjs +19 -0
  44. package/tests/figma_adapter.mjs +1 -1
  45. package/tests/landing_page.mjs +72 -2
  46. package/tests/module_loader.mjs +4 -1
  47. package/tests/projects_hub.mjs +52 -73
  48. package/tests/projects_hub_pre_uat.mjs +10 -8
package/docs/file-map.md CHANGED
@@ -129,6 +129,7 @@
129
129
  │ ├── 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.
130
130
  │ ├── 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).
131
131
  │ ├── 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.
132
+ │ ├── 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). Reserved homes kit/ (task 1003320), skills/ (1003321), styles/ (1003323), each with a README. config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
132
133
  │ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
133
134
  │ ├── 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.
134
135
  │ └── 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.
@@ -373,6 +374,7 @@ tests/
373
374
  ├── builder-start/SKILL.md ← /builder-start — list claimable tasks (parallel-safe vs active claims)
374
375
  ├── builder-sync/SKILL.md ← /builder-sync — 'sync now': re-run the box source-fetch on demand so a freshly-claimed task's code lands within seconds, re-scoped to the active claim (BV1.R96, ADR 0148)
375
376
  ├── close-box/SKILL.md ← /close-box — tear down the dev box you're working on, from inside the box terminal (POST /box/close)
377
+ ├── design/SKILL.md ← /design — the UI design session: the operating playbook for ui-discipline work on ANY instance (load the instance's pack + DESIGN.md first, look before you ship, build inside the platform floors, ship the tests with the UI); routed from a `ui` claim via discipline-modes.json; shipped by the ui-design module (task 1003324, ADR 0197)
376
378
  ├── design-sync/SKILL.md ← /design-sync — export repo design tokens + surfaces into Claude Design (token-preview bundle) and land generated UI code back as a GDS task (ADR 0081)
377
379
  ├── dev/SKILL.md ← /dev — engineering-session playbook for engineer-discipline tasks; the default understand→scoped-change→verify→ship loop made explicit ([#1272](https://example.com/builders#/task/1272))
378
380
  ├── feedback/SKILL.md ← /feedback — pull the latest Bongos feedback bundle (prompt.md + screenshot file paths) into a Claude Code session; backed by scripts/gds/feedback-latest.js (BV1.R13, ADR 0087)
@@ -246,6 +246,7 @@ Rules (enforced by the `BV1.R45` fitness check):
246
246
  | **`hall-ui`** *(core-domain web surface, default-on; R85)* | webSurfaces: `{ host: "builders.", dir: "public" }` (the authenticated builders' hall) | — (registers NO port; keeps a DEDICATED serving block in `serve-internal.js`, unlike `status-ui`) | `^1.11.0` |
247
247
  | **`lifecycle`** *(core-domain, default-on; R86)* | routes: `tasks`, `claims`, `versions`, `done-when`, `goals`, `dependencies`, `gate-approvals`, `analytics` | provides `lifecycle` (createTask, classifyTaskKind, tallyPeerVotes, work-tracking reads); resolves `reward`, `onboarding` | `^1.12.0` |
248
248
  | **`provisioning`** *(default-off; [ADR 0111](adr/<redacted>.md))* | routes: `provisioning` (own+Archon rank-gated) | provides `provisioning.instanceState` | `^1.14.0` |
249
+ | **`ui-design`** *(core-domain, default-on; [ADR 0197](adr/<redacted>.md))* | skills: `design`, `design-sync`, `figma-design-sync` (the /design playbook + the two tool round-trips); the ADR 0081 adapters, sync CLIs and validator live in the module dir | — (registers NO port, NO routes; declaration-only — disabling it withdraws the skills and touches no served surface) | `^1.19.0` |
249
250
 
250
251
  Each module's `modules/<key>/CLAUDE.md` is the authoritative per-module reference (files, seams, tests, what is NOT in the module).
251
252
 
@@ -66,7 +66,7 @@ Every one of these talks to the build system. They're grouped by when you'd reac
66
66
 
67
67
  ### Design and art pipeline
68
68
 
69
- `/design-sync` and `/figma-design-sync` round-trip the UI design system. The `/otb-*` family — `otb-tile-generate`, `otb-design-review`, `otb-character-review`, `otb-feedback-capture`, `otb-figma-sync` — is the reference instance's pixel-art pipeline; it's only meaningful on an instance that runs that pipeline.
69
+ `/design` is the ui-discipline playbook (world-first, show-first — shipped by the `ui-design` module, ADR 0197); `/design-sync` and `/figma-design-sync` round-trip the UI design system. The `/otb-*` family — `otb-tile-generate`, `otb-design-review`, `otb-character-review`, `otb-feedback-capture`, `otb-figma-sync` — is the reference instance's pixel-art pipeline; it's only meaningful on an instance that runs that pipeline.
70
70
 
71
71
  ### Session and meta
72
72
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  > End-to-end guide for the repo ↔ Claude Design sync cycle.
4
4
  > ADR: [ADR 0081](../adr/<redacted>.md).
5
- > Adapter: `src/ui/adapters/claude-design/index.js`.
6
- > CLI: `scripts/gds/design-sync.js`.
5
+ > Adapter: `modules/ui-design/adapters/claude-design/index.js` (the `ui-design` module — ADR 0197).
6
+ > CLI: `modules/ui-design/scripts/design-sync.js` (`scripts/gds/design-sync.js` is a thin shim over it).
7
7
  > Skill: `.claude/skills/design-sync/SKILL.md`.
8
8
 
9
9
  ---
@@ -93,7 +93,7 @@ When Claude Design's output is ready:
93
93
  **b. Run the export step** — either via the skill or directly:
94
94
 
95
95
  ```js
96
- const adapter = require('./src/ui/adapters/claude-design/index.js');
96
+ const adapter = require('./modules/ui-design/adapters/claude-design/index.js');
97
97
  const result = await adapter.export({
98
98
  repoRoot: process.cwd(),
99
99
  surfaceId: 'hall-ui',
@@ -2,8 +2,8 @@
2
2
 
3
3
  > End-to-end guide for the repo ↔ Figma sync cycle — the human escape hatch in ADR 0081.
4
4
  > ADR: [ADR 0081](../adr/<redacted>.md).
5
- > Adapter: `src/ui/adapters/figma/index.js`.
6
- > CLI: `scripts/gds/figma-design-sync.js`.
5
+ > Adapter: `modules/ui-design/adapters/figma/index.js` (the `ui-design` module — ADR 0197).
6
+ > CLI: `modules/ui-design/scripts/figma-design-sync.js` (`scripts/gds/figma-design-sync.js` is a thin shim over it).
7
7
  > Skill: `.claude/skills/figma-design-sync/SKILL.md`.
8
8
  >
9
9
  > Not to be confused with `otb-figma-sync` (pixel-art tile review, a separate Figma file, a separate concern).
@@ -72,7 +72,7 @@ The designer opens the pushed frames (or an existing component) and makes a manu
72
72
 
73
73
  **Code Connect output (ready code):**
74
74
  ```js
75
- const adapter = require('./src/ui/adapters/figma/index.js');
75
+ const adapter = require('./modules/ui-design/adapters/figma/index.js');
76
76
  const result = await adapter.export({
77
77
  repoRoot: process.cwd(),
78
78
  surfaceId: 'hall-ui',
@@ -113,7 +113,7 @@ node scripts/gds/ship.js 1234 --notes-file notes.md --summary-file summary.txt
113
113
 
114
114
  ## Code-canonical vs. Figma-canonical
115
115
 
116
- Code-canonical is the default: the repo is authoritative, the Figma file is a synced view. A surface may be flagged `"figmaCanonical": true` in its `config/design-sources.json` entry (docs/design-contract.md §3.4) as an explicit exception for a design-system foundation a dedicated designer actively iterates on in Figma. All other surfaces stay code-canonical.
116
+ Code-canonical is the default: the repo is authoritative, the Figma file is a synced view. A surface may be flagged `"figmaCanonical": true` in its `config/design-sources.json` entry (modules/ui-design/docs/design-contract.md §3.4) as an explicit exception for a design-system foundation a dedicated designer actively iterates on in Figma. All other surfaces stay code-canonical.
117
117
 
118
118
  ---
119
119
 
@@ -0,0 +1,15 @@
1
+ # @bongos/core — installed platform dependency (not your project's CLAUDE.md)
2
+
3
+ This file is a placeholder shipped inside the **@bongos/core** package. The core's own
4
+ internal agent notes are intentionally omitted from the published artifact so they cannot
5
+ load into your instance's context.
6
+
7
+ **It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
8
+ directory, and the live Bongos database are the source of truth for how work happens here
9
+ and who may do what.
10
+
11
+ You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
12
+ Nothing in here needs to be read to use the platform — run `bongos help` for commands.
13
+
14
+ _To work ON the platform core itself, use the standalone @bongos/core source repository,
15
+ not this vendored copy._
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  // Claude Design adapter — wires the repo design system to Claude Design.
3
3
  //
4
- // Conforms to the adapter interface in docs/design-contract.md §4.
4
+ // Conforms to the adapter interface in modules/ui-design/docs/design-contract.md §4.
5
5
  //
6
6
  // import(opts): repo → Claude Design
7
7
  // Resolves design tokens, scans src/ui/ surfaces, and writes a token-preview
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  // Figma adapter — the human escape hatch in ADR 0081's tool-agnostic design layer.
3
3
  //
4
- // Conforms to the adapter interface in docs/design-contract.md §4.
4
+ // Conforms to the adapter interface in modules/ui-design/docs/design-contract.md §4.
5
5
  //
6
6
  // import(opts): repo → Figma
7
7
  // Resolves design tokens + surfaces and builds a "push plan" (frames to
@@ -26,7 +26,7 @@ const os = require('node:os');
26
26
  const ID = 'figma';
27
27
 
28
28
  // Figma node types that carry no code-representable content — the round-trip
29
- // is honestly lossy here (docs/design-contract.md §2.3 / ADR 0081).
29
+ // is honestly lossy here (modules/ui-design/docs/design-contract.md §2.3 / ADR 0081).
30
30
  const ASSET_ONLY_NODE_TYPES = new Set(['VECTOR', 'BOOLEAN_OPERATION', 'STAR', 'ELLIPSE', 'LINE']);
31
31
 
32
32
  // ── token helpers ─────────────────────────────────────────────────────────────
@@ -170,7 +170,7 @@ function componentNameFor(node) {
170
170
  // Walks a Figma node snapshot and produces { files, warnings } — files are
171
171
  // only emitted for nodes whose content is genuinely code-representable
172
172
  // (frames, text, solid-fill rectangles). Image/vector content is skipped with
173
- // a warning, matching the asset-not-code rule (docs/design-contract.md §2.3).
173
+ // a warning, matching the asset-not-code rule (modules/ui-design/docs/design-contract.md §2.3).
174
174
  function snapshotToFiles(surfaceId, snapshot, colorTokenIndex) {
175
175
  const files = [];
176
176
  const warnings = [];
@@ -1,8 +1,8 @@
1
1
  # Design contract — neutral adapter interface
2
2
 
3
- **Status:** Accepted · authored by [task 1377](https://example.com/builders#/task/1377) · governed by [ADR 0081](adr/<redacted>.md)
3
+ **Status:** Accepted · authored by [task 1377](https://example.com/builders#/task/1377) · governed by [ADR 0081](../../../docs/adr/<redacted>.md) · lives in the `ui-design` module since [task 1003324](https://cloudbongos.com/builders#/task/1003324) ([ADR 0197](../../../docs/adr/<redacted>.md)) — the token files it names stay in `config/` as the host boundary
4
4
 
5
- The repo is the single source of truth for design. Every design tool is a swappable adapter that reads from and writes to this contract — it does not own the source. This file is the contract every adapter must conform to. The minimal validator (`scripts/gds/validate-design.js`) enforces the machine-checkable parts.
5
+ The repo is the single source of truth for design. Every design tool is a swappable adapter that reads from and writes to this contract — it does not own the source. This file is the contract every adapter must conform to. The minimal validator (`modules/ui-design/scripts/validate-design.js`; `node scripts/gds/validate-design.js` is a thin shim over it) enforces the machine-checkable parts.
6
6
 
7
7
  ---
8
8
 
@@ -96,7 +96,7 @@ Rules:
96
96
  | Freeform canvas art | — | `assets/ui/<surface>/` | Asset |
97
97
  | Pixel-art game tiles | see `art/` pipeline | — | Art pipeline (not this contract) |
98
98
 
99
- Pixel-art game tiles (world tiles, characters, objects) are out of scope for this contract — they are governed by the `art/` pipeline and [ADR 0088](adr/<redacted>.md).
99
+ Pixel-art game tiles (world tiles, characters, objects) are out of scope for this contract — they are governed by the `art/` pipeline and [ADR 0088](../../../docs/adr/<redacted>.md).
100
100
 
101
101
  ---
102
102
 
@@ -152,7 +152,7 @@ Figma-canonical is appropriate for design-system foundations that a dedicated de
152
152
  Each design tool that integrates with this contract must provide an adapter module. An adapter lives at:
153
153
 
154
154
  ```
155
- src/ui/adapters/<id>/index.js (e.g. claude-design, figma)
155
+ modules/ui-design/adapters/<id>/index.js (e.g. claude-design, figma)
156
156
  ```
157
157
 
158
158
  ### 4.1 Required exports
@@ -235,7 +235,7 @@ module.exports = {
235
235
 
236
236
  ### 4.6 Adapter registration
237
237
 
238
- Adapters are discovered by the design subsystem at runtime. No static registry — the loader walks `src/ui/adapters/*/index.js` and requires each, verifying the `id` field is present. A missing or broken adapter is a non-fatal warning; the unaffected adapters continue loading.
238
+ Adapters are discovered by the design subsystem at runtime. No static registry — the loader walks `modules/ui-design/adapters/*/index.js` and requires each, verifying the `id` field is present. A missing or broken adapter is a non-fatal warning; the unaffected adapters continue loading.
239
239
 
240
240
  ### 4.7 Adapter error contract
241
241
 
@@ -280,7 +280,7 @@ The `--dt-` prefix is reserved for this design system. Adapters must not emit CS
280
280
 
281
281
  ## 6. Validator
282
282
 
283
- `scripts/gds/validate-design.js` is the minimal validator for this contract. It checks:
283
+ `modules/ui-design/scripts/validate-design.js` is the minimal validator for this contract (`node scripts/gds/validate-design.js` is a thin shim over it). It checks:
284
284
 
285
285
  1. `config/design-tokens.neutral.json` exists and validates against the schema.
286
286
  2. If `config/design-tokens.json` exists, it validates against the schema.
@@ -288,19 +288,19 @@ The `--dt-` prefix is reserved for this design system. Adapters must not emit CS
288
288
  4. `config/design-sources.json` is valid JSON and every surface entry has a `tool` field from the allowed enum.
289
289
  5. Every `sourceUrl` in `design-sources.json` is a valid URI string.
290
290
  6. Every `surfaceId` key in `design-sources.json` has a matching directory under `src/ui/` or `assets/ui/` (if any such directory exists — the file may precede the directories for a new instance).
291
- 7. If `src/ui/adapters/` exists, each adapter directory has an `index.js` that exports `id`, `import`, and `export`.
291
+ 7. If `modules/ui-design/adapters/` exists, each adapter directory has an `index.js` that exports `id`, `import`, and `export`.
292
292
 
293
293
  The neutral-pair lockstep (§1.3) is deliberately not a validator check — it lives in `tests/design_tokens_sync.mjs`, so an instance running only `validate-design.js` is not forced to mirror its own pack.
294
294
 
295
295
  Usage:
296
296
 
297
297
  ```
298
- node scripts/gds/validate-design.js # exits 0 on pass, 1 on failure
299
- node scripts/gds/validate-design.js --json # machine-readable report
300
- node scripts/gds/validate-design.js --strict # fail on warnings too
298
+ node modules/ui-design/scripts/validate-design.js # exits 0 on pass, 1 on failure
299
+ node modules/ui-design/scripts/validate-design.js --json # machine-readable report
300
+ node modules/ui-design/scripts/validate-design.js --strict # fail on warnings too
301
301
  ```
302
302
 
303
- The validator is hooked into `scripts/gds/fitness.js` as an opt-in check (enabled once the first adapter is committed — task 1378 or 1379).
303
+ The validator is wired into `scripts/gds/fitness.js` (`checkDesignContract` in `fitness-checks-identity.js`) and hard-fails while any adapter is present.
304
304
 
305
305
  ---
306
306
 
@@ -316,7 +316,7 @@ The validator is hooked into `scripts/gds/fitness.js` as an opt-in check (enable
316
316
 
317
317
  ## 8. Adding a new adapter
318
318
 
319
- 1. Create `src/ui/adapters/<id>/index.js` exporting `{ id, import, export }`.
319
+ 1. Create `modules/ui-design/adapters/<id>/index.js` exporting `{ id, import, export }`.
320
320
  2. Add a `<surface-id>` entry to `config/design-sources.json` with `"tool": "<id>"`.
321
321
  3. Write a task for the adapter (it unblocks from [task 1377](https://example.com/builders#/task/1377)) and ship it under a claimed Bongos task.
322
322
  4. The validator discovers the adapter automatically on the next run.
@@ -0,0 +1,3 @@
1
+ # `modules/ui-design/kit/` — reserved home for the look-before-you-ship kit
2
+
3
+ Lands with [task 1003320](https://cloudbongos.com/builders#/task/1003320): an instance-agnostic stub server, a renderer/auditor (every state × width × mode, with the contrast audit), the interaction probes and the mock checker any builder points at any surface. Until it lands, the apex probes live in session scratchpads (see the [module CLAUDE.md](../CLAUDE.md)).
@@ -0,0 +1,11 @@
1
+ {
2
+ "key": "ui-design",
3
+ "title": "UI design (the design contract, its adapters and sync, the /design playbook, the kit, the skills, the styles)",
4
+ "description": "The platform capability that lets any builder on any instance create and design good-looking, functional UIs in line with that instance's world (scoped with the owner 2026-08-28; task 1003324, ADR 0197). It ships METHOD and never a world: the neutral design contract of ADR 0081 with its tool adapters (Claude Design, Figma), their sync CLIs and the validator; the /design playbook for ui-discipline work; and the reserved homes for the look-before-you-ship kit (kit/), the vetted UI skills (skills/) and the style library (styles/) the sibling tasks land. The instance's world — its branding pack, config/design-tokens.*, config/design-sources.json and its DESIGN.md — stays in config/ and at the repo root as the host boundary this module reads and never owns (ADR 0062 §7). Cloud Bongos's own front door is this module's first customer, not its owner. default-on because every instance has a face. No routes, no migrations, no port: declaration-only like character-anim, so disabling it withdraws only the skills it contributes and touches no served surface.",
5
+ "coreVersion": "^1.19.0",
6
+ "default": true,
7
+ "maintenance": { "status": "core-maintained" },
8
+ "contributes": { "skills": ["design", "design-sync", "figma-design-sync"] },
9
+ "provides": [],
10
+ "consumes": []
11
+ }
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ // Build a Claude Design token-preview bundle from the repo's design system.
3
+ //
4
+ // Usage:
5
+ // node modules/ui-design/scripts/design-sync.js — build bundle, print manifest JSON
6
+ // node modules/ui-design/scripts/design-sync.js --surface <id> — limit to one surface
7
+ // (node scripts/gds/design-sync.js is a thin shim over this file — the form the skills use)
8
+ //
9
+ // Output (stdout, JSON):
10
+ // { bundlePath, files, surfaceIds, summary }
11
+ //
12
+ // The /design-sync skill calls this, then uses the DesignSync MCP tool
13
+ // to push the bundle to a Claude Design project.
14
+
15
+ const path = require('node:path');
16
+ const fs = require('node:fs');
17
+
18
+ // The design system (src/ui/, config/design-tokens.json) is HOST content (ADR 0108
19
+ // §1), and this CLI lives in a module, which may not reach src/instance-config.js
20
+ // (the one-way rule, ADR 0083). The instance root IS the repo root this module sits
21
+ // in today, so the walk-up is byte-identical to the resolveInstanceRoot() the
22
+ // core-side entry used to call; when the core installs as a package the root
23
+ // arrives through the doorway (task 1003324, ADR 0197).
24
+ const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
25
+
26
+ function arg(flag, args) {
27
+ const i = args.indexOf(flag);
28
+ return i !== -1 ? args[i + 1] : null;
29
+ }
30
+
31
+ (async () => {
32
+ const args = process.argv.slice(2);
33
+ const targetSurface = arg('--surface', args) || undefined;
34
+
35
+ const adapter = require('../adapters/claude-design/index.js');
36
+
37
+ const result = await adapter.import({
38
+ repoRoot: REPO_ROOT,
39
+ ...(targetSurface ? { surfaces: [{ id: targetSurface, path: path.join(REPO_ROOT, 'src', 'ui', targetSurface) }] } : {}),
40
+ });
41
+
42
+ // Build the file list from the bundle dir for the skill to pass to finalize_plan
43
+ const { bundlePath, surfaceIds, summary } = result;
44
+ const files = collectFiles(bundlePath, bundlePath);
45
+
46
+ const manifest = { bundlePath, files, surfaceIds, summary };
47
+ process.stdout.write(JSON.stringify(manifest, null, 2) + '\n');
48
+ })().catch(err => {
49
+ process.stderr.write(`design-sync: ${err.message}\n`);
50
+ process.exit(1);
51
+ });
52
+
53
+ function collectFiles(dir, root) {
54
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
55
+ const out = [];
56
+ for (const e of entries) {
57
+ const full = path.join(dir, e.name);
58
+ if (e.isDirectory()) {
59
+ out.push(...collectFiles(full, root));
60
+ } else {
61
+ out.push(path.relative(root, full));
62
+ }
63
+ }
64
+ return out;
65
+ }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ // Build a Figma push plan from the repo's design system (task 2048, redo of 1379).
3
+ //
4
+ // Usage:
5
+ // node modules/ui-design/scripts/figma-design-sync.js — build plan, print manifest JSON
6
+ // node modules/ui-design/scripts/figma-design-sync.js --surface <id> — limit to one surface
7
+ // (node scripts/gds/figma-design-sync.js is a thin shim over this file — the form the skills use)
8
+ //
9
+ // Output (stdout, JSON):
10
+ // { planPath, files, surfaceIds, summary }
11
+ //
12
+ // The figma-design-sync skill calls this, then executes the plan's frames
13
+ // against the live Figma MCP tool.
14
+
15
+ const path = require('node:path');
16
+ const fs = require('node:fs');
17
+
18
+ // The design system (src/ui/, config/design-tokens.json) is HOST content (ADR 0108
19
+ // §1), and this CLI lives in a module, which may not reach src/instance-config.js
20
+ // (the one-way rule, ADR 0083). The instance root IS the repo root this module sits
21
+ // in today, so the walk-up is byte-identical to the resolveInstanceRoot() the
22
+ // core-side entry used to call; when the core installs as a package the root
23
+ // arrives through the doorway (task 1003324, ADR 0197).
24
+ const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
25
+
26
+ function arg(flag, args) {
27
+ const i = args.indexOf(flag);
28
+ return i !== -1 ? args[i + 1] : null;
29
+ }
30
+
31
+ (async () => {
32
+ const args = process.argv.slice(2);
33
+ const targetSurface = arg('--surface', args) || undefined;
34
+
35
+ const adapter = require('../adapters/figma/index.js');
36
+
37
+ const result = await adapter.import({
38
+ repoRoot: REPO_ROOT,
39
+ ...(targetSurface ? { surfaces: [{ id: targetSurface, path: path.join(REPO_ROOT, 'src', 'ui', targetSurface) }] } : {}),
40
+ });
41
+
42
+ const { planPath, surfaceIds, summary } = result;
43
+ const files = collectFiles(planPath, planPath);
44
+
45
+ const manifest = { planPath, files, surfaceIds, summary };
46
+ process.stdout.write(JSON.stringify(manifest, null, 2) + '\n');
47
+ })().catch(err => {
48
+ process.stderr.write(`figma-design-sync: ${err.message}\n`);
49
+ process.exit(1);
50
+ });
51
+
52
+ function collectFiles(dir, root) {
53
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
54
+ const out = [];
55
+ for (const e of entries) {
56
+ const full = path.join(dir, e.name);
57
+ if (e.isDirectory()) {
58
+ out.push(...collectFiles(full, root));
59
+ } else {
60
+ out.push(path.relative(root, full));
61
+ }
62
+ }
63
+ return out;
64
+ }
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // validate-design.js — minimal validator for the neutral design contract (task 1377).
4
+ // See modules/ui-design/docs/design-contract.md §6 for the full spec. Lives in the
5
+ // ui-design module since task 1003324 (ADR 0197); node scripts/gds/validate-design.js
6
+ // is a thin shim over this file.
7
+ //
8
+ // Usage:
9
+ // node modules/ui-design/scripts/validate-design.js # exits 0 on pass, 1 on failure
10
+ // node modules/ui-design/scripts/validate-design.js --json # machine-readable { ok, errors, warnings }
11
+ // node modules/ui-design/scripts/validate-design.js --strict # treat warnings as errors
12
+
13
+ const fs = require('node:fs');
14
+ const path = require('node:path');
15
+
16
+ // The instance root is the repo root this module sits in (ADR 0108 §1); a module
17
+ // may not reach src/instance-config.js for it (ADR 0083).
18
+ const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
19
+ const ARGS = new Set(process.argv.slice(2));
20
+ const JSON_MODE = ARGS.has('--json');
21
+ const STRICT = ARGS.has('--strict');
22
+
23
+ const NEUTRAL_TOKENS = path.join(REPO_ROOT, 'config', 'design-tokens.neutral.json');
24
+ const INSTANCE_TOKENS = path.join(REPO_ROOT, 'config', 'design-tokens.json');
25
+ const TOKENS_SCHEMA = path.join(REPO_ROOT, 'config', 'design-tokens.schema.json');
26
+ const DESIGN_SOURCES = path.join(REPO_ROOT, 'config', 'design-sources.json');
27
+ const UI_SRC = path.join(REPO_ROOT, 'src', 'ui');
28
+ const UI_ASSETS = path.join(REPO_ROOT, 'assets', 'ui');
29
+ const ADAPTERS_DIR = path.join(__dirname, '..', 'adapters'); // the module's own adapters
30
+
31
+ const TOOL_ENUM = new Set(['claude-design', 'figma', 'hand', 'asset']);
32
+ const REQUIRED_COLORS = ['bg', 'bgCard', 'ink', 'accent'];
33
+ const REQUIRED_FONTS = ['display', 'body'];
34
+
35
+ const errors = [];
36
+ const warnings = [];
37
+
38
+ function err(msg) { errors.push(msg); }
39
+ function warn(msg) { warnings.push(msg); }
40
+
41
+ // ── helpers ──────────────────────────────────────────────────────────────────
42
+
43
+ function readJson(filePath, label) {
44
+ try {
45
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
46
+ } catch (e) {
47
+ err(`${label}: cannot read/parse — ${e.message}`);
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function deepMerge(base, override) {
53
+ if (!override) return base;
54
+ const out = { ...base };
55
+ for (const [k, v] of Object.entries(override)) {
56
+ if (v && typeof v === 'object' && !Array.isArray(v) && typeof out[k] === 'object') {
57
+ out[k] = deepMerge(out[k], v);
58
+ } else {
59
+ out[k] = v;
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+
65
+ function isUri(s) {
66
+ try { new URL(s); return true; } catch { return false; }
67
+ }
68
+
69
+ function dirExists(p) {
70
+ try { return fs.statSync(p).isDirectory(); } catch { return false; }
71
+ }
72
+
73
+ function fileExists(p) {
74
+ try { return fs.statSync(p).isFile(); } catch { return false; }
75
+ }
76
+
77
+ function listDirs(p) {
78
+ if (!dirExists(p)) return [];
79
+ return fs.readdirSync(p, { withFileTypes: true })
80
+ .filter(e => e.isDirectory())
81
+ .map(e => e.name);
82
+ }
83
+
84
+ // ── Check 1: neutral tokens file exists + parses ─────────────────────────────
85
+
86
+ const schema = readJson(TOKENS_SCHEMA, 'config/design-tokens.schema.json');
87
+ const neutral = readJson(NEUTRAL_TOKENS, 'config/design-tokens.neutral.json');
88
+
89
+ if (!neutral) {
90
+ err('config/design-tokens.neutral.json is missing — this is the required baseline');
91
+ }
92
+
93
+ // ── Check 2: instance tokens file, if present, parses ────────────────────────
94
+
95
+ let instance = null;
96
+ if (fileExists(INSTANCE_TOKENS)) {
97
+ instance = readJson(INSTANCE_TOKENS, 'config/design-tokens.json');
98
+ }
99
+
100
+ // ── Check 3: merged tokens satisfy required fields ───────────────────────────
101
+
102
+ const merged = neutral ? deepMerge(neutral, instance) : null;
103
+
104
+ if (merged) {
105
+ if (!merged.colors || typeof merged.colors !== 'object') {
106
+ err('merged tokens: "colors" group is missing');
107
+ } else {
108
+ for (const key of REQUIRED_COLORS) {
109
+ if (!merged.colors[key]) err(`merged tokens: colors.${key} is required but missing`);
110
+ else if (typeof merged.colors[key] !== 'string') err(`merged tokens: colors.${key} must be a string`);
111
+ }
112
+ for (const [key, val] of Object.entries(merged.colors)) {
113
+ if (typeof val !== 'string') err(`merged tokens: colors.${key} must be a string`);
114
+ }
115
+ }
116
+
117
+ if (!merged.fonts || typeof merged.fonts !== 'object') {
118
+ err('merged tokens: "fonts" group is missing');
119
+ } else {
120
+ for (const key of REQUIRED_FONTS) {
121
+ if (!merged.fonts[key]) err(`merged tokens: fonts.${key} is required but missing`);
122
+ else if (typeof merged.fonts[key] !== 'string') err(`merged tokens: fonts.${key} must be a string`);
123
+ }
124
+ if (merged.fonts.links !== undefined) {
125
+ if (!Array.isArray(merged.fonts.links)) {
126
+ err('merged tokens: fonts.links must be an array');
127
+ } else {
128
+ for (const link of merged.fonts.links) {
129
+ if (typeof link !== 'string' || !isUri(link)) {
130
+ err(`merged tokens: fonts.links entry is not a valid URI: ${link}`);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ // Optional groups: check value types if present
138
+ if (merged.scale) {
139
+ for (const [k, v] of Object.entries(merged.scale)) {
140
+ if (typeof v !== 'string') err(`merged tokens: scale.${k} must be a string`);
141
+ }
142
+ }
143
+ if (merged.radius) {
144
+ for (const [k, v] of Object.entries(merged.radius)) {
145
+ if (typeof v !== 'string') err(`merged tokens: radius.${k} must be a string`);
146
+ }
147
+ }
148
+ if (merged.motion) {
149
+ for (const [k, v] of Object.entries(merged.motion)) {
150
+ if (typeof v !== 'string') err(`merged tokens: motion.${k} must be a string`);
151
+ }
152
+ }
153
+ }
154
+
155
+ // ── Check 4: design-sources.json is valid + entries have required fields ─────
156
+
157
+ let sources = null;
158
+ if (fileExists(DESIGN_SOURCES)) {
159
+ sources = readJson(DESIGN_SOURCES, 'config/design-sources.json');
160
+ if (sources) {
161
+ if (!sources.surfaces || typeof sources.surfaces !== 'object') {
162
+ err('config/design-sources.json: "surfaces" key must be an object');
163
+ } else {
164
+ for (const [surfaceId, entry] of Object.entries(sources.surfaces)) {
165
+ if (!entry || typeof entry !== 'object') {
166
+ err(`design-sources: surface "${surfaceId}" entry must be an object`);
167
+ continue;
168
+ }
169
+ if (!entry.tool) {
170
+ err(`design-sources: surface "${surfaceId}" is missing "tool" field`);
171
+ } else if (!TOOL_ENUM.has(entry.tool)) {
172
+ err(`design-sources: surface "${surfaceId}" tool "${entry.tool}" is not in allowed enum (${[...TOOL_ENUM].join(', ')})`);
173
+ }
174
+ if (entry.sourceUrl !== undefined) {
175
+ if (typeof entry.sourceUrl !== 'string' || !isUri(entry.sourceUrl)) {
176
+ err(`design-sources: surface "${surfaceId}" sourceUrl is not a valid URI`);
177
+ }
178
+ } else if (entry.tool && !['hand', 'asset'].includes(entry.tool)) {
179
+ warn(`design-sources: surface "${surfaceId}" tool "${entry.tool}" has no sourceUrl`);
180
+ }
181
+ if (entry.lastSyncedAt !== undefined) {
182
+ if (typeof entry.lastSyncedAt !== 'string' || isNaN(Date.parse(entry.lastSyncedAt))) {
183
+ err(`design-sources: surface "${surfaceId}" lastSyncedAt is not a valid ISO 8601 date`);
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+ } else {
190
+ warn('config/design-sources.json not found — this is optional until adapters are in use');
191
+ }
192
+
193
+ // ── Check 5: design-sources surface ids match directories (if dirs exist) ─────
194
+
195
+ const hasUiSrc = dirExists(UI_SRC);
196
+ const hasUiAssets = dirExists(UI_ASSETS);
197
+
198
+ if (sources && sources.surfaces) {
199
+ const uiSrcDirs = hasUiSrc ? new Set(listDirs(UI_SRC)) : new Set();
200
+ const uiAssetsDirs = hasUiAssets ? new Set(listDirs(UI_ASSETS)) : new Set();
201
+
202
+ for (const surfaceId of Object.keys(sources.surfaces)) {
203
+ const inSrc = uiSrcDirs.has(surfaceId);
204
+ const inAssets = uiAssetsDirs.has(surfaceId);
205
+ if (!inSrc && !inAssets && (hasUiSrc || hasUiAssets)) {
206
+ warn(`design-sources: surface "${surfaceId}" has no matching directory in src/ui/ or assets/ui/`);
207
+ }
208
+ }
209
+ }
210
+
211
+ // ── Check 6: adapter directories have valid index.js ─────────────────────────
212
+
213
+ if (dirExists(ADAPTERS_DIR)) {
214
+ for (const adapterName of listDirs(ADAPTERS_DIR)) {
215
+ const indexPath = path.join(ADAPTERS_DIR, adapterName, 'index.js');
216
+ if (!fileExists(indexPath)) {
217
+ err(`adapter "${adapterName}": missing index.js at modules/ui-design/adapters/${adapterName}/index.js`);
218
+ continue;
219
+ }
220
+ let mod;
221
+ try {
222
+ mod = require(indexPath);
223
+ } catch (e) {
224
+ err(`adapter "${adapterName}": index.js threw on require — ${e.message}`);
225
+ continue;
226
+ }
227
+ if (typeof mod.id !== 'string' || !mod.id) {
228
+ err(`adapter "${adapterName}": index.js must export a non-empty string "id"`);
229
+ } else if (mod.id !== adapterName) {
230
+ warn(`adapter "${adapterName}": exported id "${mod.id}" does not match directory name`);
231
+ }
232
+ if (typeof mod.import !== 'function') {
233
+ err(`adapter "${adapterName}": index.js must export an "import" function`);
234
+ }
235
+ if (typeof mod.export !== 'function') {
236
+ err(`adapter "${adapterName}": index.js must export an "export" function`);
237
+ }
238
+ }
239
+ }
240
+
241
+ // ── Report ────────────────────────────────────────────────────────────────────
242
+
243
+ const effectiveErrors = STRICT ? [...errors, ...warnings] : errors;
244
+ const ok = effectiveErrors.length === 0;
245
+
246
+ if (JSON_MODE) {
247
+ process.stdout.write(JSON.stringify({ ok, errors, warnings }, null, 2) + '\n');
248
+ } else {
249
+ const label = ok ? '✓ design contract valid' : '✗ design contract invalid';
250
+ process.stdout.write(`${label}\n`);
251
+ for (const e of errors) process.stdout.write(` ERROR ${e}\n`);
252
+ for (const w of warnings) process.stdout.write(` WARNING ${w}\n`);
253
+ if (ok && !STRICT && warnings.length) {
254
+ process.stdout.write(` (${warnings.length} warning(s); use --strict to fail on warnings)\n`);
255
+ }
256
+ }
257
+
258
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,3 @@
1
+ # `modules/ui-design/skills/` — reserved home for the vetted UI skills
2
+
3
+ Lands with [task 1003321](https://cloudbongos.com/builders#/task/1003321): the vendored design skills (impeccable, the style/taste skills, image-to-code, imagegen, brandkit), each scanned with `/scan-before-install`, carrying a PROVENANCE sidecar and a licence on the allowlist, and prefixed with the PLATFORM PREAMBLE that reads the instance's branding pack and `DESIGN.md` before any taste rule applies. The vendoring policy is that task's ADR; [ADR 0197](../../../docs/adr/<redacted>.md) only points at it.
@@ -0,0 +1,3 @@
1
+ # `modules/ui-design/styles/` — reserved home for the style library
2
+
3
+ Lands with [task 1003323](https://cloudbongos.com/builders#/task/1003323): looks shipped as branding packs (the fifteen host-overridable tokens plus a `DESIGN.md` per look), with two alternates beside the chrome world, so an instance picks a world instead of inheriting Cloud Bongos's. A look is a pack; it is never a page's private palette.