@xenonbyte/da-vinci-workflow 0.1.21 → 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.
@@ -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
+ };