spec-layer 0.5.0 → 0.7.0

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 (3) hide show
  1. package/README.md +138 -17
  2. package/dist/cli.js +1253 -319
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,9 +14,12 @@ After publishing a library from the plugin's Library screen, it shows a setup
14
14
  command. Run it once in your repository:
15
15
 
16
16
  ```bash
17
- npx spec-layer setup --id lib_... --key sl_...
17
+ npx spec-layer setup --id lib_... --key sl_... --platform web
18
18
  ```
19
19
 
20
+ `--platform` says what you build; without it, `setup` reads the repository
21
+ root and writes a token file only when it finds a web signal.
22
+
20
23
  That records the library id, stores the key in a gitignored
21
24
  `speclayer.local.json`, and writes `.speclayer/`. Every later command needs no
22
25
  flags at all:
@@ -55,15 +58,19 @@ also keeps `.speclayer/manifest.json` on one format: 0.1.0 wrote no
55
58
  versions have no such command, so the setup command the plugin copies fails
56
59
  against them. The Foundation landing under `tokens/` as Design Tokens Format
57
60
  Module 2025.10 files, rather than `ai/foundation.yaml`, needs 0.4.0 or later.
58
- `tools` and `skill` need 0.5.0 or later.
61
+ `tools` and `skill` need 0.5.0 or later. `components/` in place of
62
+ `ai/components/`, `path` in place of `aiPath` in the manifest, and the
63
+ `outputs` block need 0.6.0 or later. `component-specs/` beside `tokens/`,
64
+ `componentSpecsDir`, cwd-relative manifest paths, and the `tokens/` directory
65
+ need 0.7.0 or later.
59
66
 
60
67
  ## Commands
61
68
 
62
69
  | Command | What it does |
63
70
  |---|---|
64
- | `setup --id lib_... --key sl_... [--out DIR] [selection]` | Writes `speclayer.json`, stores the key in `speclayer.local.json`, then pulls. The command the plugin copies. |
65
- | `init --id lib_... [--out DIR] [selection]` | Writes `speclayer.json` so later commands need no flags. No key, no network. |
66
- | `pull [--id lib_...] [--key sl_...] [selection]` | Fetches the library and writes it into `DIR` (default `.speclayer`). |
71
+ | `setup --id lib_... --key sl_... [--out DIR] [--platform P]... [selection]` | Writes `speclayer.json`, stores the key in `speclayer.local.json`, then pulls. The command the plugin copies. |
72
+ | `init --id lib_... [--out DIR] [--platform P]... [selection]` | Writes `speclayer.json` so later commands need no flags. No key, no network. |
73
+ | `pull [--id lib_...] [--key sl_...] [--platform P]... [selection]` | Fetches the library and writes it into `DIR` (default `.speclayer`). |
67
74
  | `status [--id lib_...] [--key sl_...]` | Checks freshness without writing. Exits `2` when the local copy is behind. |
68
75
  | `list` | Lists every artifact in the last pull, with its file path or `not written`. |
69
76
  | `show foundation [--canonical]` | Prints the Foundation's DTCG document to stdout. |
@@ -141,7 +148,7 @@ carry no key and are meant to be committed with the rest of the repository.
141
148
  ## Pulling part of a library
142
149
 
143
150
  By default `pull` writes the Foundation and every documented component. When
144
- your repo implements only some of them, narrow what lands in `ai/`:
151
+ your repo implements only some of them, narrow what lands in `component-specs/`:
145
152
 
146
153
  ```bash
147
154
  npx spec-layer pull --only foundation # tokens and styles, no components
@@ -172,7 +179,7 @@ That stores an `include` block in `speclayer.json`:
172
179
  Selection flags on `pull` replace the stored selection for that run; they are
173
180
  never merged with it.
174
181
 
175
- The selection only decides which `ai/` files are written. `bundle.json`
182
+ The selection only decides which `component-specs/` files are written. `bundle.json`
176
183
  always holds the whole library, so `list` and `show` can answer for any
177
184
  artifact, written or not, and `status` compares one hash.
178
185
 
@@ -239,7 +246,7 @@ matter what the ignore rules say.
239
246
  ```text
240
247
  .speclayer/
241
248
  bundle.json the published bundle, verbatim
242
- manifest.json every artifact indexed by content hash and path, plus the selection
249
+ manifest.json every artifact indexed by content hash and path, plus the selection, outputs, and componentSpecsDir
243
250
  tokens/ the Foundation as Design Tokens Format Module 2025.10 files
