@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.
- package/.bongos-core.json +108 -58
- package/.claude/skills/design/SKILL.md +68 -0
- package/.claude/skills/design-sync/SKILL.md +2 -2
- package/.claude/skills/figma-design-sync/SKILL.md +3 -3
- package/config/design-tokens.neutral.json +1 -1
- package/config/design-tokens.schema.json +1 -1
- package/docs/adr/0197-the-ui-design-module.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +1 -1
- package/docs/copy-inventory.md +249 -236
- package/docs/copy-registry.json +543 -324
- package/docs/file-map.md +2 -0
- package/docs/modules-contract.md +1 -0
- package/docs/onboarding/slash-commands.md +1 -1
- package/docs/recipes/claude-design-loop.md +3 -3
- package/docs/recipes/figma-design-loop.md +4 -4
- package/modules/ui-design/CLAUDE.md +15 -0
- package/{src/ui → modules/ui-design}/adapters/claude-design/index.js +1 -1
- package/{src/ui → modules/ui-design}/adapters/figma/index.js +3 -3
- package/{docs → modules/ui-design/docs}/design-contract.md +12 -12
- package/modules/ui-design/kit/README.md +3 -0
- package/modules/ui-design/module.json +11 -0
- package/modules/ui-design/scripts/design-sync.js +65 -0
- package/modules/ui-design/scripts/figma-design-sync.js +64 -0
- package/modules/ui-design/scripts/validate-design.js +258 -0
- package/modules/ui-design/skills/README.md +3 -0
- package/modules/ui-design/styles/README.md +3 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +235 -103
- package/public-landing/index.html +228 -75
- package/public-landing/projects.html +256 -140
- package/scripts/gds/copy-inventory.js +7 -5
- package/scripts/gds/design-sync.js +9 -59
- package/scripts/gds/doc-cli-guard.js +11 -5
- package/scripts/gds/figma-design-sync.js +9 -59
- package/scripts/gds/fitness-checks-identity.js +5 -3
- package/scripts/gds/publish-manifest.js +1 -1
- package/scripts/gds/validate-design.js +7 -249
- package/src/bongos/module-scope-map.js +18 -0
- package/src/module-api.js +5 -1
- package/tests/design_tokens_sync.mjs +1 -1
- package/tests/doc_cli_guard.mjs +19 -0
- package/tests/figma_adapter.mjs +1 -1
- package/tests/landing_page.mjs +72 -2
- package/tests/module_loader.mjs +4 -1
- package/tests/projects_hub.mjs +52 -73
- 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)
|
package/docs/modules-contract.md
CHANGED
|
@@ -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: `
|
|
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('./
|
|
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: `
|
|
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('./
|
|
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
|
-
|
|
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 `
|
|
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/
|
|
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 `
|
|
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/
|
|
299
|
-
node scripts/
|
|
300
|
-
node scripts/
|
|
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
|
|
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 `
|
|
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.
|