@xenonbyte/da-vinci-workflow 0.1.22 → 0.1.23
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 +16 -0
- package/README.md +6 -1
- package/README.zh-CN.md +6 -1
- package/lib/audit-parsers.js +452 -0
- package/lib/audit.js +102 -448
- package/lib/fs-safety.js +116 -0
- package/lib/icon-aliases.js +3 -21
- package/lib/icon-search.js +4 -21
- package/lib/icon-text.js +27 -0
- package/lib/install.js +18 -10
- package/lib/pencil-preflight.js +167 -18
- package/package.json +2 -1
- package/scripts/test-audit-safety.js +92 -0
- package/scripts/test-icon-aliases.js +9 -0
- package/scripts/test-icon-search.js +5 -0
- package/scripts/test-pencil-preflight.js +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.1.23 - 2026-03-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `lib/audit-parsers.js` to isolate markdown/checkpoint/context-delta/design-supervisor parsing concerns from audit orchestration
|
|
7
|
+
- `lib/fs-safety.js` for bounded recursive traversal and project-root path-boundary checks
|
|
8
|
+
- `lib/icon-text.js` to share icon text normalization/tokenization across icon modules
|
|
9
|
+
- `scripts/test-audit-safety.js` to cover out-of-root registry paths and traversal truncation warnings
|
|
10
|
+
- `test:audit-safety` npm script
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `lib/audit.js` now uses bounded safe scans, emits traversal warnings, and rejects out-of-root registry `.pen` references
|
|
14
|
+
- `lib/install.js` now uses bounded safe traversal for asset enumeration
|
|
15
|
+
- `lib/pencil-preflight.js` now hardens VM execution with unsafe-source checks, disabled dynamic code generation, source-size limits, and explicit timeout classification
|
|
16
|
+
- `lib/icon-search.js` and `lib/icon-aliases.js` now consume shared text utilities instead of duplicate local implementations
|
|
17
|
+
- preflight/icon tests now include safety and normalization regression coverage
|
|
18
|
+
|
|
3
19
|
## v0.1.22 - 2026-03-29
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -28,10 +28,15 @@ This workflow is intended for:
|
|
|
28
28
|
|
|
29
29
|
Latest published npm package:
|
|
30
30
|
|
|
31
|
-
- `@xenonbyte/da-vinci-workflow@0.1.
|
|
31
|
+
- `@xenonbyte/da-vinci-workflow@0.1.23`
|
|
32
32
|
|
|
33
33
|
Release highlights:
|
|
34
34
|
|
|
35
|
+
- audit parser logic is now split into `lib/audit-parsers.js`, reducing `lib/audit.js` size and making maintenance safer
|
|
36
|
+
- recursive file traversal now uses bounded safe scans with depth/count limits and symlink skipping
|
|
37
|
+
- completion/integrity audit now rejects out-of-root `.pen` references from `design-registry.md`
|
|
38
|
+
- `preflight-pencil` sandbox now blocks unsafe runtime tokens, disables dynamic code generation, and reports timeout failures explicitly
|
|
39
|
+
- icon text normalization/tokenization is now shared across `icon-search` and `icon-aliases`, removing duplicated logic and adding parity coverage
|
|
35
40
|
- `da-vinci pencil-session end` now requires live snapshot input (`--nodes-file`) unless `--force` is used, preventing silent session close while live MCP and disk are out of sync
|
|
36
41
|
- `build` route discipline now treats compile success as non-terminal and requires `da-vinci audit --mode completion --change <change-id> <project-path>` before reporting terminal completion
|
|
37
42
|
- `continue` route guidance now blocks `build` recommendation whenever core design gates remain unresolved (missing project-local `.pen`, active session, runtime/design-source BLOCK, or required design-supervisor BLOCK)
|
package/README.zh-CN.md
CHANGED
|
@@ -30,10 +30,15 @@ Da Vinci 是一个把产品需求一路推进到结构化规格、Pencil 设计
|
|
|
30
30
|
|
|
31
31
|
最新已发布 npm 包:
|
|
32
32
|
|
|
33
|
-
- `@xenonbyte/da-vinci-workflow@0.1.
|
|
33
|
+
- `@xenonbyte/da-vinci-workflow@0.1.23`
|
|
34
34
|
|
|
35
35
|
已发布版本重点:
|
|
36
36
|
|
|
37
|
+
- audit 解析职责已拆分到 `lib/audit-parsers.js`,`lib/audit.js` 体量下降,可维护性更好
|
|
38
|
+
- 递归文件扫描改为安全有界遍历:增加深度/数量上限,并跳过符号链接
|
|
39
|
+
- completion/integrity audit 现在会拦截并忽略 `design-registry.md` 中越出项目根目录的 `.pen` 引用
|
|
40
|
+
- `preflight-pencil` 沙箱增强:拦截危险运行时 token、禁用动态代码生成,并对超时失败给出明确分类
|
|
41
|
+
- `icon-search` 与 `icon-aliases` 现在复用同一套文本归一化/分词逻辑,去掉重复实现并补了一致性回归测试
|
|
37
42
|
- `da-vinci pencil-session end` 现在默认要求提供 live 快照输入(`--nodes-file`);只有显式 `--force` 才允许跳过,避免 live MCP 与磁盘未同步时被静默关闭
|
|
38
43
|
- `build` 路由现在明确:编译成功不等于流程完成;对外宣布终态前必须通过 `da-vinci audit --mode completion --change <change-id> <project-path>`
|
|
39
44
|
- `continue` 的推荐规则现在会拦截未过设计门禁时的 `build` 选路(缺少项目内 `.pen`、session 未关闭、runtime/design-source BLOCK、required design-supervisor BLOCK)
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
function escapeRegExp(value) {
|
|
2
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function getMarkdownSection(text, heading) {
|
|
6
|
+
if (!text) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const escapedHeading = escapeRegExp(heading);
|
|
11
|
+
const headingPattern = new RegExp(`^##\\s+${escapedHeading}\\s*$`, "i");
|
|
12
|
+
const anyHeadingPattern = /^##\s+/;
|
|
13
|
+
const lines = String(text).replace(/\r\n?/g, "\n").split("\n");
|
|
14
|
+
const sectionLines = [];
|
|
15
|
+
let capturing = false;
|
|
16
|
+
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
if (capturing && anyHeadingPattern.test(line)) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!capturing && headingPattern.test(line)) {
|
|
23
|
+
capturing = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (capturing) {
|
|
28
|
+
sectionLines.push(line);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return sectionLines.join("\n").trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeCheckpointLabel(value) {
|
|
36
|
+
return String(value || "")
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/`/g, "")
|
|
39
|
+
.replace(/[_-]+/g, " ")
|
|
40
|
+
.replace(/\s+/g, " ")
|
|
41
|
+
.trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseCheckpointStatusMap(markdownText) {
|
|
45
|
+
const section = getMarkdownSection(markdownText, "Checkpoint Status");
|
|
46
|
+
if (!section) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const statuses = {};
|
|
51
|
+
const matches = section.matchAll(/(?:^|\n)\s*-\s*`?([^`:\n]+?)`?\s*:\s*(PASS|WARN|BLOCK)\b/gi);
|
|
52
|
+
for (const match of matches) {
|
|
53
|
+
const label = normalizeCheckpointLabel(match[1]);
|
|
54
|
+
if (!label) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
statuses[label] = String(match[2]).toUpperCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return statuses;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasContextDeltaExpectationSignals(markdownText) {
|
|
64
|
+
const text = String(markdownText || "");
|
|
65
|
+
return (
|
|
66
|
+
/##\s+(Checkpoint Status|MCP Runtime Gate)\b/i.test(text) ||
|
|
67
|
+
/(?:^|\n)\s*(?:[-*]\s*)?`?Context Delta Required`?\s*:\s*(?:true|yes|on|1)\b/i.test(text)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseSupersedesTokens(value) {
|
|
72
|
+
return String(value || "")
|
|
73
|
+
.split(/[,\n;]/)
|
|
74
|
+
.map((token) => token.trim())
|
|
75
|
+
.filter(Boolean);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeTimeToken(value) {
|
|
79
|
+
const raw = String(value || "").trim();
|
|
80
|
+
if (!raw) {
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const parseCandidates = [];
|
|
85
|
+
const rawWithT = raw.includes(" ") ? raw.replace(/\s+/, "T") : raw;
|
|
86
|
+
const hasExplicitTimezone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(rawWithT);
|
|
87
|
+
|
|
88
|
+
if (!hasExplicitTimezone && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(rawWithT)) {
|
|
89
|
+
parseCandidates.push(`${rawWithT}Z`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
parseCandidates.push(rawWithT);
|
|
93
|
+
parseCandidates.push(raw);
|
|
94
|
+
|
|
95
|
+
for (const candidate of parseCandidates) {
|
|
96
|
+
const timestamp = Date.parse(candidate);
|
|
97
|
+
if (!Number.isFinite(timestamp)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
return new Date(timestamp).toISOString();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getTimeReferenceKeys(value) {
|
|
107
|
+
const raw = String(value || "").trim();
|
|
108
|
+
if (!raw) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const keys = new Set([raw]);
|
|
113
|
+
|
|
114
|
+
if (raw.includes(" ")) {
|
|
115
|
+
keys.add(raw.replace(/\s+/, "T"));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (raw.endsWith(".000Z")) {
|
|
119
|
+
keys.add(raw.replace(".000Z", "Z"));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const normalized = normalizeTimeToken(raw);
|
|
123
|
+
if (normalized) {
|
|
124
|
+
keys.add(normalized);
|
|
125
|
+
if (normalized.endsWith(".000Z")) {
|
|
126
|
+
keys.add(normalized.replace(".000Z", "Z"));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return [...keys];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildContextDeltaReferenceIndex(entries) {
|
|
134
|
+
const referenceIndex = new Map();
|
|
135
|
+
|
|
136
|
+
function addReference(key, entryIndex) {
|
|
137
|
+
const normalizedKey = String(key || "").trim();
|
|
138
|
+
if (!normalizedKey) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const existing = referenceIndex.get(normalizedKey) || [];
|
|
143
|
+
existing.push(entryIndex);
|
|
144
|
+
referenceIndex.set(normalizedKey, existing);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
entries.forEach((entry, entryIndex) => {
|
|
148
|
+
if (entry.time) {
|
|
149
|
+
for (const timeKey of getTimeReferenceKeys(entry.time)) {
|
|
150
|
+
addReference(timeKey, entryIndex);
|
|
151
|
+
addReference(`time:${timeKey}`, entryIndex);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (entry.checkpointType && entry.time) {
|
|
156
|
+
const normalizedCheckpointType = normalizeCheckpointLabel(entry.checkpointType);
|
|
157
|
+
for (const timeKey of getTimeReferenceKeys(entry.time)) {
|
|
158
|
+
addReference(`${entry.checkpointType}@${timeKey}`, entryIndex);
|
|
159
|
+
addReference(`${normalizedCheckpointType}@${timeKey}`, entryIndex);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return referenceIndex;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getSupersedesCandidateKeys(token) {
|
|
168
|
+
const trimmed = String(token || "").trim();
|
|
169
|
+
if (!trimmed) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const keys = new Set([trimmed]);
|
|
174
|
+
const prefixedTimeMatch = trimmed.match(/^time\s*:\s*(.+)$/i);
|
|
175
|
+
if (prefixedTimeMatch) {
|
|
176
|
+
for (const timeKey of getTimeReferenceKeys(prefixedTimeMatch[1])) {
|
|
177
|
+
keys.add(timeKey);
|
|
178
|
+
keys.add(`time:${timeKey}`);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
keys.add(`time:${trimmed}`);
|
|
182
|
+
for (const timeKey of getTimeReferenceKeys(trimmed)) {
|
|
183
|
+
keys.add(timeKey);
|
|
184
|
+
keys.add(`time:${timeKey}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const atIndex = trimmed.indexOf("@");
|
|
189
|
+
if (atIndex > 0 && atIndex < trimmed.length - 1) {
|
|
190
|
+
const checkpointType = trimmed.slice(0, atIndex).trim();
|
|
191
|
+
const time = trimmed.slice(atIndex + 1).trim();
|
|
192
|
+
if (checkpointType && time) {
|
|
193
|
+
const normalizedCheckpointType = normalizeCheckpointLabel(checkpointType);
|
|
194
|
+
for (const timeKey of getTimeReferenceKeys(time)) {
|
|
195
|
+
keys.add(`${checkpointType}@${timeKey}`);
|
|
196
|
+
keys.add(`${normalizedCheckpointType}@${timeKey}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return [...keys];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveSupersedesReferenceIndices(token, referenceIndex) {
|
|
205
|
+
const indices = new Set();
|
|
206
|
+
for (const key of getSupersedesCandidateKeys(token)) {
|
|
207
|
+
const matches = referenceIndex.get(key);
|
|
208
|
+
if (!matches) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
for (const entryIndex of matches) {
|
|
212
|
+
indices.add(entryIndex);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return [...indices].sort((a, b) => a - b);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function inspectContextDelta(markdownText) {
|
|
219
|
+
const section = getMarkdownSection(markdownText, "Context Delta");
|
|
220
|
+
if (!section) {
|
|
221
|
+
return {
|
|
222
|
+
found: false,
|
|
223
|
+
hasConcreteEntry: false,
|
|
224
|
+
entries: [],
|
|
225
|
+
incompleteEntryCount: 0
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const lines = String(section).replace(/\r\n?/g, "\n").split("\n");
|
|
230
|
+
const entries = [];
|
|
231
|
+
let current = null;
|
|
232
|
+
|
|
233
|
+
function ensureCurrent() {
|
|
234
|
+
if (!current) {
|
|
235
|
+
current = {};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function flushCurrent() {
|
|
240
|
+
if (!current) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const hasAnyValue = [
|
|
245
|
+
current.time,
|
|
246
|
+
current.checkpointType,
|
|
247
|
+
current.goal,
|
|
248
|
+
current.decision,
|
|
249
|
+
current.constraints,
|
|
250
|
+
current.impact,
|
|
251
|
+
current.status,
|
|
252
|
+
current.nextAction,
|
|
253
|
+
current.supersedes
|
|
254
|
+
].some((value) => Boolean(value));
|
|
255
|
+
|
|
256
|
+
if (hasAnyValue) {
|
|
257
|
+
entries.push(current);
|
|
258
|
+
}
|
|
259
|
+
current = null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
for (const rawLine of lines) {
|
|
263
|
+
const line = rawLine.trim();
|
|
264
|
+
|
|
265
|
+
if (!line) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const timeMatch = line.match(/^-+\s*`?time`?\s*:\s*(.+)$/i);
|
|
270
|
+
if (timeMatch) {
|
|
271
|
+
flushCurrent();
|
|
272
|
+
current = {
|
|
273
|
+
time: timeMatch[1].trim()
|
|
274
|
+
};
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const checkpointTypeMatch = line.match(/^-+\s*`?checkpoint(?:[_ -]?type)?`?\s*:\s*(.+)$/i);
|
|
279
|
+
if (checkpointTypeMatch) {
|
|
280
|
+
ensureCurrent();
|
|
281
|
+
current.checkpointType = checkpointTypeMatch[1].trim();
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const goalMatch = line.match(/^-+\s*`?goal`?\s*:\s*(.+)$/i);
|
|
286
|
+
if (goalMatch) {
|
|
287
|
+
ensureCurrent();
|
|
288
|
+
current.goal = goalMatch[1].trim();
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const decisionMatch = line.match(/^-+\s*`?decision`?\s*:\s*(.+)$/i);
|
|
293
|
+
if (decisionMatch) {
|
|
294
|
+
ensureCurrent();
|
|
295
|
+
current.decision = decisionMatch[1].trim();
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const constraintsMatch = line.match(/^-+\s*`?constraints`?\s*:\s*(.+)$/i);
|
|
300
|
+
if (constraintsMatch) {
|
|
301
|
+
ensureCurrent();
|
|
302
|
+
current.constraints = constraintsMatch[1].trim();
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const impactMatch = line.match(/^-+\s*`?impact`?\s*:\s*(.+)$/i);
|
|
307
|
+
if (impactMatch) {
|
|
308
|
+
ensureCurrent();
|
|
309
|
+
current.impact = impactMatch[1].trim();
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const statusMatch = line.match(/^-+\s*`?status`?\s*:\s*(PASS|WARN|BLOCK)\b/i);
|
|
314
|
+
if (statusMatch) {
|
|
315
|
+
ensureCurrent();
|
|
316
|
+
current.status = String(statusMatch[1]).toUpperCase();
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const nextActionMatch = line.match(/^-+\s*`?next(?:[_ -]?action)?`?\s*:\s*(.+)$/i);
|
|
321
|
+
if (nextActionMatch) {
|
|
322
|
+
ensureCurrent();
|
|
323
|
+
current.nextAction = nextActionMatch[1].trim();
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const supersedesMatch = line.match(/^-+\s*`?supersedes`?\s*:\s*(.+)$/i);
|
|
328
|
+
if (supersedesMatch) {
|
|
329
|
+
ensureCurrent();
|
|
330
|
+
current.supersedes = supersedesMatch[1].trim();
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
flushCurrent();
|
|
336
|
+
|
|
337
|
+
const hasConcreteEntry = entries.some(
|
|
338
|
+
(entry) => entry.time || entry.checkpointType || entry.status || entry.decision || entry.nextAction
|
|
339
|
+
);
|
|
340
|
+
const incompleteEntryCount = entries.filter(
|
|
341
|
+
(entry) => !entry.time || !entry.checkpointType || !entry.status
|
|
342
|
+
).length;
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
found: true,
|
|
346
|
+
hasConcreteEntry,
|
|
347
|
+
entries,
|
|
348
|
+
incompleteEntryCount
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function getVisualAssistFieldValues(daVinciText, fieldName) {
|
|
353
|
+
const section = getMarkdownSection(daVinciText, "Visual Assist");
|
|
354
|
+
if (!section) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const fieldPattern = new RegExp(`^\\s*-\\s*${escapeRegExp(fieldName)}\\s*:\\s*(.*)$`, "i");
|
|
359
|
+
const nestedValuePattern = /^\s{2,}-\s*(.+?)\s*$/;
|
|
360
|
+
const nextFieldPattern = /^\s*-\s+[^:]+:\s*.*$/;
|
|
361
|
+
const values = [];
|
|
362
|
+
let capturing = false;
|
|
363
|
+
|
|
364
|
+
for (const rawLine of String(section).replace(/\r\n?/g, "\n").split("\n")) {
|
|
365
|
+
const fieldMatch = rawLine.match(fieldPattern);
|
|
366
|
+
if (!capturing && fieldMatch) {
|
|
367
|
+
capturing = true;
|
|
368
|
+
const inlineValue = (fieldMatch[1] || "").trim();
|
|
369
|
+
if (inlineValue) {
|
|
370
|
+
values.push(inlineValue);
|
|
371
|
+
}
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (capturing && nextFieldPattern.test(rawLine)) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (capturing) {
|
|
380
|
+
const nestedMatch = rawLine.match(nestedValuePattern);
|
|
381
|
+
if (nestedMatch) {
|
|
382
|
+
const value = nestedMatch[1].trim();
|
|
383
|
+
if (value) {
|
|
384
|
+
values.push(value);
|
|
385
|
+
}
|
|
386
|
+
} else if (rawLine.trim() === "") {
|
|
387
|
+
continue;
|
|
388
|
+
} else {
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return values;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function hasConfiguredDesignSupervisorReview(daVinciText) {
|
|
398
|
+
return getVisualAssistFieldValues(daVinciText, "Design-supervisor reviewers").length > 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function isDesignSupervisorReviewRequired(daVinciText) {
|
|
402
|
+
return getVisualAssistFieldValues(daVinciText, "Require Supervisor Review").some((value) =>
|
|
403
|
+
/^true$/i.test(String(value).trim())
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function inspectDesignSupervisorReview(pencilDesignText) {
|
|
408
|
+
const section = getMarkdownSection(pencilDesignText, "Design-Supervisor Review");
|
|
409
|
+
if (!section) {
|
|
410
|
+
return {
|
|
411
|
+
found: false,
|
|
412
|
+
status: null,
|
|
413
|
+
acceptedWarn: false,
|
|
414
|
+
hasIssueList: false,
|
|
415
|
+
hasRevisionOutcome: false
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const statusMatch = section.match(/(?:^|\n)\s*-\s*(?:Status|状态)\s*:\s*(PASS|WARN|BLOCK)\b/i);
|
|
420
|
+
const status = statusMatch ? statusMatch[1].toUpperCase() : null;
|
|
421
|
+
const issueListMatch = section.match(/(?:^|\n)\s*-\s*(?:Issue list|问题列表)\s*:\s*(.+)$/im);
|
|
422
|
+
const revisionOutcomeMatch = section.match(
|
|
423
|
+
/(?:^|\n)\s*-\s*(?:Revision outcome|修订结果)\s*:\s*(.+)$/im
|
|
424
|
+
);
|
|
425
|
+
const revisionOutcome = revisionOutcomeMatch ? revisionOutcomeMatch[1].trim() : "";
|
|
426
|
+
const acceptedWarn =
|
|
427
|
+
status === "WARN" &&
|
|
428
|
+
/(accepted|accepted with follow-up|accepted warning|warn accepted|接受|已接受|接受警告)/i.test(
|
|
429
|
+
revisionOutcome
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
found: true,
|
|
434
|
+
status,
|
|
435
|
+
acceptedWarn,
|
|
436
|
+
hasIssueList: Boolean(issueListMatch && issueListMatch[1].trim()),
|
|
437
|
+
hasRevisionOutcome: Boolean(revisionOutcome)
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = {
|
|
442
|
+
normalizeCheckpointLabel,
|
|
443
|
+
parseCheckpointStatusMap,
|
|
444
|
+
hasContextDeltaExpectationSignals,
|
|
445
|
+
parseSupersedesTokens,
|
|
446
|
+
buildContextDeltaReferenceIndex,
|
|
447
|
+
resolveSupersedesReferenceIndices,
|
|
448
|
+
inspectContextDelta,
|
|
449
|
+
hasConfiguredDesignSupervisorReview,
|
|
450
|
+
isDesignSupervisorReviewRequired,
|
|
451
|
+
inspectDesignSupervisorReview
|
|
452
|
+
};
|