sot-validator 0.1.0 → 0.1.1
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/index.js +75 -31
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @license CC-BY-4.0
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const VERSION = '0.1.
|
|
15
|
+
const VERSION = '0.1.1';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Validate a Source of Truth (.sot) file
|
|
@@ -23,8 +23,11 @@ function validate(content) {
|
|
|
23
23
|
const errors = [];
|
|
24
24
|
const warnings = [];
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
|
|
26
|
+
// Strip BOM if present
|
|
27
|
+
const cleanContent = content.replace(/^\uFEFF/, '');
|
|
28
|
+
|
|
29
|
+
// Required: Header block (case-insensitive)
|
|
30
|
+
if (!/--\s*source\s+of\s+truth/i.test(cleanContent)) {
|
|
28
31
|
errors.push({
|
|
29
32
|
code: 'MISSING_HEADER',
|
|
30
33
|
message: 'Document must contain "-- Source of Truth" header',
|
|
@@ -32,17 +35,32 @@ function validate(content) {
|
|
|
32
35
|
});
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
// Required: Last Updated
|
|
36
|
-
if (
|
|
38
|
+
// Required: Last Updated (case-insensitive)
|
|
39
|
+
if (!/\*\*last\s*updated:\*\*/i.test(cleanContent)) {
|
|
37
40
|
errors.push({
|
|
38
41
|
code: 'MISSING_DATE',
|
|
39
42
|
message: 'Document must contain "**Last Updated:**" field',
|
|
40
43
|
line: null
|
|
41
44
|
});
|
|
45
|
+
} else {
|
|
46
|
+
// Validate date is not in future
|
|
47
|
+
const dateMatch = cleanContent.match(/\*\*last\s*updated:\*\*\s*(\d{4}-\d{2}-\d{2})/i);
|
|
48
|
+
if (dateMatch) {
|
|
49
|
+
const docDate = new Date(dateMatch[1]);
|
|
50
|
+
const today = new Date();
|
|
51
|
+
today.setHours(0, 0, 0, 0);
|
|
52
|
+
if (docDate > today) {
|
|
53
|
+
errors.push({
|
|
54
|
+
code: 'FUTURE_DATE',
|
|
55
|
+
message: `Last Updated date (${dateMatch[1]}) is in the future`,
|
|
56
|
+
line: null
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
42
60
|
}
|
|
43
61
|
|
|
44
|
-
// Required: Owner
|
|
45
|
-
if (
|
|
62
|
+
// Required: Owner (case-insensitive)
|
|
63
|
+
if (!/\*\*owner:\*\*/i.test(cleanContent)) {
|
|
46
64
|
errors.push({
|
|
47
65
|
code: 'MISSING_OWNER',
|
|
48
66
|
message: 'Document must contain "**Owner:**" field',
|
|
@@ -50,8 +68,8 @@ function validate(content) {
|
|
|
50
68
|
});
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
// Required: Status
|
|
54
|
-
if (
|
|
71
|
+
// Required: Status (case-insensitive)
|
|
72
|
+
if (!/\*\*status:\*\*/i.test(cleanContent)) {
|
|
55
73
|
errors.push({
|
|
56
74
|
code: 'MISSING_STATUS',
|
|
57
75
|
message: 'Document must contain "**Status:**" field',
|
|
@@ -59,17 +77,28 @@ function validate(content) {
|
|
|
59
77
|
});
|
|
60
78
|
}
|
|
61
79
|
|
|
62
|
-
// Required: Verification Status
|
|
63
|
-
|
|
80
|
+
// Required: Verification Status section (case-insensitive)
|
|
81
|
+
const hasVerificationSection = /##\s*verification\s+status/i.test(cleanContent);
|
|
82
|
+
if (!hasVerificationSection) {
|
|
64
83
|
errors.push({
|
|
65
84
|
code: 'MISSING_VERIFICATION_TABLE',
|
|
66
85
|
message: 'Document must contain "## Verification Status" section',
|
|
67
86
|
line: null
|
|
68
87
|
});
|
|
88
|
+
} else {
|
|
89
|
+
// BUG FIX #4: Check that section contains a table
|
|
90
|
+
const verificationSection = cleanContent.match(/##\s*verification\s+status[\s\S]*?(?=##|$)/i);
|
|
91
|
+
if (verificationSection && !verificationSection[0].includes('|')) {
|
|
92
|
+
warnings.push({
|
|
93
|
+
code: 'NO_TABLE_IN_VERIFICATION',
|
|
94
|
+
message: 'Verification Status section should contain a markdown table',
|
|
95
|
+
line: null
|
|
96
|
+
});
|
|
97
|
+
}
|
|
69
98
|
}
|
|
70
99
|
|
|
71
|
-
// Warning: Status says VERIFIED without qualification
|
|
72
|
-
if (
|
|
100
|
+
// Warning: Status says VERIFIED without qualification (case-insensitive)
|
|
101
|
+
if (/status:\*\*\s*verified\s*$/im.test(cleanContent)) {
|
|
73
102
|
warnings.push({
|
|
74
103
|
code: 'UNQUALIFIED_VERIFIED',
|
|
75
104
|
message: 'Status "VERIFIED" should be qualified (e.g., "with noted exceptions")',
|
|
@@ -77,23 +106,37 @@ function validate(content) {
|
|
|
77
106
|
});
|
|
78
107
|
}
|
|
79
108
|
|
|
80
|
-
//
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
warnings.push({
|
|
84
|
-
code: 'ESTIMATES_IN_VERIFIED',
|
|
85
|
-
message: 'Estimates found in Verified Data section - move to Estimates section',
|
|
86
|
-
line: null
|
|
87
|
-
});
|
|
88
|
-
}
|
|
109
|
+
// Extract Verified Data section for scoped checks
|
|
110
|
+
const verifiedDataMatch = cleanContent.match(/##\s*verified\s+data[\s\S]*?(?=##|$)/i);
|
|
111
|
+
const verifiedDataSection = verifiedDataMatch ? verifiedDataMatch[0] : null;
|
|
89
112
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
if (verifiedDataSection) {
|
|
114
|
+
// BUG FIX #4: Check that Verified Data contains a table
|
|
115
|
+
if (!verifiedDataSection.includes('|')) {
|
|
116
|
+
warnings.push({
|
|
117
|
+
code: 'NO_TABLE_IN_VERIFIED_DATA',
|
|
118
|
+
message: 'Verified Data section should contain a markdown table',
|
|
119
|
+
line: null
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Warning: Estimates in Verified Data section
|
|
124
|
+
if (/~\d|estimated|approximately/i.test(verifiedDataSection)) {
|
|
125
|
+
warnings.push({
|
|
126
|
+
code: 'ESTIMATES_IN_VERIFIED',
|
|
127
|
+
message: 'Estimates found in Verified Data section - move to Estimates section',
|
|
128
|
+
line: null
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// BUG FIX #1: Check staleness markers WITHIN Verified Data section only
|
|
133
|
+
if (!/\[STABLE\]|\[VOLATILE\]|\[CHECK BEFORE CITING\]|\[SNAPSHOT\]/i.test(verifiedDataSection)) {
|
|
134
|
+
warnings.push({
|
|
135
|
+
code: 'MISSING_STALENESS',
|
|
136
|
+
message: 'Verified Data should include staleness markers ([STABLE], [VOLATILE], etc.)',
|
|
137
|
+
line: null
|
|
138
|
+
});
|
|
139
|
+
}
|
|
97
140
|
}
|
|
98
141
|
|
|
99
142
|
return {
|
|
@@ -119,8 +162,9 @@ function isValid(content) {
|
|
|
119
162
|
* @returns {boolean} True if appears to be .sot format
|
|
120
163
|
*/
|
|
121
164
|
function detect(content) {
|
|
122
|
-
|
|
123
|
-
|
|
165
|
+
const cleanContent = content.replace(/^\uFEFF/, '');
|
|
166
|
+
return /--\s*source\s+of\s+truth/i.test(cleanContent) &&
|
|
167
|
+
/##\s*verification\s+status/i.test(cleanContent);
|
|
124
168
|
}
|
|
125
169
|
|
|
126
170
|
module.exports = {
|
package/package.json
CHANGED