@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
|
@@ -27,6 +27,9 @@ runTest("healthy completion passes", () => {
|
|
|
27
27
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
28
28
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
29
29
|
shellVisiblePenExists: true,
|
|
30
|
+
penSyncVerified: true,
|
|
31
|
+
liveSnapshotHash: "abc123",
|
|
32
|
+
persistedSnapshotHash: "abc123",
|
|
30
33
|
claimedAnchorIds: ["GfwiK", "mCZ1G", "V8zfE"],
|
|
31
34
|
claimedReviewedScreenIds: ["GfwiK", "mCZ1G", "V8zfE"],
|
|
32
35
|
reviewTargets: ["GfwiK", "mCZ1G", "V8zfE"],
|
|
@@ -55,6 +58,9 @@ runTest("unnamed editor blocks runtime gate", () => {
|
|
|
55
58
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
56
59
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
57
60
|
shellVisiblePenExists: true,
|
|
61
|
+
penSyncVerified: true,
|
|
62
|
+
liveSnapshotHash: "abc123",
|
|
63
|
+
persistedSnapshotHash: "abc123",
|
|
58
64
|
claimedAnchorIds: ["GfwiK"],
|
|
59
65
|
claimedReviewedScreenIds: ["GfwiK"],
|
|
60
66
|
reviewTargets: ["GfwiK"],
|
|
@@ -74,6 +80,9 @@ runTest("live screens without shell-visible pen block source convergence", () =>
|
|
|
74
80
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
75
81
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
76
82
|
shellVisiblePenExists: false,
|
|
83
|
+
penSyncVerified: true,
|
|
84
|
+
liveSnapshotHash: "abc123",
|
|
85
|
+
persistedSnapshotHash: "abc123",
|
|
77
86
|
claimedAnchorIds: ["GfwiK"],
|
|
78
87
|
claimedReviewedScreenIds: ["GfwiK"],
|
|
79
88
|
reviewTargets: ["GfwiK"],
|
|
@@ -93,6 +102,9 @@ runTest("missing claimed anchor blocks screen presence", () => {
|
|
|
93
102
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
94
103
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
95
104
|
shellVisiblePenExists: true,
|
|
105
|
+
penSyncVerified: true,
|
|
106
|
+
liveSnapshotHash: "abc123",
|
|
107
|
+
persistedSnapshotHash: "abc123",
|
|
96
108
|
claimedAnchorIds: ["GfwiK", "mCZ1G"],
|
|
97
109
|
claimedReviewedScreenIds: ["GfwiK"],
|
|
98
110
|
reviewTargets: ["GfwiK"],
|
|
@@ -112,6 +124,9 @@ runTest("ignored review blockers block review execution", () => {
|
|
|
112
124
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
113
125
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
114
126
|
shellVisiblePenExists: true,
|
|
127
|
+
penSyncVerified: true,
|
|
128
|
+
liveSnapshotHash: "abc123",
|
|
129
|
+
persistedSnapshotHash: "abc123",
|
|
115
130
|
claimedAnchorIds: ["GfwiK"],
|
|
116
131
|
claimedReviewedScreenIds: ["GfwiK"],
|
|
117
132
|
reviewTargets: ["GfwiK"],
|
|
@@ -148,6 +163,9 @@ runTest("first-write phase can skip screen and review checks", () => {
|
|
|
148
163
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
149
164
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
150
165
|
shellVisiblePenExists: true,
|
|
166
|
+
penSyncVerified: true,
|
|
167
|
+
liveSnapshotHash: "abc123",
|
|
168
|
+
persistedSnapshotHash: "abc123",
|
|
151
169
|
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
152
170
|
});
|
|
153
171
|
|
|
@@ -166,6 +184,9 @@ runTest("documented reconciliation downgrades mismatch to warn", () => {
|
|
|
166
184
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
167
185
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
168
186
|
shellVisiblePenExists: true,
|
|
187
|
+
penSyncVerified: true,
|
|
188
|
+
liveSnapshotHash: "abc123",
|
|
189
|
+
persistedSnapshotHash: "abc123",
|
|
169
190
|
documentedReconciliation: true,
|
|
170
191
|
claimedAnchorIds: ["GfwiK"],
|
|
171
192
|
claimedReviewedScreenIds: ["GfwiK"],
|
|
@@ -186,6 +207,73 @@ runTest("same basename but different absolute path blocks without reconciliation
|
|
|
186
207
|
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
187
208
|
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
188
209
|
shellVisiblePenExists: true,
|
|
210
|
+
penSyncVerified: true,
|
|
211
|
+
liveSnapshotHash: "abc123",
|
|
212
|
+
persistedSnapshotHash: "abc123",
|
|
213
|
+
claimedAnchorIds: ["GfwiK"],
|
|
214
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
215
|
+
reviewTargets: ["GfwiK"],
|
|
216
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
assert.equal(result.sourceConvergence.status, BLOCK);
|
|
220
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
runTest("completion without explicit pen sync verification blocks", () => {
|
|
224
|
+
const result = evaluateMcpRuntimeGate({
|
|
225
|
+
phase: "completion",
|
|
226
|
+
mcpAvailable: true,
|
|
227
|
+
projectRoot: "/repo",
|
|
228
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
229
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
230
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
231
|
+
shellVisiblePenExists: true,
|
|
232
|
+
claimedAnchorIds: ["GfwiK"],
|
|
233
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
234
|
+
reviewTargets: ["GfwiK"],
|
|
235
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
assert.equal(result.sourceConvergence.status, BLOCK);
|
|
239
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
runTest("completion with mismatched live and persisted hashes blocks", () => {
|
|
243
|
+
const result = evaluateMcpRuntimeGate({
|
|
244
|
+
phase: "completion",
|
|
245
|
+
mcpAvailable: true,
|
|
246
|
+
projectRoot: "/repo",
|
|
247
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
248
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
249
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
250
|
+
shellVisiblePenExists: true,
|
|
251
|
+
penSyncVerified: true,
|
|
252
|
+
liveSnapshotHash: "newer",
|
|
253
|
+
persistedSnapshotHash: "older",
|
|
254
|
+
claimedAnchorIds: ["GfwiK"],
|
|
255
|
+
claimedReviewedScreenIds: ["GfwiK"],
|
|
256
|
+
reviewTargets: ["GfwiK"],
|
|
257
|
+
liveScreens: [{ id: "GfwiK", name: "Splash" }]
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
assert.equal(result.sourceConvergence.status, BLOCK);
|
|
261
|
+
assert.equal(result.finalStatus, BLOCK);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
runTest("registered pen plus empty filePath usage blocks", () => {
|
|
265
|
+
const result = evaluateMcpRuntimeGate({
|
|
266
|
+
phase: "completion",
|
|
267
|
+
mcpAvailable: true,
|
|
268
|
+
projectRoot: "/repo",
|
|
269
|
+
activeEditor: "/repo/.da-vinci/designs/cipher-redesign.pen",
|
|
270
|
+
registeredPenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
271
|
+
shellVisiblePenPath: ".da-vinci/designs/cipher-redesign.pen",
|
|
272
|
+
shellVisiblePenExists: true,
|
|
273
|
+
penSyncVerified: true,
|
|
274
|
+
liveSnapshotHash: "abc123",
|
|
275
|
+
persistedSnapshotHash: "abc123",
|
|
276
|
+
usedEmptyFilePath: true,
|
|
189
277
|
claimedAnchorIds: ["GfwiK"],
|
|
190
278
|
claimedReviewedScreenIds: ["GfwiK"],
|
|
191
279
|
reviewTargets: ["GfwiK"],
|
|
@@ -5,8 +5,16 @@ const path = require("path");
|
|
|
5
5
|
const {
|
|
6
6
|
buildPenDocument,
|
|
7
7
|
writePenFromPayloadFiles,
|
|
8
|
-
snapshotPenFile
|
|
8
|
+
snapshotPenFile,
|
|
9
|
+
ensurePenFile,
|
|
10
|
+
comparePenSync,
|
|
11
|
+
getStandardPenStatePath
|
|
9
12
|
} = require("../lib/pen-persistence");
|
|
13
|
+
const {
|
|
14
|
+
acquirePencilLock,
|
|
15
|
+
releasePencilLock,
|
|
16
|
+
getPencilLockStatus
|
|
17
|
+
} = require("../lib/pencil-lock");
|
|
10
18
|
|
|
11
19
|
const fixturePath = path.join(__dirname, "fixtures", "complex-sample.pen");
|
|
12
20
|
|
|
@@ -24,6 +32,17 @@ function createTempDir() {
|
|
|
24
32
|
return fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-pen-persistence-"));
|
|
25
33
|
}
|
|
26
34
|
|
|
35
|
+
function writePayloadFiles(tempDir, fixture) {
|
|
36
|
+
const nodesFile = path.join(tempDir, "nodes.json");
|
|
37
|
+
const variablesFile = path.join(tempDir, "variables.json");
|
|
38
|
+
fs.writeFileSync(nodesFile, JSON.stringify({ nodes: fixture.children }, null, 2));
|
|
39
|
+
fs.writeFileSync(variablesFile, JSON.stringify({ variables: fixture.variables }, null, 2));
|
|
40
|
+
return {
|
|
41
|
+
nodesFile,
|
|
42
|
+
variablesFile
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
runTest("buildPenDocument preserves fixture structure", () => {
|
|
28
47
|
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
29
48
|
const document = buildPenDocument({
|
|
@@ -38,13 +57,9 @@ runTest("buildPenDocument preserves fixture structure", () => {
|
|
|
38
57
|
runTest("writePenFromPayloadFiles writes and reopens a complex sample", () => {
|
|
39
58
|
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
40
59
|
const tempDir = createTempDir();
|
|
41
|
-
const nodesFile =
|
|
42
|
-
const variablesFile = path.join(tempDir, "variables.json");
|
|
60
|
+
const { nodesFile, variablesFile } = writePayloadFiles(tempDir, fixture);
|
|
43
61
|
const outputPath = path.join(tempDir, "written.pen");
|
|
44
62
|
|
|
45
|
-
fs.writeFileSync(nodesFile, JSON.stringify({ nodes: fixture.children }, null, 2));
|
|
46
|
-
fs.writeFileSync(variablesFile, JSON.stringify({ variables: fixture.variables }, null, 2));
|
|
47
|
-
|
|
48
63
|
const result = writePenFromPayloadFiles({
|
|
49
64
|
outputPath,
|
|
50
65
|
nodesFile,
|
|
@@ -56,6 +71,7 @@ runTest("writePenFromPayloadFiles writes and reopens a complex sample", () => {
|
|
|
56
71
|
const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
57
72
|
assert.deepEqual(written, fixture);
|
|
58
73
|
assert.deepEqual(result.verification.topLevelIds, fixture.children.map((node) => node.id));
|
|
74
|
+
assert.equal(result.state.snapshotHash, JSON.parse(fs.readFileSync(result.statePath, "utf8")).snapshotHash);
|
|
59
75
|
});
|
|
60
76
|
|
|
61
77
|
runTest("snapshotPenFile round-trips a complex sample", () => {
|
|
@@ -74,6 +90,130 @@ runTest("snapshotPenFile round-trips a complex sample", () => {
|
|
|
74
90
|
assert.deepEqual(result.verification.topLevelIds, fixture.children.map((node) => node.id));
|
|
75
91
|
});
|
|
76
92
|
|
|
93
|
+
runTest("ensurePenFile seeds a missing project-local .pen and state", () => {
|
|
94
|
+
const tempDir = createTempDir();
|
|
95
|
+
const projectRoot = path.join(tempDir, "project");
|
|
96
|
+
const outputPath = path.join(projectRoot, ".da-vinci", "designs", "seed.pen");
|
|
97
|
+
|
|
98
|
+
const result = ensurePenFile({
|
|
99
|
+
outputPath,
|
|
100
|
+
verifyWithPencil: true
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
104
|
+
assert.equal(result.created, true);
|
|
105
|
+
assert.deepEqual(written, { version: "2.9", children: [] });
|
|
106
|
+
assert.equal(fs.existsSync(getStandardPenStatePath(outputPath)), true);
|
|
107
|
+
assert.deepEqual(result.verification.topLevelIds, []);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
runTest("ensurePenFile preserves an existing .pen and refreshes state", () => {
|
|
111
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
112
|
+
const tempDir = createTempDir();
|
|
113
|
+
const outputPath = path.join(tempDir, "project", ".da-vinci", "designs", "existing.pen");
|
|
114
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
115
|
+
fs.writeFileSync(outputPath, JSON.stringify(fixture, null, 2));
|
|
116
|
+
|
|
117
|
+
const result = ensurePenFile({ outputPath });
|
|
118
|
+
|
|
119
|
+
assert.equal(result.created, false);
|
|
120
|
+
assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf8")), fixture);
|
|
121
|
+
assert.equal(fs.existsSync(result.statePath), true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
runTest("comparePenSync passes when payload matches persisted .pen", () => {
|
|
125
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
126
|
+
const tempDir = createTempDir();
|
|
127
|
+
const { nodesFile, variablesFile } = writePayloadFiles(tempDir, fixture);
|
|
128
|
+
const outputPath = path.join(tempDir, "written.pen");
|
|
129
|
+
|
|
130
|
+
writePenFromPayloadFiles({
|
|
131
|
+
outputPath,
|
|
132
|
+
nodesFile,
|
|
133
|
+
variablesFile,
|
|
134
|
+
version: fixture.version
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = comparePenSync({
|
|
138
|
+
penPath: outputPath,
|
|
139
|
+
nodesFile,
|
|
140
|
+
variablesFile,
|
|
141
|
+
version: fixture.version
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
assert.equal(result.inSync, true);
|
|
145
|
+
assert.equal(result.usedStateFile, true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
runTest("comparePenSync fails when live payload is newer than disk", () => {
|
|
149
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
150
|
+
const tempDir = createTempDir();
|
|
151
|
+
const { nodesFile, variablesFile } = writePayloadFiles(tempDir, fixture);
|
|
152
|
+
const outputPath = path.join(tempDir, "written.pen");
|
|
153
|
+
|
|
154
|
+
writePenFromPayloadFiles({
|
|
155
|
+
outputPath,
|
|
156
|
+
nodesFile,
|
|
157
|
+
variablesFile,
|
|
158
|
+
version: fixture.version
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const drifted = JSON.parse(fs.readFileSync(nodesFile, "utf8"));
|
|
162
|
+
drifted.nodes = drifted.nodes.concat([
|
|
163
|
+
{
|
|
164
|
+
id: "new-screen",
|
|
165
|
+
type: "frame",
|
|
166
|
+
children: []
|
|
167
|
+
}
|
|
168
|
+
]);
|
|
169
|
+
fs.writeFileSync(nodesFile, JSON.stringify(drifted, null, 2));
|
|
170
|
+
|
|
171
|
+
const result = comparePenSync({
|
|
172
|
+
penPath: outputPath,
|
|
173
|
+
nodesFile,
|
|
174
|
+
variablesFile,
|
|
175
|
+
version: fixture.version
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
assert.equal(result.inSync, false);
|
|
179
|
+
assert.notEqual(result.liveHash, result.persistedHash);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
runTest("global Pencil lock serializes projects", () => {
|
|
183
|
+
const tempDir = createTempDir();
|
|
184
|
+
const homeDir = path.join(tempDir, "home");
|
|
185
|
+
const projectA = path.join(tempDir, "project-a");
|
|
186
|
+
const projectB = path.join(tempDir, "project-b");
|
|
187
|
+
|
|
188
|
+
const first = acquirePencilLock({
|
|
189
|
+
projectPath: projectA,
|
|
190
|
+
owner: "project-a",
|
|
191
|
+
homeDir
|
|
192
|
+
});
|
|
193
|
+
assert.equal(first.acquired, true);
|
|
194
|
+
|
|
195
|
+
assert.throws(
|
|
196
|
+
() =>
|
|
197
|
+
acquirePencilLock({
|
|
198
|
+
projectPath: projectB,
|
|
199
|
+
owner: "project-b",
|
|
200
|
+
homeDir,
|
|
201
|
+
waitMs: 0
|
|
202
|
+
}),
|
|
203
|
+
/already held/i
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const status = getPencilLockStatus({ homeDir });
|
|
207
|
+
assert.equal(status.lock.projectPath, path.resolve(projectA));
|
|
208
|
+
|
|
209
|
+
const released = releasePencilLock({
|
|
210
|
+
projectPath: projectA,
|
|
211
|
+
homeDir
|
|
212
|
+
});
|
|
213
|
+
assert.equal(released.released, true);
|
|
214
|
+
assert.equal(getPencilLockStatus({ homeDir }).lock, null);
|
|
215
|
+
});
|
|
216
|
+
|
|
77
217
|
runTest("truncated nodes payload is rejected", () => {
|
|
78
218
|
const tempDir = createTempDir();
|
|
79
219
|
const nodesFile = path.join(tempDir, "nodes.json");
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const assert = require("assert/strict");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const {
|
|
6
|
+
beginPencilSession,
|
|
7
|
+
persistPencilSession,
|
|
8
|
+
endPencilSession,
|
|
9
|
+
getPencilSessionStatus,
|
|
10
|
+
getSessionStatePath
|
|
11
|
+
} = require("../lib/pencil-session");
|
|
12
|
+
|
|
13
|
+
const fixturePath = path.join(__dirname, "fixtures", "complex-sample.pen");
|
|
14
|
+
|
|
15
|
+
function runTest(name, fn) {
|
|
16
|
+
try {
|
|
17
|
+
fn();
|
|
18
|
+
console.log(`PASS ${name}`);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error(`FAIL ${name}`);
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createTempDir() {
|
|
26
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-pencil-session-"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writePayloadFiles(tempDir, fixture) {
|
|
30
|
+
const nodesFile = path.join(tempDir, "nodes.json");
|
|
31
|
+
const variablesFile = path.join(tempDir, "variables.json");
|
|
32
|
+
fs.writeFileSync(nodesFile, JSON.stringify({ nodes: fixture.children }, null, 2));
|
|
33
|
+
fs.writeFileSync(variablesFile, JSON.stringify({ variables: fixture.variables }, null, 2));
|
|
34
|
+
return { nodesFile, variablesFile };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
runTest("pencil-session begin seeds pen and acquires lock", () => {
|
|
38
|
+
const tempDir = createTempDir();
|
|
39
|
+
const projectRoot = path.join(tempDir, "project");
|
|
40
|
+
const homeDir = path.join(tempDir, "home");
|
|
41
|
+
const penPath = path.join(projectRoot, ".da-vinci", "designs", "cipher.pen");
|
|
42
|
+
|
|
43
|
+
const result = beginPencilSession({
|
|
44
|
+
projectPath: projectRoot,
|
|
45
|
+
penPath,
|
|
46
|
+
homeDir
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
assert.equal(fs.existsSync(penPath), true);
|
|
50
|
+
assert.equal(result.session.status, "active");
|
|
51
|
+
assert.equal(result.session.penPath, path.resolve(penPath));
|
|
52
|
+
assert.equal(fs.existsSync(getSessionStatePath(projectRoot)), true);
|
|
53
|
+
|
|
54
|
+
endPencilSession({
|
|
55
|
+
projectPath: projectRoot,
|
|
56
|
+
penPath,
|
|
57
|
+
homeDir,
|
|
58
|
+
force: true
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
runTest("pencil-session persist writes and syncs latest payload", () => {
|
|
63
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
64
|
+
const tempDir = createTempDir();
|
|
65
|
+
const projectRoot = path.join(tempDir, "project");
|
|
66
|
+
const homeDir = path.join(tempDir, "home");
|
|
67
|
+
const penPath = path.join(projectRoot, ".da-vinci", "designs", "cipher.pen");
|
|
68
|
+
const { nodesFile, variablesFile } = writePayloadFiles(tempDir, fixture);
|
|
69
|
+
|
|
70
|
+
beginPencilSession({
|
|
71
|
+
projectPath: projectRoot,
|
|
72
|
+
penPath,
|
|
73
|
+
homeDir
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const persistResult = persistPencilSession({
|
|
77
|
+
projectPath: projectRoot,
|
|
78
|
+
penPath,
|
|
79
|
+
nodesFile,
|
|
80
|
+
variablesFile,
|
|
81
|
+
version: fixture.version,
|
|
82
|
+
homeDir
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
assert.equal(persistResult.syncResult.inSync, true);
|
|
86
|
+
assert.equal(persistResult.session.status, "active");
|
|
87
|
+
assert.ok(persistResult.session.lastPersistedHash);
|
|
88
|
+
|
|
89
|
+
const status = getPencilSessionStatus({
|
|
90
|
+
projectPath: projectRoot,
|
|
91
|
+
homeDir
|
|
92
|
+
});
|
|
93
|
+
assert.equal(status.session.lastPersistedHash, persistResult.session.lastPersistedHash);
|
|
94
|
+
|
|
95
|
+
endPencilSession({
|
|
96
|
+
projectPath: projectRoot,
|
|
97
|
+
penPath,
|
|
98
|
+
nodesFile,
|
|
99
|
+
variablesFile,
|
|
100
|
+
version: fixture.version,
|
|
101
|
+
homeDir
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
runTest("pencil-session end fails when live payload is stale", () => {
|
|
106
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
107
|
+
const tempDir = createTempDir();
|
|
108
|
+
const projectRoot = path.join(tempDir, "project");
|
|
109
|
+
const homeDir = path.join(tempDir, "home");
|
|
110
|
+
const penPath = path.join(projectRoot, ".da-vinci", "designs", "cipher.pen");
|
|
111
|
+
const { nodesFile, variablesFile } = writePayloadFiles(tempDir, fixture);
|
|
112
|
+
|
|
113
|
+
beginPencilSession({
|
|
114
|
+
projectPath: projectRoot,
|
|
115
|
+
penPath,
|
|
116
|
+
homeDir
|
|
117
|
+
});
|
|
118
|
+
persistPencilSession({
|
|
119
|
+
projectPath: projectRoot,
|
|
120
|
+
penPath,
|
|
121
|
+
nodesFile,
|
|
122
|
+
variablesFile,
|
|
123
|
+
version: fixture.version,
|
|
124
|
+
homeDir
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const drifted = JSON.parse(fs.readFileSync(nodesFile, "utf8"));
|
|
128
|
+
drifted.nodes.push({ id: "drift", type: "frame", children: [] });
|
|
129
|
+
fs.writeFileSync(nodesFile, JSON.stringify(drifted, null, 2));
|
|
130
|
+
|
|
131
|
+
assert.throws(
|
|
132
|
+
() =>
|
|
133
|
+
endPencilSession({
|
|
134
|
+
projectPath: projectRoot,
|
|
135
|
+
penPath,
|
|
136
|
+
nodesFile,
|
|
137
|
+
variablesFile,
|
|
138
|
+
version: fixture.version,
|
|
139
|
+
homeDir
|
|
140
|
+
}),
|
|
141
|
+
/out of sync/i
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
endPencilSession({
|
|
145
|
+
projectPath: projectRoot,
|
|
146
|
+
penPath,
|
|
147
|
+
homeDir,
|
|
148
|
+
force: true
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log("All Pencil session tests passed.");
|