@xenonbyte/da-vinci-workflow 0.1.19 → 0.1.21
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 +24 -0
- package/README.md +40 -54
- package/README.zh-CN.md +34 -54
- package/SKILL.md +4 -0
- package/commands/claude/dv/build.md +6 -0
- package/commands/claude/dv/continue.md +5 -0
- package/commands/codex/prompts/dv-build.md +4 -0
- package/commands/codex/prompts/dv-continue.md +5 -0
- package/commands/gemini/dv/build.toml +4 -0
- package/commands/gemini/dv/continue.toml +5 -0
- package/docs/codex-natural-language-usage.md +3 -0
- package/docs/dv-command-reference.md +10 -0
- package/docs/mode-use-cases.md +2 -0
- package/docs/pencil-rendering-workflow.md +16 -0
- package/docs/prompt-entrypoints.md +7 -0
- package/docs/prompt-presets/README.md +2 -0
- package/docs/prompt-presets/desktop-app.md +4 -0
- package/docs/prompt-presets/mobile-app.md +4 -0
- package/docs/prompt-presets/tablet-app.md +4 -0
- package/docs/prompt-presets/web-app.md +4 -0
- package/docs/visual-adapters.md +24 -80
- package/docs/visual-assist-presets/desktop-app.md +20 -68
- package/docs/visual-assist-presets/mobile-app.md +20 -68
- package/docs/visual-assist-presets/tablet-app.md +20 -68
- package/docs/visual-assist-presets/web-app.md +20 -68
- package/docs/workflow-examples.md +2 -0
- package/docs/workflow-overview.md +11 -0
- package/docs/zh-CN/codex-natural-language-usage.md +3 -0
- package/docs/zh-CN/dv-command-reference.md +10 -0
- package/docs/zh-CN/mode-use-cases.md +2 -0
- package/docs/zh-CN/pencil-rendering-workflow.md +16 -0
- package/docs/zh-CN/prompt-entrypoints.md +7 -0
- package/docs/zh-CN/prompt-presets/README.md +2 -0
- package/docs/zh-CN/prompt-presets/desktop-app.md +3 -0
- package/docs/zh-CN/prompt-presets/mobile-app.md +3 -0
- package/docs/zh-CN/prompt-presets/tablet-app.md +3 -0
- package/docs/zh-CN/prompt-presets/web-app.md +3 -0
- package/docs/zh-CN/visual-adapters.md +24 -80
- package/docs/zh-CN/visual-assist-presets/desktop-app.md +20 -68
- package/docs/zh-CN/visual-assist-presets/mobile-app.md +20 -68
- package/docs/zh-CN/visual-assist-presets/tablet-app.md +20 -68
- package/docs/zh-CN/visual-assist-presets/web-app.md +20 -68
- package/docs/zh-CN/workflow-examples.md +2 -0
- package/docs/zh-CN/workflow-overview.md +11 -0
- package/examples/greenfield-spec-markupflow/DA-VINCI.md +4 -13
- package/lib/audit.js +455 -0
- package/lib/cli.js +6 -1
- package/lib/pencil-session.js +6 -0
- package/package.json +2 -1
- package/references/artifact-templates.md +38 -0
- package/references/checkpoints.md +16 -0
- package/references/prompt-recipes.md +5 -0
- package/scripts/test-audit-context-delta.js +446 -0
- package/scripts/test-mode-consistency.js +50 -0
- package/scripts/test-pencil-session.js +40 -0
- package/scripts/test-persistence-flows.js +31 -1
|
@@ -18,20 +18,10 @@
|
|
|
18
18
|
|
|
19
19
|
```md
|
|
20
20
|
## Visual Assist
|
|
21
|
-
- Preferred adapters:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
- visual contract refinement
|
|
26
|
-
- page composition
|
|
27
|
-
- hierarchy and spacing
|
|
28
|
-
- responsive motion guidance
|
|
29
|
-
- anchor-surface composition
|
|
30
|
-
- Pencil design refinement
|
|
31
|
-
- Fallback:
|
|
32
|
-
- native-da-vinci
|
|
33
|
-
- Require Adapter:
|
|
34
|
-
- false
|
|
21
|
+
- Preferred adapters: ui-ux-pro-max, frontend-skill
|
|
22
|
+
- Scope: visual contract refinement, page composition, hierarchy and spacing, responsive motion guidance, anchor-surface composition, Pencil design refinement
|
|
23
|
+
- Fallback: native-da-vinci
|
|
24
|
+
- Require Adapter: false
|
|
35
25
|
```
|
|
36
26
|
|
|
37
27
|
### 变体 2:reviewer 建议性审查
|
|
@@ -43,33 +33,14 @@
|
|
|
43
33
|
|
|
44
34
|
```md
|
|
45
35
|
## Visual Assist
|
|
46
|
-
- Preferred adapters:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- Design-supervisor
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
- Design-supervisor review inputs:
|
|
55
|
-
- screenshots
|
|
56
|
-
- pencil variables
|
|
57
|
-
- visual thesis
|
|
58
|
-
- content plan
|
|
59
|
-
- interaction thesis
|
|
60
|
-
- Scope:
|
|
61
|
-
- visual contract refinement
|
|
62
|
-
- page composition
|
|
63
|
-
- hierarchy and spacing
|
|
64
|
-
- responsive motion guidance
|
|
65
|
-
- anchor-surface composition
|
|
66
|
-
- Pencil design refinement
|
|
67
|
-
- Fallback:
|
|
68
|
-
- native-da-vinci
|
|
69
|
-
- Require Adapter:
|
|
70
|
-
- false
|
|
71
|
-
- Require Supervisor Review:
|
|
72
|
-
- false
|
|
36
|
+
- Preferred adapters: ui-ux-pro-max, frontend-skill
|
|
37
|
+
- Design-supervisor reviewers: frontend-skill, ui-ux-pro-max
|
|
38
|
+
- Design-supervisor review mode: screenshot-and-theme
|
|
39
|
+
- Design-supervisor review inputs: screenshots, pencil variables, visual thesis, content plan, interaction thesis
|
|
40
|
+
- Scope: visual contract refinement, page composition, hierarchy and spacing, responsive motion guidance, anchor-surface composition, Pencil design refinement
|
|
41
|
+
- Fallback: native-da-vinci
|
|
42
|
+
- Require Adapter: false
|
|
43
|
+
- Require Supervisor Review: false
|
|
73
44
|
```
|
|
74
45
|
|
|
75
46
|
### 变体 3:reviewer 硬签字
|
|
@@ -82,33 +53,14 @@
|
|
|
82
53
|
|
|
83
54
|
```md
|
|
84
55
|
## Visual Assist
|
|
85
|
-
- Preferred adapters:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- Design-supervisor
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
- Design-supervisor review inputs:
|
|
94
|
-
- screenshots
|
|
95
|
-
- pencil variables
|
|
96
|
-
- visual thesis
|
|
97
|
-
- content plan
|
|
98
|
-
- interaction thesis
|
|
99
|
-
- Scope:
|
|
100
|
-
- visual contract refinement
|
|
101
|
-
- page composition
|
|
102
|
-
- hierarchy and spacing
|
|
103
|
-
- responsive motion guidance
|
|
104
|
-
- anchor-surface composition
|
|
105
|
-
- Pencil design refinement
|
|
106
|
-
- Fallback:
|
|
107
|
-
- native-da-vinci
|
|
108
|
-
- Require Adapter:
|
|
109
|
-
- true
|
|
110
|
-
- Require Supervisor Review:
|
|
111
|
-
- true
|
|
56
|
+
- Preferred adapters: frontend-skill, ui-ux-pro-max
|
|
57
|
+
- Design-supervisor reviewers: frontend-skill, ui-ux-pro-max
|
|
58
|
+
- Design-supervisor review mode: screenshot-and-theme
|
|
59
|
+
- Design-supervisor review inputs: screenshots, pencil variables, visual thesis, content plan, interaction thesis
|
|
60
|
+
- Scope: visual contract refinement, page composition, hierarchy and spacing, responsive motion guidance, anchor-surface composition, Pencil design refinement
|
|
61
|
+
- Fallback: native-da-vinci
|
|
62
|
+
- Require Adapter: true
|
|
63
|
+
- Require Supervisor Review: true
|
|
112
64
|
```
|
|
113
65
|
|
|
114
66
|
通用说明:
|
|
@@ -130,6 +130,7 @@ anchor 通过后,再抽 shared primitives,然后再扩更多页面。
|
|
|
130
130
|
- `design-source checkpoint`
|
|
131
131
|
- 如果启用了 Pencil MCP,则跑 `MCP runtime gate`
|
|
132
132
|
- 工作中期跑 `da-vinci audit --mode integrity <project-path>`
|
|
133
|
+
- 在现有 change 工件里记录 checkpoint 邻近的 `Context Delta`
|
|
133
134
|
|
|
134
135
|
这些检查用来确认:
|
|
135
136
|
|
|
@@ -137,6 +138,7 @@ anchor 通过后,再抽 shared primitives,然后再扩更多页面。
|
|
|
137
138
|
- active editor 是正确的设计源
|
|
138
139
|
- shell 上确实有 `.pen`
|
|
139
140
|
- live snapshot 和 persisted snapshot 已同步
|
|
141
|
+
- 最近关键执行上下文可恢复,但不会替代工件真相的选路权
|
|
140
142
|
|
|
141
143
|
### 6. Mapping
|
|
142
144
|
|
|
@@ -165,6 +167,10 @@ mapping 通过后:
|
|
|
165
167
|
- 如果用了 Pencil MCP,runtime gate 结果可接受
|
|
166
168
|
- `da-vinci audit --mode completion --change <change-id> <project-path>` 通过
|
|
167
169
|
|
|
170
|
+
补充说明:
|
|
171
|
+
|
|
172
|
+
- Context Delta 的告警用于提升续跑质量,本身不会单独形成新的 completion 阻断
|
|
173
|
+
|
|
168
174
|
## 门禁与审计
|
|
169
175
|
|
|
170
176
|
### Design Checkpoint
|
|
@@ -219,6 +225,11 @@ mapping 通过后:
|
|
|
219
225
|
|
|
220
226
|
在任何终态完成声明前跑。
|
|
221
227
|
|
|
228
|
+
audit 中对 Context Delta 的处理:
|
|
229
|
+
|
|
230
|
+
- 缺失、字段不完整、或过期冲突都属于告警级信号
|
|
231
|
+
- 选路和完成真相仍以工件、checkpoint、runtime gate 与文件系统审计为准
|
|
232
|
+
|
|
222
233
|
## 流程图
|
|
223
234
|
|
|
224
235
|
```mermaid
|
|
@@ -35,19 +35,10 @@
|
|
|
35
35
|
- CTA regions should preserve the same dark product language
|
|
36
36
|
|
|
37
37
|
## Visual Assist
|
|
38
|
-
- Preferred adapters:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
- visual contract refinement
|
|
43
|
-
- page composition
|
|
44
|
-
- hierarchy and spacing
|
|
45
|
-
- anchor-surface composition
|
|
46
|
-
- Pencil design refinement
|
|
47
|
-
- Fallback:
|
|
48
|
-
- native-da-vinci
|
|
49
|
-
- Require Adapter:
|
|
50
|
-
- false
|
|
38
|
+
- Preferred adapters: frontend-skill, ui-ux-pro-max
|
|
39
|
+
- Scope: visual contract refinement, page composition, hierarchy and spacing, anchor-surface composition, Pencil design refinement
|
|
40
|
+
- Fallback: native-da-vinci
|
|
41
|
+
- Require Adapter: false
|
|
51
42
|
|
|
52
43
|
## Do
|
|
53
44
|
- keep page sections clearly bounded
|
package/lib/audit.js
CHANGED
|
@@ -121,6 +121,12 @@ function addMissingArtifacts(projectRoot, artifactPaths, targetList) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function pushUnique(targetList, message) {
|
|
125
|
+
if (!targetList.includes(message)) {
|
|
126
|
+
targetList.push(message);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
124
130
|
function getMarkdownSection(text, heading) {
|
|
125
131
|
if (!text) {
|
|
126
132
|
return "";
|
|
@@ -151,6 +157,323 @@ function getMarkdownSection(text, heading) {
|
|
|
151
157
|
return sectionLines.join("\n").trim();
|
|
152
158
|
}
|
|
153
159
|
|
|
160
|
+
function normalizeCheckpointLabel(value) {
|
|
161
|
+
return String(value || "")
|
|
162
|
+
.toLowerCase()
|
|
163
|
+
.replace(/`/g, "")
|
|
164
|
+
.replace(/[_-]+/g, " ")
|
|
165
|
+
.replace(/\s+/g, " ")
|
|
166
|
+
.trim();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseCheckpointStatusMap(markdownText) {
|
|
170
|
+
const section = getMarkdownSection(markdownText, "Checkpoint Status");
|
|
171
|
+
if (!section) {
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const statuses = {};
|
|
176
|
+
const matches = section.matchAll(/(?:^|\n)\s*-\s*`?([^`:\n]+?)`?\s*:\s*(PASS|WARN|BLOCK)\b/gi);
|
|
177
|
+
for (const match of matches) {
|
|
178
|
+
const label = normalizeCheckpointLabel(match[1]);
|
|
179
|
+
if (!label) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
statuses[label] = String(match[2]).toUpperCase();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return statuses;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function hasContextDeltaExpectationSignals(markdownText) {
|
|
189
|
+
const text = String(markdownText || "");
|
|
190
|
+
return (
|
|
191
|
+
/##\s+(Checkpoint Status|MCP Runtime Gate)\b/i.test(text) ||
|
|
192
|
+
/(?:^|\n)\s*(?:[-*]\s*)?`?Context Delta Required`?\s*:\s*(?:true|yes|on|1)\b/i.test(text)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function parseSupersedesTokens(value) {
|
|
197
|
+
return String(value || "")
|
|
198
|
+
.split(/[,\n;]/)
|
|
199
|
+
.map((token) => token.trim())
|
|
200
|
+
.filter(Boolean);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizeTimeToken(value) {
|
|
204
|
+
const raw = String(value || "").trim();
|
|
205
|
+
if (!raw) {
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const parseCandidates = [];
|
|
210
|
+
const rawWithT = raw.includes(" ") ? raw.replace(/\s+/, "T") : raw;
|
|
211
|
+
const hasExplicitTimezone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(rawWithT);
|
|
212
|
+
|
|
213
|
+
if (!hasExplicitTimezone && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(rawWithT)) {
|
|
214
|
+
parseCandidates.push(`${rawWithT}Z`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
parseCandidates.push(rawWithT);
|
|
218
|
+
parseCandidates.push(raw);
|
|
219
|
+
|
|
220
|
+
for (const candidate of parseCandidates) {
|
|
221
|
+
const timestamp = Date.parse(candidate);
|
|
222
|
+
if (!Number.isFinite(timestamp)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
return new Date(timestamp).toISOString();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return "";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getTimeReferenceKeys(value) {
|
|
232
|
+
const raw = String(value || "").trim();
|
|
233
|
+
if (!raw) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const keys = new Set([raw]);
|
|
238
|
+
|
|
239
|
+
if (raw.includes(" ")) {
|
|
240
|
+
keys.add(raw.replace(/\s+/, "T"));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (raw.endsWith(".000Z")) {
|
|
244
|
+
keys.add(raw.replace(".000Z", "Z"));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const normalized = normalizeTimeToken(raw);
|
|
248
|
+
if (normalized) {
|
|
249
|
+
keys.add(normalized);
|
|
250
|
+
if (normalized.endsWith(".000Z")) {
|
|
251
|
+
keys.add(normalized.replace(".000Z", "Z"));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return [...keys];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function buildContextDeltaReferenceIndex(entries) {
|
|
259
|
+
const referenceIndex = new Map();
|
|
260
|
+
|
|
261
|
+
function addReference(key, entryIndex) {
|
|
262
|
+
const normalizedKey = String(key || "").trim();
|
|
263
|
+
if (!normalizedKey) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const existing = referenceIndex.get(normalizedKey) || [];
|
|
268
|
+
existing.push(entryIndex);
|
|
269
|
+
referenceIndex.set(normalizedKey, existing);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
entries.forEach((entry, entryIndex) => {
|
|
273
|
+
if (entry.time) {
|
|
274
|
+
for (const timeKey of getTimeReferenceKeys(entry.time)) {
|
|
275
|
+
addReference(timeKey, entryIndex);
|
|
276
|
+
addReference(`time:${timeKey}`, entryIndex);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (entry.checkpointType && entry.time) {
|
|
281
|
+
const normalizedCheckpointType = normalizeCheckpointLabel(entry.checkpointType);
|
|
282
|
+
for (const timeKey of getTimeReferenceKeys(entry.time)) {
|
|
283
|
+
addReference(`${entry.checkpointType}@${timeKey}`, entryIndex);
|
|
284
|
+
addReference(`${normalizedCheckpointType}@${timeKey}`, entryIndex);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return referenceIndex;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function getSupersedesCandidateKeys(token) {
|
|
293
|
+
const trimmed = String(token || "").trim();
|
|
294
|
+
if (!trimmed) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const keys = new Set([trimmed]);
|
|
299
|
+
const prefixedTimeMatch = trimmed.match(/^time\s*:\s*(.+)$/i);
|
|
300
|
+
if (prefixedTimeMatch) {
|
|
301
|
+
for (const timeKey of getTimeReferenceKeys(prefixedTimeMatch[1])) {
|
|
302
|
+
keys.add(timeKey);
|
|
303
|
+
keys.add(`time:${timeKey}`);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
keys.add(`time:${trimmed}`);
|
|
307
|
+
for (const timeKey of getTimeReferenceKeys(trimmed)) {
|
|
308
|
+
keys.add(timeKey);
|
|
309
|
+
keys.add(`time:${timeKey}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const atIndex = trimmed.indexOf("@");
|
|
314
|
+
if (atIndex > 0 && atIndex < trimmed.length - 1) {
|
|
315
|
+
const checkpointType = trimmed.slice(0, atIndex).trim();
|
|
316
|
+
const time = trimmed.slice(atIndex + 1).trim();
|
|
317
|
+
if (checkpointType && time) {
|
|
318
|
+
const normalizedCheckpointType = normalizeCheckpointLabel(checkpointType);
|
|
319
|
+
for (const timeKey of getTimeReferenceKeys(time)) {
|
|
320
|
+
keys.add(`${checkpointType}@${timeKey}`);
|
|
321
|
+
keys.add(`${normalizedCheckpointType}@${timeKey}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return [...keys];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function resolveSupersedesReferenceIndices(token, referenceIndex) {
|
|
330
|
+
const indices = new Set();
|
|
331
|
+
for (const key of getSupersedesCandidateKeys(token)) {
|
|
332
|
+
const matches = referenceIndex.get(key);
|
|
333
|
+
if (!matches) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
for (const entryIndex of matches) {
|
|
337
|
+
indices.add(entryIndex);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return [...indices].sort((a, b) => a - b);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function inspectContextDelta(markdownText) {
|
|
344
|
+
const section = getMarkdownSection(markdownText, "Context Delta");
|
|
345
|
+
if (!section) {
|
|
346
|
+
return {
|
|
347
|
+
found: false,
|
|
348
|
+
hasConcreteEntry: false,
|
|
349
|
+
entries: [],
|
|
350
|
+
incompleteEntryCount: 0
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const lines = String(section).replace(/\r\n?/g, "\n").split("\n");
|
|
355
|
+
const entries = [];
|
|
356
|
+
let current = null;
|
|
357
|
+
|
|
358
|
+
function ensureCurrent() {
|
|
359
|
+
if (!current) {
|
|
360
|
+
current = {};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function flushCurrent() {
|
|
365
|
+
if (!current) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const hasAnyValue = [
|
|
370
|
+
current.time,
|
|
371
|
+
current.checkpointType,
|
|
372
|
+
current.goal,
|
|
373
|
+
current.decision,
|
|
374
|
+
current.constraints,
|
|
375
|
+
current.impact,
|
|
376
|
+
current.status,
|
|
377
|
+
current.nextAction,
|
|
378
|
+
current.supersedes
|
|
379
|
+
].some((value) => Boolean(value));
|
|
380
|
+
|
|
381
|
+
if (hasAnyValue) {
|
|
382
|
+
entries.push(current);
|
|
383
|
+
}
|
|
384
|
+
current = null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
for (const rawLine of lines) {
|
|
388
|
+
const line = rawLine.trim();
|
|
389
|
+
|
|
390
|
+
if (!line) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const timeMatch = line.match(/^-+\s*`?time`?\s*:\s*(.+)$/i);
|
|
395
|
+
if (timeMatch) {
|
|
396
|
+
flushCurrent();
|
|
397
|
+
current = {
|
|
398
|
+
time: timeMatch[1].trim()
|
|
399
|
+
};
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const checkpointTypeMatch = line.match(/^-+\s*`?checkpoint(?:[_ -]?type)?`?\s*:\s*(.+)$/i);
|
|
404
|
+
if (checkpointTypeMatch) {
|
|
405
|
+
ensureCurrent();
|
|
406
|
+
current.checkpointType = checkpointTypeMatch[1].trim();
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const goalMatch = line.match(/^-+\s*`?goal`?\s*:\s*(.+)$/i);
|
|
411
|
+
if (goalMatch) {
|
|
412
|
+
ensureCurrent();
|
|
413
|
+
current.goal = goalMatch[1].trim();
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const decisionMatch = line.match(/^-+\s*`?decision`?\s*:\s*(.+)$/i);
|
|
418
|
+
if (decisionMatch) {
|
|
419
|
+
ensureCurrent();
|
|
420
|
+
current.decision = decisionMatch[1].trim();
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const constraintsMatch = line.match(/^-+\s*`?constraints`?\s*:\s*(.+)$/i);
|
|
425
|
+
if (constraintsMatch) {
|
|
426
|
+
ensureCurrent();
|
|
427
|
+
current.constraints = constraintsMatch[1].trim();
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const impactMatch = line.match(/^-+\s*`?impact`?\s*:\s*(.+)$/i);
|
|
432
|
+
if (impactMatch) {
|
|
433
|
+
ensureCurrent();
|
|
434
|
+
current.impact = impactMatch[1].trim();
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const statusMatch = line.match(/^-+\s*`?status`?\s*:\s*(PASS|WARN|BLOCK)\b/i);
|
|
439
|
+
if (statusMatch) {
|
|
440
|
+
ensureCurrent();
|
|
441
|
+
current.status = String(statusMatch[1]).toUpperCase();
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const nextActionMatch = line.match(/^-+\s*`?next(?:[_ -]?action)?`?\s*:\s*(.+)$/i);
|
|
446
|
+
if (nextActionMatch) {
|
|
447
|
+
ensureCurrent();
|
|
448
|
+
current.nextAction = nextActionMatch[1].trim();
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const supersedesMatch = line.match(/^-+\s*`?supersedes`?\s*:\s*(.+)$/i);
|
|
453
|
+
if (supersedesMatch) {
|
|
454
|
+
ensureCurrent();
|
|
455
|
+
current.supersedes = supersedesMatch[1].trim();
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
flushCurrent();
|
|
461
|
+
|
|
462
|
+
const hasConcreteEntry = entries.some(
|
|
463
|
+
(entry) => entry.time || entry.checkpointType || entry.status || entry.decision || entry.nextAction
|
|
464
|
+
);
|
|
465
|
+
const incompleteEntryCount = entries.filter(
|
|
466
|
+
(entry) => !entry.time || !entry.checkpointType || !entry.status
|
|
467
|
+
).length;
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
found: true,
|
|
471
|
+
hasConcreteEntry,
|
|
472
|
+
entries,
|
|
473
|
+
incompleteEntryCount
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
154
477
|
function getVisualAssistFieldValues(daVinciText, fieldName) {
|
|
155
478
|
const section = getMarkdownSection(daVinciText, "Visual Assist");
|
|
156
479
|
if (!section) {
|
|
@@ -547,6 +870,138 @@ function auditProject(projectPathInput, options = {}) {
|
|
|
547
870
|
}
|
|
548
871
|
}
|
|
549
872
|
|
|
873
|
+
const tasksPath = path.join(changeDir, "tasks.md");
|
|
874
|
+
const verificationPath = path.join(changeDir, "verification.md");
|
|
875
|
+
const contextArtifacts = [pencilDesignPath, tasksPath, verificationPath]
|
|
876
|
+
.filter((artifactPath) => pathExists(artifactPath))
|
|
877
|
+
.map((artifactPath) => ({
|
|
878
|
+
path: artifactPath,
|
|
879
|
+
text: readTextIfExists(artifactPath)
|
|
880
|
+
}));
|
|
881
|
+
const contextCandidates = contextArtifacts.filter((artifact) =>
|
|
882
|
+
hasContextDeltaExpectationSignals(artifact.text)
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
if (contextCandidates.length > 0) {
|
|
886
|
+
const contextInspections = contextCandidates.map((artifact) => ({
|
|
887
|
+
path: artifact.path,
|
|
888
|
+
inspection: inspectContextDelta(artifact.text),
|
|
889
|
+
checkpointStatuses: parseCheckpointStatusMap(artifact.text)
|
|
890
|
+
}));
|
|
891
|
+
|
|
892
|
+
const hasAnyConcreteContextDelta = contextInspections.some(
|
|
893
|
+
(item) => item.inspection.hasConcreteEntry
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
if (!hasAnyConcreteContextDelta) {
|
|
897
|
+
pushUnique(
|
|
898
|
+
warnings,
|
|
899
|
+
`Checkpoint-bearing artifacts in ${changeRel} do not record any concrete \`## Context Delta\` entries.`
|
|
900
|
+
);
|
|
901
|
+
} else {
|
|
902
|
+
notes.push(`Detected context-delta recovery notes in ${changeRel}.`);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
for (const item of contextInspections) {
|
|
906
|
+
if (!item.inspection.found || item.inspection.incompleteEntryCount === 0) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
pushUnique(
|
|
911
|
+
warnings,
|
|
912
|
+
`${relativeTo(projectRoot, item.path)} has ${item.inspection.incompleteEntryCount} context-delta entr` +
|
|
913
|
+
`${item.inspection.incompleteEntryCount === 1 ? "y" : "ies"} missing one of: time/checkpoint_type/status.`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
for (const item of contextInspections) {
|
|
918
|
+
if (!item.inspection.found || item.inspection.entries.length === 0) {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const referenceIndex = buildContextDeltaReferenceIndex(item.inspection.entries);
|
|
923
|
+
|
|
924
|
+
item.inspection.entries.forEach((entry, entryIndex) => {
|
|
925
|
+
if (!entry.supersedes) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const entryLabel = entry.time ? `time \`${entry.time}\`` : `entry #${entryIndex + 1}`;
|
|
930
|
+
const supersedesTokens = parseSupersedesTokens(entry.supersedes);
|
|
931
|
+
|
|
932
|
+
if (supersedesTokens.length === 0) {
|
|
933
|
+
pushUnique(
|
|
934
|
+
warnings,
|
|
935
|
+
`${relativeTo(projectRoot, item.path)} has ${entryLabel} with an empty \`supersedes\` reference.`
|
|
936
|
+
);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
for (const token of supersedesTokens) {
|
|
941
|
+
const resolvedIndices = resolveSupersedesReferenceIndices(token, referenceIndex);
|
|
942
|
+
|
|
943
|
+
if (resolvedIndices.length === 0) {
|
|
944
|
+
pushUnique(
|
|
945
|
+
warnings,
|
|
946
|
+
`${relativeTo(projectRoot, item.path)} has ${entryLabel} superseding \`${token}\`, ` +
|
|
947
|
+
"but no referenced context-delta entry exists in the same artifact."
|
|
948
|
+
);
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const hasOlderMatch = resolvedIndices.some((resolvedIndex) => resolvedIndex < entryIndex);
|
|
953
|
+
if (!hasOlderMatch) {
|
|
954
|
+
pushUnique(
|
|
955
|
+
warnings,
|
|
956
|
+
`${relativeTo(projectRoot, item.path)} has ${entryLabel} superseding \`${token}\`, ` +
|
|
957
|
+
"but the reference does not point to an earlier context-delta entry."
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (mode === "completion" && scopedChangeDirs.includes(changeDir)) {
|
|
965
|
+
for (const item of contextInspections) {
|
|
966
|
+
if (!item.inspection.hasConcreteEntry || Object.keys(item.checkpointStatuses).length === 0) {
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const entriesByType = new Map();
|
|
971
|
+
for (const entry of item.inspection.entries) {
|
|
972
|
+
if (!entry.checkpointType || !entry.status) {
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
const checkpointType = normalizeCheckpointLabel(entry.checkpointType);
|
|
976
|
+
const existing = entriesByType.get(checkpointType) || [];
|
|
977
|
+
existing.push(entry);
|
|
978
|
+
entriesByType.set(checkpointType, existing);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
for (const [checkpointType, entries] of entriesByType.entries()) {
|
|
982
|
+
const currentStatus = item.checkpointStatuses[checkpointType];
|
|
983
|
+
if (!currentStatus || entries.length === 0) {
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const latestEntry = entries[entries.length - 1];
|
|
988
|
+
if (currentStatus === latestEntry.status) {
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const statusHistory = entries.map((entry) => entry.status).join(" -> ");
|
|
993
|
+
pushUnique(
|
|
994
|
+
warnings,
|
|
995
|
+
`Context-delta/checkpoint status mismatch in ${relativeTo(projectRoot, item.path)} for ` +
|
|
996
|
+
`\`${latestEntry.checkpointType}\`: latest context-delta status is ${latestEntry.status}, ` +
|
|
997
|
+
`current checkpoint status is ${currentStatus} (history: ${statusHistory}). ` +
|
|
998
|
+
"This may indicate stale checkpoint status or stale context-delta notes."
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
550
1005
|
const specDirs = listChildDirs(path.join(changeDir, "specs"));
|
|
551
1006
|
for (const specDir of specDirs) {
|
|
552
1007
|
const specFile = path.join(specDir, "spec.md");
|
package/lib/cli.js
CHANGED
|
@@ -100,7 +100,7 @@ function printHelp() {
|
|
|
100
100
|
" da-vinci pencil-lock status",
|
|
101
101
|
" da-vinci pencil-session begin --project <path> --pen <path>",
|
|
102
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>
|
|
103
|
+
" da-vinci pencil-session end --project <path> --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
104
104
|
" da-vinci pencil-session status --project <path>",
|
|
105
105
|
" da-vinci --version",
|
|
106
106
|
"",
|
|
@@ -456,6 +456,11 @@ async function runCli(argv) {
|
|
|
456
456
|
if (!penPath) {
|
|
457
457
|
throw new Error("`pencil-session end` requires `--pen <path>`.");
|
|
458
458
|
}
|
|
459
|
+
if (!nodesFile && !force) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
"`pencil-session end` requires `--nodes-file <path>` (and `--variables-file <path>` when available). Use `--force` only for emergency lock release."
|
|
462
|
+
);
|
|
463
|
+
}
|
|
459
464
|
|
|
460
465
|
const result = endPencilSession({
|
|
461
466
|
projectPath,
|
package/lib/pencil-session.js
CHANGED
|
@@ -168,6 +168,12 @@ function endPencilSession(options) {
|
|
|
168
168
|
throw new Error("A registered `.pen` path is required for session shutdown.");
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
if (!options.nodesFile && !options.force) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
"Cannot end Pencil session without a live MCP snapshot. Provide `--nodes-file` (and `--variables-file` when available), or use `--force` for emergency shutdown."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
171
177
|
let syncResult = null;
|
|
172
178
|
if (options.nodesFile) {
|
|
173
179
|
syncResult = comparePenSync({
|