244
251
  <collection>.<mode>.json one file per collection and mode, rooted at the collection name
245
252
  styles.typography.json text styles as typography composites (when present)
@@ -247,15 +254,24 @@ matter what the ignore rules say.
247
254
  resolver.json Design Tokens Resolver Module 2025.10: sets, modifiers, order
248
255
  spec-layer.meta.json Figma ids, scopes, code syntax, publication, keyed by DTCG path
249
256
  report.json what DTCG could not express, with reasons and stable ids
250
- ai/components/<name>.yaml one file per selected component
257
+ outputs/
258
+ web-css.map.json DTCG path -> CSS custom property, where the name came from, and which file declares it
259
+ web-css.report.json what the CSS files could not express
260
+ component-specs/
261
+ <name>.yaml one brief per selected component, byte-identical to Copy for AI
262
+ tokens/
263
+ index.css imports every file below, in resolver order
264
+ <collection>.css one file per single-mode collection, at :root
265
+ <collection>.<mode>.css one file per mode of a multi-mode collection; the default at :root
251
266
  ```
252
267
 
253
- Point your agent at `.speclayer/ai/` and `.speclayer/tokens/`. The component
254
- YAML is the same compact form the plugin's **Copy for AI** puts on your
255
- clipboard; `bundle.json` additionally holds the full canonical artifacts if
256
- you need them.
268
+ Point your agent at `component-specs/` and `.speclayer/tokens/`, and import
269
+ `tokens/index.css` from the platform's root stylesheet. The component YAML is
270
+ the same compact form the plugin's **Copy for AI** puts on your clipboard;
271
+ `bundle.json` additionally holds the full canonical artifacts if you need
272
+ them.
257
273
 
258
- In `manifest.json`, an artifact the selection left unwritten has `"aiPath":
274
+ In `manifest.json`, an artifact the selection left unwritten has `"path":
259
275
  null`. A manifest from CLI 0.1.0 has no `selection` field and means
260
276
  everything was written.
261
277
 
@@ -264,11 +280,19 @@ interrupted pull never leaves a half-written directory. `pull` refuses an
264
280
  output directory that is the current directory, a parent of it, or an existing
265
281
  non-empty directory it did not write, since the swap replaces that directory.
266
282
 
