@xenonbyte/da-vinci-workflow 0.1.15 → 0.1.17
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 +17 -1
- package/README.md +35 -1
- package/README.zh-CN.md +35 -1
- package/SKILL.md +18 -0
- package/commands/claude/dv/design.md +7 -0
- package/commands/codex/prompts/dv-design.md +7 -0
- package/commands/gemini/dv/design.toml +7 -0
- package/docs/mode-use-cases.md +5 -1
- package/docs/prompt-presets/README.md +3 -0
- package/docs/prompt-presets/desktop-app.md +16 -1
- package/docs/prompt-presets/mobile-app.md +16 -1
- package/docs/prompt-presets/tablet-app.md +16 -1
- package/docs/prompt-presets/web-app.md +16 -1
- package/docs/visual-assist-presets/README.md +5 -0
- package/docs/workflow-examples.md +22 -8
- package/docs/zh-CN/mode-use-cases.md +13 -4
- package/docs/zh-CN/prompt-presets/README.md +3 -0
- package/docs/zh-CN/prompt-presets/desktop-app.md +16 -1
- package/docs/zh-CN/prompt-presets/mobile-app.md +16 -1
- package/docs/zh-CN/prompt-presets/tablet-app.md +16 -1
- package/docs/zh-CN/prompt-presets/web-app.md +16 -1
- package/docs/zh-CN/visual-assist-presets/README.md +5 -0
- package/docs/zh-CN/workflow-examples.md +22 -8
- package/lib/audit.js +66 -2
- package/lib/cli.js +356 -1
- package/lib/mcp-runtime-gate.js +53 -1
- package/lib/pen-persistence.js +515 -0
- package/lib/pencil-lock.js +128 -0
- package/lib/pencil-preflight.js +438 -0
- package/lib/pencil-session.js +229 -0
- package/package.json +6 -2
- package/references/artifact-templates.md +2 -0
- package/references/checkpoints.md +14 -0
- package/references/pencil-design-to-code.md +15 -0
- package/scripts/fixtures/complex-sample.pen +295 -0
- package/scripts/test-mcp-runtime-gate.js +88 -0
- package/scripts/test-pen-persistence.js +250 -0
- package/scripts/test-pencil-preflight.js +153 -0
- package/scripts/test-pencil-session.js +152 -0
- package/scripts/test-persistence-flows.js +315 -0
package/lib/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
1
2
|
const {
|
|
2
3
|
VERSION,
|
|
3
4
|
installPlatforms,
|
|
@@ -6,6 +7,28 @@ const {
|
|
|
6
7
|
validateAssets
|
|
7
8
|
} = require("./install");
|
|
8
9
|
const { auditProject, formatAuditReport } = require("./audit");
|
|
10
|
+
const {
|
|
11
|
+
preflightPencilBatch,
|
|
12
|
+
formatPencilPreflightReport,
|
|
13
|
+
readOperations
|
|
14
|
+
} = require("./pencil-preflight");
|
|
15
|
+
const {
|
|
16
|
+
writePenFromPayloadFiles,
|
|
17
|
+
snapshotPenFile,
|
|
18
|
+
ensurePenFile,
|
|
19
|
+
comparePenSync
|
|
20
|
+
} = require("./pen-persistence");
|
|
21
|
+
const {
|
|
22
|
+
acquirePencilLock,
|
|
23
|
+
releasePencilLock,
|
|
24
|
+
getPencilLockStatus
|
|
25
|
+
} = require("./pencil-lock");
|
|
26
|
+
const {
|
|
27
|
+
beginPencilSession,
|
|
28
|
+
persistPencilSession,
|
|
29
|
+
endPencilSession,
|
|
30
|
+
getPencilSessionStatus
|
|
31
|
+
} = require("./pencil-session");
|
|
9
32
|
|
|
10
33
|
function getOption(args, name) {
|
|
11
34
|
const direct = args.find((arg) => arg.startsWith(`${name}=`));
|
|
@@ -67,14 +90,37 @@ function printHelp() {
|
|
|
67
90
|
" da-vinci status",
|
|
68
91
|
" da-vinci validate-assets",
|
|
69
92
|
" da-vinci audit [project-path]",
|
|
93
|
+
" da-vinci preflight-pencil --ops-file <path>",
|
|
94
|
+
" da-vinci ensure-pen --output <path>",
|
|
95
|
+
" da-vinci write-pen --output <path> --nodes-file <path> [--variables-file <path>]",
|
|
96
|
+
" da-vinci check-pen-sync --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
97
|
+
" da-vinci snapshot-pen --input <path> --output <path>",
|
|
98
|
+
" da-vinci pencil-lock acquire --project <path>",
|
|
99
|
+
" da-vinci pencil-lock release --project <path>",
|
|
100
|
+
" da-vinci pencil-lock status",
|
|
101
|
+
" da-vinci pencil-session begin --project <path> --pen <path>",
|
|
102
|
+
" da-vinci pencil-session persist --project <path> --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
103
|
+
" da-vinci pencil-session end --project <path> --pen <path> [--nodes-file <path>]",
|
|
104
|
+
" da-vinci pencil-session status --project <path>",
|
|
70
105
|
" da-vinci --version",
|
|
71
106
|
"",
|
|
72
107
|
"Options:",
|
|
73
108
|
" --platform <value> codex, claude, gemini, or all",
|
|
74
109
|
" --home <path> override HOME for installation targets",
|
|
75
110
|
" --project <path> override project path for audit",
|
|
111
|
+
" --pen <path> registered .pen path for sync checks",
|
|
112
|
+
" --ops-file <path> Pencil batch operations file for preflight",
|
|
113
|
+
" --input <path> input .pen file for snapshot-pen",
|
|
114
|
+
" --output <path> output .pen file for write-pen or snapshot-pen",
|
|
115
|
+
" --nodes-file <path> JSON payload from batch_get for write-pen",
|
|
116
|
+
" --variables-file <path> JSON payload from get_variables for write-pen",
|
|
117
|
+
" --verify-open reopen the written .pen with Pencil after writing",
|
|
118
|
+
" --version <value> explicit .pen version when writing from MCP payloads",
|
|
76
119
|
" --mode <value> integrity or completion",
|
|
77
|
-
" --change <id> scope completion audit to one change id"
|
|
120
|
+
" --change <id> scope completion audit to one change id",
|
|
121
|
+
" --wait-ms <value> wait for the global Pencil lock before failing",
|
|
122
|
+
" --owner <value> human-readable lock owner label",
|
|
123
|
+
" --force force a lock release held by another project"
|
|
78
124
|
].join("\n")
|
|
79
125
|
);
|
|
80
126
|
}
|
|
@@ -138,6 +184,315 @@ async function runCli(argv) {
|
|
|
138
184
|
return;
|
|
139
185
|
}
|
|
140
186
|
|
|
187
|
+
if (command === "preflight-pencil") {
|
|
188
|
+
const opsFile = getOption(argv, "--ops-file");
|
|
189
|
+
let operations = "";
|
|
190
|
+
|
|
191
|
+
if (opsFile) {
|
|
192
|
+
operations = readOperations(opsFile);
|
|
193
|
+
} else if (!process.stdin.isTTY) {
|
|
194
|
+
operations = fs.readFileSync(0, "utf8");
|
|
195
|
+
} else {
|
|
196
|
+
throw new Error("`preflight-pencil` requires `--ops-file <path>` or piped stdin input.");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const result = preflightPencilBatch(operations);
|
|
200
|
+
const report = formatPencilPreflightReport(result);
|
|
201
|
+
|
|
202
|
+
if (result.status === "FAIL") {
|
|
203
|
+
throw new Error(report);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log(report);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (command === "ensure-pen") {
|
|
211
|
+
const outputPath = getOption(argv, "--output");
|
|
212
|
+
const version = getOption(argv, "--version");
|
|
213
|
+
const verifyWithPencil = argv.includes("--verify-open");
|
|
214
|
+
|
|
215
|
+
if (!outputPath) {
|
|
216
|
+
throw new Error("`ensure-pen` requires `--output <path>`.");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const result = ensurePenFile({
|
|
220
|
+
outputPath,
|
|
221
|
+
version,
|
|
222
|
+
verifyWithPencil
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
console.log(`${result.created ? "Created" : "Verified existing"} .pen source at ${result.outputPath}`);
|
|
226
|
+
console.log(`State file: ${result.statePath}`);
|
|
227
|
+
console.log(`Snapshot hash: ${result.state.snapshotHash}`);
|
|
228
|
+
if (result.verification) {
|
|
229
|
+
console.log(`Verified reopen with Pencil (${result.verification.topLevelCount} top-level nodes).`);
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (command === "write-pen") {
|
|
235
|
+
const outputPath = getOption(argv, "--output");
|
|
236
|
+
const nodesFile = getOption(argv, "--nodes-file");
|
|
237
|
+
const variablesFile = getOption(argv, "--variables-file");
|
|
238
|
+
const version = getOption(argv, "--version");
|
|
239
|
+
const verifyWithPencil = argv.includes("--verify-open");
|
|
240
|
+
|
|
241
|
+
if (!outputPath || !nodesFile) {
|
|
242
|
+
throw new Error("`write-pen` requires `--output <path>` and `--nodes-file <path>`.");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const result = writePenFromPayloadFiles({
|
|
246
|
+
outputPath,
|
|
247
|
+
nodesFile,
|
|
248
|
+
variablesFile,
|
|
249
|
+
version,
|
|
250
|
+
verifyWithPencil
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
console.log(`Wrote .pen file to ${result.outputPath}`);
|
|
254
|
+
console.log(`State file: ${result.statePath}`);
|
|
255
|
+
console.log(`Snapshot hash: ${result.state.snapshotHash}`);
|
|
256
|
+
console.log(`Top-level nodes: ${result.document.children.length}`);
|
|
257
|
+
if (result.verification) {
|
|
258
|
+
console.log(`Verified reopen with Pencil (${result.verification.topLevelCount} top-level nodes).`);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (command === "check-pen-sync") {
|
|
264
|
+
const penPath = getOption(argv, "--pen");
|
|
265
|
+
const nodesFile = getOption(argv, "--nodes-file");
|
|
266
|
+
const variablesFile = getOption(argv, "--variables-file");
|
|
267
|
+
const version = getOption(argv, "--version");
|
|
268
|
+
|
|
269
|
+
if (!penPath || !nodesFile) {
|
|
270
|
+
throw new Error("`check-pen-sync` requires `--pen <path>` and `--nodes-file <path>`.");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const result = comparePenSync({
|
|
274
|
+
penPath,
|
|
275
|
+
nodesFile,
|
|
276
|
+
variablesFile,
|
|
277
|
+
version
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (!result.inSync) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
[
|
|
283
|
+
"Registered `.pen` is out of sync with the provided live payload.",
|
|
284
|
+
`Pen: ${result.penPath}`,
|
|
285
|
+
`State file: ${result.statePath}`,
|
|
286
|
+
`Persisted hash: ${result.persistedHash}`,
|
|
287
|
+
`Live hash: ${result.liveHash}`
|
|
288
|
+
].join("\n")
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log(`Registered .pen is in sync: ${result.penPath}`);
|
|
293
|
+
console.log(`State file: ${result.statePath}`);
|
|
294
|
+
console.log(`Snapshot hash: ${result.liveHash}`);
|
|
295
|
+
if (!result.usedStateFile) {
|
|
296
|
+
console.log("State file was missing; sync comparison fell back to hashing the disk .pen file directly.");
|
|
297
|
+
}
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (command === "snapshot-pen") {
|
|
302
|
+
const inputPath = getOption(argv, "--input");
|
|
303
|
+
const outputPath = getOption(argv, "--output");
|
|
304
|
+
const version = getOption(argv, "--version");
|
|
305
|
+
const verifyWithPencil = argv.includes("--verify-open");
|
|
306
|
+
|
|
307
|
+
if (!inputPath || !outputPath) {
|
|
308
|
+
throw new Error("`snapshot-pen` requires `--input <path>` and `--output <path>`.");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const result = snapshotPenFile({
|
|
312
|
+
inputPath,
|
|
313
|
+
outputPath,
|
|
314
|
+
version,
|
|
315
|
+
verifyWithPencil
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
console.log(`Snapshotted ${result.inputPath} to ${result.outputPath}`);
|
|
319
|
+
console.log(`State file: ${result.statePath}`);
|
|
320
|
+
console.log(`Snapshot hash: ${result.state.snapshotHash}`);
|
|
321
|
+
console.log(`Top-level nodes: ${result.document.children.length}`);
|
|
322
|
+
if (result.verification) {
|
|
323
|
+
console.log(`Verified reopen with Pencil (${result.verification.topLevelCount} top-level nodes).`);
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (command === "pencil-lock") {
|
|
329
|
+
const subcommand = getPositionalArgs(argv.slice(1), [
|
|
330
|
+
"--home",
|
|
331
|
+
"--project",
|
|
332
|
+
"--owner",
|
|
333
|
+
"--wait-ms"
|
|
334
|
+
])[0];
|
|
335
|
+
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
336
|
+
const owner = getOption(argv, "--owner");
|
|
337
|
+
const waitMs = getOption(argv, "--wait-ms");
|
|
338
|
+
const force = argv.includes("--force");
|
|
339
|
+
|
|
340
|
+
if (!subcommand || ["acquire", "release", "status"].includes(subcommand) === false) {
|
|
341
|
+
throw new Error("`pencil-lock` requires one of: acquire, release, status.");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (subcommand === "acquire") {
|
|
345
|
+
const result = acquirePencilLock({
|
|
346
|
+
projectPath,
|
|
347
|
+
owner,
|
|
348
|
+
waitMs,
|
|
349
|
+
homeDir
|
|
350
|
+
});
|
|
351
|
+
console.log(`${result.alreadyHeld ? "Reused" : "Acquired"} Pencil lock at ${result.lockPath}`);
|
|
352
|
+
console.log(`Project: ${result.lock.projectPath}`);
|
|
353
|
+
console.log(`Owner: ${result.lock.owner}`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (subcommand === "release") {
|
|
358
|
+
const result = releasePencilLock({
|
|
359
|
+
projectPath,
|
|
360
|
+
force,
|
|
361
|
+
homeDir
|
|
362
|
+
});
|
|
363
|
+
if (!result.hadLock) {
|
|
364
|
+
console.log(`No Pencil lock was present at ${result.lockPath}`);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
console.log(`Released Pencil lock at ${result.lockPath}`);
|
|
368
|
+
console.log(`Previous project: ${result.lock.projectPath}`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const result = getPencilLockStatus({ homeDir });
|
|
373
|
+
console.log(`Lock path: ${result.lockPath}`);
|
|
374
|
+
if (!result.lock) {
|
|
375
|
+
console.log("Status: unlocked");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
console.log("Status: locked");
|
|
379
|
+
console.log(`Project: ${result.lock.projectPath}`);
|
|
380
|
+
console.log(`Owner: ${result.lock.owner}`);
|
|
381
|
+
console.log(`PID: ${result.lock.pid}`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (command === "pencil-session") {
|
|
386
|
+
const subcommand = getPositionalArgs(argv.slice(1), [
|
|
387
|
+
"--home",
|
|
388
|
+
"--project",
|
|
389
|
+
"--pen",
|
|
390
|
+
"--nodes-file",
|
|
391
|
+
"--variables-file",
|
|
392
|
+
"--version",
|
|
393
|
+
"--owner",
|
|
394
|
+
"--wait-ms"
|
|
395
|
+
])[0];
|
|
396
|
+
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
397
|
+
const penPath = getOption(argv, "--pen");
|
|
398
|
+
const nodesFile = getOption(argv, "--nodes-file");
|
|
399
|
+
const variablesFile = getOption(argv, "--variables-file");
|
|
400
|
+
const version = getOption(argv, "--version");
|
|
401
|
+
const verifyWithPencil = argv.includes("--verify-open");
|
|
402
|
+
const owner = getOption(argv, "--owner");
|
|
403
|
+
const waitMs = getOption(argv, "--wait-ms");
|
|
404
|
+
const force = argv.includes("--force");
|
|
405
|
+
|
|
406
|
+
if (!subcommand || ["begin", "persist", "end", "status"].includes(subcommand) === false) {
|
|
407
|
+
throw new Error("`pencil-session` requires one of: begin, persist, end, status.");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (subcommand === "begin") {
|
|
411
|
+
if (!penPath) {
|
|
412
|
+
throw new Error("`pencil-session begin` requires `--pen <path>`.");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const result = beginPencilSession({
|
|
416
|
+
projectPath,
|
|
417
|
+
penPath,
|
|
418
|
+
version,
|
|
419
|
+
verifyWithPencil,
|
|
420
|
+
owner,
|
|
421
|
+
waitMs,
|
|
422
|
+
homeDir
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
console.log(`Began Pencil session for ${result.projectRoot}`);
|
|
426
|
+
console.log(`Pen path: ${result.penPath}`);
|
|
427
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
428
|
+
console.log(`Snapshot hash: ${result.session.lastPersistedHash}`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (subcommand === "persist") {
|
|
433
|
+
if (!penPath || !nodesFile) {
|
|
434
|
+
throw new Error("`pencil-session persist` requires `--pen <path>` and `--nodes-file <path>`.");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const result = persistPencilSession({
|
|
438
|
+
projectPath,
|
|
439
|
+
penPath,
|
|
440
|
+
nodesFile,
|
|
441
|
+
variablesFile,
|
|
442
|
+
version,
|
|
443
|
+
verifyWithPencil,
|
|
444
|
+
homeDir
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
console.log(`Persisted Pencil session for ${result.projectRoot}`);
|
|
448
|
+
console.log(`Pen path: ${result.penPath}`);
|
|
449
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
450
|
+
console.log(`Snapshot hash: ${result.session.lastPersistedHash}`);
|
|
451
|
+
console.log(`In sync: ${result.syncResult.inSync ? "yes" : "no"}`);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (subcommand === "end") {
|
|
456
|
+
if (!penPath) {
|
|
457
|
+
throw new Error("`pencil-session end` requires `--pen <path>`.");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const result = endPencilSession({
|
|
461
|
+
projectPath,
|
|
462
|
+
penPath,
|
|
463
|
+
nodesFile,
|
|
464
|
+
variablesFile,
|
|
465
|
+
version,
|
|
466
|
+
homeDir,
|
|
467
|
+
force
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
console.log(`Ended Pencil session for ${result.projectRoot}`);
|
|
471
|
+
console.log(`Pen path: ${result.penPath}`);
|
|
472
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
473
|
+
console.log(`Final status: ${result.session.status}`);
|
|
474
|
+
if (result.syncResult) {
|
|
475
|
+
console.log(`Final sync verified: ${result.syncResult.inSync ? "yes" : "no"}`);
|
|
476
|
+
}
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const result = getPencilSessionStatus({
|
|
481
|
+
projectPath,
|
|
482
|
+
homeDir
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
console.log(`Project: ${result.projectRoot}`);
|
|
486
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
487
|
+
console.log(`Session status: ${result.session ? result.session.status : "missing"}`);
|
|
488
|
+
console.log(`Lock status: ${result.lockStatus.lock ? "locked" : "unlocked"}`);
|
|
489
|
+
if (result.session) {
|
|
490
|
+
console.log(`Pen path: ${result.session.penPath}`);
|
|
491
|
+
console.log(`Last persisted hash: ${result.session.lastPersistedHash || "(missing)"}`);
|
|
492
|
+
}
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
141
496
|
throw new Error(`Unknown command: ${command}`);
|
|
142
497
|
}
|
|
143
498
|
|
package/lib/mcp-runtime-gate.js
CHANGED
|
@@ -52,6 +52,10 @@ function resolveProjectPath(projectRoot, targetPath) {
|
|
|
52
52
|
return path.normalize(path.resolve(projectRoot, targetPath));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function normalizeHash(value) {
|
|
56
|
+
return typeof value === "string" ? value.trim() : "";
|
|
57
|
+
}
|
|
58
|
+
|
|
55
59
|
function isUnnamedEditor(activeEditor) {
|
|
56
60
|
const normalized = normalizeEditorName(activeEditor).toLowerCase();
|
|
57
61
|
return normalized === "" || normalized === "new";
|
|
@@ -91,12 +95,17 @@ function derivePhase(snapshot) {
|
|
|
91
95
|
|
|
92
96
|
function evaluateSourceConvergence(snapshot) {
|
|
93
97
|
const notes = [];
|
|
98
|
+
const phase = derivePhase(snapshot);
|
|
94
99
|
const activeEditor = normalizeEditorName(snapshot.activeEditor);
|
|
95
100
|
const registeredPenPath = normalizeRegisteredPath(snapshot.registeredPenPath);
|
|
96
101
|
const projectRoot = typeof snapshot.projectRoot === "string" ? snapshot.projectRoot.trim() : "";
|
|
97
102
|
const shellVisiblePenExists = Boolean(snapshot.shellVisiblePenExists);
|
|
98
103
|
const documentedReconciliation = Boolean(snapshot.documentedReconciliation);
|
|
99
104
|
const noNewPencilEditsYet = Boolean(snapshot.noNewPencilEditsYet);
|
|
105
|
+
const usedEmptyFilePath = Boolean(snapshot.usedEmptyFilePath);
|
|
106
|
+
const penSyncVerified = snapshot.penSyncVerified === true;
|
|
107
|
+
const liveSnapshotHash = normalizeHash(snapshot.liveSnapshotHash);
|
|
108
|
+
const persistedSnapshotHash = normalizeHash(snapshot.persistedSnapshotHash);
|
|
100
109
|
|
|
101
110
|
if (Boolean(snapshot.mcpAvailable) === false) {
|
|
102
111
|
return {
|
|
@@ -119,6 +128,13 @@ function evaluateSourceConvergence(snapshot) {
|
|
|
119
128
|
};
|
|
120
129
|
}
|
|
121
130
|
|
|
131
|
+
if (usedEmptyFilePath) {
|
|
132
|
+
return {
|
|
133
|
+
status: BLOCK,
|
|
134
|
+
notes: ["Pencil operations used an empty `filePath` even though the workflow has a registered project-local `.pen` source."]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
if (!shellVisiblePenExists) {
|
|
123
139
|
return {
|
|
124
140
|
status: BLOCK,
|
|
@@ -142,6 +158,29 @@ function evaluateSourceConvergence(snapshot) {
|
|
|
142
158
|
};
|
|
143
159
|
}
|
|
144
160
|
|
|
161
|
+
if (phase === "completion") {
|
|
162
|
+
if (!penSyncVerified) {
|
|
163
|
+
return {
|
|
164
|
+
status: BLOCK,
|
|
165
|
+
notes: ["No explicit live-to-disk `.pen` sync verification was recorded before completion."]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!liveSnapshotHash || !persistedSnapshotHash) {
|
|
170
|
+
return {
|
|
171
|
+
status: BLOCK,
|
|
172
|
+
notes: ["Completion requires both live and persisted `.pen` snapshot hashes."]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (liveSnapshotHash !== persistedSnapshotHash) {
|
|
177
|
+
return {
|
|
178
|
+
status: BLOCK,
|
|
179
|
+
notes: ["Current live Pencil snapshot does not match the last persisted `.pen` snapshot."]
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
145
184
|
if (noNewPencilEditsYet) {
|
|
146
185
|
notes.push("No new Pencil edits were recorded yet; source convergence is only provisionally confirmed.");
|
|
147
186
|
return {
|
|
@@ -150,9 +189,16 @@ function evaluateSourceConvergence(snapshot) {
|
|
|
150
189
|
};
|
|
151
190
|
}
|
|
152
191
|
|
|
192
|
+
if (penSyncVerified) {
|
|
193
|
+
notes.push("Live Pencil snapshot was explicitly verified against the persisted project-local `.pen` source.");
|
|
194
|
+
}
|
|
195
|
+
|
|
153
196
|
return {
|
|
154
197
|
status: PASS,
|
|
155
|
-
notes: [
|
|
198
|
+
notes: [
|
|
199
|
+
"Active Pencil editor matches the registered project-local `.pen` source and the file exists on disk.",
|
|
200
|
+
...notes
|
|
201
|
+
]
|
|
156
202
|
};
|
|
157
203
|
}
|
|
158
204
|
|
|
@@ -296,6 +342,9 @@ function evaluateMcpRuntimeGate(snapshot = {}) {
|
|
|
296
342
|
shellVisiblePenPath:
|
|
297
343
|
typeof snapshot.shellVisiblePenPath === "string" ? snapshot.shellVisiblePenPath.trim() : "",
|
|
298
344
|
shellVisiblePenExists: Boolean(snapshot.shellVisiblePenExists),
|
|
345
|
+
penSyncVerified: snapshot.penSyncVerified === true,
|
|
346
|
+
liveSnapshotHash: normalizeHash(snapshot.liveSnapshotHash),
|
|
347
|
+
persistedSnapshotHash: normalizeHash(snapshot.persistedSnapshotHash),
|
|
299
348
|
claimedAnchorIds: normalizeArray(snapshot.claimedAnchorIds),
|
|
300
349
|
claimedReviewedScreenIds: normalizeArray(snapshot.claimedReviewedScreenIds),
|
|
301
350
|
reviewTargets: normalizeArray(snapshot.reviewTargets),
|
|
@@ -320,6 +369,9 @@ function formatMcpRuntimeGateSection(snapshot, result, options = {}) {
|
|
|
320
369
|
`- Registered \`.pen\` path: ${result.registeredPenPath || "(missing)"}`,
|
|
321
370
|
`- Shell-visible \`.pen\` path: ${result.shellVisiblePenPath || "(missing)"}`,
|
|
322
371
|
`- Shell-visible \`.pen\` exists: ${result.shellVisiblePenExists ? "yes" : "no"}`,
|
|
372
|
+
`- Pen sync verified: ${result.penSyncVerified ? "yes" : "no"}`,
|
|
373
|
+
`- Live snapshot hash: ${result.liveSnapshotHash || "(missing)"}`,
|
|
374
|
+
`- Persisted snapshot hash: ${result.persistedSnapshotHash || "(missing)"}`,
|
|
323
375
|
`- Claimed anchor ids: ${result.claimedAnchorIds.length > 0 ? result.claimedAnchorIds.join(", ") : "(none)"}`,
|
|
324
376
|
`- Reviewed screen ids: ${result.claimedReviewedScreenIds.length > 0 ? result.claimedReviewedScreenIds.join(", ") : "(none)"}`,
|
|
325
377
|
`- Screenshot target ids: ${result.reviewTargets.length > 0 ? result.reviewTargets.join(", ") : "(none)"}`,
|