cgd-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 +106 -20
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
* @license CC-BY-4.0
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const VERSION = '0.1.
|
|
15
|
+
const VERSION = '0.1.1';
|
|
16
|
+
|
|
17
|
+
// Valid Clarity Gate versions
|
|
18
|
+
const VALID_VERSIONS = ['1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6'];
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Parse YAML frontmatter from CGD file
|
|
@@ -20,16 +23,23 @@ const VERSION = '0.1.0';
|
|
|
20
23
|
* @returns {Object|null} Parsed frontmatter or null
|
|
21
24
|
*/
|
|
22
25
|
function parseFrontmatter(content) {
|
|
23
|
-
|
|
26
|
+
// BUG FIX #3: Strip BOM and leading whitespace
|
|
27
|
+
const cleanContent = content.replace(/^\uFEFF/, '').trimStart();
|
|
28
|
+
|
|
29
|
+
const match = cleanContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
24
30
|
if (!match) return null;
|
|
25
31
|
|
|
26
32
|
const frontmatter = {};
|
|
27
|
-
const lines = match[1].split(
|
|
33
|
+
const lines = match[1].split(/\r?\n/);
|
|
28
34
|
|
|
29
35
|
for (const line of lines) {
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
|
|
36
|
+
const colonIndex = line.indexOf(':');
|
|
37
|
+
if (colonIndex > 0) {
|
|
38
|
+
const key = line.slice(0, colonIndex).trim();
|
|
39
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
40
|
+
if (key) {
|
|
41
|
+
frontmatter[key] = value;
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
44
|
}
|
|
35
45
|
|
|
@@ -45,8 +55,11 @@ function validate(content) {
|
|
|
45
55
|
const errors = [];
|
|
46
56
|
const warnings = [];
|
|
47
57
|
|
|
58
|
+
// Strip BOM if present
|
|
59
|
+
const cleanContent = content.replace(/^\uFEFF/, '');
|
|
60
|
+
|
|
48
61
|
// Required: YAML frontmatter
|
|
49
|
-
const frontmatter = parseFrontmatter(
|
|
62
|
+
const frontmatter = parseFrontmatter(cleanContent);
|
|
50
63
|
if (!frontmatter) {
|
|
51
64
|
errors.push({
|
|
52
65
|
code: 'MISSING_FRONTMATTER',
|
|
@@ -54,23 +67,57 @@ function validate(content) {
|
|
|
54
67
|
line: 1
|
|
55
68
|
});
|
|
56
69
|
} else {
|
|
57
|
-
// Required
|
|
70
|
+
// Required: clarity-gate-version
|
|
58
71
|
if (!frontmatter['clarity-gate-version']) {
|
|
59
72
|
errors.push({
|
|
60
73
|
code: 'MISSING_VERSION',
|
|
61
74
|
message: 'Frontmatter must contain "clarity-gate-version"',
|
|
62
75
|
line: 1
|
|
63
76
|
});
|
|
77
|
+
} else {
|
|
78
|
+
// BUG FIX #8: Validate version is a known version
|
|
79
|
+
const version = frontmatter['clarity-gate-version'].trim();
|
|
80
|
+
if (!VALID_VERSIONS.includes(version)) {
|
|
81
|
+
warnings.push({
|
|
82
|
+
code: 'UNKNOWN_VERSION',
|
|
83
|
+
message: `Clarity Gate version "${version}" is not recognized. Known versions: ${VALID_VERSIONS.join(', ')}`,
|
|
84
|
+
line: 1
|
|
85
|
+
});
|
|
86
|
+
}
|
|
64
87
|
}
|
|
65
88
|
|
|
89
|
+
// Required: verified-date
|
|
66
90
|
if (!frontmatter['verified-date']) {
|
|
67
91
|
errors.push({
|
|
68
92
|
code: 'MISSING_VERIFIED_DATE',
|
|
69
93
|
message: 'Frontmatter must contain "verified-date"',
|
|
70
94
|
line: 1
|
|
71
95
|
});
|
|
96
|
+
} else {
|
|
97
|
+
// BUG FIX #9: Validate date format and not in future
|
|
98
|
+
const dateStr = frontmatter['verified-date'].trim();
|
|
99
|
+
const dateMatch = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
100
|
+
if (!dateMatch) {
|
|
101
|
+
warnings.push({
|
|
102
|
+
code: 'INVALID_DATE_FORMAT',
|
|
103
|
+
message: `verified-date "${dateStr}" should be in YYYY-MM-DD format`,
|
|
104
|
+
line: 1
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
const docDate = new Date(dateStr);
|
|
108
|
+
const today = new Date();
|
|
109
|
+
today.setHours(0, 0, 0, 0);
|
|
110
|
+
if (docDate > today) {
|
|
111
|
+
errors.push({
|
|
112
|
+
code: 'FUTURE_DATE',
|
|
113
|
+
message: `verified-date (${dateStr}) is in the future`,
|
|
114
|
+
line: 1
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
72
118
|
}
|
|
73
119
|
|
|
120
|
+
// Required: verified-by
|
|
74
121
|
if (!frontmatter['verified-by']) {
|
|
75
122
|
errors.push({
|
|
76
123
|
code: 'MISSING_VERIFIED_BY',
|
|
@@ -79,45 +126,84 @@ function validate(content) {
|
|
|
79
126
|
});
|
|
80
127
|
}
|
|
81
128
|
|
|
129
|
+
// Required: hitl-status
|
|
82
130
|
if (!frontmatter['hitl-status']) {
|
|
83
131
|
errors.push({
|
|
84
132
|
code: 'MISSING_HITL_STATUS',
|
|
85
133
|
message: 'Frontmatter must contain "hitl-status"',
|
|
86
134
|
line: 1
|
|
87
135
|
});
|
|
136
|
+
} else {
|
|
137
|
+
// BUG FIX #10: Case-insensitive comparison, trim whitespace
|
|
138
|
+
const hitlStatus = frontmatter['hitl-status'].trim().toUpperCase();
|
|
139
|
+
if (hitlStatus !== 'CONFIRMED') {
|
|
140
|
+
warnings.push({
|
|
141
|
+
code: 'HITL_NOT_CONFIRMED',
|
|
142
|
+
message: `HITL status is "${frontmatter['hitl-status']}" - should be "CONFIRMED" for full validation`,
|
|
143
|
+
line: 1
|
|
144
|
+
});
|
|
145
|
+
}
|
|
88
146
|
}
|
|
89
147
|
|
|
90
|
-
//
|
|
91
|
-
if (frontmatter['
|
|
148
|
+
// BUG FIX #7: Check for points-passed
|
|
149
|
+
if (!frontmatter['points-passed']) {
|
|
92
150
|
warnings.push({
|
|
93
|
-
code: '
|
|
94
|
-
message:
|
|
151
|
+
code: 'MISSING_POINTS_PASSED',
|
|
152
|
+
message: 'Frontmatter should contain "points-passed" listing which verification points were checked',
|
|
95
153
|
line: 1
|
|
96
154
|
});
|
|
97
155
|
}
|
|
98
156
|
}
|
|
99
157
|
|
|
100
|
-
// Required: Verification summary at end
|
|
101
|
-
if (
|
|
158
|
+
// Required: Verification summary at end (case-insensitive)
|
|
159
|
+
if (!/##\s*clarity\s+gate\s+verification/i.test(cleanContent)) {
|
|
102
160
|
errors.push({
|
|
103
161
|
code: 'MISSING_VERIFICATION_SUMMARY',
|
|
104
162
|
message: 'Document must contain "## Clarity Gate Verification" section',
|
|
105
163
|
line: null
|
|
106
164
|
});
|
|
165
|
+
} else {
|
|
166
|
+
// BUG FIX #4: Check that section contains a table
|
|
167
|
+
const verificationSection = cleanContent.match(/##\s*clarity\s+gate\s+verification[\s\S]*$/i);
|
|
168
|
+
if (verificationSection && !verificationSection[0].includes('|')) {
|
|
169
|
+
warnings.push({
|
|
170
|
+
code: 'NO_TABLE_IN_VERIFICATION',
|
|
171
|
+
message: 'Clarity Gate Verification section should contain a points table',
|
|
172
|
+
line: null
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// BUG FIX #2: Check EACH projection phrase for markers
|
|
178
|
+
const projectionPhrases = cleanContent.match(/will\s+(be|reach|reduce|increase|decrease|grow|achieve)/gi) || [];
|
|
179
|
+
let unmarkedProjections = 0;
|
|
180
|
+
|
|
181
|
+
for (const phrase of projectionPhrases) {
|
|
182
|
+
// Find the phrase in content and check if it has a marker nearby
|
|
183
|
+
const phraseIndex = cleanContent.toLowerCase().indexOf(phrase.toLowerCase());
|
|
184
|
+
if (phraseIndex >= 0) {
|
|
185
|
+
// Check 50 chars before and after for *(projected)* or similar
|
|
186
|
+
const context = cleanContent.slice(Math.max(0, phraseIndex - 50), phraseIndex + phrase.length + 50);
|
|
187
|
+
if (!/\*\(projected|\(projected\)|\*projected\*/i.test(context)) {
|
|
188
|
+
unmarkedProjections++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
107
191
|
}
|
|
108
192
|
|
|
109
|
-
|
|
110
|
-
if (content.match(/will be|will reach|will reduce/i) && !content.match(/\*\(projected/i)) {
|
|
193
|
+
if (unmarkedProjections > 0) {
|
|
111
194
|
warnings.push({
|
|
112
195
|
code: 'UNMARKED_PROJECTION',
|
|
113
|
-
message:
|
|
196
|
+
message: `Found ${unmarkedProjections} projection phrase(s) without nearby *(projected)* marker`,
|
|
114
197
|
line: null
|
|
115
198
|
});
|
|
116
199
|
}
|
|
117
200
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
201
|
+
// BUG FIX #5: Better vague quantifier detection - exclude those in annotations
|
|
202
|
+
// Split content into annotated and non-annotated parts
|
|
203
|
+
const withoutAnnotations = cleanContent.replace(/\*\([^)]*\)\*/g, ''); // Remove *(...)*
|
|
204
|
+
const vagueQuantifiers = withoutAnnotations.match(/\b(several|many|most|some)\b/gi) || [];
|
|
205
|
+
|
|
206
|
+
if (vagueQuantifiers.length > 0) {
|
|
121
207
|
warnings.push({
|
|
122
208
|
code: 'UNMARKED_VAGUE_QUANTIFIER',
|
|
123
209
|
message: `Found ${vagueQuantifiers.length} vague quantifier(s) without annotation (several, many, most, some)`,
|