claude-code-templates 1.22.0 → 1.22.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/bin/create-claude-config.js +1 -0
- package/package.json +7 -2
- package/src/analytics-web/chats_mobile.html +17 -16
- package/src/console-bridge.js +3 -3
- package/src/index.js +121 -9
- package/src/security-audit.js +164 -0
- package/src/validation/ARCHITECTURE.md +309 -0
- package/src/validation/BaseValidator.js +152 -0
- package/src/validation/README.md +543 -0
- package/src/validation/ValidationOrchestrator.js +305 -0
- package/src/validation/validators/IntegrityValidator.js +338 -0
- package/src/validation/validators/ProvenanceValidator.js +399 -0
- package/src/validation/validators/ReferenceValidator.js +373 -0
- package/src/validation/validators/SemanticValidator.js +449 -0
- package/src/validation/validators/StructuralValidator.js +376 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
const BaseValidator = require('../BaseValidator');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ProvenanceValidator - Validates component provenance and metadata
|
|
8
|
+
*
|
|
9
|
+
* Checks:
|
|
10
|
+
* - Author information
|
|
11
|
+
* - Source repository tracking
|
|
12
|
+
* - Git commit SHA tracking
|
|
13
|
+
* - Timestamp tracking
|
|
14
|
+
* - Version consistency
|
|
15
|
+
* - Verified author status (future)
|
|
16
|
+
*/
|
|
17
|
+
class ProvenanceValidator extends BaseValidator {
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validate component provenance
|
|
24
|
+
* @param {object} component - Component data
|
|
25
|
+
* @param {string} component.content - Raw markdown content
|
|
26
|
+
* @param {string} component.path - File path
|
|
27
|
+
* @param {object} options - Validation options
|
|
28
|
+
* @param {boolean} options.requireGit - Require Git metadata
|
|
29
|
+
* @param {boolean} options.requireAuthor - Require author information
|
|
30
|
+
* @returns {Promise<object>} Validation results
|
|
31
|
+
*/
|
|
32
|
+
async validate(component, options = {}) {
|
|
33
|
+
this.reset();
|
|
34
|
+
|
|
35
|
+
const { content, path: filePath } = component;
|
|
36
|
+
const { requireGit = false, requireAuthor = false } = options;
|
|
37
|
+
|
|
38
|
+
if (!content) {
|
|
39
|
+
this.addError('PROV_E001', 'Component content is empty or missing', { path: filePath });
|
|
40
|
+
return this.getResults();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 1. Extract frontmatter metadata
|
|
44
|
+
const metadata = this.extractMetadata(content, filePath);
|
|
45
|
+
|
|
46
|
+
// 2. Validate author information
|
|
47
|
+
this.validateAuthor(metadata, filePath, requireAuthor);
|
|
48
|
+
|
|
49
|
+
// 3. Extract Git metadata if file exists
|
|
50
|
+
if (fs.existsSync(filePath)) {
|
|
51
|
+
const gitMetadata = await this.extractGitMetadata(filePath);
|
|
52
|
+
if (gitMetadata) {
|
|
53
|
+
this.validateGitMetadata(gitMetadata, filePath);
|
|
54
|
+
} else if (requireGit) {
|
|
55
|
+
this.addWarning('PROV_W001', 'No Git metadata found', { path: filePath });
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
this.addInfo('PROV_I001', 'File path does not exist (in-memory component)', { path: filePath });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 4. Validate repository URL if provided
|
|
62
|
+
if (metadata.repository) {
|
|
63
|
+
this.validateRepository(metadata.repository, filePath);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 5. Validate version information
|
|
67
|
+
if (metadata.version) {
|
|
68
|
+
this.validateVersionConsistency(metadata.version, filePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add metadata to results
|
|
72
|
+
const results = this.getResults();
|
|
73
|
+
results.metadata = {
|
|
74
|
+
author: metadata.author || 'unknown',
|
|
75
|
+
repository: metadata.repository || 'unknown',
|
|
76
|
+
version: metadata.version || 'unversioned',
|
|
77
|
+
extractedAt: new Date().toISOString()
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract metadata from frontmatter
|
|
85
|
+
* @param {string} content - Component content
|
|
86
|
+
* @param {string} filePath - File path
|
|
87
|
+
* @returns {object} Metadata object
|
|
88
|
+
*/
|
|
89
|
+
extractMetadata(content, filePath) {
|
|
90
|
+
const metadata = {};
|
|
91
|
+
|
|
92
|
+
// Extract frontmatter
|
|
93
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
94
|
+
if (!frontmatterMatch) {
|
|
95
|
+
return metadata;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const frontmatter = frontmatterMatch[1];
|
|
99
|
+
|
|
100
|
+
// Extract author
|
|
101
|
+
const authorMatch = frontmatter.match(/^author:\s*(.+)$/m);
|
|
102
|
+
if (authorMatch) {
|
|
103
|
+
metadata.author = authorMatch[1].trim().replace(/['"]/g, '');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Extract repository
|
|
107
|
+
const repoMatch = frontmatter.match(/^repository:\s*(.+)$/m);
|
|
108
|
+
if (repoMatch) {
|
|
109
|
+
metadata.repository = repoMatch[1].trim().replace(/['"]/g, '');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Extract version
|
|
113
|
+
const versionMatch = frontmatter.match(/^version:\s*(.+)$/m);
|
|
114
|
+
if (versionMatch) {
|
|
115
|
+
metadata.version = versionMatch[1].trim().replace(/['"]/g, '');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return metadata;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate author information
|
|
123
|
+
*/
|
|
124
|
+
validateAuthor(metadata, filePath, required) {
|
|
125
|
+
if (!metadata.author) {
|
|
126
|
+
if (required) {
|
|
127
|
+
this.addError('PROV_E002', 'Author information is required but missing', { path: filePath });
|
|
128
|
+
} else {
|
|
129
|
+
// Author is optional for components - metadata is stored in marketplace.json
|
|
130
|
+
this.addInfo('PROV_I007', 'No author in component (metadata in marketplace.json)', {
|
|
131
|
+
path: filePath
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
this.addInfo('PROV_I002', `Author: ${metadata.author}`, { path: filePath });
|
|
136
|
+
|
|
137
|
+
// Validate author format (basic check)
|
|
138
|
+
if (metadata.author.length < 2) {
|
|
139
|
+
this.addWarning('PROV_W003', 'Author name seems too short', {
|
|
140
|
+
path: filePath,
|
|
141
|
+
author: metadata.author
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Extract Git metadata for a file
|
|
149
|
+
* @param {string} filePath - Path to file
|
|
150
|
+
* @returns {Promise<object|null>} Git metadata or null
|
|
151
|
+
*/
|
|
152
|
+
async extractGitMetadata(filePath) {
|
|
153
|
+
try {
|
|
154
|
+
// Get last commit SHA for this file
|
|
155
|
+
const commitSha = execSync(
|
|
156
|
+
`git log -1 --format=%H -- "${filePath}"`,
|
|
157
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
158
|
+
).trim();
|
|
159
|
+
|
|
160
|
+
if (!commitSha) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Get commit author
|
|
165
|
+
const author = execSync(
|
|
166
|
+
`git log -1 --format=%an -- "${filePath}"`,
|
|
167
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
168
|
+
).trim();
|
|
169
|
+
|
|
170
|
+
// Get commit date
|
|
171
|
+
const date = execSync(
|
|
172
|
+
`git log -1 --format=%ai -- "${filePath}"`,
|
|
173
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
174
|
+
).trim();
|
|
175
|
+
|
|
176
|
+
// Get remote URL
|
|
177
|
+
let remoteUrl = '';
|
|
178
|
+
try {
|
|
179
|
+
remoteUrl = execSync(
|
|
180
|
+
'git config --get remote.origin.url',
|
|
181
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
182
|
+
).trim();
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// No remote configured
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
commitSha,
|
|
189
|
+
author,
|
|
190
|
+
date,
|
|
191
|
+
remoteUrl
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
// Not a git repository or file not tracked
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Validate Git metadata
|
|
201
|
+
*/
|
|
202
|
+
validateGitMetadata(gitMetadata, filePath) {
|
|
203
|
+
const { commitSha, author, date, remoteUrl } = gitMetadata;
|
|
204
|
+
|
|
205
|
+
this.addInfo('PROV_I003', `Git commit: ${commitSha.substring(0, 7)}`, {
|
|
206
|
+
path: filePath,
|
|
207
|
+
fullSha: commitSha,
|
|
208
|
+
author,
|
|
209
|
+
date
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (remoteUrl) {
|
|
213
|
+
this.addInfo('PROV_I004', `Repository: ${remoteUrl}`, {
|
|
214
|
+
path: filePath,
|
|
215
|
+
remoteUrl
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Validate that remote URL is a recognized platform
|
|
219
|
+
if (!this.isRecognizedGitPlatform(remoteUrl)) {
|
|
220
|
+
this.addWarning('PROV_W004', 'Git remote is not a recognized platform', {
|
|
221
|
+
path: filePath,
|
|
222
|
+
remoteUrl,
|
|
223
|
+
recognized: ['github.com', 'gitlab.com', 'bitbucket.org']
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Validate repository URL
|
|
231
|
+
*/
|
|
232
|
+
validateRepository(repository, filePath) {
|
|
233
|
+
// Check if it's a valid GitHub/GitLab/Bitbucket URL
|
|
234
|
+
const gitPlatforms = [
|
|
235
|
+
'github.com',
|
|
236
|
+
'gitlab.com',
|
|
237
|
+
'bitbucket.org',
|
|
238
|
+
'codeberg.org'
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
const isValid = gitPlatforms.some(platform => repository.includes(platform));
|
|
242
|
+
|
|
243
|
+
if (!isValid) {
|
|
244
|
+
this.addWarning('PROV_W005', 'Repository URL is not from a recognized platform', {
|
|
245
|
+
path: filePath,
|
|
246
|
+
repository,
|
|
247
|
+
recognized: gitPlatforms
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
this.addInfo('PROV_I005', `Repository: ${repository}`, { path: filePath });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check for HTTPS
|
|
254
|
+
if (repository.startsWith('http://')) {
|
|
255
|
+
this.addWarning('PROV_W006', 'Repository URL uses HTTP (HTTPS recommended)', {
|
|
256
|
+
path: filePath,
|
|
257
|
+
repository
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Validate version consistency
|
|
264
|
+
*/
|
|
265
|
+
validateVersionConsistency(version, filePath) {
|
|
266
|
+
// Check if version follows semantic versioning
|
|
267
|
+
const semverPattern = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
268
|
+
|
|
269
|
+
if (!semverPattern.test(version)) {
|
|
270
|
+
this.addWarning('PROV_W007', 'Version does not follow semantic versioning', {
|
|
271
|
+
path: filePath,
|
|
272
|
+
version,
|
|
273
|
+
expected: 'X.Y.Z or X.Y.Z-tag'
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
this.addInfo('PROV_I006', `Version: ${version}`, { path: filePath });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if Git remote is a recognized platform
|
|
282
|
+
*/
|
|
283
|
+
isRecognizedGitPlatform(remoteUrl) {
|
|
284
|
+
const platforms = [
|
|
285
|
+
'github.com',
|
|
286
|
+
'gitlab.com',
|
|
287
|
+
'bitbucket.org',
|
|
288
|
+
'codeberg.org'
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
return platforms.some(platform => remoteUrl.includes(platform));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Generate provenance report
|
|
296
|
+
* @param {object} component - Component to analyze
|
|
297
|
+
* @returns {Promise<object>} Provenance report
|
|
298
|
+
*/
|
|
299
|
+
async generateProvenanceReport(component) {
|
|
300
|
+
const result = await this.validate(component);
|
|
301
|
+
|
|
302
|
+
// Extract Git metadata if available
|
|
303
|
+
let gitMetadata = null;
|
|
304
|
+
if (component.path && fs.existsSync(component.path)) {
|
|
305
|
+
gitMetadata = await this.extractGitMetadata(component.path);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
traceable: result.valid && result.metadata.author !== 'unknown',
|
|
310
|
+
metadata: result.metadata,
|
|
311
|
+
git: gitMetadata,
|
|
312
|
+
issues: {
|
|
313
|
+
errors: result.errors,
|
|
314
|
+
warnings: result.warnings
|
|
315
|
+
},
|
|
316
|
+
trustScore: this.calculateTrustScore(result, gitMetadata),
|
|
317
|
+
timestamp: new Date().toISOString()
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Calculate trust score based on provenance data
|
|
323
|
+
* @param {object} result - Validation results
|
|
324
|
+
* @param {object} gitMetadata - Git metadata
|
|
325
|
+
* @returns {number} Trust score (0-100)
|
|
326
|
+
*/
|
|
327
|
+
calculateTrustScore(result, gitMetadata) {
|
|
328
|
+
let score = 50; // Base score
|
|
329
|
+
|
|
330
|
+
// Has author: +15
|
|
331
|
+
if (result.metadata.author !== 'unknown') {
|
|
332
|
+
score += 15;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Has repository: +15
|
|
336
|
+
if (result.metadata.repository !== 'unknown') {
|
|
337
|
+
score += 15;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Has version: +10
|
|
341
|
+
if (result.metadata.version !== 'unversioned') {
|
|
342
|
+
score += 10;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Has Git metadata: +10
|
|
346
|
+
if (gitMetadata) {
|
|
347
|
+
score += 10;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Deduct for warnings and errors
|
|
351
|
+
score -= result.warningCount * 2;
|
|
352
|
+
score -= result.errorCount * 10;
|
|
353
|
+
|
|
354
|
+
return Math.max(0, Math.min(100, score));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Batch validate provenance for multiple components
|
|
359
|
+
* @param {Array<object>} components - Components to validate
|
|
360
|
+
* @returns {Promise<object>} Batch validation results
|
|
361
|
+
*/
|
|
362
|
+
async batchValidate(components) {
|
|
363
|
+
const results = {
|
|
364
|
+
total: components.length,
|
|
365
|
+
traceable: 0,
|
|
366
|
+
untraceable: 0,
|
|
367
|
+
averageTrustScore: 0,
|
|
368
|
+
components: []
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
let totalTrustScore = 0;
|
|
372
|
+
|
|
373
|
+
for (const component of components) {
|
|
374
|
+
const report = await this.generateProvenanceReport(component);
|
|
375
|
+
|
|
376
|
+
results.components.push({
|
|
377
|
+
path: component.path,
|
|
378
|
+
traceable: report.traceable,
|
|
379
|
+
trustScore: report.trustScore,
|
|
380
|
+
author: report.metadata.author,
|
|
381
|
+
repository: report.metadata.repository
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (report.traceable) {
|
|
385
|
+
results.traceable++;
|
|
386
|
+
} else {
|
|
387
|
+
results.untraceable++;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
totalTrustScore += report.trustScore;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
results.averageTrustScore = Math.round(totalTrustScore / components.length);
|
|
394
|
+
|
|
395
|
+
return results;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
module.exports = ProvenanceValidator;
|