mosaic-headless 1.17.0 → 1.17.2
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/README.ja.md +220 -69
- package/README.ko.md +219 -76
- package/README.md +184 -70
- package/README.zh-TW.md +204 -61
- package/SKILL.md +3 -1
- package/assets/templates/platforms/claude-ai.json +1 -1
- package/assets/templates/platforms/claude-code.json +1 -1
- package/assets/templates/platforms/codex-cli.json +1 -1
- package/assets/templates/platforms/copilot.json +1 -1
- package/assets/templates/platforms/gemini-cli.json +1 -1
- package/data/token-benchmark.csv +7 -0
- package/package.json +1 -1
- package/tools/benchmark_tokens.py +155 -0
package/README.md
CHANGED
|
@@ -3,19 +3,91 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/mosaic-headless)
|
|
4
4
|
|
|
5
5
|
Build and modify [Mosaic Pro](https://mosaicbuilder.com) (Nextend) sites by writing
|
|
6
|
-
the data model directly — no visual editor, no DOM.
|
|
6
|
+
the data model directly — no visual editor, no DOM. Convert Elementor pages into it.
|
|
7
|
+
Move whole themes between installs. Every claim measured on a live site.
|
|
7
8
|
|
|
8
9
|
*Read this in [繁體中文](README.zh-TW.md) · [日本語](README.ja.md) · [한국어](README.ko.md)*
|
|
9
10
|
|
|
10
11
|
---
|
|
11
12
|
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx mosaic-headless # interactive: pick a platform
|
|
17
|
+
npx mosaic-headless claude-code --global # Claude Code, into ~/.claude/skills/
|
|
18
|
+
npx mosaic-headless cursor --to ./my-project
|
|
19
|
+
npx mosaic-headless --list # all eight platforms
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| platform | what is installed | where |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Claude Code | full skill: SKILL.md + references/ + tools/ + data/ + sites/ | `~/.claude/skills/` or `./.claude/skills/` |
|
|
25
|
+
| Codex CLI | full skill | `~/.codex/` |
|
|
26
|
+
| Gemini CLI | full skill | `~/.gemini/` |
|
|
27
|
+
| GitHub Copilot | full skill, plus a section appended to `copilot-instructions.md` | `./.github/` |
|
|
28
|
+
| Cursor | one `.mdc` rule with the references embedded | `~/.cursor/rules/` |
|
|
29
|
+
| Windsurf | one rule file with the references embedded | `./.devin/` |
|
|
30
|
+
| Continue | one rule file with the references embedded | `~/.continue/` |
|
|
31
|
+
| Claude.ai | a zip to upload as a project skill | wherever you save it |
|
|
32
|
+
|
|
33
|
+
Every platform install is verified by the release gate against its template. The
|
|
34
|
+
tools need Python 3 and Playwright to run; the rule-file platforms get the knowledge
|
|
35
|
+
without the tools.
|
|
36
|
+
|
|
37
|
+
**Updating does not happen on its own.** A new version on npm changes nothing in
|
|
38
|
+
the folder your agent loads; re-run the installer with `--force` (without it, it
|
|
39
|
+
refuses to overwrite a SKILL.md you may have edited):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx mosaic-headless@latest claude-code --global --force
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## What this is
|
|
47
|
+
|
|
12
48
|
Mosaic keeps a page in **23 custom database tables**, not in `post_content` and not
|
|
13
49
|
in `postmeta`. One row per element, the tree carried by a `parentID` column, sibling
|
|
14
50
|
order by a fractional-index string. The editor is one client of that model. It is not
|
|
15
51
|
the format, and you do not need it.
|
|
16
52
|
|
|
17
53
|
This skill is the map of that model — measured against a live install rather than
|
|
18
|
-
read off the source
|
|
54
|
+
read off the source — plus the tools to write through it, check what came out, and
|
|
55
|
+
bring pages in from Elementor.
|
|
56
|
+
|
|
57
|
+
## How the pieces fit
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
flowchart LR
|
|
61
|
+
subgraph measure["measured once, against a live install"]
|
|
62
|
+
SRC[plugin source] -->|extract_*.py| D[(data/*.csv)]
|
|
63
|
+
SW[sweep_*.py / probe_*.py] -->|commit, render, assert| D
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
subgraph write["every page you build"]
|
|
67
|
+
Q[mo.py] -->|one answer, verdict first| SPEC[page spec]
|
|
68
|
+
EL[Elementor _elementor_data] -->|from_elementor.py| SPEC
|
|
69
|
+
SPEC -->|build_page.py refuses what breaks| REST[Mosaic REST: checkout, check, commit]
|
|
70
|
+
REST --> DB[(23 tables)]
|
|
71
|
+
DB --> PAGE[served page]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
subgraph verify["never trust the commit"]
|
|
75
|
+
PAGE --> V1[verify_rwd.py]
|
|
76
|
+
PAGE --> V2[verify_browser.py + design audit]
|
|
77
|
+
PAGE --> V3[verify_intro.py / verify_loop.py]
|
|
78
|
+
PAGE --> V4[verify_conversion.py]
|
|
79
|
+
V1 & V2 & V3 & V4 --> CSV[(verification CSVs)]
|
|
80
|
+
CSV --> GATE[check-release.mjs]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
D --> Q
|
|
84
|
+
D --> SPEC
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Left to right: the tables are measured once and shipped; every page is written
|
|
88
|
+
through them and refused when they say no; and nothing is believed until the
|
|
89
|
+
delivered page has been read back and the result recorded in a table that the
|
|
90
|
+
release gate checks.
|
|
19
91
|
|
|
20
92
|
## The one rule
|
|
21
93
|
|
|
@@ -33,13 +105,14 @@ keyboard-operable disclosure. `mo.py type` shows all three at once.
|
|
|
33
105
|
```bash
|
|
34
106
|
python tools/mo.py type accordion-content # one type, joined to every live sweep
|
|
35
107
|
python tools/mo.py check div text button # exits 1 on an unsafe or unknown type
|
|
108
|
+
python tools/mo.py params text # everything settable on one type
|
|
36
109
|
python tools/mo.py style --grouped # the 20 that are inert set on their own
|
|
37
110
|
python tools/mo.py states --verified # the states measured to compile
|
|
38
|
-
python tools/mo.py
|
|
111
|
+
python tools/mo.py css grid-column # which Mosaic key drives this CSS
|
|
39
112
|
```
|
|
40
113
|
|
|
41
|
-
Then check the page. Mosaic has
|
|
42
|
-
HTTP status code
|
|
114
|
+
Then check the page. Mosaic has **seven** failure modes and only two of them change
|
|
115
|
+
the HTTP status code:
|
|
43
116
|
|
|
44
117
|
```
|
|
45
118
|
clean validator rejection HTTP 200 + an `exceptions` array in the body
|
|
@@ -50,10 +123,17 @@ wrong value SHAPE HTTP 200, stored, and the CSS rule is simply absent
|
|
|
50
123
|
right rule, wrong result HTTP 200, in the stylesheet, correct, and the BROWSER
|
|
51
124
|
computes something else
|
|
52
125
|
no template for the URL HTTP 406 with an EMPTY BODY for anyone not logged in
|
|
126
|
+
render-time fatal from HTTP 500 - the commit went through, the page dies when
|
|
127
|
+
CONTENT Mosaic parses it. A `code` node's content is a template:
|
|
128
|
+
`@media(` as every minifier writes it is read as a
|
|
129
|
+
function call. `@media (` renders. build_page refuses
|
|
130
|
+
the former.
|
|
53
131
|
```
|
|
54
132
|
|
|
55
133
|
A successful commit is not evidence of a working page, and neither is a correct
|
|
56
|
-
stylesheet.
|
|
134
|
+
stylesheet. Every tool here fetches the page afterwards — and treats a 5xx as an
|
|
135
|
+
empty page, because WordPress's "critical error" screen is 2,697 bytes and larger
|
|
136
|
+
than any naive healthy-page floor.
|
|
57
137
|
|
|
58
138
|
## What was verified, and how
|
|
59
139
|
|
|
@@ -63,25 +143,47 @@ factories, so Pro types register and render regardless.
|
|
|
63
143
|
|
|
64
144
|
| pass | result |
|
|
65
145
|
|---|---|
|
|
66
|
-
| **node types** | 122 / 122 swept one per document, committed → rendered → asserted → deleted: 70 RENDERED, 30 COMMITTED, 15 COMMIT_5xx, 7 BROKE_PAGE |
|
|
146
|
+
| **node types** | 122 / 122 swept one per document, committed → rendered → asserted → deleted: 70 RENDERED, 30 COMMITTED, 15 COMMIT_5xx, 7 BROKE_PAGE. Three of the non-rendering rows are artefacts of committing without the required parent, and say so beside the row |
|
|
67
147
|
| **style properties** | 98 / 98 written to a live page and checked against the compiled CSS: 58 COMPILED, 18 ABSENT, 21 SKIPPED |
|
|
68
148
|
| **node properties** | 181 / 181 re-probed with a value shaped by each property's own validator chain: 35 APPLIED, 42 NO_EFFECT, 55 NO_HOST, 47 SKIPPED |
|
|
69
149
|
| **responsive** | 731 `_t`/`_m` declarations across two sites asserted against the stylesheet the site actually served — all verified |
|
|
70
|
-
| **components** | the component system driven end to end, **8 of 8**: created under a category, document healed, tree filled through the writable instance, the read-only one refused the same write as a negative control, and two instances on a page rendering one definition twice |
|
|
71
|
-
| **style states** | 52 of the 53 states written to a live page and matched against the selector the table promises: **36 compiled exactly**, 12 NO_HOST, 3 SKIPPED, 1 BROKE_PAGE. All seven globally usable states verified |
|
|
72
|
-
| **interactions** | the JS animation path probed with negative controls and the row read back: `propertyMetas` **is** accepted and stored; the property values still do not bind, and the boundary is now exact |
|
|
73
|
-
| **entrance animation** | the page-load sequence sampled at fifteen timestamps on a monotonic clock and asserted on eight counts — it plays, its animated `@property` counter reaches 100, the veil leaves hit-testing, nothing in the viewport is stranded at opacity 0, a real click reaches the document, under `prefers-reduced-motion` the veil never exists, and something is still moving once everything has settled. Costs one late frame over a page with no animation at all, because it waits for the document's first layout |
|
|
74
|
-
| **perpetual animation** | a corner plate that keeps printing itself and opens to full size when tapped, **28 checks**: periodicity proved by scrubbing a paused timeline, occlusion of text *and* controls at five widths and twenty-five scroll stops with "never readable" as the failing condition, opened by pointer and by Enter, still under reduced motion. Built on Mosaic's own accordion |
|
|
75
|
-
| **accordion** | `accordion-item` and `accordion-content` sit in the sweep table as BROKE_PAGE; nested as their factory requires they commit and render, **7 of 7**. The note now lives beside the row |
|
|
76
150
|
| **browser** | 3,988 computed-style readings on two delivered pages in Chromium at three viewports: 2,929 compared and agreed, 912 not-comparable and labelled, **0 overridden** |
|
|
77
151
|
| **design audit** | contrast, font fallback, CJK tracking, overflow, clipped text, line measure — run in the browser, **26 findings, every one ruled on in writing** — an acknowledgement without a reason is refused by the release gate |
|
|
78
|
-
| **
|
|
152
|
+
| **components** | the component system driven end to end, **8 of 8**: created under a category, document healed, tree filled through the writable instance, the read-only one refused the same write as a negative control, two instances on a page rendering one definition twice |
|
|
153
|
+
| **style states** | 52 of the 53 states written to a live page and matched against the selector the table promises: **36 compiled exactly**, 12 NO_HOST, 3 SKIPPED, 1 BROKE_PAGE. Pseudo-classes are emitted UPPERCASE (`.M_EL9:HOVER`) |
|
|
154
|
+
| **interactions** | the JS animation path probed with negative controls and the row read back: `propertyMetas` **is** accepted and stored; the property values still do not bind, and the boundary is now exact |
|
|
155
|
+
| **accordion** | `accordion-item` and `accordion-content` sit in the sweep table as BROKE_PAGE; nested as their factory requires they commit and render, **7 of 7** |
|
|
156
|
+
| **entrance animation** | the page-load sequence sampled at fifteen timestamps on a monotonic clock and asserted on eight counts. Costs one late frame over a page with no animation at all, because it waits for the document's first layout |
|
|
157
|
+
| **perpetual animation** | a corner plate that keeps printing itself and opens to full size when tapped, **28 checks**: periodicity by scrubbing a paused timeline, occlusion of text *and* controls at five widths and twenty-five scroll stops with "never readable" as the failing condition, opened by pointer and by Enter, still under reduced motion |
|
|
158
|
+
| **Elementor conversion** | every Elementor page of a production site — 19 pages, 3,292 elements — converted, built and checked against its source: **19 of 19**, 3,281 elements carried, 11 declared. Then the converted page through rwd, browser and the audit, with every finding classified inherited-or-introduced: **0 introduced** |
|
|
159
|
+
| **theme export/import** | two paths, both round-tripped. `theme_export.php` moves rows as JSON over WP-CLI, ids intact. `theme_zip.py` drives Mosaic's **own** ZIP export/import — import lands in test mode unless told `--activate`, because the default is to switch the live site — and **22 checks** hold the copy against the source tree for tree |
|
|
160
|
+
| **the skill itself** | `claude plugin eval .` — five cases a user would ask, three runs each, with and without the skill loaded, three LLM judges a run. **With: 1.00 on all five. Without: 0.00 on all five.** The baseline's best answer was to refuse |
|
|
79
161
|
| **measured live** | 114 REST routes, 151 element classes, 59 condition subjects, 23 tables / 206 columns |
|
|
80
162
|
|
|
81
163
|
`SKIPPED`, `NO_HOST` and `INCONCLUSIVE` are never folded into a pass rate. A sweep
|
|
82
164
|
that scores its own blind spots as successes is the thing this skill argues against.
|
|
83
165
|
|
|
84
|
-
|
|
166
|
+
## What it costs to consult
|
|
167
|
+
|
|
168
|
+
Three ways an agent can learn what a Mosaic node type or style key actually takes,
|
|
169
|
+
priced on the same six tasks with tiktoken (`tools/benchmark_tokens.py`; run it
|
|
170
|
+
yourself):
|
|
171
|
+
|
|
172
|
+
| task | read the source | load every table | `mo.py` query |
|
|
173
|
+
|---|---:|---:|---:|
|
|
174
|
+
| place a heading, a paragraph and a linked button | 10,005 | 259,539 | **961** |
|
|
175
|
+
| set padding, a border and a radius, responsively | 3,490 | 259,539 | **396** |
|
|
176
|
+
| decide whether the accordion is usable, and how to nest it | 15,615 | 259,539 | **397** |
|
|
177
|
+
| find which hover/focus states actually compile | 3,619 | 259,539 | **1,054** |
|
|
178
|
+
| find which Mosaic key drives one CSS property | 1,862 | 259,539 | **51** |
|
|
179
|
+
| know what is unsafe before committing anything | 63,172 | 259,539 | **288** |
|
|
180
|
+
|
|
181
|
+
**71–99.5% fewer tokens than reading the source, 99.6%+ fewer than loading the
|
|
182
|
+
tables** — and the source could not have answered four of the six at all, because
|
|
183
|
+
"declared" and "compiles" are different questions and only the sweeps asked the
|
|
184
|
+
second one. The tables total 259,539 tokens; never load them. `mo.py` is the query.
|
|
185
|
+
|
|
186
|
+
### Results worth knowing before you write anything
|
|
85
187
|
|
|
86
188
|
**A property that belongs to a `group` is inert when set on its own.** Exact in both
|
|
87
189
|
directions: 78 ungrouped properties gave 58 COMPILED and 0 ABSENT; all 20 grouped
|
|
@@ -90,24 +192,66 @@ three instances of one rule, not three oddities. Use the grouped shape — `bord
|
|
|
90
192
|
takes `{width, style, color}` — or `customStyles`.
|
|
91
193
|
|
|
92
194
|
**A breakpoint override can CHANGE a property but never REMOVE one.** Narrow-screen
|
|
93
|
-
`customStyles` that merely omits a border leaves the wide-screen border standing
|
|
94
|
-
|
|
195
|
+
`customStyles` that merely omits a border leaves the wide-screen border standing.
|
|
196
|
+
Say `border-left:0` out loud.
|
|
95
197
|
|
|
96
|
-
|
|
198
|
+
**Only four types take a `url`**: `button`, `menu-link`, `wysiwyg-link`,
|
|
199
|
+
`dropdown-toggle`. On a `text` or an `image` it is accepted, stored, and emits no
|
|
200
|
+
anchor. Wrap the thing in a `menu-link` instead — it takes any children and becomes
|
|
201
|
+
a real `<a href>`.
|
|
202
|
+
|
|
203
|
+
**An image's attachment-protocol path is relative to the uploads directory.**
|
|
204
|
+
`wp-attachment://image/<id>/full/2026/09/pic.png` resolves and carries the
|
|
205
|
+
attachment's width and height. Give it the full `wp-content/uploads/...` path — the
|
|
206
|
+
obvious guess — and Mosaic prefixes the uploads base a second time, silently.
|
|
207
|
+
|
|
208
|
+
## Elementor → Mosaic
|
|
97
209
|
|
|
98
210
|
```bash
|
|
99
|
-
wp
|
|
100
|
-
python tools/
|
|
101
|
-
|
|
102
|
-
python tools/
|
|
103
|
-
|
|
104
|
-
wp eval-file tools/theme_import.php theme.json "Name" rebind activate
|
|
211
|
+
wp post meta get 2360 _elementor_data > page.json
|
|
212
|
+
python tools/from_elementor.py --data page.json --out spec.json --report conv.csv \
|
|
213
|
+
--uploads-base https://site/wp-content/uploads --slug works --post 208
|
|
214
|
+
python tools/build_site.py --config c.json --site spec.json
|
|
215
|
+
python tools/verify_conversion.py --data page.json --url https://site/works/ --report conv.csv
|
|
105
216
|
```
|
|
106
217
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
218
|
+
Scope was decided by counting, not by taste: across a real site's 19 pages,
|
|
219
|
+
container / heading / text-editor / button / html / icon-list / divider / image are
|
|
220
|
+
99.6% of every element present. The long tail — loop grids, forms, countdowns,
|
|
221
|
+
third-party addons — is dynamic and has no node to become; each is reported by
|
|
222
|
+
name and reason, never dropped, and `--strict` refuses to write a lossy spec.
|
|
223
|
+
|
|
224
|
+
Layout, typography, colour, borders, links and images cross over, at all three
|
|
225
|
+
breakpoints (`_tablet`/`_mobile` → `_t`/`_m`). What does not: entrance animations
|
|
226
|
+
(Mosaic's interaction binding is unsolved), shape dividers, gradient overlays. The
|
|
227
|
+
verifier then holds the built page against the source — every string, image, link
|
|
228
|
+
and heading level — and it earned its place at once: it caught the converter losing
|
|
229
|
+
19 of 21 links by writing `url` onto nodes that ignore it.
|
|
230
|
+
|
|
231
|
+
## Tools
|
|
232
|
+
|
|
233
|
+
| tool | does |
|
|
234
|
+
|---|---|
|
|
235
|
+
| `mo.py` | query the measured surface — **the front door** |
|
|
236
|
+
| `build_page.py` / `build_site.py` | commit a spec through the guarded write path; refuses what is measured to break |
|
|
237
|
+
| `from_elementor.py` / `verify_conversion.py` | Elementor → Mosaic, and proof the content arrived |
|
|
238
|
+
| `verify_browser.py` | does the browser compute what the stylesheet promised, and does it pass a design audit |
|
|
239
|
+
| `verify_rwd.py` | does every `_t`/`_m` declaration reach the served stylesheet |
|
|
240
|
+
| `verify_intro.py` / `verify_loop.py` | a page-load sequence that ENDS; a perpetual one that loops, hides nothing, and opens |
|
|
241
|
+
| `theme_export.php` / `theme_import.php` | a whole theme as JSON rows over WP-CLI, ids intact |
|
|
242
|
+
| `theme_zip.py` / `theme_zip_compare.php` / `theme_delete.php` | Mosaic's own ZIP export/import from outside the editor, the copy held against the source, and a clean delete that refuses the live theme |
|
|
243
|
+
| `sweep_*.py` / `probe_*.py` | the instruments the tables were made with |
|
|
244
|
+
| `bootstrap_probe_theme.php` / `mint_session.php` | a licence-free scratch theme and a REST session from WP-CLI |
|
|
245
|
+
|
|
246
|
+
## The worked example
|
|
247
|
+
|
|
248
|
+
`sites/_moksa.py` builds a real studio site through the tables alone and ships as
|
|
249
|
+
the reference: a homepage of 1,286 nodes with a scroll-tracking clause index on named
|
|
250
|
+
view timelines, an entrance sequence that prints an ukiyo-e sheet one carved block
|
|
251
|
+
at a time, a corner plate that keeps printing forever and opens when tapped, and a
|
|
252
|
+
WooCommerce My Account page whose UI arrives through one `code` node running a
|
|
253
|
+
shortcode. No JavaScript of its own anywhere. Every verification table in `data/`
|
|
254
|
+
was produced against it.
|
|
111
255
|
|
|
112
256
|
## Where to start
|
|
113
257
|
|
|
@@ -120,51 +264,21 @@ clause index built on named view timelines and no JavaScript.
|
|
|
120
264
|
|
|
121
265
|
## Releasing
|
|
122
266
|
|
|
123
|
-
One command. The version lives in three places — `package.json`, the SKILL.md
|
|
124
|
-
frontmatter an agent reads, and the frontmatter each platform template writes on
|
|
125
|
-
install — and nothing keeps them together on its own.
|
|
126
|
-
|
|
127
267
|
```bash
|
|
128
|
-
npm version
|
|
268
|
+
npm version minor # bumps package.json, SKILL.md and eight platform templates,
|
|
269
|
+
# commits, tags, pushes; the tag triggers release.yml
|
|
129
270
|
```
|
|
130
271
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
unless the tag matches `package.json`, re-runs the release checks, proves the
|
|
142
|
-
installer runs, prints the tarball, then publishes with provenance and opens a
|
|
143
|
-
GitHub release.
|
|
144
|
-
|
|
145
|
-
`bin/check-release.mjs` is the gate, and it checks the things that are easy to get
|
|
146
|
-
wrong rather than the things that are easy to check:
|
|
147
|
-
|
|
148
|
-
- the three version numbers agree
|
|
149
|
-
- every glob in `files` matches something
|
|
150
|
-
- the row counts in the verification CSVs still equal the numbers SKILL.md quotes
|
|
151
|
-
- `SKIPPED` labels survive into the shipped data, because a sweep that hides its
|
|
152
|
-
blind spots is the failure this skill argues against
|
|
153
|
-
- **the tarball itself is inspected**, not the intent. npm's `files` allowlist
|
|
154
|
-
*overrides* `.gitignore`: naming a directory ships everything inside it, ignored
|
|
155
|
-
or not. Listing `sites/` once put a real client's generator and content into the
|
|
156
|
-
tarball — gitignored, and about to be published anyway.
|
|
157
|
-
|
|
158
|
-
Publishing runs on npm trusted publishing (OIDC): npm trusts this repository's
|
|
159
|
-
`release.yml` directly, so there is no token in the repository's secrets and
|
|
160
|
-
nothing to rotate. Provenance is attached automatically.
|
|
161
|
-
|
|
162
|
-
One-time setup, on npmjs.com under the package's Settings → Trusted Publisher:
|
|
163
|
-
publisher `GitHub Actions`, organisation `Moksa1123`, repository
|
|
164
|
-
`mosaic-headless`, workflow filename `release.yml`, environment name left
|
|
165
|
-
**empty** — the workflow declares no environment, and a value here that the run
|
|
166
|
-
does not match is refused. The connection cannot be edited afterwards, only
|
|
167
|
-
deleted and recreated.
|
|
272
|
+
`bin/check-release.mjs` gates every release on the things that are easy to get
|
|
273
|
+
wrong: the version numbers agree, every `files` glob matches, every verification
|
|
274
|
+
CSV still has the row count SKILL.md and the four READMEs quote, no design-audit
|
|
275
|
+
finding is unreviewed, the eval suite is present, and **the tarball itself is
|
|
276
|
+
inspected** — npm's `files` allowlist overrides `.gitignore`, and once put a real
|
|
277
|
+
client's site into a package that was about to publish.
|
|
278
|
+
|
|
279
|
+
Publishing runs on npm trusted publishing (OIDC): no token anywhere. Setup on
|
|
280
|
+
npmjs.com under the package's Trusted Publisher: GitHub Actions, `Moksa1123` /
|
|
281
|
+
`mosaic-headless`, workflow `release.yml`, environment **empty**.
|
|
168
282
|
|
|
169
283
|
## Licence
|
|
170
284
|
|