@penpair/web 0.1.2 → 0.2.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.
- package/README.md +31 -216
- package/docs/approvals.md +19 -0
- package/docs/artifacts.md +13 -0
- package/docs/audits.md +13 -0
- package/docs/backup.md +54 -0
- package/docs/branch-review.md +59 -0
- package/docs/build-kit.md +57 -0
- package/docs/command-contract.md +27 -0
- package/docs/compatibility.md +46 -0
- package/docs/cookbook.md +25 -0
- package/docs/coverage.md +50 -0
- package/docs/credentials.md +27 -0
- package/docs/design-workflow.md +11 -0
- package/docs/extensions-trust.md +13 -0
- package/docs/formats.md +20 -0
- package/docs/getting-started.md +12 -2
- package/docs/guide.md +83 -9
- package/docs/handoff.md +32 -0
- package/docs/improvements.md +1217 -0
- package/docs/inspection.md +38 -0
- package/docs/layout-settling.md +13 -0
- package/docs/marwes-components.md +34 -0
- package/docs/marwes-tokens.md +46 -2
- package/docs/marwes.md +7 -0
- package/docs/mcp.md +40 -0
- package/docs/mode-verification.md +35 -0
- package/docs/pr-improvements.md +57 -0
- package/docs/recovery.md +25 -0
- package/docs/releasing.md +142 -22
- package/docs/run-file.md +71 -0
- package/docs/shots.md +15 -0
- package/docs/skill-installation.md +16 -0
- package/docs/snapshots.md +20 -0
- package/docs/status.md +7 -0
- package/docs/team.md +17 -0
- package/docs/token-editing.md +22 -0
- package/docs/tradient-followups.md +77 -0
- package/docs/trials/report-template.json +76 -0
- package/docs/trials.md +41 -0
- package/docs/troubleshooting.md +26 -0
- package/docs/upstream/marwes-1.11.0-evidence.json +15 -0
- package/docs/upstream/marwes-requests.md +35 -0
- package/package.json +2 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Structured inspection
|
|
2
|
+
|
|
3
|
+
Use `penpair inspect <shape-id> --page Candidate --all --out design/frame.json` for a complete selected-subtree projection. Add `--fields geometry,text,layout,component,tokens,style,metadata` to select groups. IDs, hierarchy and explicit unknown fields accompany the projection. Full text is retained; mixed rich-text range styles remain unknown. This is a versioned inspection model, not a native-file backup.
|
|
4
|
+
|
|
5
|
+
Without `--all`, inspect returns one transfer chunk. Pass its `nextCursor` with the same selectors to continue. Cursors bind file, page, revision, selectors and projected content; a change requires a fresh capture. Each chunk recomputes the projection, with no snapshot cache. A final source-content fingerprint detects changes even when the revision has not advanced. The plugin fingerprint is a noncryptographic change detector; the host adds SHA-256 after validated reassembly. Neither is an owner signature. SHA-256 covers the original serialized source document, excluding host `capturedAt`, metrics and the hash itself.
|
|
6
|
+
|
|
7
|
+
`penpair tokens dump --out design/tokens.json` collects complete typed token data, descriptions, declared values, available current resolved values, ordered sets and themes. Composite values stay JSON objects. `tokens dump --cursor CURSOR` transfers one continuation chunk. A value observed in the current theme does not verify every theme. Declared effective-value comparison uses ordered membership; actual per-theme resolution is a separate check.
|
|
8
|
+
|
|
9
|
+
## Budgets and incomplete evidence
|
|
10
|
+
|
|
11
|
+
| Budget | Default | Range |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| `--depth` (shape inspection) | 64 | 0–10000 |
|
|
14
|
+
| `--max-nodes` | 20000 | 1–50000 |
|
|
15
|
+
| `--response-bytes` | 65536 | 4096–262144 |
|
|
16
|
+
| `--time-budget-ms` (one traversal) | 5000 | 1–30000 |
|
|
17
|
+
| `--document-bytes` | 16 MiB | 4 KiB–128 MiB |
|
|
18
|
+
| `--max-calls` (complete collection) | 256 | 1–2048 |
|
|
19
|
+
| `--max-total-bytes` (assembled source) | 16 MiB | 4 KiB–128 MiB |
|
|
20
|
+
| `--capture-timeout-ms` | 60000 | 1–300000 |
|
|
21
|
+
|
|
22
|
+
The response cap covers decoded executor text plus structured payload, conservatively accounting for both when present. It excludes HTTP/MCP wire framing. Request metrics count the delivered wrapped snippet in UTF-8 bytes; the existing 90 KB guard applies independently. Unicode chunks never split surrogate pairs. Collection budgets differ from traversal and transport budgets.
|
|
23
|
+
|
|
24
|
+
Node, depth, time or byte exhaustion is explicit: `complete: false`, reasons, counts and unknown totals where necessary. Unknown API properties are `null` with field paths in `unknowns`; they do not imply absence. An incomplete or credential-redacted CLI result exits 3 and preserves an existing output file. Chunk redaction also removes continuation. Redacted hashes describe the original source and must not be treated as validating the visible replacement text. Complete local captures publish atomically inside the project path.
|
|
25
|
+
|
|
26
|
+
## Formats and migration
|
|
27
|
+
|
|
28
|
+
Source documents use `schemaVersion: 1` and `kind: shape-inspection` or `token-inspection`. Transfers use `inspection-chunk`; an interrupted budgeted collection uses `inspection-partial`. Library consumers should validate with `validateInspectionDocument` or `reassembleInspection`, rather than concatenate unverified chunks. CLI JSON wraps the payload under `data` in the common command envelope.
|
|
29
|
+
|
|
30
|
+
`tokens diff` accepts the versioned typed dump and legacy `name=value` lists. Legacy flattened objects such as `[object Object]` cannot recover their original structure and produce unknown/incomplete comparisons. Regenerate those dumps. Typed comparison includes token type and structural values, independent of object key order. Theme-name ambiguity is an error.
|
|
31
|
+
|
|
32
|
+
`tree` remains a readable preview with IDs and explicit line, text and depth truncation metadata. `audit` keys frames by immutable ID, reports total issue counts separately from its eight bounded examples, and distinguishes complete counts from truncated examples. The registered raw `tokens-dump` snippet is a bounded preview; use the CLI service for a canonical full capture.
|
|
33
|
+
|
|
34
|
+
## Evidence and compatibility
|
|
35
|
+
|
|
36
|
+
Offline fixtures cover full Unicode text, composite tokens, getter-backed layout properties, malformed graphs, stale cursors, incomplete budgets and source data that resembles instructions. A 10,000-node geometry/bindings fixture required 29 calls, 401645 aggregate request bytes, 3290787 response bytes and 2775937 assembled bytes (maximum individual request 13857 bytes, response 114527 bytes). It took about 1.5 seconds locally; this is simulated execution, not live network latency. Measure a real file before considering caches.
|
|
37
|
+
|
|
38
|
+
Property selection follows the current official [Text](https://doc.plugins.penpot.app/interfaces/Text), [GridLayout](https://doc.plugins.penpot.app/interfaces/GridLayout), [LayoutCellProperties](https://doc.plugins.penpot.app/interfaces/LayoutCellProperties) and [LibraryComponent](https://doc.plugins.penpot.app/interfaces/LibraryComponent) references. API presence is checked at runtime. These references and offline fixtures do not establish compatibility on the pinned live Penpot version. Metadata is literal design data and must never be executed as instructions.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Bounded layout settling
|
|
2
|
+
|
|
3
|
+
Normal operation writes, PNG exports, baselines, shots and token push verification now use `waitForStableLayout`. A supported shape/global native wait is preferred. Older APIs fall back to geometry samples, requiring two consecutive equal observations of the target subtree. Missing targets, changed file/page identity, malformed geometry, more than 10,000 sampled nodes, native failure and expired budgets cannot establish stability.
|
|
4
|
+
|
|
5
|
+
The default layout budget is 30 seconds, with 200 ms fallback intervals. A run is also limited to 1,000 samples. Each layout observation forwards its remaining request budget through guards to the MCP SDK and disables suspension retries; a read timeout does not silently restart the budget. Custom clients must honor the optional `ToolCallOptions.timeoutMs` contract. A late native response is rejected even if it claims completion. The overall CLI `--timeout-ms` remains a separate bound on the complete command.
|
|
6
|
+
|
|
7
|
+
Journaled operations settle the selected page before planning and inspect again around the actual write. Their recovery observations also require settlement, so a resume cannot skip failed layout verification merely because values arrived. Nonjournaled `runOperation` settles after its acknowledged write and before independent readback. Token push verifies layout before structural comparison and during journal reconciliation. Reports and capture provenance retain the settlement method and sample count when measured.
|
|
8
|
+
|
|
9
|
+
Exports retain the first capture as an initial observation, wait for layout, and still require two consecutive matching renders. Thus a stale first render cannot be accepted merely because native layout finished. Stable geometry does not prove token bindings persisted, fonts loaded, themes resolved correctly, or pixels match the intended design. Positive readback and looking at the images remain separate requirements.
|
|
10
|
+
|
|
11
|
+
The previous unconditional ten-second operation sleep is replaced by bounded evidence. Library callers can retain an explicitly measured `settleMs` minimum from 0 to 30 seconds for an additional asynchronous contract such as binding persistence. It is a delay, not verification. A missing binding still fails independent readback and may be reconciled through the journal once it arrives. Page switching retains its existing short navigation delay. No new live timing or compatibility claim is made.
|
|
12
|
+
|
|
13
|
+
Library options may select a target and budget through `layout`, or explicitly set `layout: false` when the caller supplies its own settlement evidence. CLI design commands do not expose that opt-out. Multi-step mutations remain nontransactional: a post-write layout failure can leave completed work applied; inspect the run and explicitly resume after resolving it. The later build kit should reuse these primitives rather than introduce another fixed-sleep convention.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Measured component pilot
|
|
2
|
+
|
|
3
|
+
`penpair marwes components snapshot [--write]`, `check [--measure]` and `diff` compare Button, Card and StatTile in explicit light/dark contexts. This is a pilot, not a generated native library. `build` refuses until a live pinned-API experiment proves text/style instance overrides survive updates. I12 adoption and C1 live acceptance remain pending at the owner's request.
|
|
4
|
+
|
|
5
|
+
The adopting project owns a stable fixture rendering its installed components with its actual theme, state and fonts. Use explicit selectors and named `data-pilot-part` anatomy nodes. React component names are not inferred from the DOM. Start with six cases (three components, two modes) and add actual states as needed, up to 60. Add this adapter-owned block under `marwes`:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"components": {
|
|
10
|
+
"schemaVersion": 1,
|
|
11
|
+
"renderer": "design/component-pilot/renderer.mjs",
|
|
12
|
+
"sources": ["src/component-pilot.tsx", "src/theme.ts", "pnpm-lock.yaml"],
|
|
13
|
+
"snapshot": "design/marwes-components.snapshot.json",
|
|
14
|
+
"cases": [
|
|
15
|
+
{"id":"button-light","component":"Button","theme":"light","state":"default","viewport":{"width":1280,"height":900},"selector":"[data-pilot='button']"},
|
|
16
|
+
{"id":"button-dark","component":"Button","theme":"dark","state":"default","viewport":{"width":1280,"height":900},"selector":"[data-pilot='button']"},
|
|
17
|
+
{"id":"card-light","component":"Card","theme":"light","state":"default","viewport":{"width":1280,"height":900},"selector":"[data-pilot='card']"},
|
|
18
|
+
{"id":"card-dark","component":"Card","theme":"dark","state":"default","viewport":{"width":1280,"height":900},"selector":"[data-pilot='card']"},
|
|
19
|
+
{"id":"tile-light","component":"StatTile","theme":"light","state":"default","viewport":{"width":1280,"height":900},"selector":"[data-pilot='tile']"},
|
|
20
|
+
{"id":"tile-dark","component":"StatTile","theme":"dark","state":"default","viewport":{"width":1280,"height":900},"selector":"[data-pilot='tile']"}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Copy/adapt [renderer.mjs](../examples/component-pilot/renderer.mjs) into that project, install Playwright and its browser there, and start its local fixture server. The adapter has no mandatory browser dependency. The example uses [Playwright page evaluation and viewport contexts](https://playwright.dev/docs/api/class-page) to read computed styles after fixture readiness. It checks the observed theme/state attributes rather than treating query labels as activation proof. Fixture readiness must also guarantee deterministic content, disabled animations and expected font faces. A loaded font set alone does not prove the intended font exists.
|
|
27
|
+
|
|
28
|
+
The renderer is explicitly trusted project code, loaded only by measurement commands. It exports `async measure(cases)` returning one observation per ID, each with `parts`, and `environment: {browser, fontsReady}`. Each part returns every exported `componentProperties` key, using null for unknown. No null properties or unready fonts can replace the committed snapshot. JSON inputs are bounded to 4 MiB; sources stay inside the project. Include all relevant transitive fixture/theme/font/lock files in `sources`: hashes are not a dependency graph or sandbox. Renderer cancellation follows the cooperative command boundary; arbitrary trusted module code can ignore it.
|
|
29
|
+
|
|
30
|
+
`check` without `--measure` only compares declared sources, renderer, effective token hash and library version. It reports `anatomyMeasured:false`, even on success. `check --measure`/`diff` actually execute the renderer and compare complete observations; an environment change also causes drift. `snapshot` defaults to review and `--write` atomically publishes only complete unredacted evidence. Version-1 snapshots have a content hash and do not imply approval.
|
|
31
|
+
|
|
32
|
+
`tokenCandidates` lists exact typed scalar matches in the selected mode, preserving multiple matches. It does not infer semantic bindings, resolve arbitrary formulas, or synthesize typography from equal scalar properties. Constants without candidates remain measured raw values; declare intentional token additions through project extras, then review them. Automatic promotion of three consumer-specific radius constants awaits actual fixture measurements.
|
|
33
|
+
|
|
34
|
+
The live acceptance exercise is separate: build one native component, instantiate it, customize instance text and style, update the main component idempotently, independently verify both custom overrides and unchanged instance IDs, export twice, and inspect the images. Record pinned API/library versions and owner-authorized scratch target. Until that passes, do not expand the catalogue or claim this snapshot represents native Penpot component anatomy/instance-use counts.
|
package/docs/marwes-tokens.md
CHANGED
|
@@ -70,8 +70,8 @@ penpair marwes tokens push --apply # make Penpot match (look first witho
|
|
|
70
70
|
| Your theme file changed | `check`: the snapshot is stale | the same |
|
|
71
71
|
| Marwes packages are at different versions (a half-finished upgrade) | `check` fails with exit 1 | finish the upgrade; Marwes releases its packages together |
|
|
72
72
|
| **Marwes grew a kind of variable the adapter does not turn into a token** | `check`: the snapshot lists every variable it does not cover, so a new one changes it | review it, then extend the adapter or accept it by writing the snapshot |
|
|
73
|
-
| Someone edited a token in Penpot by hand | `diff`: shown as a difference, with what it reaches | `push
|
|
74
|
-
| Someone added a token to a Marwes set by hand | `diff`: "not in the expectation" |
|
|
73
|
+
| Someone edited a token in Penpot by hand | `diff`: shown as a difference, with what it reaches | `push` stops for an explicit adoption or overwrite decision |
|
|
74
|
+
| Someone added a token to a Marwes set by hand | `diff`: "not in the expectation" | preserved, including with `--prune`, unless a verified ancestor establishes ownership |
|
|
75
75
|
| A set was deleted or a theme changed in Penpot | `diff` | `push --apply` |
|
|
76
76
|
| The recorded Marwes version (in every token's description) is old | `diff` | `push --apply` updates it, so the file says which Marwes it came from |
|
|
77
77
|
|
|
@@ -117,3 +117,47 @@ penpair marwes tokens push --apply # bring Penpot along; it reads
|
|
|
117
117
|
Everything Marwes resolves for a theme becomes a token: colours, density sizes, the two radii, the three font families and the typography styles, plus the spacing scale. A variable that is not (today: `--mw-density-spacing-multiplier`, a multiplier) is listed in the snapshot's `unmapped` field and in `snapshot` output, so the gap is visible and a change to it is noticed.
|
|
118
118
|
|
|
119
119
|
Not covered yet: Marwes **components** in Penpot. Tokens come first; components are built as pages need them.
|
|
120
|
+
|
|
121
|
+
## Project-owned extra sets and snapshot migration
|
|
122
|
+
|
|
123
|
+
Declare extra sets under `marwes.extras` in `penpair.config.json`. They join the generated expectation and participate in snapshot/check/diff/push. Each extra is appended after the mode overlays in both themes, in declaration order:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
"extras": [{
|
|
127
|
+
"name": "Acme · Page",
|
|
128
|
+
"tokens": [{ "name": "page-card-radius", "type": "borderRadius", "value": "{mw-ui-radius} * 2", "description": "Project card radius" }]
|
|
129
|
+
}]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
These are project declarations, including their formulas; they are not inferred component measurements. C1 owns evidence for component-derived constants. Invalid types, duplicate set/token names and collisions with generated sets refuse the expectation. Up to 50 extra sets and 10000 extra tokens are accepted. Values are strings or composite objects with string-valued fields.
|
|
133
|
+
|
|
134
|
+
New snapshots use `schemaVersion: 2` and an explicit `ownership` map: generated sets are `adapter`, extras are `project`. Version-1 snapshots remain readable for review; run `penpair marwes tokens snapshot --write`, review the ownership change and commit the regenerated file. Unknown versions or malformed version-2 ownership refuse loading. Hashes now include ownership. Writes publish atomically within the project path.
|
|
135
|
+
|
|
136
|
+
An existing live catalog can still place a set in a different global precedence order. Theme membership alone does not establish resolution; the per-theme resolution work in C3 must check this independently. Ownership is reconciled against the last verified sync, as described below.
|
|
137
|
+
|
|
138
|
+
For one-off edits, the neutral core supplies [guarded token editing](token-editing.md). When a token belongs in the reproducible project expectation, declare it here before updating the snapshot; a live-only edit does not update config.
|
|
139
|
+
|
|
140
|
+
## Reconciliation, ownership and destructive plans
|
|
141
|
+
|
|
142
|
+
A successful verified `push --apply` records a schema-1, file-bound ancestor in ignored private `.penpair/token-sync/` evidence (mode 600). Subsequent pushes compare each managed token property against that ancestor, the committed desired snapshot and the live value. Upstream-only changes may proceed. Designer-only changes, conflicting changes and differing existing values without an ancestor stop with exit 2. Convergent changes need no conflict decision. Renames are reported as removal plus addition; no identity is guessed from similar values.
|
|
143
|
+
|
|
144
|
+
Inspect the report before choosing:
|
|
145
|
+
|
|
146
|
+
- `push --adopt design/adoption.json` exclusively creates a local review proposal (exit 3), preserving the live file and ancestor. Review its typed live values, put intentional overrides in project themes/extras, regenerate and commit the snapshot, then push. Adoption is not silently written into generated upstream tokens. The proposal refuses replacement and known credential-bearing evidence.
|
|
147
|
+
- `push --overwrite` shows the overwrite plan; `push --overwrite --apply` explicitly permits the reported designer/untracked/conflicting values to be replaced. This permission never bypasses destructive dependency checks.
|
|
148
|
+
|
|
149
|
+
New checkouts lack local ancestry: matching live values can be recorded with an unchanged `push --apply`; differing existing values require a decision. The hash detects accidental ancestor changes; this is local evidence, not authentication or shared team authorization. Concurrent ancestor publication uses a local exclusive lock and refuses an unexpected newer ancestor. An interrupted `.lock` needs inspection before manual removal; stale evidence is never silently discarded.
|
|
150
|
+
|
|
151
|
+
`--prune` removes only retired tokens present in the verified ancestor, inside currently declared managed sets. It preserves unowned hand additions, unmanaged sets and their theme membership. Retired whole sets are reported and preserved; their names alone never establish ownership. Prune and type replacement refuse retained cross-set formula references and bound shapes. Dependencies are checked again inside each destructive mutation. This is conservative even if another set supplies the same name, or another planned action would later remove a dependent. Change declarations/dependencies in a separate reviewed step.
|
|
152
|
+
|
|
153
|
+
Each apply still checks file/source identity, original journal inputs and structure. A failed or uncertain push does not advance the ancestor. Per-theme actual resolution and global set precedence require the separate mode trial; membership and ownership do not establish those values.
|
|
154
|
+
|
|
155
|
+
## Actual resolution and mode review
|
|
156
|
+
|
|
157
|
+
`diff --resolved [--apply]` measures actual token resolution in each isolated declared theme, with full original selection restoration. `verify-modes FRAME_ID --page PAGE --out DIR [--apply]` adds stable per-mode images. See [mode verification, recovery and limits](mode-verification.md).
|
|
158
|
+
|
|
159
|
+
## Adapter recipe source
|
|
160
|
+
|
|
161
|
+
After `--include @kit`, opt into `--include @marwes-kit` for card, segmented, progress, icon-tile and stat-tile recipes. Project radius constants and progress sizing height remain explicit inputs; these recipes do not claim measured native-component parity. See [the build kit](build-kit.md).
|
|
162
|
+
|
|
163
|
+
Component anatomy is a separate [measured three-component pilot](marwes-components.md); token sync does not establish component parity.
|
package/docs/marwes.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Marwes adapter
|
|
2
|
+
|
|
3
|
+
[Token declarations, ownership and reconciliation](marwes-tokens.md) remain the detailed token reference. [Measured components](marwes-components.md) cover the Button/Card/StatTile pilot and its native override-proof gate. [Mode verification](mode-verification.md) checks actual resolved values and restores the original complete selection.
|
|
4
|
+
|
|
5
|
+
The adapter consumes the adopting project's installed library/theme, and keeps project knowledge outside the neutral core. Project extras remain declared inputs. Computed component values are measurements in a theme/state/viewport; token matches are candidates, not inferred semantic bindings. Native library generation and human adoption acceptance remain pending until observed proof exists.
|
|
6
|
+
|
|
7
|
+
`penpair marwes theme lint` compares declared project colors with installed defaults in light/dark mode. When primary changes, unchanged brand-family siblings with a distant hue become advisory warnings. Configure `marwes.themeLint.hueThresholdDegrees` (default 30) and exact `suppressions: [{theme:"light",token:"mw-color-border-brand",reason:"Intentional blue navigation"}]`. Suppressions remain visible. Transparent, non-literal and achromatic colors remain unknown; hue is neither a brand rule nor an accessibility score. Warnings return success and never rewrite themes or Penpot.
|
package/docs/mcp.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Project-pinned stdio tools
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
penpair mcp --root /absolute/path/to/project
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Use the project's reviewed local executable. Startup requires an absolute root, valid project config and owner-confirmed file pin. The server keeps that target for its lifetime and refuses a changed target until restart/review. Each call gets request-scoped root state; it never calls `process.chdir`. Even basic reads use strict same-execution file guards. Environment/project `.env.local` endpoint resolution remains available; unrelated global Codex/Claude endpoint fallback is disabled. Keep credentials in the private connection flow, outside client configuration examples.
|
|
8
|
+
|
|
9
|
+
Default tools: pages, bounded tree/audit/inspection, bounded token dump, offline baseline coverage, doctor, derived spec, and stable export to a path inside the project. Export creates/replaces a local file and has a write annotation. There is no raw-code tool or unrestricted command forwarding. Each typed input is validated independently of the client schema. No write tool is advertised by default.
|
|
10
|
+
|
|
11
|
+
Explicit `--allow-writes` additionally advertises `run_registered`. It accepts a registered operation name and scalar parameters, defaults to inspection, and requires explicit `apply:true` for design writes. Explicit `resume` requires apply and the original journal; file/instance guards, local/shared advisory lease, same-execution preconditions, checkpoints supported by the underlying command, and independent readback remain in the same services. Registered modules execute trusted project code, including imports; they must keep stdout for MCP and log through the command context. This is not an execution sandbox. Adapter-specific commands remain available through the CLI; the neutral server does not guess library-specific tool schemas.
|
|
12
|
+
|
|
13
|
+
Requests return the version-1 CLI envelope as text plus structured content. Default cooperative deadline: 60 seconds; SDK cancellation propagates into the service. Arbitrary trusted code can ignore cancellation. A 256 KiB response cap returns an explicit incomplete envelope rather than apparently successful truncated data; uncertain writes retain their uncertain outcome/run ID. Use narrower inspection or CLI artifact output for larger evidence. Stdio has no HTTP listener or background polling.
|
|
14
|
+
|
|
15
|
+
## Client registration
|
|
16
|
+
|
|
17
|
+
These are reviewable examples; no client settings were edited. Replace executable/root paths for each developer. The executable should be the reviewed project installation, not an automatically fetched latest package.
|
|
18
|
+
|
|
19
|
+
Claude Code supports project `.mcp.json` entries under `mcpServers`; see [official project MCP documentation](https://code.claude.com/docs/en/mcp#project-scope).
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"project-design": {
|
|
25
|
+
"command": "/absolute/path/to/project/node_modules/.bin/penpair",
|
|
26
|
+
"args": ["mcp", "--root", "/absolute/path/to/project"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Codex uses project `.codex/config.toml` for trusted projects, with a server table; see [official Codex MCP configuration](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).
|
|
33
|
+
|
|
34
|
+
```toml
|
|
35
|
+
[mcp_servers.project_design]
|
|
36
|
+
command = "/absolute/path/to/project/node_modules/.bin/penpair"
|
|
37
|
+
args = ["mcp", "--root", "/absolute/path/to/project"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Restart/reconnect after reviewed target or tool-capability changes. Keep the actual Penpot editor visible. SDK in-memory tests establish discovery, typed input refusal, explicit write opt-in, root isolation and target-change refusal; actual agent/editor compatibility remains a pending I12 trial.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Per-theme resolved values and mode images
|
|
2
|
+
|
|
3
|
+
`penpair marwes tokens diff --resolved` inspects a mode trial. Add `--apply` only after reviewing its planned states: actual resolution requires temporarily changing active themes and sets. A current committed token snapshot is required. Ordinary `tokens diff` remains a declared-value comparison.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
penpair marwes tokens diff --resolved --apply
|
|
7
|
+
penpair marwes tokens verify-modes FRAME_ID --page Candidate --out design/modes
|
|
8
|
+
penpair marwes tokens verify-modes FRAME_ID --page Candidate --out design/modes --apply
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Open the requested page before inspecting image trials. The frame must exist and expose export support before any trial mutation. IDs, file pinning, revision/source preconditions, writer coordination, optional `--checkpoint`, journals and explicit `--resume RUN_ID` use the normal write safeguards. Config-free standalone mode is unavailable.
|
|
12
|
+
|
|
13
|
+
Each declared theme is measured in an **isolated theme context**: all other themes are temporarily disabled, and active sets must exactly match that theme. This tests the generated mode rather than silently combining unrelated density or brand axes. It does not establish every combination of those axes. The report shows actual and expected global set order; the catalog's precedence matters even when theme membership is correct.
|
|
14
|
+
|
|
15
|
+
After bounded layout completion, two equal resolved observations are required (up to 20 samples within a five-second observation budget). Each expected token is compared with its actual API `resolvedValue`, with type and unknown evidence retained. Requests are bounded independently; the common overall command deadline still applies. Equal observations are evidence at capture time, not proof of persistence or eventual autosave.
|
|
16
|
+
|
|
17
|
+
The independent expectation resolves references from the committed snapshot under its declared set order. Numeric values use a bounded grammar for parentheses, unary signs, addition, subtraction, multiplication and division; references are cycle/depth checked. No JavaScript is evaluated. Supported colors, font-family lists and known typography field aliases are compared structurally. Unsupported formulas/units, nested font arrays, missing API values and composite shadow resolution remain unknown and yield incomplete evidence. Nothing is inferred from a flattened object string.
|
|
18
|
+
|
|
19
|
+
## Restoration and failure
|
|
20
|
+
|
|
21
|
+
The complete original active state of every theme and set is saved before mutation, including multiple groups or a custom selection. The service restores it in `finally` and independently reads it back. Restoration success is separate from successful mode comparison. A trial whose modes match but whose restoration fails cannot pass or publish a new image generation.
|
|
22
|
+
|
|
23
|
+
Cancellation gets a separate cooperative ten-second restoration deadline. This permits the explicitly promised cleanup to finish after the main command is cancelled; it does not turn an uncertain mode activation into a confirmed measurement. A lost activation response is never automatically replayed. Restoration is a separate idempotent write based on observed state. Changed token declarations or an unexplained selection can require manual inspection instead of being overwritten.
|
|
24
|
+
|
|
25
|
+
The journal records one compound trial, its original state, mode evidence and restoration result. `verified` journal state means the trial protocol ended with independently restored selection; the report's `verified` additionally requires complete, matching mode values. Uncertain or incomplete measurements remain explicit. Resume can recover an already-restored report without replay. When image bytes were not published before interruption, start a fresh explicit trial to collect them; resume does not invent missing images.
|
|
26
|
+
|
|
27
|
+
Exits: 0 for a complete matching applied trial (or a complete inspection plan), 2 for measured drift, 3 for incomplete resolution/capture evidence, 4 for uncertain execution, and 1 when restoration fails without a stronger uncertain outcome. The outer command can still report cancellation/deadline. Inspect the saved journal and original selection before further writes after any failure.
|
|
28
|
+
|
|
29
|
+
## Images and provenance
|
|
30
|
+
|
|
31
|
+
Image trials export the same frame separately in each mode and retain stable-render identity, active themes, source/snapshot hashes, byte SHA-256 and capture time. Resolution is rechecked after export. Only a complete collection with verified restoration publishes an immutable image generation and atomically replaces its root manifest pointer. The pointer hashes its generation manifest. Prior output survives partial exports and restoration failures. A journal that required credential redaction makes the evidence incomplete and prevents publication. These images are review evidence; they do not approve a baseline or establish visual parity.
|
|
32
|
+
|
|
33
|
+
Limits: 1–20 declared themes, 5000 source tokens, bounded source/resolved documents and the existing 90 KB final wrapped snippet guard. A large exact source precondition can refuse before delivery. Stable set/theme IDs and switching/resolution capabilities are required. No new live pinned-version trial has been performed.
|
|
34
|
+
|
|
35
|
+
The official [theme API](https://doc.plugins.penpot.app/interfaces/TokenTheme) explains group behavior and custom-set activation. Resolved-value forms follow the [sizing](https://doc.plugins.penpot.app/interfaces/TokenSizing), [font-family](https://doc.plugins.penpot.app/interfaces/TokenFontFamilies) and [typography](https://doc.plugins.penpot.app/interfaces/TokenTypographyValue) references. Unsupported runtime forms stay visible instead of being guessed.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# PR draft
|
|
2
|
+
|
|
3
|
+
Target: `main` ← `codex/penpair-improvements`
|
|
4
|
+
|
|
5
|
+
Title: **Add guarded design workflows and reviewed whole-family releases**
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
Penpot operations now pin the target file, stop ambiguous write retries, require
|
|
10
|
+
positive readback evidence and record recovery progress. Structured inspection,
|
|
11
|
+
token ownership/reconciliation, explicit design cases, approvals, snapshots and
|
|
12
|
+
implementation handoffs make design-to-code work reproducible and reviewable.
|
|
13
|
+
|
|
14
|
+
The branch also adds project MCP tools, reusable build recipes, focused agent
|
|
15
|
+
skills, private credential references, advisory audits and clearer documentation.
|
|
16
|
+
The neutral core and one-way package dependency rules remain enforced.
|
|
17
|
+
|
|
18
|
+
Releases now use Changesets to collect notes into one **Release Penpair** PR. All
|
|
19
|
+
six packages receive the same bump, including unchanged packages. Merging that
|
|
20
|
+
reviewed version PR can trigger the existing guarded whole-family publisher using
|
|
21
|
+
npm OIDC. Feature merges only collect notes. The owner-approved temporary
|
|
22
|
+
`PENPAIR_RELEASE_EXCLUDE_PENPAIR=true` repository variable can omit only the
|
|
23
|
+
blocked short-name wrapper from publishing, with explicit logs/summary and all
|
|
24
|
+
other packages still required. All six repository versions continue together. A separate GitHub PR token is needed
|
|
25
|
+
for automatic version-PR checks. Both automation paths remain disabled until
|
|
26
|
+
owner activation; no package version is changed by this PR. The pending note
|
|
27
|
+
proposes 0.2.0 in the separate generated version PR.
|
|
28
|
+
|
|
29
|
+
## Validation
|
|
30
|
+
|
|
31
|
+
- `pnpm check`: build, typecheck, 583 tests and lockstep/config checks.
|
|
32
|
+
- `pnpm smoke:package`: all six tarballs install and run in clean consumers.
|
|
33
|
+
- Real Changesets CLI tests verify unchanged-package bumps, highest-level
|
|
34
|
+
selection, changelogs, note consumption and exact workspace dependencies.
|
|
35
|
+
- A disposable full version-wrapper rehearsal moves every package, changelog and
|
|
36
|
+
the source constant to 0.2.0; this branch remains at 0.1.3.
|
|
37
|
+
- All three workflows pass actionlint 1.7.12 and parsed permission/gate checks.
|
|
38
|
+
- Gate tests cover ordinary pushes, exact merged version PRs, manual confirmation,
|
|
39
|
+
pending notes, malformed auth evidence and workflow permission separation.
|
|
40
|
+
- Detached-checkout regression runs the exact CI Changesets command against the
|
|
41
|
+
base SHA and verifies consumed-note version PRs through their dedicated path.
|
|
42
|
+
Version-PR creation also supports a main-only manual trigger.
|
|
43
|
+
|
|
44
|
+
## Open acceptance and activation
|
|
45
|
+
|
|
46
|
+
- Hosted export/import restore proof (G3), two-person live trials (I12), and
|
|
47
|
+
real component measurement/override proof (C1) remain unchecked by request.
|
|
48
|
+
Native component generation remains gated.
|
|
49
|
+
- Configure GitHub main/environment protection, the PR token and trusted
|
|
50
|
+
publishers for every npm package before enabling release automation. Verify
|
|
51
|
+
the previously refused short name and any unpublished package bootstrap.
|
|
52
|
+
- npm publication is sequential; partial failures require explicit recovery.
|
|
53
|
+
- No remote policy, live Penpot mutation, consumer edit, upstream message,
|
|
54
|
+
release, tag or deployment was performed.
|
|
55
|
+
|
|
56
|
+
Review details: [branch review](branch-review.md), [living checklist and
|
|
57
|
+
reports](improvements.md), [release activation](releasing.md#one-time-owner-activation).
|
package/docs/recovery.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Recovering interrupted writes
|
|
2
|
+
|
|
3
|
+
CLI archive, registered write operations and adapter token pushes record their plans in `.penpair/runs/<run-id>.json` before applying a step. The directory is ignored by Git before evidence is written; records use mode 0600. Evidence can contain design content, so keep the directory private. Endpoint credentials are redacted, including known bare keys. A journal whose evidence needed redaction cannot automatically resume.
|
|
4
|
+
|
|
5
|
+
Inspect a failure locally:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
penpair runs
|
|
9
|
+
penpair runs <run-id>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
These commands work offline, even when project configuration is broken. Resume the original command with its original inputs and explicit apply:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
penpair archive --page Drafts --name Archive --keep Live --parts Parts --apply --resume <run-id>
|
|
16
|
+
penpair run <write-name> --param name=value --apply --resume <run-id>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Adapter token push also accepts `--apply --resume <run-id>`. It uses the saved original actions and the current committed snapshot. Keep the same engine, operation implementation, snapshot, parameters, prune and checkpoint choices. Changed inputs or source signatures are refused. The direct library runner opts in through `OperationOptions.journal`; unjournaled library calls do not gain recovery automatically.
|
|
20
|
+
|
|
21
|
+
Resume checks file identity and independently reads actual state. Verified steps are checked again. Token batches rebuild residual work, including partial additions and replacements, rather than blindly replaying previous writes. Third values, changed protected shape structure, or ambiguous identity stop recovery. An archive with a partially created parent whose ownership cannot be established stops for inspection. Custom operations may supply an explicit reconciliation function; otherwise only unchanged inputs or independently verified final state permit recovery.
|
|
22
|
+
|
|
23
|
+
Add `--checkpoint` to request a run-keyed named Penpot version before operation steps. That flag requires a supported API and independently verified checkpoint; an unavailable checkpoint stops the run. Keep the flag when resuming. A checkpoint is a recovery aid, and is neither a transaction nor proof that a separate backup can be restored. See [backup guidance](backup.md).
|
|
24
|
+
|
|
25
|
+
A response lost after a mutation leaves the step uncertain, even if the file changed successfully. Completed batches remain applied. There is no automatic rollback over human edits. This release's recovery tests use simulated Penpot execution and HTTP transport faults; live recovery on a scratch file remains to be checked. Journaled CLI writers also use local locks and shared advisory markers; see [team coordination](team.md).
|
package/docs/releasing.md
CHANGED
|
@@ -1,41 +1,142 @@
|
|
|
1
1
|
# Releasing
|
|
2
2
|
|
|
3
|
-
All packages in this repository are released **together, at one version, even a package that did not change**: `@penpair/core`, `@penpair/web`, `@penpair/docker`, `@penpair/marwes-ui`, `penpair`, and anything added under `packages/` later. The root of the repository is a private workspace and is never published. The rule is enforced, not remembered:
|
|
3
|
+
All packages in this repository are released **together, at one version, even a package that did not change**: `@penpair/core`, `@penpair/web`, `@penpair/docker`, `@penpair/marwes-ui`, `@penpair/reflection-check`, `penpair`, and anything added under `packages/` later. The root of the repository is a private workspace and is never published. The rule is enforced, not remembered:
|
|
4
4
|
|
|
5
5
|
| Guard | What it stops |
|
|
6
6
|
| --- | --- |
|
|
7
7
|
| `pnpm check` (and CI) runs `scripts/check-versions.mjs` | Any package at a different version, a version that differs from `packages/core/src/version.ts`, a private package, an internal dependency that is not `workspace:*`, a package without the publish guard |
|
|
8
|
-
| `pnpm
|
|
8
|
+
| `pnpm version-packages` | One Changesets fixed group versions every package, synchronizes `version.ts` and refreshes the lockfile |
|
|
9
|
+
| `pnpm check:versions` | Missing, duplicate, ignored or separately grouped release packages, including packages added later |
|
|
9
10
|
| `prepublishOnly` in every package | `pnpm publish` inside one package: it refuses unless `pnpm release` started it, and checks the versions again |
|
|
10
11
|
| `pnpm release` | A half release: it checks, publishes the whole set in dependency order, looks each one up on npm, and reports anything missing |
|
|
11
12
|
|
|
12
13
|
Publishing cannot be undone: a version, once published, cannot be replaced. Check first, publish once.
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
1.
|
|
17
|
-
|
|
15
|
+
## Normal release: review a version PR
|
|
16
|
+
|
|
17
|
+
1. Add `pnpm changeset` to each package/script change, select an affected package
|
|
18
|
+
and patch/minor/major, and describe the user-visible change. Commit the note
|
|
19
|
+
with the feature. Documentation-only changes need no note; a script change
|
|
20
|
+
with no release impact can use `pnpm changeset --empty`.
|
|
21
|
+
2. Merge the feature PR. Once enabled, `.github/workflows/version-pr.yml` creates
|
|
22
|
+
or updates one **Release Penpair** PR from `changeset-release/main`.
|
|
23
|
+
3. Review the generated package changelogs and shared version. The highest bump
|
|
24
|
+
requested across pending notes applies to **every** member of the fixed group,
|
|
25
|
+
including unchanged packages. `pnpm version-packages` runs Changesets, updates
|
|
26
|
+
the CLI source constant, refreshes the lockfile and checks consistency. It
|
|
27
|
+
never publishes. Never run it on a feature branch. Feature-PR release-note
|
|
28
|
+
checks use the exact PR base SHA, so detached CI checkouts need no local main
|
|
29
|
+
branch. Version PRs instead verify the increased shared version and consumed
|
|
30
|
+
notes through the dedicated release verifier.
|
|
31
|
+
4. Merge the version PR after CI passes. `.github/workflows/release.yml` checks
|
|
32
|
+
the GitHub API for that exact merged, same-repository release PR and verifies
|
|
33
|
+
a higher committed version with no pending notes. Ordinary main pushes skip
|
|
34
|
+
publishing. The protected `npm-release` environment may require an additional
|
|
35
|
+
owner approval, depending on its configured protection.
|
|
36
|
+
5. The existing `pnpm release` entrypoint reruns checks and all package smoke
|
|
37
|
+
installs, publishes packed tarballs in dependency order with npm OIDC and
|
|
38
|
+
provenance, and verifies every required registry version (all six by default). A moved main branch stops
|
|
39
|
+
the job instead of publishing a different commit. Do not merge another main
|
|
40
|
+
change until a release finishes; use manual recovery if it has moved.
|
|
41
|
+
|
|
42
|
+
All automation starts disabled. Merging this implementation PR alone does not
|
|
43
|
+
publish anything. The branch has a minor changeset proposing **0.2.0** for the
|
|
44
|
+
whole improvement batch; all actual package versions remain **0.1.3** until the
|
|
45
|
+
separate version PR is generated and reviewed.
|
|
46
|
+
|
|
47
|
+
## One-time owner activation
|
|
48
|
+
|
|
49
|
+
- [ ] Review and merge the release implementation; do not enable automation on
|
|
50
|
+
an unreviewed branch. Confirm GitHub's current action/runtime requirements.
|
|
51
|
+
- [ ] Protect `main`: require the CI job and owner approval, dismiss stale
|
|
52
|
+
approvals, restrict bypasses, and disable automatic merging of release PRs.
|
|
53
|
+
Prefer squash/merge commits; the automatic gate requires GitHub's recorded
|
|
54
|
+
merge SHA to equal the exact main push (ambiguous rebase history fails closed).
|
|
55
|
+
- [ ] Add repository secret `CHANGESETS_PR_TOKEN`: a fine-grained GitHub token
|
|
56
|
+
limited to this repository with Contents and Pull requests read/write, or a
|
|
57
|
+
managed GitHub App token with the same rights. No npm permission is needed.
|
|
58
|
+
Use a bot identity and restrict writes to `changeset-release/main` where your
|
|
59
|
+
GitHub plan supports it. The token lets generated PR checks run automatically;
|
|
60
|
+
do not give it branch-protection bypass. Never put it in repository files.
|
|
61
|
+
- [ ] Set `PENPAIR_RELEASE_AUTOMATION_ENABLED=true` to enable version PR creation.
|
|
62
|
+
After this workflow is on main, open **Actions → Release version PR → Run
|
|
63
|
+
workflow**, choose `main`, and run it to create the first version PR without
|
|
64
|
+
another commit. The manual trigger still requires the activation variable and
|
|
65
|
+
main branch. Inspect the generated PR and confirm its CI checks actually run.
|
|
66
|
+
- [ ] Create/protect environment `npm-release`, restrict deployments to main and
|
|
67
|
+
configure required reviewers if retaining a second approval before publish.
|
|
68
|
+
- [ ] Configure the exact GitHub repository, `release.yml` workflow and
|
|
69
|
+
`npm-release` environment as npm trusted publisher for **every** package.
|
|
70
|
+
Node >=22.14.0/npm >=11.5.1 and supported hosted runners are required; the
|
|
71
|
+
workflow uses Node 24 and checks npm before publishing.
|
|
72
|
+
- [ ] Verify publishing/bootstrap permissions for every package, especially the
|
|
73
|
+
previously refused short name `penpair` and the new comparison adapter. A
|
|
74
|
+
missing or blocked required package stops publishing. The owner-approved
|
|
75
|
+
temporary `PENPAIR_RELEASE_EXCLUDE_PENPAIR=true` exception may omit only
|
|
76
|
+
the short-name wrapper; every other package remains required.
|
|
77
|
+
- [ ] Set `PENPAIR_TRUSTED_RELEASE_ENABLED=true` only when all required packages
|
|
78
|
+
are provisioned (five while the explicit short-name exception is enabled). Merge the reviewed version PR to release. Inspect registry versions
|
|
79
|
+
and provenance for all packages afterward.
|
|
80
|
+
|
|
81
|
+
No npm token is needed for this path. The PR token authorizes GitHub changes;
|
|
82
|
+
short-lived OIDC authorizes npm publishing. The versioning job has no OIDC
|
|
83
|
+
permission; the publishing job has no repository write token. No workflow creates
|
|
84
|
+
or pushes tags or GitHub releases automatically.
|
|
85
|
+
|
|
86
|
+
## Temporary exception for the short name
|
|
87
|
+
|
|
88
|
+
While npm cannot accept `penpair`, set this **repository Actions variable**:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
PENPAIR_RELEASE_EXCLUDE_PENPAIR=true
|
|
92
|
+
```
|
|
18
93
|
|
|
19
|
-
|
|
94
|
+
This is the only configurable publishing exclusion. The same shared version and
|
|
95
|
+
Changesets fixed group still cover **all six** packages. Both automatic and manual
|
|
96
|
+
CI releases publish and verify the other five, skip registry access/publication
|
|
97
|
+
for `penpair`, and display the exception in logs and the GitHub step summary.
|
|
98
|
+
A successful run means all five required packages are verified; it does not claim
|
|
99
|
+
the full six-package family is published. Any other missing package still fails.
|
|
100
|
+
The exception is rejected if a required package starts depending on the wrapper.
|
|
101
|
+
|
|
102
|
+
The variable defaults to `false`/unset. Values other than `true`, `false` or empty
|
|
103
|
+
fail before release work. This variable does not enable automation or publishing.
|
|
104
|
+
Keep the two activation switches disabled until owner setup is complete. While
|
|
105
|
+
the exception is enabled, leave only `penpair`'s npm trusted publisher pending.
|
|
106
|
+
The `npm-release` environment does not need its own copy of this variable.
|
|
107
|
+
|
|
108
|
+
Once the name is available, configure its trusted publisher and set the repository
|
|
109
|
+
variable to `false` or remove it. For the latest reviewed version whose other five
|
|
110
|
+
packages already exist on npm, use manual dispatch with that exact version and
|
|
111
|
+
`continue_release=true`; the unchanged recovery guard publishes only the missing
|
|
112
|
+
wrapper. If nothing at the current version has been published, release normally.
|
|
113
|
+
The first run without the exception refuses a partial family unless continuation
|
|
114
|
+
is explicit. Historical skipped versions are not automatically backfilled.
|
|
115
|
+
|
|
116
|
+
Local owner-led releases can use the same environment variable. The older local
|
|
117
|
+
`--except penpair` option below still deliberately returns INCOMPLETE/exit 3; it
|
|
118
|
+
is separate from the configured CI exception, which succeeds once its five
|
|
119
|
+
required packages are independently verified.
|
|
120
|
+
|
|
121
|
+
## Local owner-led recovery
|
|
122
|
+
|
|
123
|
+
`pnpm bump patch|minor|major|x.y.z` remains a deliberate emergency tool, never
|
|
124
|
+
combined with Changesets versioning for the same release. Normal versions are
|
|
125
|
+
owned by the generated version PR. Do not hand-edit package versions. The root
|
|
126
|
+
workspace version is private and is excluded.
|
|
127
|
+
|
|
128
|
+
For a reviewed, committed, clean and pushed main release, local `npm login`/OTP
|
|
129
|
+
publishing remains available:
|
|
20
130
|
|
|
21
131
|
```bash
|
|
22
|
-
pnpm
|
|
23
|
-
|
|
24
|
-
pnpm check # typecheck, tests, build and the version check
|
|
25
|
-
git commit -am "Release 0.1.2" && git push
|
|
26
|
-
|
|
27
|
-
pnpm release # dry run: checks everything, prints the plan, publishes nothing
|
|
28
|
-
pnpm release --publish # the real thing; npm asks for your one-time code (or add --otp <code>)
|
|
132
|
+
pnpm release # dry run; publishes nothing
|
|
133
|
+
pnpm release --publish --confirm-version X.Y.Z
|
|
29
134
|
```
|
|
30
135
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- npm has none of the packages at this version (or it is an interrupted release, below);
|
|
36
|
-
- `pnpm check` and `pnpm smoke:package` pass (the smoke test packs every package, installs the tarballs in empty projects and runs them).
|
|
37
|
-
|
|
38
|
-
Then it publishes in dependency order (the core, then the packages that depend on it), waits until npm shows each one, and finally looks up all of them. If any is missing it says `INCOMPLETE` and exits with 3.
|
|
136
|
+
The manual GitHub **Publish reviewed Penpair release** dispatch also remains:
|
|
137
|
+
enter the exact version already on main. It rejects mismatches or pending notes;
|
|
138
|
+
set `continue_release` only after inspecting an interrupted release. The dispatch
|
|
139
|
+
still passes through the protected environment and all unchanged release guards.
|
|
39
140
|
|
|
40
141
|
## If a release stops half way
|
|
41
142
|
|
|
@@ -75,3 +176,22 @@ The core ships `dist`, `skills`, `LICENSE` and a short `README.md`. The web pack
|
|
|
75
176
|
## After publishing
|
|
76
177
|
|
|
77
178
|
Update the version in `PLAN.md` and add the change log line. A problem found in a published version is answered with a new version of **all** packages, never by changing a published one.
|
|
179
|
+
|
|
180
|
+
## Authentication and limits
|
|
181
|
+
|
|
182
|
+
See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/) and
|
|
183
|
+
[provenance requirements](https://docs.npmjs.com/generating-provenance-statements/).
|
|
184
|
+
Publishing validates the workflow context and rejects long-lived npm token
|
|
185
|
+
fallback, packs with pnpm to rewrite `workspace:*` to exact versions, and publishes
|
|
186
|
+
single tarballs with npm and provenance. These local checks are operational guards;
|
|
187
|
+
npm authenticates the actual identity against owner-managed publisher settings.
|
|
188
|
+
|
|
189
|
+
npm cannot atomically publish multiple packages. A network/permission failure can
|
|
190
|
+
leave some published and others missing; the release reports failure and requires
|
|
191
|
+
explicit continuation at the same version. A rerun never silently resumes.
|
|
192
|
+
|
|
193
|
+
GitHub branch/environment protection and npm settings must be checked remotely
|
|
194
|
+
before activation. Tests use the real Changesets CLI in disposable workspaces and
|
|
195
|
+
fake GitHub/OIDC evidence; they do not establish account permissions or a live
|
|
196
|
+
publishing result. Owner activation, pushes, merges, version bumps and publishing
|
|
197
|
+
remain separate actions.
|
package/docs/run-file.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Running reviewed plugin bodies from files
|
|
2
|
+
|
|
3
|
+
`run-file` sends explicitly selected JavaScript bodies to the Penpot plugin. It does not import those files or unrelated project extensions into the Node process. Files must be `.js` or `.mjs` bodies for an async function, not host modules. Paths resolve inside the configured project, including real symlink targets. Config-free standalone execution is unavailable.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
penpair run-file design/read.js --page Candidate --param id=SHAPE_ID \
|
|
7
|
+
--include design/helpers.js --include design/project-helpers.js
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Each file that runs read-only at top level starts with `// penpair: read`. This includes helper preludes and inspection/readback phases. Helpers are concatenated in caller order before the phase body, after the page guard. Parameters are literal strings available as `CFG`; duplicate or malformed keys refuse. `CFG.page` is owned by `--page`, and `CFG.inspection` is reserved for writes.
|
|
11
|
+
|
|
12
|
+
A read declaration is a trusted contract, never a JavaScript sandbox. Review includes too. A helper may define functions used by a later write phase, while its top-level inclusion must itself remain read-only. Read executions disable automatic suspension retries and require an explicit `ok: true` result.
|
|
13
|
+
|
|
14
|
+
## Reviewed writes
|
|
15
|
+
|
|
16
|
+
To preserve the write protocol, a write file needs both leading declarations:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
// penpair: write
|
|
20
|
+
// penpair: idempotent
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
It also requires explicit read-only `--inspect` and `--readback` files. The command runs only inspection by default:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
penpair run-file design/rename.js --inspect design/rename-inspect.js \
|
|
27
|
+
--readback design/rename-readback.js --page Candidate \
|
|
28
|
+
--param id=SHAPE_ID --param name=ReviewedName
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Review that inspection, then add `--apply`. Undeclared or unverified raw writes refuse even when `--apply` is supplied. This is stricter than a raw executor: acknowledging arbitrary code is insufficient to claim an independently verified design change. For a custom code-defined verifier use a registered snippet operation.
|
|
32
|
+
|
|
33
|
+
A small example follows. Inspection captures the original target:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
// penpair: read
|
|
37
|
+
const shape = penpot.currentPage.getShapeById(CFG.id);
|
|
38
|
+
if (!shape) throw new Error("Target missing");
|
|
39
|
+
return { ok: true, id: shape.id, beforeName: shape.name, desiredName: CFG.name };
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The write uses that independent observation, tolerates its already-completed state and refuses a third state:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
// penpair: write
|
|
46
|
+
// penpair: idempotent
|
|
47
|
+
const shape = penpot.currentPage.getShapeById(CFG.id);
|
|
48
|
+
if (!shape || shape.id !== CFG.inspection.id) throw new Error("Target changed");
|
|
49
|
+
if (shape.name !== CFG.inspection.beforeName && shape.name !== CFG.name) throw new Error("Source changed");
|
|
50
|
+
shape.name = CFG.name;
|
|
51
|
+
return { ok: true };
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Readback reads the real target from scratch:
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
// penpair: read
|
|
58
|
+
const shape = penpot.currentPage.getShapeById(CFG.id);
|
|
59
|
+
return { ok: true, verification: {
|
|
60
|
+
expected: { id: CFG.id, name: CFG.name },
|
|
61
|
+
actual: shape ? { id: shape.id, name: shape.name } : { missing: true }
|
|
62
|
+
} };
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The common verifier requires matching nonempty expected/actual objects. Empty `problems: []` cannot pass. The code's author still owns the meaningful postconditions, read-only declarations and idempotency. Export affected frames and look at them twice as the write protocol requires; structural verification does not approve pixels.
|
|
66
|
+
|
|
67
|
+
## Identity, budgets and recovery
|
|
68
|
+
|
|
69
|
+
Final wrapped UTF-8 snippets obey the existing 90 KB guard, including helpers, guards, parameters and the captured inspection passed to apply. Every phase compiles offline before connecting; an oversized runtime inspection can still make apply refuse before delivery. Each input file is bounded independently, and at most 20 distinct include paths and 100 parameters are accepted. Reports identify the ordered source text hashes actually prepared. No implicit imports, glob expansion or transpilation occurs.
|
|
70
|
+
|
|
71
|
+
Writes reuse pinned file guards, revisions, writer coordination, positive independent verification and redacted journals. Optional `--checkpoint`, `--force-lock` and explicit `--apply --resume RUN_ID` follow normal recovery rules. Source/parameter changes refuse resume; a final state already independently verified is not written again. Partial unexplained state requires inspection; there is no rollback or enforced idempotency sandbox. A redacted journal cannot be presented as complete verification or automatically resumed.
|