@penpair/core 0.2.0 → 0.3.1
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/dist/adapters.js +58 -11
- package/dist/approvals.js +143 -29
- package/dist/atomic-output.js +9 -4
- package/dist/audit-rules.js +345 -79
- package/dist/backup-note.js +29 -8
- package/dist/baseline-coverage.js +169 -35
- package/dist/baselines-config.js +119 -29
- package/dist/baselines.js +280 -57
- package/dist/cli-arguments.js +18 -6
- package/dist/cli-main.js +1645 -392
- package/dist/client.d.ts +5 -2
- package/dist/client.js +127 -28
- package/dist/command-result.js +341 -52
- package/dist/coordination.js +91 -29
- package/dist/credentials.js +57 -13
- package/dist/deep-check.js +56 -12
- package/dist/design-spec.js +189 -49
- package/dist/endpoint.js +102 -34
- package/dist/export.js +86 -18
- package/dist/file-archive.js +33 -7
- package/dist/file-guard.js +29 -11
- package/dist/formats.js +215 -14
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/inspection.d.ts +1 -1
- package/dist/inspection.js +378 -65
- package/dist/instance/config-schema.js +400 -41
- package/dist/instance/config.js +101 -30
- package/dist/instance/connect.js +151 -44
- package/dist/instance/connection.d.ts +26 -5
- package/dist/instance/connection.js +194 -21
- package/dist/instance/gitignore.js +5 -2
- package/dist/instance/init.js +32 -7
- package/dist/instance/runner.js +32 -10
- package/dist/instance/setup.js +46 -13
- package/dist/instance/tty.js +26 -8
- package/dist/journal.d.ts +10 -0
- package/dist/journal.js +212 -49
- package/dist/kit-smoke.js +82 -20
- package/dist/kit-source.js +10 -40
- package/dist/kit.js +102 -34
- package/dist/layout-policy.d.ts +2 -0
- package/dist/layout-policy.js +86 -0
- package/dist/layout.d.ts +7 -1
- package/dist/layout.js +27 -70
- package/dist/live-smoke.js +13 -4
- package/dist/local-profile.js +2 -1
- package/dist/mcp-server.js +360 -47
- package/dist/native-api.js +47 -11
- package/dist/onboard.js +33 -7
- package/dist/operation.d.ts +2 -2
- package/dist/operation.js +279 -69
- package/dist/page-session.d.ts +23 -0
- package/dist/page-session.js +142 -0
- package/dist/png.js +18 -3
- package/dist/portable-tokens.js +254 -57
- package/dist/probe.d.ts +1 -1
- package/dist/probe.js +48 -11
- package/dist/project-input.js +24 -10
- package/dist/project-snippets.js +18 -6
- package/dist/project-status.js +133 -24
- package/dist/registry.js +217 -60
- package/dist/run-file.js +129 -30
- package/dist/shots.js +108 -26
- package/dist/skill-check-entry.d.ts +17 -0
- package/dist/skill-check-entry.js +89 -0
- package/dist/skill-check-instructions.d.ts +7 -0
- package/dist/skill-check-instructions.js +39 -0
- package/dist/skill-check.d.ts +15 -0
- package/dist/skill-check.js +36 -0
- package/dist/skill-file.d.ts +2 -0
- package/dist/skill-file.js +31 -0
- package/dist/skill-instructions.d.ts +4 -0
- package/dist/skill-instructions.js +97 -22
- package/dist/skill-metadata.d.ts +8 -0
- package/dist/skill-metadata.js +52 -0
- package/dist/skill-routing.d.ts +2 -0
- package/dist/skill-routing.js +36 -0
- package/dist/skills.js +131 -41
- package/dist/snippet.js +27 -7
- package/dist/snippets/read.js +8 -2
- package/dist/structure-snapshot.d.ts +12 -0
- package/dist/structure-snapshot.js +439 -85
- package/dist/token-edit.js +219 -64
- package/dist/token-projection.d.ts +56 -0
- package/dist/token-projection.js +286 -0
- package/dist/tokens-diff.js +125 -32
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/skills/design-judgment/SKILL.md +1 -1
- package/skills/design-judgment/agents/openai.yaml +6 -0
- package/skills/penpair-design/SKILL.md +14 -1
- package/skills/penpair-design/agents/openai.yaml +6 -0
- package/skills/penpair-design/references/penpot-plugin-gotchas.md +2 -2
- package/skills/penpair-design/references/project-facts.md +2 -0
- package/skills/penpair-handoff/SKILL.md +14 -1
- package/skills/penpair-handoff/agents/openai.yaml +6 -0
- package/skills/penpair-handoff/references/comparison-companion.md +16 -0
- package/skills/penpair-handoff/references/handoff-flow.md +2 -0
- package/skills/penpair-recipes/SKILL.md +13 -1
- package/skills/penpair-recipes/agents/openai.yaml +6 -0
- package/skills/penpair-recipes/assets/visual-checkpoint.md +48 -0
- package/skills/penpair-recipes/references/visual-checkpoint.md +37 -0
- package/skills/penpair-setup/SKILL.md +9 -3
- package/skills/penpair-setup/agents/openai.yaml +6 -0
- package/skills/penpair-verify/SKILL.md +49 -0
- package/skills/penpair-verify/agents/openai.yaml +6 -0
- package/skills/penpair-verify/references/browser-evidence.md +33 -0
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -3
- package/dist/docker/lifecycle.d.ts +0 -96
- package/dist/docker/lifecycle.js +0 -273
- package/dist/docker/secrets.d.ts +0 -11
- package/dist/docker/secrets.js +0 -38
- package/dist/docker/template.d.ts +0 -8
- package/dist/docker/template.js +0 -158
- package/dist/docker-profile.d.ts +0 -114
- package/dist/docker-profile.js +0 -18
- package/dist/instance/lifecycle.d.ts +0 -129
- package/dist/instance/lifecycle.js +0 -336
- package/dist/instance/secrets.d.ts +0 -11
- package/dist/instance/secrets.js +0 -38
- package/dist/instance/template.d.ts +0 -8
- package/dist/instance/template.js +0 -158
package/dist/export.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withPageSession } from "./page-session.js";
|
|
1
2
|
import { waitForStableLayout } from "./layout.js";
|
|
2
3
|
import { PenpairError } from "./errors.js";
|
|
3
4
|
import { commandSleep } from "./command-result.js";
|
|
@@ -19,34 +20,76 @@ const afterShape = penpot.currentPage.getShapeById(CFG.id);
|
|
|
19
20
|
if (penpot.currentFile.id !== capture.fileId || penpot.currentPage.id !== capture.pageId || penpot.currentFile.revn !== capture.revision || !afterShape || afterShape.id !== capture.frameId || JSON.stringify([afterShape.x,afterShape.y,afterShape.width,afterShape.height]) !== geometry)
|
|
20
21
|
return {ok:false, message:"Export source changed during capture"};
|
|
21
22
|
let bin = ""; for (let i = 0; i < bytes.length; i += 8192) bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192));
|
|
22
|
-
return { ok: true, base64: btoa(bin), length: bytes.length, capture };`, {
|
|
23
|
+
return { ok: true, base64: btoa(bin), length: bytes.length, capture };`, {
|
|
24
|
+
id: shapeId,
|
|
25
|
+
scale,
|
|
26
|
+
page,
|
|
27
|
+
}, "read");
|
|
23
28
|
const defaultSleep = commandSleep;
|
|
24
29
|
/** Exports one shape as PNG. A first call can fail once and then work; this retries, and by default waits for a stable render. */
|
|
25
30
|
export async function exportShapePng(client, shapeId, options = {}) {
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
return withPageSession(client, () => exportInSession(client, shapeId, options));
|
|
32
|
+
}
|
|
33
|
+
async function exportInSession(client, shapeId, options) {
|
|
34
|
+
const { page, scale = 1, attempts = 3, stable = true, maxExports = 4, sleep = defaultSleep, } = options;
|
|
35
|
+
if (!Number.isFinite(scale) ||
|
|
36
|
+
scale <= 0 ||
|
|
37
|
+
scale > 8 ||
|
|
38
|
+
!Number.isInteger(attempts) ||
|
|
39
|
+
attempts < 1 ||
|
|
40
|
+
attempts > 10 ||
|
|
41
|
+
!Number.isInteger(maxExports) ||
|
|
42
|
+
maxExports < (stable ? 2 : 1) ||
|
|
43
|
+
maxExports > 20) {
|
|
28
44
|
throw new Error("Invalid export scale/attempt limits");
|
|
45
|
+
}
|
|
29
46
|
const once = async () => {
|
|
30
47
|
for (let attempt = 1;; attempt++) {
|
|
31
48
|
try {
|
|
32
49
|
const record = await runOnPage(client, exportSnippet(shapeId, scale, page), page, options);
|
|
33
50
|
const capture = record.capture;
|
|
34
|
-
if (!capture ||
|
|
51
|
+
if (!capture ||
|
|
52
|
+
typeof capture.fileId !== "string" ||
|
|
53
|
+
!capture.fileId ||
|
|
54
|
+
typeof capture.pageId !== "string" ||
|
|
55
|
+
!capture.pageId ||
|
|
56
|
+
capture.frameId !== shapeId ||
|
|
57
|
+
!Number.isInteger(capture.revision) ||
|
|
58
|
+
capture.scale !== scale ||
|
|
59
|
+
!Array.isArray(capture.activeThemes)) {
|
|
35
60
|
throw new Error("Export did not return target provenance");
|
|
36
|
-
|
|
61
|
+
}
|
|
62
|
+
if (capture.designSize &&
|
|
63
|
+
![
|
|
64
|
+
capture.designSize.width,
|
|
65
|
+
capture.designSize.height,
|
|
66
|
+
].every((value) => Number.isFinite(value) && value >= 0)) {
|
|
37
67
|
throw new Error("Export has malformed design dimensions");
|
|
38
|
-
|
|
68
|
+
}
|
|
69
|
+
if (capture.activeThemeIds !== undefined &&
|
|
70
|
+
capture.activeThemeIds !== null &&
|
|
71
|
+
(!Array.isArray(capture.activeThemeIds) ||
|
|
72
|
+
capture.activeThemeIds.some((id) => typeof id !== "string" || !id) ||
|
|
73
|
+
new Set(capture.activeThemeIds).size !== capture.activeThemeIds.length)) {
|
|
39
74
|
throw new Error("Export has malformed active theme identity");
|
|
40
|
-
|
|
75
|
+
}
|
|
76
|
+
if (typeof record.base64 !== "string" ||
|
|
77
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(record.base64)) {
|
|
41
78
|
throw new Error("Invalid export byte encoding");
|
|
79
|
+
}
|
|
42
80
|
const png = Buffer.from(record.base64, "base64");
|
|
43
|
-
if (!png.length || png.length !== record.length)
|
|
81
|
+
if (!png.length || png.length !== record.length) {
|
|
44
82
|
throw new Error("Export byte length does not match its evidence");
|
|
45
|
-
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
png,
|
|
86
|
+
capture,
|
|
87
|
+
};
|
|
46
88
|
}
|
|
47
89
|
catch (error) {
|
|
48
|
-
if (attempt >= attempts)
|
|
90
|
+
if (attempt >= attempts) {
|
|
49
91
|
throw error;
|
|
92
|
+
}
|
|
50
93
|
await sleep(5_000);
|
|
51
94
|
}
|
|
52
95
|
}
|
|
@@ -54,22 +97,47 @@ export async function exportShapePng(client, shapeId, options = {}) {
|
|
|
54
97
|
let previous = await once();
|
|
55
98
|
let layout;
|
|
56
99
|
if (options.layout !== false) {
|
|
57
|
-
layout = await waitForStableLayout(client, {
|
|
58
|
-
|
|
100
|
+
layout = await waitForStableLayout(client, {
|
|
101
|
+
shapeId,
|
|
102
|
+
sleep,
|
|
103
|
+
...options.layout,
|
|
104
|
+
});
|
|
105
|
+
if (!layout.stable) {
|
|
59
106
|
throw new PenpairError("incomplete-output", `Layout did not settle before export: ${layout.reason ?? "unknown reason"}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!stable) {
|
|
110
|
+
return {
|
|
111
|
+
layout,
|
|
112
|
+
...previous,
|
|
113
|
+
stable: false,
|
|
114
|
+
exports: 1,
|
|
115
|
+
};
|
|
60
116
|
}
|
|
61
|
-
if (!stable)
|
|
62
|
-
return { layout, ...previous, stable: false, exports: 1 };
|
|
63
117
|
const hash = (buffer) => createHash("sha256").update(buffer).digest("hex");
|
|
64
118
|
for (let count = 2; count <= maxExports; count++) {
|
|
65
119
|
await sleep(2_000);
|
|
66
120
|
const next = await once();
|
|
67
121
|
const identity = ({ designSize: _size, ...value }) => value;
|
|
68
|
-
if (JSON.stringify(identity(next.capture)) !== JSON.stringify(identity(previous.capture)))
|
|
122
|
+
if (JSON.stringify(identity(next.capture)) !== JSON.stringify(identity(previous.capture))) {
|
|
69
123
|
throw new Error("Export identity, revision or render environment changed between captures");
|
|
70
|
-
|
|
71
|
-
|
|
124
|
+
}
|
|
125
|
+
if (JSON.stringify(next.capture.designSize ?? null) ===
|
|
126
|
+
JSON.stringify(previous.capture.designSize ?? null) &&
|
|
127
|
+
hash(next.png) === hash(previous.png)) {
|
|
128
|
+
return {
|
|
129
|
+
layout,
|
|
130
|
+
...next,
|
|
131
|
+
stable: true,
|
|
132
|
+
exports: count,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
72
135
|
previous = next;
|
|
73
136
|
}
|
|
74
|
-
return {
|
|
137
|
+
return {
|
|
138
|
+
layout,
|
|
139
|
+
...previous,
|
|
140
|
+
stable: false,
|
|
141
|
+
exports: maxExports,
|
|
142
|
+
};
|
|
75
143
|
}
|
package/dist/file-archive.js
CHANGED
|
@@ -4,8 +4,9 @@ import { redactEndpoint } from "./endpoint.js";
|
|
|
4
4
|
/** Full-fidelity recovery is established by import/readback, not by receiving archive bytes. */
|
|
5
5
|
export async function exportFileArchive(client, options = {}) {
|
|
6
6
|
const maxBytes = options.maxBytes ?? 512 * 1024;
|
|
7
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > 16 * 1024 * 1024)
|
|
7
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || maxBytes > 16 * 1024 * 1024) {
|
|
8
8
|
throw new Error("Archive budget must be positive and at most 16 MiB");
|
|
9
|
+
}
|
|
9
10
|
const result = await runSnippet(client, snippet("file-archive", `
|
|
10
11
|
const file = penpot.currentFile, fileId = file.id, revn = file.revn;
|
|
11
12
|
if (typeof file.export !== "function") return { ok: true, status: "unsupported", message: "File export is unavailable; use Penpot's File export menu" };
|
|
@@ -17,16 +18,41 @@ if (!bytes || !Number.isSafeInteger(bytes.length) || bytes.length === 0) return
|
|
|
17
18
|
if (bytes.length > CFG.maxBytes) return { ok: true, status: "too-large", message: "Archive exceeds the configured response budget; export it manually" };
|
|
18
19
|
let binary = "";
|
|
19
20
|
for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192));
|
|
20
|
-
return { ok: true, status: "exported", fileId, revn, length: bytes.length, base64: btoa(binary) };`, {
|
|
21
|
+
return { ok: true, status: "exported", fileId, revn, length: bytes.length, base64: btoa(binary) };`, {
|
|
22
|
+
maxBytes,
|
|
23
|
+
}, "read"));
|
|
21
24
|
if (result.status !== "exported") {
|
|
22
|
-
if (![
|
|
25
|
+
if (![
|
|
26
|
+
"unsupported",
|
|
27
|
+
"failed",
|
|
28
|
+
"too-large",
|
|
29
|
+
"changed",
|
|
30
|
+
].includes(String(result.status)) ||
|
|
31
|
+
typeof result.message !== "string") {
|
|
23
32
|
throw new Error("Archive response contained no valid outcome");
|
|
24
|
-
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
status: result.status,
|
|
36
|
+
message: redactEndpoint(result.message),
|
|
37
|
+
};
|
|
25
38
|
}
|
|
26
|
-
if (result.ok !== true ||
|
|
39
|
+
if (result.ok !== true ||
|
|
40
|
+
typeof result.fileId !== "string" ||
|
|
41
|
+
!Number.isInteger(result.revn) ||
|
|
42
|
+
typeof result.base64 !== "string" ||
|
|
43
|
+
!/^[A-Za-z0-9+/]*={0,2}$/.test(result.base64)) {
|
|
27
44
|
throw new Error("Archive response contained incomplete metadata");
|
|
45
|
+
}
|
|
28
46
|
const bytes = Buffer.from(result.base64, "base64");
|
|
29
|
-
if (bytes.length === 0 || bytes.length > maxBytes || bytes.length !== result.length)
|
|
47
|
+
if (bytes.length === 0 || bytes.length > maxBytes || bytes.length !== result.length) {
|
|
30
48
|
throw new Error("Archive response was empty, truncated or over budget");
|
|
31
|
-
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
status: "exported",
|
|
52
|
+
bytes,
|
|
53
|
+
fileId: result.fileId,
|
|
54
|
+
revn: Number(result.revn),
|
|
55
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
56
|
+
restoreVerified: false,
|
|
57
|
+
};
|
|
32
58
|
}
|
package/dist/file-guard.js
CHANGED
|
@@ -1,40 +1,58 @@
|
|
|
1
|
+
import { inheritPageSession } from "./page-session.js";
|
|
1
2
|
import { executionEnvelope, maxSnippetBytes } from "./client.js";
|
|
2
3
|
import { PenpairError } from "./errors.js";
|
|
3
4
|
/** A guard in the delivered execution closes the gap between a connection check and a later batch. */
|
|
4
5
|
export function guardFile(client, expected, options = {}) {
|
|
5
6
|
if (!expected || options.anyFile) {
|
|
6
|
-
options.warn?.(options.anyFile
|
|
7
|
+
options.warn?.(options.anyFile
|
|
8
|
+
? "--any-file overrides the project's file guard for this run."
|
|
9
|
+
: "No file ID is pinned. Run `penpair connect --pin-file` to protect this project's file.");
|
|
7
10
|
return client;
|
|
8
11
|
}
|
|
9
12
|
const warningPrefix = "penpair-file-warning: ";
|
|
10
|
-
return {
|
|
13
|
+
return inheritPageSession({
|
|
11
14
|
...client,
|
|
12
15
|
close: () => client.close(),
|
|
13
|
-
...(client.listTools
|
|
16
|
+
...(client.listTools
|
|
17
|
+
? {
|
|
18
|
+
listTools: () => client.listTools(),
|
|
19
|
+
}
|
|
20
|
+
: {}),
|
|
14
21
|
async callTool(name, args, callOptions) {
|
|
15
|
-
if (name !== "execute_code" || typeof args.code !== "string")
|
|
22
|
+
if (name !== "execute_code" || typeof args.code !== "string") {
|
|
16
23
|
throw new PenpairError("file-mismatch", "The file guard only permits guarded execute_code calls.");
|
|
24
|
+
}
|
|
17
25
|
const guard = `{ const __penpairExpectedFile = ${JSON.stringify(expected)};
|
|
18
26
|
const __penpairActualFile = penpot.currentFile;
|
|
19
27
|
if (!__penpairActualFile || __penpairActualFile.id !== __penpairExpectedFile.id) {
|
|
20
28
|
const message = "Expected file " + (__penpairExpectedFile.name || __penpairExpectedFile.id) + " (" + __penpairExpectedFile.id + "), connected to " + (__penpairActualFile ? __penpairActualFile.name + " (" + __penpairActualFile.id + ")" : "no file") + ". Open the expected file and reconnect, or explicitly use --any-file.";
|
|
21
|
-
${options.mode === "warn" ? `console.warn(${JSON.stringify(warningPrefix)} + message);` :
|
|
29
|
+
${options.mode === "warn" ? `console.warn(${JSON.stringify(warningPrefix)} + message);` : "return { ok: false, fileMismatch: true, message };"}
|
|
22
30
|
} }
|
|
23
31
|
`;
|
|
24
32
|
const code = guard + args.code;
|
|
25
|
-
if (Buffer.byteLength(code, "utf8") > maxSnippetBytes)
|
|
33
|
+
if (Buffer.byteLength(code, "utf8") > maxSnippetBytes) {
|
|
26
34
|
throw new PenpairError("snippet-too-large", "The snippet including its file guard exceeds 90 KB");
|
|
27
|
-
|
|
35
|
+
}
|
|
36
|
+
const outcome = await client.callTool(name, {
|
|
37
|
+
...args,
|
|
38
|
+
code,
|
|
39
|
+
}, callOptions);
|
|
28
40
|
const envelope = executionEnvelope(outcome);
|
|
29
41
|
const result = envelope?.result;
|
|
30
|
-
if (typeof result === "object" &&
|
|
42
|
+
if (typeof result === "object" &&
|
|
43
|
+
result !== null &&
|
|
44
|
+
"fileMismatch" in result &&
|
|
45
|
+
result.fileMismatch === true) {
|
|
31
46
|
throw new PenpairError("file-mismatch", String("message" in result ? result.message : "The connected file changed"));
|
|
47
|
+
}
|
|
32
48
|
if (typeof envelope?.log === "string") {
|
|
33
|
-
for (const line of envelope.log.split("\n"))
|
|
34
|
-
if (line.includes(warningPrefix))
|
|
49
|
+
for (const line of envelope.log.split("\n")) {
|
|
50
|
+
if (line.includes(warningPrefix)) {
|
|
35
51
|
options.warn?.(line.slice(line.indexOf(warningPrefix) + warningPrefix.length));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
36
54
|
}
|
|
37
55
|
return outcome;
|
|
38
56
|
},
|
|
39
|
-
};
|
|
57
|
+
}, client);
|
|
40
58
|
}
|
package/dist/formats.js
CHANGED
|
@@ -1,28 +1,229 @@
|
|
|
1
1
|
import { projectConfigSchema } from "./instance/config-schema.js";
|
|
2
2
|
import { validateProjectConfig } from "./instance/config.js";
|
|
3
3
|
import { commandResultSchema } from "./command-result.js";
|
|
4
|
-
const string = {
|
|
5
|
-
|
|
4
|
+
const string = {
|
|
5
|
+
type: "string",
|
|
6
|
+
};
|
|
7
|
+
const array = {
|
|
8
|
+
type: "array",
|
|
9
|
+
};
|
|
10
|
+
const schema = (name, version, kind, properties, required = Object.keys(properties)) => ({
|
|
11
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
12
|
+
$id: `urn:penpair:schema:v${version}:${name}`,
|
|
13
|
+
title: `${name} v${version}`,
|
|
14
|
+
type: "object",
|
|
15
|
+
required: [
|
|
16
|
+
"schemaVersion",
|
|
17
|
+
...(kind
|
|
18
|
+
? [
|
|
19
|
+
"kind",
|
|
20
|
+
]
|
|
21
|
+
: []),
|
|
22
|
+
...required,
|
|
23
|
+
],
|
|
24
|
+
properties: {
|
|
25
|
+
schemaVersion: {
|
|
26
|
+
const: version,
|
|
27
|
+
},
|
|
28
|
+
...(kind
|
|
29
|
+
? {
|
|
30
|
+
kind: {
|
|
31
|
+
const: kind,
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
: {}),
|
|
35
|
+
...properties,
|
|
36
|
+
},
|
|
37
|
+
additionalProperties: true,
|
|
38
|
+
});
|
|
6
39
|
/** Public envelope schemas; runtime validators additionally enforce hashes, identities, hierarchy and budgets. */
|
|
7
40
|
export const artifactSchemas = {
|
|
8
|
-
"project-config": {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
"project-config": {
|
|
42
|
+
...projectConfigSchema,
|
|
43
|
+
$id: "urn:penpair:schema:v1:project-config",
|
|
44
|
+
},
|
|
45
|
+
"command-result": {
|
|
46
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
47
|
+
$id: "urn:penpair:schema:v1:command-result",
|
|
48
|
+
...commandResultSchema,
|
|
49
|
+
},
|
|
50
|
+
inspection: schema("inspection", 1, undefined, {
|
|
51
|
+
kind: {
|
|
52
|
+
enum: [
|
|
53
|
+
"shape-inspection",
|
|
54
|
+
"token-inspection",
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
fileId: string,
|
|
58
|
+
pageId: {
|
|
59
|
+
type: [
|
|
60
|
+
"string",
|
|
61
|
+
"null",
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
revision: {
|
|
65
|
+
type: "integer",
|
|
66
|
+
minimum: 0,
|
|
67
|
+
},
|
|
68
|
+
complete: {
|
|
69
|
+
type: "boolean",
|
|
70
|
+
},
|
|
71
|
+
counts: {
|
|
72
|
+
type: "object",
|
|
73
|
+
},
|
|
74
|
+
reasons: array,
|
|
75
|
+
producer: {
|
|
76
|
+
type: "object",
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
"component-map": schema("component-map", 1, undefined, {
|
|
80
|
+
components: {
|
|
81
|
+
type: "array",
|
|
82
|
+
maxItems: 1000,
|
|
83
|
+
items: {
|
|
84
|
+
type: "object",
|
|
85
|
+
required: [
|
|
86
|
+
"libraryId",
|
|
87
|
+
"componentId",
|
|
88
|
+
"codeComponent",
|
|
89
|
+
],
|
|
90
|
+
properties: {
|
|
91
|
+
libraryId: string,
|
|
92
|
+
componentId: string,
|
|
93
|
+
codeComponent: string,
|
|
94
|
+
props: {
|
|
95
|
+
type: "object",
|
|
96
|
+
},
|
|
97
|
+
textProps: {
|
|
98
|
+
type: "object",
|
|
99
|
+
additionalProperties: string,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
"design-spec": schema("design-spec", 1, "design-spec", {
|
|
106
|
+
source: {
|
|
107
|
+
type: "object",
|
|
108
|
+
},
|
|
109
|
+
mappingHash: string,
|
|
110
|
+
nodes: array,
|
|
111
|
+
unknowns: array,
|
|
112
|
+
}),
|
|
113
|
+
approvals: schema("approvals", 1, "design-approvals", {
|
|
114
|
+
records: {
|
|
115
|
+
type: "array",
|
|
116
|
+
maxItems: 10000,
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
"structural-manifest": schema("structural-manifest", 1, "structural-snapshot-manifest", {
|
|
120
|
+
fileId: string,
|
|
121
|
+
revision: {
|
|
122
|
+
type: "integer",
|
|
123
|
+
},
|
|
124
|
+
producer: string,
|
|
125
|
+
fidelity: {
|
|
126
|
+
const: "review-only",
|
|
127
|
+
},
|
|
128
|
+
pages: {
|
|
129
|
+
type: "array",
|
|
130
|
+
minItems: 1,
|
|
131
|
+
maxItems: 100,
|
|
132
|
+
},
|
|
133
|
+
tokens: {
|
|
134
|
+
type: "object",
|
|
135
|
+
},
|
|
136
|
+
hash: string,
|
|
137
|
+
}),
|
|
138
|
+
"baseline-manifest": schema("baseline-manifest", 2, undefined, {
|
|
139
|
+
complete: {
|
|
140
|
+
type: "boolean",
|
|
141
|
+
},
|
|
142
|
+
fileId: string,
|
|
143
|
+
pageId: string,
|
|
144
|
+
captureId: string,
|
|
145
|
+
frames: {
|
|
146
|
+
type: "array",
|
|
147
|
+
maxItems: 1000,
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
"projected-theme-preview": schema("projected-theme-preview", 1, "projected-theme-preview", {
|
|
151
|
+
sourceHash: string,
|
|
152
|
+
theme: {
|
|
153
|
+
type: "object",
|
|
154
|
+
},
|
|
155
|
+
sets: array,
|
|
156
|
+
resolved: array,
|
|
157
|
+
unknowns: array,
|
|
158
|
+
complete: {
|
|
159
|
+
type: "boolean",
|
|
160
|
+
},
|
|
161
|
+
nativeBindings: {
|
|
162
|
+
const: false,
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
"portable-tokens": schema("portable-tokens", 1, "portable-tokens", {
|
|
166
|
+
format: {
|
|
167
|
+
const: "dtcg-2025.10-subset",
|
|
168
|
+
},
|
|
169
|
+
tokens: {
|
|
170
|
+
type: "array",
|
|
171
|
+
minItems: 1,
|
|
172
|
+
maxItems: 10000,
|
|
173
|
+
items: {
|
|
174
|
+
type: "object",
|
|
175
|
+
required: [
|
|
176
|
+
"path",
|
|
177
|
+
"type",
|
|
178
|
+
"value",
|
|
179
|
+
],
|
|
180
|
+
properties: {
|
|
181
|
+
path: {
|
|
182
|
+
type: "array",
|
|
183
|
+
minItems: 1,
|
|
184
|
+
maxItems: 32,
|
|
185
|
+
items: string,
|
|
186
|
+
},
|
|
187
|
+
type: {
|
|
188
|
+
enum: [
|
|
189
|
+
"color",
|
|
190
|
+
"dimension",
|
|
191
|
+
"number",
|
|
192
|
+
"fontFamily",
|
|
193
|
+
"fontWeight",
|
|
194
|
+
"typography",
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
value: {},
|
|
198
|
+
description: string,
|
|
199
|
+
extensions: {
|
|
200
|
+
type: "object",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
groups: {
|
|
206
|
+
type: "object",
|
|
207
|
+
},
|
|
208
|
+
losses: {
|
|
209
|
+
type: "array",
|
|
210
|
+
maxItems: 0,
|
|
211
|
+
},
|
|
212
|
+
}),
|
|
17
213
|
};
|
|
18
214
|
/** Legacy unversioned config is v1; preserve every extension block and require explicit new output for persistence. */
|
|
19
215
|
export function migrateProjectConfig(raw) {
|
|
20
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
216
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
21
217
|
throw new Error("Config migration requires a JSON object");
|
|
218
|
+
}
|
|
22
219
|
const v = raw;
|
|
23
|
-
if (v.schemaVersion !== undefined && v.schemaVersion !== 1)
|
|
220
|
+
if (v.schemaVersion !== undefined && v.schemaVersion !== 1) {
|
|
24
221
|
throw new Error("Unsupported project config major version; use the producer's documented migration before opening it");
|
|
25
|
-
|
|
222
|
+
}
|
|
223
|
+
const migrated = {
|
|
224
|
+
...structuredClone(v),
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
};
|
|
26
227
|
validateProjectConfig(migrated);
|
|
27
228
|
return migrated;
|
|
28
229
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -31,21 +31,21 @@ export * from "./instance/gitignore.js";
|
|
|
31
31
|
export { defaultInstanceName, init } from "./instance/init.js";
|
|
32
32
|
export type { InitOptions, InitResult } from "./instance/init.js";
|
|
33
33
|
export * from "./instance/runner.js";
|
|
34
|
-
export { writeCoordinator, guardRevision, type WriteLease, type CoordinationOptions } from "./coordination.js";
|
|
34
|
+
export { writeCoordinator, guardRevision, type WriteLease, type CoordinationOptions, } from "./coordination.js";
|
|
35
35
|
export { onboardingChecklist } from "./onboard.js";
|
|
36
36
|
export { atomicWriteFile } from "./atomic-output.js";
|
|
37
|
-
export { commandSleep, withCleanupDeadline, commandResultSchema, collectCommand, commandOutput, type CommandResult, type CommandFailure, type CommandOutput } from "./command-result.js";
|
|
37
|
+
export { commandSleep, withCleanupDeadline, commandResultSchema, collectCommand, commandOutput, type CommandResult, type CommandFailure, type CommandOutput, } from "./command-result.js";
|
|
38
38
|
export { resolveProjectInput, resolveProjectOutput, designDataBlock } from "./project-input.js";
|
|
39
|
-
export { planSkillInstructions, applySkillInstructions, type InstructionReport } from "./skill-instructions.js";
|
|
39
|
+
export { planSkillInstructions, applySkillInstructions, type InstructionReport, } from "./skill-instructions.js";
|
|
40
40
|
export { parseCommandArgs, CommandArgumentError } from "./cli-arguments.js";
|
|
41
|
-
export { findShapesByName, exportShapeByName, captureShots, type ShapeLookup, type ShapeMatch, type ShotsReport } from "./shots.js";
|
|
41
|
+
export { findShapesByName, exportShapeByName, captureShots, type ShapeLookup, type ShapeMatch, type ShotsReport, } from "./shots.js";
|
|
42
42
|
export * from "./inspection.js";
|
|
43
|
-
export { tokenTypes, validateEditableToken, validateTokenEdit, planTokenEdit, tokenEditOperation, type EditableToken, type TokenEdit, type TokenEditState, type TokenEditPlan } from "./token-edit.js";
|
|
43
|
+
export { tokenTypes, validateEditableToken, validateTokenEdit, planTokenEdit, tokenEditOperation, type EditableToken, type TokenEdit, type TokenEditState, type TokenEditPlan, } from "./token-edit.js";
|
|
44
44
|
export { ensureGitignoreEntry } from "./instance/gitignore.js";
|
|
45
|
-
export { prepareFileScript, runFileScript, type FileScriptOptions, type PreparedFileScript } from "./run-file.js";
|
|
45
|
+
export { prepareFileScript, runFileScript, type FileScriptOptions, type PreparedFileScript, } from "./run-file.js";
|
|
46
46
|
export { kit, coreKit, checkKits, loadKits, type BuildKit, type RecipeInfo } from "./kit.js";
|
|
47
47
|
export { runKitSmoke, kitSmokeOperation } from "./kit-smoke.js";
|
|
48
|
-
export { baselineCoverage, readBaselineCoverage, designCaseKey, type BaselineCoverage, type CoverageRow } from "./baseline-coverage.js";
|
|
48
|
+
export { baselineCoverage, readBaselineCoverage, designCaseKey, type BaselineCoverage, type CoverageRow, } from "./baseline-coverage.js";
|
|
49
49
|
export * from "./design-spec.js";
|
|
50
50
|
export * from "./mcp-server.js";
|
|
51
51
|
export * from "./approvals.js";
|
|
@@ -55,3 +55,4 @@ export * from "./formats.js";
|
|
|
55
55
|
export * from "./project-status.js";
|
|
56
56
|
export * from "./audit-rules.js";
|
|
57
57
|
export * from "./credentials.js";
|
|
58
|
+
export * from "./token-projection.js";
|
package/dist/index.js
CHANGED
|
@@ -30,21 +30,21 @@ export * from "./instance/connection.js";
|
|
|
30
30
|
export * from "./instance/gitignore.js";
|
|
31
31
|
export { defaultInstanceName, init } from "./instance/init.js";
|
|
32
32
|
export * from "./instance/runner.js";
|
|
33
|
-
export { writeCoordinator, guardRevision } from "./coordination.js";
|
|
33
|
+
export { writeCoordinator, guardRevision, } from "./coordination.js";
|
|
34
34
|
export { onboardingChecklist } from "./onboard.js";
|
|
35
35
|
export { atomicWriteFile } from "./atomic-output.js";
|
|
36
|
-
export { commandSleep, withCleanupDeadline, commandResultSchema, collectCommand, commandOutput } from "./command-result.js";
|
|
36
|
+
export { commandSleep, withCleanupDeadline, commandResultSchema, collectCommand, commandOutput, } from "./command-result.js";
|
|
37
37
|
export { resolveProjectInput, resolveProjectOutput, designDataBlock } from "./project-input.js";
|
|
38
|
-
export { planSkillInstructions, applySkillInstructions } from "./skill-instructions.js";
|
|
38
|
+
export { planSkillInstructions, applySkillInstructions, } from "./skill-instructions.js";
|
|
39
39
|
export { parseCommandArgs, CommandArgumentError } from "./cli-arguments.js";
|
|
40
|
-
export { findShapesByName, exportShapeByName, captureShots } from "./shots.js";
|
|
40
|
+
export { findShapesByName, exportShapeByName, captureShots, } from "./shots.js";
|
|
41
41
|
export * from "./inspection.js";
|
|
42
|
-
export { tokenTypes, validateEditableToken, validateTokenEdit, planTokenEdit, tokenEditOperation } from "./token-edit.js";
|
|
42
|
+
export { tokenTypes, validateEditableToken, validateTokenEdit, planTokenEdit, tokenEditOperation, } from "./token-edit.js";
|
|
43
43
|
export { ensureGitignoreEntry } from "./instance/gitignore.js";
|
|
44
|
-
export { prepareFileScript, runFileScript } from "./run-file.js";
|
|
44
|
+
export { prepareFileScript, runFileScript, } from "./run-file.js";
|
|
45
45
|
export { kit, coreKit, checkKits, loadKits } from "./kit.js";
|
|
46
46
|
export { runKitSmoke, kitSmokeOperation } from "./kit-smoke.js";
|
|
47
|
-
export { baselineCoverage, readBaselineCoverage, designCaseKey } from "./baseline-coverage.js";
|
|
47
|
+
export { baselineCoverage, readBaselineCoverage, designCaseKey, } from "./baseline-coverage.js";
|
|
48
48
|
export * from "./design-spec.js";
|
|
49
49
|
export * from "./mcp-server.js";
|
|
50
50
|
export * from "./approvals.js";
|
|
@@ -54,3 +54,4 @@ export * from "./formats.js";
|
|
|
54
54
|
export * from "./project-status.js";
|
|
55
55
|
export * from "./audit-rules.js";
|
|
56
56
|
export * from "./credentials.js";
|
|
57
|
+
export * from "./token-projection.js";
|
package/dist/inspection.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ExecutionMetrics, type PenpotToolClient } from "./client.js";
|
|
2
2
|
export declare const inspectionFields: readonly ["geometry", "text", "layout", "component", "tokens", "style", "metadata"];
|
|
3
|
-
export type InspectionField = typeof inspectionFields[number];
|
|
3
|
+
export type InspectionField = (typeof inspectionFields)[number];
|
|
4
4
|
export interface InspectionQuery {
|
|
5
5
|
kind?: "shape" | "tokens";
|
|
6
6
|
shapeId?: string;
|