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.
Files changed (2) hide show
  1. package/index.js +106 -20
  2. 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.0';
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
- const match = content.match(/^---\n([\s\S]*?)\n---/);
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('\n');
33
+ const lines = match[1].split(/\r?\n/);
28
34
 
29
35
  for (const line of lines) {
30
- const [key, ...valueParts] = line.split(':');
31
- if (key && valueParts.length) {
32
- frontmatter[key.trim()] = valueParts.join(':').trim();
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(content);
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 frontmatter fields
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
- // Warning: HITL not confirmed
91
- if (frontmatter['hitl-status'] && frontmatter['hitl-status'] !== 'CONFIRMED') {
148
+ // BUG FIX #7: Check for points-passed
149
+ if (!frontmatter['points-passed']) {
92
150
  warnings.push({
93
- code: 'HITL_NOT_CONFIRMED',
94
- message: `HITL status is "${frontmatter['hitl-status']}" - should be "CONFIRMED" for full validation`,
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 (!content.includes('## Clarity Gate Verification')) {
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
- // Warning: Projections without markers
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: 'Document contains "will be/reach/reduce" without *(projected)* marker',
196
+ message: `Found ${unmarkedProjections} projection phrase(s) without nearby *(projected)* marker`,
114
197
  line: null
115
198
  });
116
199
  }
117
200
 
118
- // Warning: Vague quantifiers without annotation
119
- const vagueQuantifiers = content.match(/\b(several|many|most|some)\b(?!\s*\*\()/gi);
120
- if (vagueQuantifiers && vagueQuantifiers.length > 0) {
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)`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cgd-validator",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Validator for Clarity-Gated Document (.cgd) files - verified documents for safe LLM ingestion",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",