@xenonbyte/da-vinci-workflow 0.1.14 → 0.1.16
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/CHANGELOG.md +20 -2
- package/README.md +41 -1
- package/README.zh-CN.md +42 -1
- package/SKILL.md +22 -0
- package/commands/claude/dv/design.md +8 -0
- package/commands/claude/dv/verify.md +2 -0
- package/commands/codex/prompts/dv-design.md +8 -0
- package/commands/codex/prompts/dv-verify.md +1 -0
- package/commands/gemini/dv/design.toml +8 -0
- package/commands/gemini/dv/verify.toml +1 -0
- package/docs/mcp-aware-gate-implementation.md +291 -0
- package/docs/mcp-aware-gate-tests.md +244 -0
- package/docs/mcp-aware-gate.md +246 -0
- package/docs/mode-use-cases.md +7 -1
- package/docs/prompt-presets/README.md +3 -0
- package/docs/prompt-presets/desktop-app.md +19 -1
- package/docs/prompt-presets/mobile-app.md +19 -1
- package/docs/prompt-presets/tablet-app.md +19 -1
- package/docs/prompt-presets/web-app.md +19 -1
- package/docs/visual-assist-presets/README.md +5 -0
- package/docs/workflow-examples.md +24 -5
- package/docs/zh-CN/mcp-aware-gate-implementation.md +290 -0
- package/docs/zh-CN/mcp-aware-gate-tests.md +244 -0
- package/docs/zh-CN/mcp-aware-gate.md +249 -0
- package/docs/zh-CN/mode-use-cases.md +15 -4
- package/docs/zh-CN/prompt-presets/README.md +3 -0
- package/docs/zh-CN/prompt-presets/desktop-app.md +19 -1
- package/docs/zh-CN/prompt-presets/mobile-app.md +19 -1
- package/docs/zh-CN/prompt-presets/tablet-app.md +19 -1
- package/docs/zh-CN/prompt-presets/web-app.md +19 -1
- package/docs/zh-CN/visual-assist-presets/README.md +5 -0
- package/docs/zh-CN/workflow-examples.md +24 -5
- package/lib/audit.js +348 -0
- package/lib/cli.js +142 -1
- package/lib/mcp-runtime-gate.js +342 -0
- package/lib/pen-persistence.js +326 -0
- package/lib/pencil-preflight.js +438 -0
- package/package.json +5 -2
- package/references/artifact-templates.md +28 -1
- package/references/checkpoints.md +75 -1
- package/references/design-inputs.md +2 -1
- package/references/pencil-design-to-code.md +16 -0
- package/scripts/fixtures/complex-sample.pen +295 -0
- package/scripts/test-mcp-runtime-gate.js +199 -0
- package/scripts/test-pen-persistence.js +110 -0
- package/scripts/test-pencil-preflight.js +153 -0
|
@@ -10,6 +10,11 @@ Use this priority order:
|
|
|
10
10
|
2. Pencil data for presentation
|
|
11
11
|
3. screenshots only as a visual check
|
|
12
12
|
|
|
13
|
+
Exported screenshots are review artifacts only.
|
|
14
|
+
|
|
15
|
+
- store them under `.da-vinci/changes/<change-id>/exports/`
|
|
16
|
+
- do not treat them as the persisted design source
|
|
17
|
+
|
|
13
18
|
Do not infer behavior from appearance alone.
|
|
14
19
|
|
|
15
20
|
## Read From Pencil
|
|
@@ -33,6 +38,7 @@ If the current active Pencil editor does not match the preferred path in `design
|
|
|
33
38
|
- do not silently continue from the unrelated editor
|
|
34
39
|
- switch to the registered file when possible
|
|
35
40
|
- otherwise reconstruct the registered project-local `.pen` file from MCP-readable document data before implementation depends on it
|
|
41
|
+
- if the editor is still `new` or another unnamed live document, do not describe the project-local `.pen` source as saved yet
|
|
36
42
|
|
|
37
43
|
## Visual Adapter Use
|
|
38
44
|
|
|
@@ -57,9 +63,19 @@ When generating or editing Pencil data:
|
|
|
57
63
|
|
|
58
64
|
- use only Pencil-supported properties and layout concepts
|
|
59
65
|
- do not emit web- or CSS-only properties such as `flex` or `margin`
|
|
66
|
+
- preflight non-trivial `batch_design` operation strings before sending them to Pencil when shell access is available
|
|
60
67
|
- prefer smaller, schema-safe batches on anchor screens so errors do not roll back large composition chunks
|
|
68
|
+
- prefer 12 or fewer operations on anchor surfaces; after two failed batches on the same anchor, drop to micro-batches of 6 or fewer operations until a clean pass lands
|
|
69
|
+
- if unsupported-property rollbacks repeat on the same anchor surface, stop treating that pass as stable forward progress until the schema usage is corrected
|
|
70
|
+
- after any rolled-back batch or structure-changing edit, refresh the live node structure before descendant-targeted follow-up operations
|
|
61
71
|
- verify the registered project-local `.pen` path becomes shell-visible immediately after the first successful Pencil write
|
|
72
|
+
- do not treat headless interactive `save()` as authoritative persistence; write the project-local `.pen` from MCP-readable snapshot data instead
|
|
73
|
+
- if no registered project-local `.pen` existed at the start of the session, let the first approved anchor surface happen in the live editor, then persist that first approved MCP snapshot under `.da-vinci/designs/`
|
|
74
|
+
- if a registered project-local `.pen` already existed, reopen it for continuity, but after material live edits persist a fresh MCP snapshot back to that same path instead of assuming live edits were flushed automatically
|
|
75
|
+
- use `da-vinci write-pen --output <path> --nodes-file <batch-get-json> --variables-file <get-variables-json> --version <version> --verify-open` when you already have MCP-readable snapshot payloads and need an atomic project-local `.pen` write
|
|
76
|
+
- use `da-vinci snapshot-pen --input <path> --output <path> --verify-open` when a project-local `.pen` already exists and you need to re-canonicalize it from a fresh MCP-readable snapshot
|
|
62
77
|
- keep workflow markdown out of `.da-vinci/designs/`; reserve that directory for `.pen` files only
|
|
78
|
+
- keep screenshot exports out of `.da-vinci/designs/`; write them under `.da-vinci/changes/<change-id>/exports/`
|
|
63
79
|
- after the first approved anchor surfaces, extract a shared primitive family before broad page expansion
|
|
64
80
|
|
|
65
81
|
## Map Pencil To Frontend
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.9",
|
|
3
|
+
"variables": {
|
|
4
|
+
"surface-bg": {
|
|
5
|
+
"type": "color",
|
|
6
|
+
"value": "#F4EFE7"
|
|
7
|
+
},
|
|
8
|
+
"surface-panel": {
|
|
9
|
+
"type": "color",
|
|
10
|
+
"value": "#FFFDF8"
|
|
11
|
+
},
|
|
12
|
+
"surface-border": {
|
|
13
|
+
"type": "color",
|
|
14
|
+
"value": "#D7CCBC"
|
|
15
|
+
},
|
|
16
|
+
"deck-fill": {
|
|
17
|
+
"type": "color",
|
|
18
|
+
"value": "#17304A"
|
|
19
|
+
},
|
|
20
|
+
"deck-fill-deep": {
|
|
21
|
+
"type": "color",
|
|
22
|
+
"value": "#10263A"
|
|
23
|
+
},
|
|
24
|
+
"surface-ink": {
|
|
25
|
+
"type": "color",
|
|
26
|
+
"value": "#132033"
|
|
27
|
+
},
|
|
28
|
+
"surface-muted": {
|
|
29
|
+
"type": "color",
|
|
30
|
+
"value": "#5D6673"
|
|
31
|
+
},
|
|
32
|
+
"accent-amber": {
|
|
33
|
+
"type": "color",
|
|
34
|
+
"value": "#C98A2B"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"children": [
|
|
38
|
+
{
|
|
39
|
+
"type": "frame",
|
|
40
|
+
"id": "healthy",
|
|
41
|
+
"x": 0,
|
|
42
|
+
"y": 0,
|
|
43
|
+
"name": "Diagnostics Console / Healthy",
|
|
44
|
+
"width": 420,
|
|
45
|
+
"fill": "$surface-bg",
|
|
46
|
+
"layout": "vertical",
|
|
47
|
+
"gap": 18,
|
|
48
|
+
"padding": [
|
|
49
|
+
18,
|
|
50
|
+
18,
|
|
51
|
+
24,
|
|
52
|
+
18
|
|
53
|
+
],
|
|
54
|
+
"children": [
|
|
55
|
+
{
|
|
56
|
+
"type": "frame",
|
|
57
|
+
"id": "deckHealthy",
|
|
58
|
+
"name": "Command Deck",
|
|
59
|
+
"width": "fill_container",
|
|
60
|
+
"fill": [
|
|
61
|
+
{
|
|
62
|
+
"type": "gradient",
|
|
63
|
+
"gradientType": "linear",
|
|
64
|
+
"enabled": true,
|
|
65
|
+
"rotation": 180,
|
|
66
|
+
"size": {
|
|
67
|
+
"height": 1
|
|
68
|
+
},
|
|
69
|
+
"colors": [
|
|
70
|
+
{
|
|
71
|
+
"color": "$deck-fill",
|
|
72
|
+
"position": 0
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"color": "$deck-fill-deep",
|
|
76
|
+
"position": 1
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"#FFFFFF12"
|
|
81
|
+
],
|
|
82
|
+
"cornerRadius": 26,
|
|
83
|
+
"effect": {
|
|
84
|
+
"type": "shadow",
|
|
85
|
+
"shadowType": "outer",
|
|
86
|
+
"color": "#0B16231F",
|
|
87
|
+
"offset": {
|
|
88
|
+
"x": 0,
|
|
89
|
+
"y": 14
|
|
90
|
+
},
|
|
91
|
+
"blur": 28
|
|
92
|
+
},
|
|
93
|
+
"layout": "vertical",
|
|
94
|
+
"gap": 14,
|
|
95
|
+
"padding": 22,
|
|
96
|
+
"children": [
|
|
97
|
+
{
|
|
98
|
+
"type": "text",
|
|
99
|
+
"id": "healthyEyebrow",
|
|
100
|
+
"name": "heroEyebrow",
|
|
101
|
+
"content": "Diagnostics",
|
|
102
|
+
"fill": "$accent-amber",
|
|
103
|
+
"fontFamily": "Inter",
|
|
104
|
+
"fontSize": 12,
|
|
105
|
+
"fontWeight": "700",
|
|
106
|
+
"letterSpacing": 1.4
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"type": "text",
|
|
110
|
+
"id": "healthyTitle",
|
|
111
|
+
"name": "heroTitle",
|
|
112
|
+
"content": "Healthy runway",
|
|
113
|
+
"fill": "#FFFDF8",
|
|
114
|
+
"fontFamily": "Inter",
|
|
115
|
+
"fontSize": 28,
|
|
116
|
+
"fontWeight": "700",
|
|
117
|
+
"textGrowth": "fixed-width",
|
|
118
|
+
"width": "fill_container"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"type": "frame",
|
|
124
|
+
"id": "panelHealthy",
|
|
125
|
+
"name": "Runway Panel",
|
|
126
|
+
"width": "fill_container",
|
|
127
|
+
"fill": "$surface-panel",
|
|
128
|
+
"cornerRadius": 24,
|
|
129
|
+
"stroke": {
|
|
130
|
+
"fill": "$surface-border",
|
|
131
|
+
"thickness": 1
|
|
132
|
+
},
|
|
133
|
+
"layout": "vertical",
|
|
134
|
+
"gap": 12,
|
|
135
|
+
"padding": 18,
|
|
136
|
+
"children": [
|
|
137
|
+
{
|
|
138
|
+
"type": "text",
|
|
139
|
+
"id": "panelHealthyTitle",
|
|
140
|
+
"name": "panelTitle",
|
|
141
|
+
"content": "Launch blocking scenarios",
|
|
142
|
+
"fill": "$surface-ink",
|
|
143
|
+
"fontFamily": "Inter",
|
|
144
|
+
"fontSize": 24,
|
|
145
|
+
"fontWeight": "700",
|
|
146
|
+
"textGrowth": "fixed-width",
|
|
147
|
+
"width": "fill_container"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"type": "text",
|
|
151
|
+
"id": "panelHealthyBody",
|
|
152
|
+
"name": "panelBody",
|
|
153
|
+
"content": "Routine and high-risk controls are separated so the dangerous path always reads clearly.",
|
|
154
|
+
"fill": "$surface-muted",
|
|
155
|
+
"fontFamily": "Inter",
|
|
156
|
+
"fontSize": 14,
|
|
157
|
+
"fontWeight": "normal",
|
|
158
|
+
"lineHeight": 1.45,
|
|
159
|
+
"textGrowth": "fixed-width",
|
|
160
|
+
"width": "fill_container"
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"type": "frame",
|
|
168
|
+
"id": "degraded",
|
|
169
|
+
"x": 500,
|
|
170
|
+
"y": 0,
|
|
171
|
+
"name": "Diagnostics Console / Degraded",
|
|
172
|
+
"width": 420,
|
|
173
|
+
"fill": "$surface-bg",
|
|
174
|
+
"layout": "vertical",
|
|
175
|
+
"gap": 18,
|
|
176
|
+
"padding": [
|
|
177
|
+
18,
|
|
178
|
+
18,
|
|
179
|
+
24,
|
|
180
|
+
18
|
|
181
|
+
],
|
|
182
|
+
"children": [
|
|
183
|
+
{
|
|
184
|
+
"type": "frame",
|
|
185
|
+
"id": "deckDegraded",
|
|
186
|
+
"name": "Command Deck",
|
|
187
|
+
"width": "fill_container",
|
|
188
|
+
"fill": [
|
|
189
|
+
{
|
|
190
|
+
"type": "gradient",
|
|
191
|
+
"gradientType": "linear",
|
|
192
|
+
"enabled": true,
|
|
193
|
+
"rotation": 180,
|
|
194
|
+
"size": {
|
|
195
|
+
"height": 1
|
|
196
|
+
},
|
|
197
|
+
"colors": [
|
|
198
|
+
{
|
|
199
|
+
"color": "$deck-fill",
|
|
200
|
+
"position": 0
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"color": "$deck-fill-deep",
|
|
204
|
+
"position": 1
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
"#FFFFFF12"
|
|
209
|
+
],
|
|
210
|
+
"cornerRadius": 26,
|
|
211
|
+
"effect": {
|
|
212
|
+
"type": "shadow",
|
|
213
|
+
"shadowType": "outer",
|
|
214
|
+
"color": "#0B16231F",
|
|
215
|
+
"offset": {
|
|
216
|
+
"x": 0,
|
|
217
|
+
"y": 14
|
|
218
|
+
},
|
|
219
|
+
"blur": 28
|
|
220
|
+
},
|
|
221
|
+
"layout": "vertical",
|
|
222
|
+
"gap": 14,
|
|
223
|
+
"padding": 22,
|
|
224
|
+
"children": [
|
|
225
|
+
{
|
|
226
|
+
"type": "text",
|
|
227
|
+
"id": "degradedEyebrow",
|
|
228
|
+
"name": "heroEyebrow",
|
|
229
|
+
"content": "Diagnostics",
|
|
230
|
+
"fill": "$accent-amber",
|
|
231
|
+
"fontFamily": "Inter",
|
|
232
|
+
"fontSize": 12,
|
|
233
|
+
"fontWeight": "700",
|
|
234
|
+
"letterSpacing": 1.4
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"type": "text",
|
|
238
|
+
"id": "degradedTitle",
|
|
239
|
+
"name": "heroTitle",
|
|
240
|
+
"content": "Degraded runway",
|
|
241
|
+
"fill": "#FFFDF8",
|
|
242
|
+
"fontFamily": "Inter",
|
|
243
|
+
"fontSize": 28,
|
|
244
|
+
"fontWeight": "700",
|
|
245
|
+
"textGrowth": "fixed-width",
|
|
246
|
+
"width": "fill_container"
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"type": "frame",
|
|
252
|
+
"id": "panelDegraded",
|
|
253
|
+
"name": "Runway Panel",
|
|
254
|
+
"width": "fill_container",
|
|
255
|
+
"fill": "$surface-panel",
|
|
256
|
+
"cornerRadius": 24,
|
|
257
|
+
"stroke": {
|
|
258
|
+
"fill": "$surface-border",
|
|
259
|
+
"thickness": 1
|
|
260
|
+
},
|
|
261
|
+
"layout": "vertical",
|
|
262
|
+
"gap": 12,
|
|
263
|
+
"padding": 18,
|
|
264
|
+
"children": [
|
|
265
|
+
{
|
|
266
|
+
"type": "text",
|
|
267
|
+
"id": "panelDegradedTitle",
|
|
268
|
+
"name": "panelTitle",
|
|
269
|
+
"content": "Signal drift detected",
|
|
270
|
+
"fill": "$surface-ink",
|
|
271
|
+
"fontFamily": "Inter",
|
|
272
|
+
"fontSize": 24,
|
|
273
|
+
"fontWeight": "700",
|
|
274
|
+
"textGrowth": "fixed-width",
|
|
275
|
+
"width": "fill_container"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"type": "text",
|
|
279
|
+
"id": "panelDegradedBody",
|
|
280
|
+
"name": "panelBody",
|
|
281
|
+
"content": "Telemetry stays legible while the top surface shifts the operator toward the corrective lane.",
|
|
282
|
+
"fill": "$surface-muted",
|
|
283
|
+
"fontFamily": "Inter",
|
|
284
|
+
"fontSize": 14,
|
|
285
|
+
"fontWeight": "normal",
|
|
286
|
+
"lineHeight": 1.45,
|
|
287
|
+
"textGrowth": "fixed-width",
|
|
288
|
+
"width": "fill_container"
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const assert = require("assert/strict");
|
|
2
|
+
const {
|
|
3
|
+
PASS,
|
|
4
|
+
WARN,
|
|
5
|
+
BLOCK,
|
|
6
|
+
SKIP,
|
|
7
|
+
evaluateMcpRuntimeGate,
|
|
8
|
+
formatMcpRuntimeGateSection
|
|
9
|
+
} = require("../lib/mcp-runtime-gate");
|
|
10
|
+
|
|
11
|
+
function runTest(name, fn) {
|
|
12
|
+
try {
|
|
13
|
+
fn();
|
|
14
|
+
console.log(`PASS ${name}`);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(`FAIL ${name}`);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
runTest("healthy completion passes", () => {
|
|
22
|
+
const result = evaluateMcpRuntimeGate({
|
|
23
|
+
phase: "completion",
|
|
24
|
+
mcpAvailable: true,
|
|
25
|
+
projectRoot: "/repo",
|
|
26
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
27
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
28
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
29
|
+
shellVisiblePenExists: true,
|
|
30
|
+
claimedAnchorIds: ["GfwiK", "mCZ1G", "V8zfE"],
|
|
31
|
+
claimedReviewedScreenIds: ["GfwiK", "mCZ1G", "V8zfE"],
|
|
32
|
+
reviewTargets: ["GfwiK", "mCZ1G", "V8zfE"],
|
|
33
|
+
liveScreens: [
|
|
34
|
+
{ id: "GfwiK", name: "Splash" },
|
|
35
|
+
{ id: "mCZ1G", name: "Home" },
|
|
36
|
+
{ id: "V8zfE", name: "SafeBox" }
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
assert.equal(result.sourceConvergence.status, PASS);
|
|
41
|
+
assert.equal(result.screenPresence.status, PASS);
|
|
42
|
+
assert.equal(result.reviewExecution.status, PASS);
|
|
43
|
+
assert.equal(result.finalStatus, PASS);
|
|
44
|
+
|
|
45
|
+
const section = formatMcpRuntimeGateSection({}, result, { timestamp: "2026-03-27T00:00:00.000Z" });
|
|
46
|
+
assert.match(section, /Final runtime gate status: PASS/);
|
|
47
|
+
assert.match(section, /Source convergence: PASS/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
runTest("unnamed editor blocks runtime gate", () => {
|
|
51
|
+
const result = evaluateMcpRuntimeGate({
|
|
52
|
+
phase: "completion",
|
|
53
|
+
mcpAvailable: true,
|
|
54
|
+
activeEditor: "new",
|
|
55
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
56
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
57
|
+
shellVisiblePenExists: true,
|
|
58
|
+
claimedAnchorIds: ["GfwiK"],
|
|
59
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
60
|
+
reviewTargets: ["GfwiK"],
|
|
61
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
assert.equal(result.sourceConvergence.status, BLOCK);
|
|
65
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
runTest("live screens without shell-visible pen block source convergence", () => {
|
|
69
|
+
const result = evaluateMcpRuntimeGate({
|
|
70
|
+
phase: "completion",
|
|
71
|
+
mcpAvailable: true,
|
|
72
|
+
projectRoot: "/repo",
|
|
73
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
74
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
75
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
76
|
+
shellVisiblePenExists: false,
|
|
77
|
+
claimedAnchorIds: ["GfwiK"],
|
|
78
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
79
|
+
reviewTargets: ["GfwiK"],
|
|
80
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
assert.equal(result.sourceConvergence.status, BLOCK);
|
|
84
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
runTest("missing claimed anchor blocks screen presence", () => {
|
|
88
|
+
const result = evaluateMcpRuntimeGate({
|
|
89
|
+
phase: "completion",
|
|
90
|
+
mcpAvailable: true,
|
|
91
|
+
projectRoot: "/repo",
|
|
92
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
93
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
94
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
95
|
+
shellVisiblePenExists: true,
|
|
96
|
+
claimedAnchorIds: ["GfwiK", "mCZ1G"],
|
|
97
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
98
|
+
reviewTargets: ["GfwiK"],
|
|
99
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
assert.equal(result.screenPresence.status, BLOCK);
|
|
103
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
runTest("ignored review blockers block review execution", () => {
|
|
107
|
+
const result = evaluateMcpRuntimeGate({
|
|
108
|
+
phase: "completion",
|
|
109
|
+
mcpAvailable: true,
|
|
110
|
+
projectRoot: "/repo",
|
|
111
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
112
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
113
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
114
|
+
shellVisiblePenExists: true,
|
|
115
|
+
claimedAnchorIds: ["GfwiK"],
|
|
116
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
117
|
+
reviewTargets: ["GfwiK"],
|
|
118
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }],
|
|
119
|
+
reviewBlockersIgnored: true
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
assert.equal(result.reviewExecution.status, BLOCK);
|
|
123
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
runTest("mcp unavailable warns instead of passing", () => {
|
|
127
|
+
const result = evaluateMcpRuntimeGate({
|
|
128
|
+
phase: "completion",
|
|
129
|
+
mcpAvailable: false,
|
|
130
|
+
activeEditor: "",
|
|
131
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
132
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
133
|
+
shellVisiblePenExists: true
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
assert.equal(result.sourceConvergence.status, WARN);
|
|
137
|
+
assert.equal(result.screenPresence.status, WARN);
|
|
138
|
+
assert.equal(result.reviewExecution.status, WARN);
|
|
139
|
+
assert.equal(result.finalStatus, WARN);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
runTest("first-write phase can skip screen and review checks", () => {
|
|
143
|
+
const result = evaluateMcpRuntimeGate({
|
|
144
|
+
phase: "first_write",
|
|
145
|
+
mcpAvailable: true,
|
|
146
|
+
projectRoot: "/repo",
|
|
147
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
148
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
149
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
150
|
+
shellVisiblePenExists: true,
|
|
151
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
assert.equal(result.sourceConvergence.status, PASS);
|
|
155
|
+
assert.equal(result.screenPresence.status, SKIP);
|
|
156
|
+
assert.equal(result.reviewExecution.status, SKIP);
|
|
157
|
+
assert.equal(result.finalStatus, PASS);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
runTest("documented reconciliation downgrades mismatch to warn", () => {
|
|
161
|
+
const result = evaluateMcpRuntimeGate({
|
|
162
|
+
phase: "completion",
|
|
163
|
+
mcpAvailable: true,
|
|
164
|
+
projectRoot: "/repo",
|
|
165
|
+
activeEditor: "/tmp/other-live.pen",
|
|
166
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
167
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
168
|
+
shellVisiblePenExists: true,
|
|
169
|
+
documentedReconciliation: true,
|
|
170
|
+
claimedAnchorIds: ["GfwiK"],
|
|
171
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
172
|
+
reviewTargets: ["GfwiK"],
|
|
173
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
assert.equal(result.sourceConvergence.status, WARN);
|
|
177
|
+
assert.equal(result.finalStatus, WARN);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
runTest("same basename but different absolute path blocks without reconciliation", () => {
|
|
181
|
+
const result = evaluateMcpRuntimeGate({
|
|
182
|
+
phase: "completion",
|
|
183
|
+
mcpAvailable: true,
|
|
184
|
+
projectRoot: "/repo",
|
|
185
|
+
activeEditor: "/tmp/cipher-redesign.pen",
|
|
186
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
187
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
188
|
+
shellVisiblePenExists: true,
|
|
189
|
+
claimedAnchorIds: ["GfwiK"],
|
|
190
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
191
|
+
reviewTargets: ["GfwiK"],
|
|
192
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
assert.equal(result.sourceConvergence.status, BLOCK);
|
|
196
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
console.log("All MCP runtime gate tests passed.");
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const assert = require("assert/strict");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const {
|
|
6
|
+
buildPenDocument,
|
|
7
|
+
writePenFromPayloadFiles,
|
|
8
|
+
snapshotPenFile
|
|
9
|
+
} = require("../lib/pen-persistence");
|
|
10
|
+
|
|
11
|
+
const fixturePath = path.join(__dirname, "fixtures", "complex-sample.pen");
|
|
12
|
+
|
|
13
|
+
function runTest(name, fn) {
|
|
14
|
+
try {
|
|
15
|
+
fn();
|
|
16
|
+
console.log(`PASS ${name}`);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error(`FAIL ${name}`);
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createTempDir() {
|
|
24
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-pen-persistence-"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
runTest("buildPenDocument preserves fixture structure", () => {
|
|
28
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
29
|
+
const document = buildPenDocument({
|
|
30
|
+
version: fixture.version,
|
|
31
|
+
nodes: { nodes: fixture.children },
|
|
32
|
+
variables: { variables: fixture.variables }
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
assert.deepEqual(document, fixture);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
runTest("writePenFromPayloadFiles writes and reopens a complex sample", () => {
|
|
39
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
40
|
+
const tempDir = createTempDir();
|
|
41
|
+
const nodesFile = path.join(tempDir, "nodes.json");
|
|
42
|
+
const variablesFile = path.join(tempDir, "variables.json");
|
|
43
|
+
const outputPath = path.join(tempDir, "written.pen");
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(nodesFile, JSON.stringify({ nodes: fixture.children }, null, 2));
|
|
46
|
+
fs.writeFileSync(variablesFile, JSON.stringify({ variables: fixture.variables }, null, 2));
|
|
47
|
+
|
|
48
|
+
const result = writePenFromPayloadFiles({
|
|
49
|
+
outputPath,
|
|
50
|
+
nodesFile,
|
|
51
|
+
variablesFile,
|
|
52
|
+
version: fixture.version,
|
|
53
|
+
verifyWithPencil: true
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
57
|
+
assert.deepEqual(written, fixture);
|
|
58
|
+
assert.deepEqual(result.verification.topLevelIds, fixture.children.map((node) => node.id));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
runTest("snapshotPenFile round-trips a complex sample", () => {
|
|
62
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
63
|
+
const tempDir = createTempDir();
|
|
64
|
+
const outputPath = path.join(tempDir, "snapshotted.pen");
|
|
65
|
+
|
|
66
|
+
const result = snapshotPenFile({
|
|
67
|
+
inputPath: fixturePath,
|
|
68
|
+
outputPath,
|
|
69
|
+
verifyWithPencil: true
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
73
|
+
assert.deepEqual(written, fixture);
|
|
74
|
+
assert.deepEqual(result.verification.topLevelIds, fixture.children.map((node) => node.id));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
runTest("truncated nodes payload is rejected", () => {
|
|
78
|
+
const tempDir = createTempDir();
|
|
79
|
+
const nodesFile = path.join(tempDir, "nodes.json");
|
|
80
|
+
const outputPath = path.join(tempDir, "bad.pen");
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(
|
|
83
|
+
nodesFile,
|
|
84
|
+
JSON.stringify(
|
|
85
|
+
{
|
|
86
|
+
nodes: [
|
|
87
|
+
{
|
|
88
|
+
type: "frame",
|
|
89
|
+
id: "bad",
|
|
90
|
+
children: "..."
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
null,
|
|
95
|
+
2
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
assert.throws(
|
|
100
|
+
() =>
|
|
101
|
+
writePenFromPayloadFiles({
|
|
102
|
+
outputPath,
|
|
103
|
+
nodesFile,
|
|
104
|
+
version: "2.9"
|
|
105
|
+
}),
|
|
106
|
+
/truncated/i
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log("All .pen persistence tests passed.");
|