@xenonbyte/da-vinci-workflow 0.1.16 → 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 +9 -3
- package/README.md +15 -4
- package/README.zh-CN.md +15 -5
- package/SKILL.md +11 -2
- package/commands/claude/dv/design.md +3 -2
- package/commands/codex/prompts/dv-design.md +3 -2
- package/commands/gemini/dv/design.toml +3 -2
- package/docs/mode-use-cases.md +2 -2
- package/docs/prompt-presets/README.md +1 -1
- package/docs/prompt-presets/desktop-app.md +4 -4
- package/docs/prompt-presets/mobile-app.md +4 -4
- package/docs/prompt-presets/tablet-app.md +4 -4
- package/docs/prompt-presets/web-app.md +4 -4
- package/docs/workflow-examples.md +5 -5
- package/docs/zh-CN/mode-use-cases.md +7 -6
- package/docs/zh-CN/prompt-presets/README.md +1 -1
- package/docs/zh-CN/prompt-presets/desktop-app.md +4 -4
- package/docs/zh-CN/prompt-presets/mobile-app.md +4 -4
- package/docs/zh-CN/prompt-presets/tablet-app.md +4 -4
- package/docs/zh-CN/prompt-presets/web-app.md +4 -4
- package/docs/zh-CN/workflow-examples.md +3 -3
- package/lib/audit.js +66 -2
- package/lib/cli.js +262 -2
- package/lib/mcp-runtime-gate.js +53 -1
- package/lib/pen-persistence.js +192 -3
- package/lib/pencil-lock.js +128 -0
- package/lib/pencil-session.js +229 -0
- package/package.json +3 -1
- package/references/checkpoints.md +6 -1
- package/references/pencil-design-to-code.md +9 -2
- package/scripts/test-mcp-runtime-gate.js +88 -0
- package/scripts/test-pen-persistence.js +146 -6
- package/scripts/test-pencil-session.js +152 -0
- package/scripts/test-persistence-flows.js +315 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
const assert = require("assert/strict");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawnSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
const repo = path.resolve(__dirname, "..");
|
|
8
|
+
const cli = path.join(repo, "bin", "da-vinci.js");
|
|
9
|
+
const fixture = JSON.parse(
|
|
10
|
+
fs.readFileSync(path.join(__dirname, "fixtures", "complex-sample.pen"), "utf8")
|
|
11
|
+
);
|
|
12
|
+
const changeId = "redesign-001";
|
|
13
|
+
|
|
14
|
+
function runTest(name, fn) {
|
|
15
|
+
try {
|
|
16
|
+
fn();
|
|
17
|
+
console.log(`PASS ${name}`);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(`FAIL ${name}`);
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createHarness() {
|
|
25
|
+
const base = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-flow-"));
|
|
26
|
+
return {
|
|
27
|
+
base,
|
|
28
|
+
home: path.join(base, "home")
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function runCli(harness, args) {
|
|
33
|
+
const [command, ...rest] = args;
|
|
34
|
+
const result = spawnSync(process.execPath, [cli, command, "--home", harness.home, ...rest], {
|
|
35
|
+
cwd: repo,
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
maxBuffer: 8 * 1024 * 1024
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
code: result.status,
|
|
42
|
+
stdout: (result.stdout || "").trim(),
|
|
43
|
+
stderr: (result.stderr || "").trim()
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function expectOk(step, result) {
|
|
48
|
+
assert.equal(result.code, 0, `${step} failed:\n${result.stderr || result.stdout}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function expectFail(step, result, pattern) {
|
|
52
|
+
assert.notEqual(result.code, 0, `${step} unexpectedly succeeded:\n${result.stdout}`);
|
|
53
|
+
assert.match(`${result.stdout}\n${result.stderr}`, pattern, `${step} failed for the wrong reason.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeJson(filePath, payload) {
|
|
57
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
58
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writeText(filePath, text) {
|
|
62
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
63
|
+
fs.writeFileSync(filePath, text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createProject(harness, name, options = {}) {
|
|
67
|
+
const root = path.join(harness.base, name);
|
|
68
|
+
const daVinciDir = path.join(root, ".da-vinci");
|
|
69
|
+
const penPath = path.join(daVinciDir, "designs", `${name}.pen`);
|
|
70
|
+
const changeDir = path.join(daVinciDir, "changes", changeId);
|
|
71
|
+
const nodesFile = path.join(root, `${name}-nodes.json`);
|
|
72
|
+
const variablesFile = path.join(root, `${name}-variables.json`);
|
|
73
|
+
const liveNodesFile = path.join(root, `${name}-live-nodes.json`);
|
|
74
|
+
|
|
75
|
+
writeText(path.join(root, "DA-VINCI.md"), "# Test\n");
|
|
76
|
+
writeText(path.join(daVinciDir, "project-inventory.md"), "# Inventory\n");
|
|
77
|
+
writeText(path.join(daVinciDir, "page-map.md"), "# Page Map\n");
|
|
78
|
+
writeText(
|
|
79
|
+
path.join(daVinciDir, "design-registry.md"),
|
|
80
|
+
`# Registry\n- Preferred .pen: .da-vinci/designs/${name}.pen\n`
|
|
81
|
+
);
|
|
82
|
+
writeText(path.join(changeDir, "design-brief.md"), "# Brief\n");
|
|
83
|
+
writeText(path.join(changeDir, "design.md"), "# Design\n");
|
|
84
|
+
writeText(path.join(changeDir, "pencil-design.md"), "# Pencil\n");
|
|
85
|
+
writeText(path.join(changeDir, "pencil-bindings.md"), "# Bindings\n");
|
|
86
|
+
|
|
87
|
+
writeJson(nodesFile, { nodes: fixture.children });
|
|
88
|
+
writeJson(variablesFile, { variables: fixture.variables });
|
|
89
|
+
|
|
90
|
+
const liveNodes = JSON.parse(JSON.stringify(fixture.children));
|
|
91
|
+
if (options.modifiedPayload) {
|
|
92
|
+
liveNodes.push({ id: `${name}-extra`, type: "frame", children: [] });
|
|
93
|
+
}
|
|
94
|
+
writeJson(liveNodesFile, { nodes: liveNodes });
|
|
95
|
+
|
|
96
|
+
if (options.withExistingPen) {
|
|
97
|
+
const seed = runCli(harness, [
|
|
98
|
+
"write-pen",
|
|
99
|
+
"--output",
|
|
100
|
+
penPath,
|
|
101
|
+
"--nodes-file",
|
|
102
|
+
nodesFile,
|
|
103
|
+
"--variables-file",
|
|
104
|
+
variablesFile,
|
|
105
|
+
"--version",
|
|
106
|
+
fixture.version
|
|
107
|
+
]);
|
|
108
|
+
expectOk(`${name} precreate existing .pen`, seed);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
root,
|
|
113
|
+
penPath,
|
|
114
|
+
nodesFile,
|
|
115
|
+
variablesFile,
|
|
116
|
+
liveNodesFile
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
runTest("new project flow passes end-to-end", () => {
|
|
121
|
+
const harness = createHarness();
|
|
122
|
+
const project = createProject(harness, "fresh-project");
|
|
123
|
+
|
|
124
|
+
expectOk(
|
|
125
|
+
"fresh begin",
|
|
126
|
+
runCli(harness, ["pencil-session", "begin", "--project", project.root, "--pen", project.penPath])
|
|
127
|
+
);
|
|
128
|
+
expectOk(
|
|
129
|
+
"fresh persist",
|
|
130
|
+
runCli(harness, [
|
|
131
|
+
"pencil-session",
|
|
132
|
+
"persist",
|
|
133
|
+
"--project",
|
|
134
|
+
project.root,
|
|
135
|
+
"--pen",
|
|
136
|
+
project.penPath,
|
|
137
|
+
"--nodes-file",
|
|
138
|
+
project.nodesFile,
|
|
139
|
+
"--variables-file",
|
|
140
|
+
project.variablesFile,
|
|
141
|
+
"--version",
|
|
142
|
+
fixture.version
|
|
143
|
+
])
|
|
144
|
+
);
|
|
145
|
+
expectOk(
|
|
146
|
+
"fresh end",
|
|
147
|
+
runCli(harness, [
|
|
148
|
+
"pencil-session",
|
|
149
|
+
"end",
|
|
150
|
+
"--project",
|
|
151
|
+
project.root,
|
|
152
|
+
"--pen",
|
|
153
|
+
project.penPath,
|
|
154
|
+
"--nodes-file",
|
|
155
|
+
project.nodesFile,
|
|
156
|
+
"--variables-file",
|
|
157
|
+
project.variablesFile,
|
|
158
|
+
"--version",
|
|
159
|
+
fixture.version
|
|
160
|
+
])
|
|
161
|
+
);
|
|
162
|
+
expectOk(
|
|
163
|
+
"fresh completion audit",
|
|
164
|
+
runCli(harness, ["audit", "--mode", "completion", "--change", changeId, "--project", project.root])
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
runTest("existing .pen flow persists modified live payload", () => {
|
|
169
|
+
const harness = createHarness();
|
|
170
|
+
const project = createProject(harness, "existing-project", {
|
|
171
|
+
withExistingPen: true,
|
|
172
|
+
modifiedPayload: true
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expectOk(
|
|
176
|
+
"existing begin",
|
|
177
|
+
runCli(harness, ["pencil-session", "begin", "--project", project.root, "--pen", project.penPath])
|
|
178
|
+
);
|
|
179
|
+
expectOk(
|
|
180
|
+
"existing persist",
|
|
181
|
+
runCli(harness, [
|
|
182
|
+
"pencil-session",
|
|
183
|
+
"persist",
|
|
184
|
+
"--project",
|
|
185
|
+
project.root,
|
|
186
|
+
"--pen",
|
|
187
|
+
project.penPath,
|
|
188
|
+
"--nodes-file",
|
|
189
|
+
project.liveNodesFile,
|
|
190
|
+
"--variables-file",
|
|
191
|
+
project.variablesFile,
|
|
192
|
+
"--version",
|
|
193
|
+
fixture.version
|
|
194
|
+
])
|
|
195
|
+
);
|
|
196
|
+
expectOk(
|
|
197
|
+
"existing end",
|
|
198
|
+
runCli(harness, [
|
|
199
|
+
"pencil-session",
|
|
200
|
+
"end",
|
|
201
|
+
"--project",
|
|
202
|
+
project.root,
|
|
203
|
+
"--pen",
|
|
204
|
+
project.penPath,
|
|
205
|
+
"--nodes-file",
|
|
206
|
+
project.liveNodesFile,
|
|
207
|
+
"--variables-file",
|
|
208
|
+
project.variablesFile,
|
|
209
|
+
"--version",
|
|
210
|
+
fixture.version
|
|
211
|
+
])
|
|
212
|
+
);
|
|
213
|
+
expectOk(
|
|
214
|
+
"existing completion audit",
|
|
215
|
+
runCli(harness, ["audit", "--mode", "completion", "--change", changeId, "--project", project.root])
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const pen = JSON.parse(fs.readFileSync(project.penPath, "utf8"));
|
|
219
|
+
assert.equal(pen.children.some((node) => node.id === "existing-project-extra"), true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
runTest("parallel mixed projects are serialized by the global Pencil lock", () => {
|
|
223
|
+
const harness = createHarness();
|
|
224
|
+
const freshA = createProject(harness, "parallel-fresh-a");
|
|
225
|
+
const freshB = createProject(harness, "parallel-fresh-b");
|
|
226
|
+
const existing = createProject(harness, "parallel-existing", {
|
|
227
|
+
withExistingPen: true
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expectOk(
|
|
231
|
+
"parallel A begin",
|
|
232
|
+
runCli(harness, ["pencil-session", "begin", "--project", freshA.root, "--pen", freshA.penPath])
|
|
233
|
+
);
|
|
234
|
+
expectFail(
|
|
235
|
+
"parallel B blocked while A is active",
|
|
236
|
+
runCli(harness, ["pencil-session", "begin", "--project", freshB.root, "--pen", freshB.penPath]),
|
|
237
|
+
/lock is already held/i
|
|
238
|
+
);
|
|
239
|
+
expectFail(
|
|
240
|
+
"parallel existing blocked while A is active",
|
|
241
|
+
runCli(harness, ["pencil-session", "begin", "--project", existing.root, "--pen", existing.penPath]),
|
|
242
|
+
/lock is already held/i
|
|
243
|
+
);
|
|
244
|
+
expectOk(
|
|
245
|
+
"parallel A end",
|
|
246
|
+
runCli(harness, ["pencil-session", "end", "--project", freshA.root, "--pen", freshA.penPath])
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
expectOk(
|
|
250
|
+
"parallel existing begin",
|
|
251
|
+
runCli(harness, ["pencil-session", "begin", "--project", existing.root, "--pen", existing.penPath])
|
|
252
|
+
);
|
|
253
|
+
expectFail(
|
|
254
|
+
"parallel B blocked while existing is active",
|
|
255
|
+
runCli(harness, ["pencil-session", "begin", "--project", freshB.root, "--pen", freshB.penPath]),
|
|
256
|
+
/lock is already held/i
|
|
257
|
+
);
|
|
258
|
+
expectOk(
|
|
259
|
+
"parallel existing end",
|
|
260
|
+
runCli(harness, [
|
|
261
|
+
"pencil-session",
|
|
262
|
+
"end",
|
|
263
|
+
"--project",
|
|
264
|
+
existing.root,
|
|
265
|
+
"--pen",
|
|
266
|
+
existing.penPath,
|
|
267
|
+
"--nodes-file",
|
|
268
|
+
existing.nodesFile,
|
|
269
|
+
"--variables-file",
|
|
270
|
+
existing.variablesFile,
|
|
271
|
+
"--version",
|
|
272
|
+
fixture.version
|
|
273
|
+
])
|
|
274
|
+
);
|
|
275
|
+
expectOk(
|
|
276
|
+
"parallel B begin",
|
|
277
|
+
runCli(harness, ["pencil-session", "begin", "--project", freshB.root, "--pen", freshB.penPath])
|
|
278
|
+
);
|
|
279
|
+
expectOk(
|
|
280
|
+
"parallel B persist",
|
|
281
|
+
runCli(harness, [
|
|
282
|
+
"pencil-session",
|
|
283
|
+
"persist",
|
|
284
|
+
"--project",
|
|
285
|
+
freshB.root,
|
|
286
|
+
"--pen",
|
|
287
|
+
freshB.penPath,
|
|
288
|
+
"--nodes-file",
|
|
289
|
+
freshB.nodesFile,
|
|
290
|
+
"--variables-file",
|
|
291
|
+
freshB.variablesFile,
|
|
292
|
+
"--version",
|
|
293
|
+
fixture.version
|
|
294
|
+
])
|
|
295
|
+
);
|
|
296
|
+
expectOk(
|
|
297
|
+
"parallel B end",
|
|
298
|
+
runCli(harness, [
|
|
299
|
+
"pencil-session",
|
|
300
|
+
"end",
|
|
301
|
+
"--project",
|
|
302
|
+
freshB.root,
|
|
303
|
+
"--pen",
|
|
304
|
+
freshB.penPath,
|
|
305
|
+
"--nodes-file",
|
|
306
|
+
freshB.nodesFile,
|
|
307
|
+
"--variables-file",
|
|
308
|
+
freshB.variablesFile,
|
|
309
|
+
"--version",
|
|
310
|
+
fixture.version
|
|
311
|
+
])
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
console.log("All persistence flow integration tests passed.");
|