283
+ `component-specs/` and `tokens/` are written in place, not swapped. `pull`
284
+ owns exactly the files there that begin with its marker (the CSS header, or
285
+ the brief's opening `spec_layer:` lines): it replaces or removes those,
286
+ ignores dotfiles, and refuses to run when anything else is present. A
287
+ repository that already uses a path can set `componentSpecsDir` or
288
+ `outputs[].path`.
289
+
267
290
  When nothing changed since the last pull with the same selection, `pull`
268
291
  prints `Already up to date` and writes nothing. Every republish stamps a new
269
292
  export id and time into the canonical artifacts, so `bundle.json` and
270
293
  `manifest.json` change on each republish even when the content did not. The
271
- `ai/` YAML files, the `tokens/` files, and the content hashes stay stable.
294
+ `component-specs/` YAML files, the `.speclayer/tokens/` files, and the
295
+ `tokens/` CSS stay stable.
272
296
 
273
297
  ## Configuring the token output
274
298
 
@@ -295,6 +319,103 @@ Point Style Dictionary at `.speclayer/tokens/` and load the files
295
319
  `resolver.json` names for the mode you are building. The metadata sidecar and
296
320
  the report are not token files; exclude them from token globs.
297
321
 
322
+ ## Token files for your code
323
+
324
+ `pull` also writes a `tokens/` directory at `outputs[].path`: one CSS file
325
+ per collection and mode plus `index.css`, one per platform output. The
326
+ directory is written only when that pull writes the Foundation; with
327
+ `--only components` it is left exactly as it was, and `list` shows it as
328
+ `not written`.
329
+
330
+ ```css
331
+ /* Generated by spec-layer from library lib_..., foundation sha256:..., web/css/kebab.
332
+ Do not edit. Change the design in Figma, republish, and run spec-layer pull. */
333
+
334
+ :root {
335
+ /* Foundation */
336
+ --foundation-colors-blue-500: #2e72d1;
337
+ --foundation-spacing-200: 8px;
338
+ }
339
+ ```
340
+
341
+ ```css
342
+ /* Generated by spec-layer from library lib_..., foundation sha256:..., web/css/kebab.
343
+ Do not edit. Change the design in Figma, republish, and run spec-layer pull. */
344
+
345
+ [data-theme="dark"] {
346
+ /* Mapped Colors, Dark */
347
+ --mapped-colors-surface-primary-default: var(--foundation-colors-blue-900);
348
+ }
349
+ ```
350
+
351
+ `index.css` holds the header, then one comment and one import per file, in
352
+ resolver order, and no declarations of its own:
353
+
354
+ ```css
355
+ /* Generated by spec-layer from library lib_..., foundation sha256:..., web/css/kebab.
356
+ Do not edit. Change the design in Figma, republish, and run spec-layer pull. */
357
+
358
+ /* Foundation */
359
+ @import "./foundation.css";
360
+ /* Mapped Colors, Dark */
361
+ @import "./mapped-colors.dark.css";
362
+ ```
363
+
364
+ A set, which has one mode by construction, writes one file named for the
365
+ collection alone, at `:root`. A collection with modes writes one file per
366
+ mode: the default mode's file sits at `:root`, every other mode's file under
367
+ `[data-theme="<mode>"]`. Aliases stay as `var()`. A number whose Figma scopes
368
+ state no unit stays a bare number. Nothing about a mode's name selects a
369
+ media query. To let the OS choose a theme, set that collection's selector to
370
+ `:root` under `modes` and import the mode's file yourself under `@media
371
+ (prefers-color-scheme: dark)`. The CLI never assumes that.
372
+
373
+ The directory is written **in place** at the path in `speclayer.json`,
374
+ outside the managed directory, so your bundler keeps watching it and the diff
375
+ shows up in review. Every file begins with a header naming the library and
376
+ the Foundation's content hash; `pull` owns exactly the files there that begin
377
+ with it, replacing or removing those, ignoring dotfiles, and refusing to run
378
+ when anything else is present. A `speclayer.json` written by CLI 0.6.0 names
379
+ a `.css` file at this path, and `pull` refuses it: set `path` to a directory
380
+ and delete the old file.
381
+
382
+ ```json
383
+ {
384
+ "platforms": ["web"],
385
+ "outputs": [
386
+ { "platform": "web", "format": "css", "path": "tokens", "case": "kebab" }
387
+ ]
388
+ }
389
+ ```
390
+
391
+ `setup` and `init` write this block from `--platform`, or from what they detect
392
+ at the repository root, so the path is always on record. `pull` writes every
393
+ entry; `"outputs": []` writes none. `case` chooses how derived names are
394
+ spelled: `kebab` (default), `camel`, `pascal`, `snake`, or `constant`. A name
395
+ the designer declared as `code_syntax` in Figma is used verbatim and never
396
+ re-cased. `.speclayer/outputs/web-css.map.json` records every emitted name,
397
+ whether it was declared or derived, and which file declares it; two tokens
398
+ that would share a name are both omitted and listed in `web-css.report.json`.
399
+
400
+ Two collections with modes share one attribute by default, which cannot be
401
+ right for both; the report says so, and `modes` declares a selector per
402
+ collection:
403
+
404
+ ```json
405
+ { "platform": "web", "format": "css", "path": "tokens",
406
+ "modes": { "Density": "[data-density=\"{mode}\"]" } }
407
+ ```
408
+
409
+ `root` and `modeSelector` override the defaults `:root` and
410
+ `[data-theme="{mode}"]`; `{mode}` and `{collection}` are replaced by slugs.
411
+ A repository that already builds tokens with Style Dictionary can keep reading
412
+ `.speclayer/tokens/`: the CSS files are a projection of the same record, not a
413
+ second source, so import one or the other.
414
+
415
+ Commit `.speclayer/`, `speclayer.json`, `component-specs/`, and the output
416
+ paths. A repository that would rather regenerate in CI ignores them and runs
417
+ `pull` there; `status` exits `2` when a pull is due.
418
+
298
419
  ## Exit codes
299
420
 
300
421
  | Code | Meaning |
@@ -308,5 +429,5 @@ the report are not token files; exclude them from token globs.
308
429
 
309
430
  ## Requirements
310
431
 
311
- Node 22 or newer. Publishing requires a Spec Layer Pro license; pulling does
312
- not.
432
+ Node 22 or newer. Publishing one Figma file is free; Pro publishes up to ten.
433
+ Pulling needs only the library id and pull key.