@xenonbyte/da-vinci-workflow 0.1.22 → 0.1.24

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,598 @@
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 getMarkdownSectionsByHeading(text, heading, options = {}) {
36
+ if (!text) {
37
+ return [];
38
+ }
39
+
40
+ const escapedHeading = escapeRegExp(heading);
41
+ const allowParentheticalSuffix = options.allowParentheticalSuffix === true;
42
+ const suffixPattern = allowParentheticalSuffix ? String.raw`(?:\s*\([^)\n]*\))?` : "";
43
+ const headingPattern = new RegExp(`^##\\s+${escapedHeading}${suffixPattern}\\s*$`, "i");
44
+ const anyHeadingPattern = /^##\s+/;
45
+ const lines = String(text).replace(/\r\n?/g, "\n").split("\n");
46
+ const sections = [];
47
+ let capturing = false;
48
+ let currentHeading = "";
49
+ let sectionLines = [];
50
+
51
+ function flushSection() {
52
+ if (!capturing) {
53
+ return;
54
+ }
55
+
56
+ sections.push({
57
+ heading: currentHeading,
58
+ content: sectionLines.join("\n").trim()
59
+ });
60
+ capturing = false;
61
+ currentHeading = "";
62
+ sectionLines = [];
63
+ }
64
+
65
+ for (const line of lines) {
66
+ if (capturing && anyHeadingPattern.test(line)) {
67
+ flushSection();
68
+ }
69
+
70
+ if (!capturing && headingPattern.test(line)) {
71
+ capturing = true;
72
+ currentHeading = line.trim();
73
+ sectionLines = [];
74
+ continue;
75
+ }
76
+
77
+ if (capturing) {
78
+ sectionLines.push(line);
79
+ }
80
+ }
81
+
82
+ flushSection();
83
+ return sections;
84
+ }
85
+
86
+ function extractReviewFieldValue(section, fieldNames) {
87
+ const lines = String(section || "").replace(/\r\n?/g, "\n").split("\n");
88
+ const escapedFieldNames = fieldNames.map((fieldName) => escapeRegExp(fieldName)).join("|");
89
+ const fieldPattern = new RegExp(`^\\s*-\\s*(?:${escapedFieldNames})\\s*:\\s*(.*)$`, "i");
90
+ const topLevelFieldPattern = /^\s*-\s+[^:\n]+:\s*.*$/;
91
+ const bulletPattern = /^\s*-\s+(.+)$/;
92
+ const nestedTextPattern = /^\s{2,}(.+)$/;
93
+ const values = [];
94
+ let capturing = false;
95
+
96
+ for (const rawLine of lines) {
97
+ if (/^##\s+/.test(rawLine)) {
98
+ if (capturing) {
99
+ break;
100
+ }
101
+ continue;
102
+ }
103
+
104
+ const fieldMatch = rawLine.match(fieldPattern);
105
+ if (!capturing && fieldMatch) {
106
+ capturing = true;
107
+ const inlineValue = String(fieldMatch[1] || "").trim();
108
+ if (inlineValue) {
109
+ values.push(inlineValue);
110
+ }
111
+ continue;
112
+ }
113
+
114
+ if (!capturing) {
115
+ continue;
116
+ }
117
+
118
+ if (topLevelFieldPattern.test(rawLine)) {
119
+ break;
120
+ }
121
+
122
+ const bulletMatch = rawLine.match(bulletPattern);
123
+ if (bulletMatch) {
124
+ const value = String(bulletMatch[1] || "").trim();
125
+ if (value) {
126
+ values.push(value);
127
+ }
128
+ continue;
129
+ }
130
+
131
+ const nestedTextMatch = rawLine.match(nestedTextPattern);
132
+ if (nestedTextMatch) {
133
+ const value = String(nestedTextMatch[1] || "").trim();
134
+ if (value) {
135
+ values.push(value);
136
+ }
137
+ continue;
138
+ }
139
+
140
+ if (rawLine.trim() === "") {
141
+ continue;
142
+ }
143
+
144
+ break;
145
+ }
146
+
147
+ return values.join("\n").trim();
148
+ }
149
+
150
+ function parseListTokens(value) {
151
+ return String(value || "")
152
+ .split(/[,\n;]/)
153
+ .map((token) =>
154
+ token
155
+ .replace(/^[\-\*\u2022]\s*/, "")
156
+ .replace(/`/g, "")
157
+ .trim()
158
+ )
159
+ .filter(Boolean);
160
+ }
161
+
162
+ function normalizeCheckpointLabel(value) {
163
+ return String(value || "")
164
+ .toLowerCase()
165
+ .replace(/`/g, "")
166
+ .replace(/[_-]+/g, " ")
167
+ .replace(/\s+/g, " ")
168
+ .trim();
169
+ }
170
+
171
+ function parseCheckpointStatusMap(markdownText) {
172
+ const section = getMarkdownSection(markdownText, "Checkpoint Status");
173
+ if (!section) {
174
+ return {};
175
+ }
176
+
177
+ const statuses = {};
178
+ const matches = section.matchAll(/(?:^|\n)\s*-\s*`?([^`:\n]+?)`?\s*:\s*(PASS|WARN|BLOCK)\b/gi);
179
+ for (const match of matches) {
180
+ const label = normalizeCheckpointLabel(match[1]);
181
+ if (!label) {
182
+ continue;
183
+ }
184
+ statuses[label] = String(match[2]).toUpperCase();
185
+ }
186
+
187
+ return statuses;
188
+ }
189
+
190
+ function hasContextDeltaExpectationSignals(markdownText) {
191
+ const text = String(markdownText || "");
192
+ return (
193
+ /##\s+(Checkpoint Status|MCP Runtime Gate)\b/i.test(text) ||
194
+ /(?:^|\n)\s*(?:[-*]\s*)?`?Context Delta Required`?\s*:\s*(?:true|yes|on|1)\b/i.test(text)
195
+ );
196
+ }
197
+
198
+ function parseSupersedesTokens(value) {
199
+ return String(value || "")
200
+ .split(/[,\n;]/)
201
+ .map((token) => token.trim())
202
+ .filter(Boolean);
203
+ }
204
+
205
+ function normalizeTimeToken(value) {
206
+ const raw = String(value || "").trim();
207
+ if (!raw) {
208
+ return "";
209
+ }
210
+
211
+ const parseCandidates = [];
212
+ const rawWithT = raw.includes(" ") ? raw.replace(/\s+/, "T") : raw;
213
+ const hasExplicitTimezone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(rawWithT);
214
+
215
+ if (!hasExplicitTimezone && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(rawWithT)) {
216
+ parseCandidates.push(`${rawWithT}Z`);
217
+ }
218
+
219
+ parseCandidates.push(rawWithT);
220
+ parseCandidates.push(raw);
221
+
222
+ for (const candidate of parseCandidates) {
223
+ const timestamp = Date.parse(candidate);
224
+ if (!Number.isFinite(timestamp)) {
225
+ continue;
226
+ }
227
+ return new Date(timestamp).toISOString();
228
+ }
229
+
230
+ return "";
231
+ }
232
+
233
+ function getTimeReferenceKeys(value) {
234
+ const raw = String(value || "").trim();
235
+ if (!raw) {
236
+ return [];
237
+ }
238
+
239
+ const keys = new Set([raw]);
240
+
241
+ if (raw.includes(" ")) {
242
+ keys.add(raw.replace(/\s+/, "T"));
243
+ }
244
+
245
+ if (raw.endsWith(".000Z")) {
246
+ keys.add(raw.replace(".000Z", "Z"));
247
+ }
248
+
249
+ const normalized = normalizeTimeToken(raw);
250
+ if (normalized) {
251
+ keys.add(normalized);
252
+ if (normalized.endsWith(".000Z")) {
253
+ keys.add(normalized.replace(".000Z", "Z"));
254
+ }
255
+ }
256
+
257
+ return [...keys];
258
+ }
259
+
260
+ function buildContextDeltaReferenceIndex(entries) {
261
+ const referenceIndex = new Map();
262
+
263
+ function addReference(key, entryIndex) {
264
+ const normalizedKey = String(key || "").trim();
265
+ if (!normalizedKey) {
266
+ return;
267
+ }
268
+
269
+ const existing = referenceIndex.get(normalizedKey) || [];
270
+ existing.push(entryIndex);
271
+ referenceIndex.set(normalizedKey, existing);
272
+ }
273
+
274
+ entries.forEach((entry, entryIndex) => {
275
+ if (entry.time) {
276
+ for (const timeKey of getTimeReferenceKeys(entry.time)) {
277
+ addReference(timeKey, entryIndex);
278
+ addReference(`time:${timeKey}`, entryIndex);
279
+ }
280
+ }
281
+
282
+ if (entry.checkpointType && entry.time) {
283
+ const normalizedCheckpointType = normalizeCheckpointLabel(entry.checkpointType);
284
+ for (const timeKey of getTimeReferenceKeys(entry.time)) {
285
+ addReference(`${entry.checkpointType}@${timeKey}`, entryIndex);
286
+ addReference(`${normalizedCheckpointType}@${timeKey}`, entryIndex);
287
+ }
288
+ }
289
+ });
290
+
291
+ return referenceIndex;
292
+ }
293
+
294
+ function getSupersedesCandidateKeys(token) {
295
+ const trimmed = String(token || "").trim();
296
+ if (!trimmed) {
297
+ return [];
298
+ }
299
+
300
+ const keys = new Set([trimmed]);
301
+ const prefixedTimeMatch = trimmed.match(/^time\s*:\s*(.+)$/i);
302
+ if (prefixedTimeMatch) {
303
+ for (const timeKey of getTimeReferenceKeys(prefixedTimeMatch[1])) {
304
+ keys.add(timeKey);
305
+ keys.add(`time:${timeKey}`);
306
+ }
307
+ } else {
308
+ keys.add(`time:${trimmed}`);
309
+ for (const timeKey of getTimeReferenceKeys(trimmed)) {
310
+ keys.add(timeKey);
311
+ keys.add(`time:${timeKey}`);
312
+ }
313
+ }
314
+
315
+ const atIndex = trimmed.indexOf("@");
316
+ if (atIndex > 0 && atIndex < trimmed.length - 1) {
317
+ const checkpointType = trimmed.slice(0, atIndex).trim();
318
+ const time = trimmed.slice(atIndex + 1).trim();
319
+ if (checkpointType && time) {
320
+ const normalizedCheckpointType = normalizeCheckpointLabel(checkpointType);
321
+ for (const timeKey of getTimeReferenceKeys(time)) {
322
+ keys.add(`${checkpointType}@${timeKey}`);
323
+ keys.add(`${normalizedCheckpointType}@${timeKey}`);
324
+ }
325
+ }
326
+ }
327
+
328
+ return [...keys];
329
+ }
330
+
331
+ function resolveSupersedesReferenceIndices(token, referenceIndex) {
332
+ const indices = new Set();
333
+ for (const key of getSupersedesCandidateKeys(token)) {
334
+ const matches = referenceIndex.get(key);
335
+ if (!matches) {
336
+ continue;
337
+ }
338
+ for (const entryIndex of matches) {
339
+ indices.add(entryIndex);
340
+ }
341
+ }
342
+ return [...indices].sort((a, b) => a - b);
343
+ }
344
+
345
+ function inspectContextDelta(markdownText) {
346
+ const section = getMarkdownSection(markdownText, "Context Delta");
347
+ if (!section) {
348
+ return {
349
+ found: false,
350
+ hasConcreteEntry: false,
351
+ entries: [],
352
+ incompleteEntryCount: 0
353
+ };
354
+ }
355
+
356
+ const lines = String(section).replace(/\r\n?/g, "\n").split("\n");
357
+ const entries = [];
358
+ let current = null;
359
+
360
+ function ensureCurrent() {
361
+ if (!current) {
362
+ current = {};
363
+ }
364
+ }
365
+
366
+ function flushCurrent() {
367
+ if (!current) {
368
+ return;
369
+ }
370
+
371
+ const hasAnyValue = [
372
+ current.time,
373
+ current.checkpointType,
374
+ current.goal,
375
+ current.decision,
376
+ current.constraints,
377
+ current.impact,
378
+ current.status,
379
+ current.nextAction,
380
+ current.supersedes
381
+ ].some((value) => Boolean(value));
382
+
383
+ if (hasAnyValue) {
384
+ entries.push(current);
385
+ }
386
+ current = null;
387
+ }
388
+
389
+ for (const rawLine of lines) {
390
+ const line = rawLine.trim();
391
+
392
+ if (!line) {
393
+ continue;
394
+ }
395
+
396
+ const timeMatch = line.match(/^-+\s*`?time`?\s*:\s*(.+)$/i);
397
+ if (timeMatch) {
398
+ flushCurrent();
399
+ current = {
400
+ time: timeMatch[1].trim()
401
+ };
402
+ continue;
403
+ }
404
+
405
+ const checkpointTypeMatch = line.match(/^-+\s*`?checkpoint(?:[_ -]?type)?`?\s*:\s*(.+)$/i);
406
+ if (checkpointTypeMatch) {
407
+ ensureCurrent();
408
+ current.checkpointType = checkpointTypeMatch[1].trim();
409
+ continue;
410
+ }
411
+
412
+ const goalMatch = line.match(/^-+\s*`?goal`?\s*:\s*(.+)$/i);
413
+ if (goalMatch) {
414
+ ensureCurrent();
415
+ current.goal = goalMatch[1].trim();
416
+ continue;
417
+ }
418
+
419
+ const decisionMatch = line.match(/^-+\s*`?decision`?\s*:\s*(.+)$/i);
420
+ if (decisionMatch) {
421
+ ensureCurrent();
422
+ current.decision = decisionMatch[1].trim();
423
+ continue;
424
+ }
425
+
426
+ const constraintsMatch = line.match(/^-+\s*`?constraints`?\s*:\s*(.+)$/i);
427
+ if (constraintsMatch) {
428
+ ensureCurrent();
429
+ current.constraints = constraintsMatch[1].trim();
430
+ continue;
431
+ }
432
+
433
+ const impactMatch = line.match(/^-+\s*`?impact`?\s*:\s*(.+)$/i);
434
+ if (impactMatch) {
435
+ ensureCurrent();
436
+ current.impact = impactMatch[1].trim();
437
+ continue;
438
+ }
439
+
440
+ const statusMatch = line.match(/^-+\s*`?status`?\s*:\s*(PASS|WARN|BLOCK)\b/i);
441
+ if (statusMatch) {
442
+ ensureCurrent();
443
+ current.status = String(statusMatch[1]).toUpperCase();
444
+ continue;
445
+ }
446
+
447
+ const nextActionMatch = line.match(/^-+\s*`?next(?:[_ -]?action)?`?\s*:\s*(.+)$/i);
448
+ if (nextActionMatch) {
449
+ ensureCurrent();
450
+ current.nextAction = nextActionMatch[1].trim();
451
+ continue;
452
+ }
453
+
454
+ const supersedesMatch = line.match(/^-+\s*`?supersedes`?\s*:\s*(.+)$/i);
455
+ if (supersedesMatch) {
456
+ ensureCurrent();
457
+ current.supersedes = supersedesMatch[1].trim();
458
+ continue;
459
+ }
460
+ }
461
+
462
+ flushCurrent();
463
+
464
+ const hasConcreteEntry = entries.some(
465
+ (entry) => entry.time || entry.checkpointType || entry.status || entry.decision || entry.nextAction
466
+ );
467
+ const incompleteEntryCount = entries.filter(
468
+ (entry) => !entry.time || !entry.checkpointType || !entry.status
469
+ ).length;
470
+
471
+ return {
472
+ found: true,
473
+ hasConcreteEntry,
474
+ entries,
475
+ incompleteEntryCount
476
+ };
477
+ }
478
+
479
+ function getVisualAssistFieldValues(daVinciText, fieldName) {
480
+ const section = getMarkdownSection(daVinciText, "Visual Assist");
481
+ if (!section) {
482
+ return [];
483
+ }
484
+
485
+ const fieldPattern = new RegExp(`^\\s*-\\s*${escapeRegExp(fieldName)}\\s*:\\s*(.*)$`, "i");
486
+ const nestedValuePattern = /^\s{2,}-\s*(.+?)\s*$/;
487
+ const nextFieldPattern = /^\s*-\s+[^:]+:\s*.*$/;
488
+ const values = [];
489
+ let capturing = false;
490
+
491
+ for (const rawLine of String(section).replace(/\r\n?/g, "\n").split("\n")) {
492
+ const fieldMatch = rawLine.match(fieldPattern);
493
+ if (!capturing && fieldMatch) {
494
+ capturing = true;
495
+ const inlineValue = (fieldMatch[1] || "").trim();
496
+ if (inlineValue) {
497
+ values.push(inlineValue);
498
+ }
499
+ continue;
500
+ }
501
+
502
+ if (capturing && nextFieldPattern.test(rawLine)) {
503
+ break;
504
+ }
505
+
506
+ if (capturing) {
507
+ const nestedMatch = rawLine.match(nestedValuePattern);
508
+ if (nestedMatch) {
509
+ const value = nestedMatch[1].trim();
510
+ if (value) {
511
+ values.push(value);
512
+ }
513
+ } else if (rawLine.trim() === "") {
514
+ continue;
515
+ } else {
516
+ break;
517
+ }
518
+ }
519
+ }
520
+
521
+ return values;
522
+ }
523
+
524
+ function hasConfiguredDesignSupervisorReview(daVinciText) {
525
+ return getVisualAssistFieldValues(daVinciText, "Design-supervisor reviewers").length > 0;
526
+ }
527
+
528
+ function getConfiguredDesignSupervisorReviewers(daVinciText) {
529
+ return getVisualAssistFieldValues(daVinciText, "Design-supervisor reviewers")
530
+ .flatMap((value) => parseListTokens(value))
531
+ .filter(Boolean);
532
+ }
533
+
534
+ function isDesignSupervisorReviewRequired(daVinciText) {
535
+ return getVisualAssistFieldValues(daVinciText, "Require Supervisor Review").some((value) =>
536
+ /^true$/i.test(String(value).trim())
537
+ );
538
+ }
539
+
540
+ function inspectDesignSupervisorReview(pencilDesignText) {
541
+ const sections = getMarkdownSectionsByHeading(pencilDesignText, "Design-Supervisor Review", {
542
+ allowParentheticalSuffix: true
543
+ });
544
+ if (sections.length === 0) {
545
+ return {
546
+ found: false,
547
+ status: null,
548
+ acceptedWarn: false,
549
+ hasIssueList: false,
550
+ hasRevisionOutcome: false
551
+ };
552
+ }
553
+
554
+ const section = sections[sections.length - 1].content;
555
+ const statusMatch = section.match(
556
+ /(?:^|\n)\s*-\s*(?:Status|状态|Result|结果)\s*:\s*`?(PASS|WARN|BLOCK)`?\b/i
557
+ );
558
+ const status = statusMatch ? statusMatch[1].toUpperCase() : null;
559
+ const issueList = extractReviewFieldValue(section, ["Issue list", "问题列表"]);
560
+ const revisionOutcome = extractReviewFieldValue(section, ["Revision outcome", "修订结果"]);
561
+ const configuredReviewersRaw = extractReviewFieldValue(section, ["Configured reviewers", "配置评审"]);
562
+ const executedReviewersRaw = extractReviewFieldValue(section, ["Executed reviewers", "Executed reviewer skills", "执行评审"]);
563
+ const reviewSource = extractReviewFieldValue(section, ["Review source", "评审来源"]).toLowerCase();
564
+ const acceptedWarn =
565
+ status === "WARN" &&
566
+ /(accepted|accepted with follow-up|accepted warning|warn accepted|接受|已接受|接受警告)/i.test(
567
+ revisionOutcome
568
+ );
569
+ const configuredReviewers = parseListTokens(configuredReviewersRaw);
570
+ const executedReviewers = parseListTokens(executedReviewersRaw);
571
+
572
+ return {
573
+ found: true,
574
+ status,
575
+ acceptedWarn,
576
+ hasIssueList: Boolean(issueList),
577
+ hasRevisionOutcome: Boolean(revisionOutcome),
578
+ configuredReviewers,
579
+ executedReviewers,
580
+ reviewSource,
581
+ hasConfiguredReviewers: configuredReviewers.length > 0,
582
+ hasExecutedReviewers: executedReviewers.length > 0
583
+ };
584
+ }
585
+
586
+ module.exports = {
587
+ normalizeCheckpointLabel,
588
+ parseCheckpointStatusMap,
589
+ hasContextDeltaExpectationSignals,
590
+ parseSupersedesTokens,
591
+ buildContextDeltaReferenceIndex,
592
+ resolveSupersedesReferenceIndices,
593
+ inspectContextDelta,
594
+ getConfiguredDesignSupervisorReviewers,
595
+ hasConfiguredDesignSupervisorReview,
596
+ isDesignSupervisorReviewRequired,
597
+ inspectDesignSupervisorReview
598
+ };