claude-context 1.2.4
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/README.md +121 -0
- package/bin/claude-context.js +144 -0
- package/lib/diagnose.js +337 -0
- package/lib/generate.js +302 -0
- package/lib/hooks.js +150 -0
- package/lib/index.js +17 -0
- package/lib/sync.js +263 -0
- package/lib/validate.js +292 -0
- package/package.json +48 -0
package/lib/sync.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronization module for Claude Context Engineering
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { glob } = require('glob');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Synchronize documentation with code
|
|
12
|
+
*/
|
|
13
|
+
async function sync(projectRoot, options = {}) {
|
|
14
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
15
|
+
const results = {
|
|
16
|
+
success: true,
|
|
17
|
+
driftDetected: false,
|
|
18
|
+
filesChecked: 0,
|
|
19
|
+
issuesFound: 0,
|
|
20
|
+
issuesFixed: 0
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
console.log(chalk.blue('\nš Checking documentation synchronization...\n'));
|
|
24
|
+
|
|
25
|
+
// Check for drift
|
|
26
|
+
if (options.check || options.fix || (!options.rebuildMap)) {
|
|
27
|
+
const driftResult = await checkDrift(claudeDir, projectRoot, options);
|
|
28
|
+
results.driftDetected = driftResult.hasDrift;
|
|
29
|
+
results.filesChecked = driftResult.filesChecked;
|
|
30
|
+
results.issuesFound = driftResult.issues.length;
|
|
31
|
+
|
|
32
|
+
if (options.fix && driftResult.issues.length > 0) {
|
|
33
|
+
const fixResult = await fixDrift(claudeDir, projectRoot, driftResult.issues);
|
|
34
|
+
results.issuesFixed = fixResult.fixed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (options.strict && results.driftDetected) {
|
|
38
|
+
results.success = false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Rebuild CODE_TO_WORKFLOW_MAP
|
|
43
|
+
if (options.rebuildMap) {
|
|
44
|
+
await rebuildCodeMap(claudeDir, projectRoot);
|
|
45
|
+
console.log(chalk.green('ā CODE_TO_WORKFLOW_MAP.md regenerated'));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Print summary
|
|
49
|
+
printSyncSummary(results, options);
|
|
50
|
+
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check for documentation drift
|
|
56
|
+
*/
|
|
57
|
+
async function checkDrift(claudeDir, projectRoot, options) {
|
|
58
|
+
const result = {
|
|
59
|
+
hasDrift: false,
|
|
60
|
+
filesChecked: 0,
|
|
61
|
+
issues: []
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Load staleness tracking
|
|
65
|
+
const stalenessPath = path.join(claudeDir, 'sync', 'staleness.json');
|
|
66
|
+
let staleness = {};
|
|
67
|
+
if (fs.existsSync(stalenessPath)) {
|
|
68
|
+
try {
|
|
69
|
+
staleness = JSON.parse(fs.readFileSync(stalenessPath, 'utf8'));
|
|
70
|
+
} catch {
|
|
71
|
+
// Start fresh
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check workflow files against code
|
|
76
|
+
const workflowDir = path.join(claudeDir, 'context', 'workflows');
|
|
77
|
+
if (fs.existsSync(workflowDir)) {
|
|
78
|
+
const workflowFiles = await glob('*.md', { cwd: workflowDir });
|
|
79
|
+
|
|
80
|
+
for (const file of workflowFiles) {
|
|
81
|
+
result.filesChecked++;
|
|
82
|
+
const content = fs.readFileSync(path.join(workflowDir, file), 'utf8');
|
|
83
|
+
|
|
84
|
+
// Find file:line references
|
|
85
|
+
const lineRefPattern = /`([^`]+):(\d+)`/g;
|
|
86
|
+
let match;
|
|
87
|
+
|
|
88
|
+
while ((match = lineRefPattern.exec(content)) !== null) {
|
|
89
|
+
const filePath = match[1];
|
|
90
|
+
const lineNum = parseInt(match[2], 10);
|
|
91
|
+
const targetPath = path.join(projectRoot, filePath);
|
|
92
|
+
|
|
93
|
+
if (fs.existsSync(targetPath)) {
|
|
94
|
+
const lines = fs.readFileSync(targetPath, 'utf8').split('\n');
|
|
95
|
+
|
|
96
|
+
if (lineNum > lines.length) {
|
|
97
|
+
result.hasDrift = true;
|
|
98
|
+
result.issues.push({
|
|
99
|
+
docFile: file,
|
|
100
|
+
codeFile: filePath,
|
|
101
|
+
oldLine: lineNum,
|
|
102
|
+
newLine: null,
|
|
103
|
+
type: 'line_exceeded'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
result.hasDrift = true;
|
|
108
|
+
result.issues.push({
|
|
109
|
+
docFile: file,
|
|
110
|
+
codeFile: filePath,
|
|
111
|
+
oldLine: lineNum,
|
|
112
|
+
newLine: null,
|
|
113
|
+
type: 'file_missing'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (result.issues.length > 0) {
|
|
121
|
+
console.log(chalk.yellow(`ā Found ${result.issues.length} drift issues:`));
|
|
122
|
+
for (const issue of result.issues.slice(0, 5)) {
|
|
123
|
+
if (issue.type === 'file_missing') {
|
|
124
|
+
console.log(chalk.yellow(` - ${issue.docFile}: File ${issue.codeFile} no longer exists`));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.yellow(` - ${issue.docFile}: Line ${issue.oldLine} in ${issue.codeFile} is out of range`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (result.issues.length > 5) {
|
|
130
|
+
console.log(chalk.yellow(` ... and ${result.issues.length - 5} more`));
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
console.log(chalk.green('ā No drift detected'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Fix drift issues
|
|
141
|
+
*/
|
|
142
|
+
async function fixDrift(claudeDir, projectRoot, issues) {
|
|
143
|
+
const result = { fixed: 0, failed: 0 };
|
|
144
|
+
|
|
145
|
+
// Group issues by doc file
|
|
146
|
+
const byDocFile = {};
|
|
147
|
+
for (const issue of issues) {
|
|
148
|
+
if (!byDocFile[issue.docFile]) {
|
|
149
|
+
byDocFile[issue.docFile] = [];
|
|
150
|
+
}
|
|
151
|
+
byDocFile[issue.docFile].push(issue);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const [docFile, fileIssues] of Object.entries(byDocFile)) {
|
|
155
|
+
const docPath = path.join(claudeDir, 'context', 'workflows', docFile);
|
|
156
|
+
if (!fs.existsSync(docPath)) continue;
|
|
157
|
+
|
|
158
|
+
let content = fs.readFileSync(docPath, 'utf8');
|
|
159
|
+
let modified = false;
|
|
160
|
+
|
|
161
|
+
for (const issue of fileIssues) {
|
|
162
|
+
if (issue.type === 'file_missing') {
|
|
163
|
+
// Comment out the reference
|
|
164
|
+
const pattern = new RegExp(`\`${issue.codeFile}:${issue.oldLine}\``, 'g');
|
|
165
|
+
const newContent = content.replace(pattern, `<!-- REMOVED: ${issue.codeFile}:${issue.oldLine} -->`);
|
|
166
|
+
if (newContent !== content) {
|
|
167
|
+
content = newContent;
|
|
168
|
+
modified = true;
|
|
169
|
+
result.fixed++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (modified) {
|
|
175
|
+
fs.writeFileSync(docPath, content);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(chalk.green(`ā Fixed ${result.fixed} issues`));
|
|
180
|
+
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Rebuild CODE_TO_WORKFLOW_MAP.md
|
|
186
|
+
*/
|
|
187
|
+
async function rebuildCodeMap(claudeDir, projectRoot) {
|
|
188
|
+
const mapPath = path.join(claudeDir, 'context', 'CODE_TO_WORKFLOW_MAP.md');
|
|
189
|
+
const workflowDir = path.join(claudeDir, 'context', 'workflows');
|
|
190
|
+
|
|
191
|
+
const codeToWorkflow = {};
|
|
192
|
+
|
|
193
|
+
if (fs.existsSync(workflowDir)) {
|
|
194
|
+
const workflowFiles = await glob('*.md', { cwd: workflowDir });
|
|
195
|
+
|
|
196
|
+
for (const file of workflowFiles) {
|
|
197
|
+
const content = fs.readFileSync(path.join(workflowDir, file), 'utf8');
|
|
198
|
+
|
|
199
|
+
// Extract workflow name from first heading
|
|
200
|
+
const nameMatch = content.match(/^#\s+(.+)$/m);
|
|
201
|
+
const workflowName = nameMatch ? nameMatch[1] : file.replace('.md', '');
|
|
202
|
+
|
|
203
|
+
// Find file references
|
|
204
|
+
const fileRefPattern = /`([^`]+\.[a-z]+)(?::\d+)?`/g;
|
|
205
|
+
let match;
|
|
206
|
+
|
|
207
|
+
while ((match = fileRefPattern.exec(content)) !== null) {
|
|
208
|
+
const filePath = match[1];
|
|
209
|
+
if (!codeToWorkflow[filePath]) {
|
|
210
|
+
codeToWorkflow[filePath] = [];
|
|
211
|
+
}
|
|
212
|
+
if (!codeToWorkflow[filePath].includes(workflowName)) {
|
|
213
|
+
codeToWorkflow[filePath].push(workflowName);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Generate markdown
|
|
220
|
+
let output = `# Code to Workflow Map
|
|
221
|
+
|
|
222
|
+
> Auto-generated by \`npx claude-context generate --code-map\`
|
|
223
|
+
> Last updated: ${new Date().toISOString().split('T')[0]}
|
|
224
|
+
|
|
225
|
+
This maps source files to their documenting workflows.
|
|
226
|
+
|
|
227
|
+
| File | Workflows |
|
|
228
|
+
|------|-----------|
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
const sortedFiles = Object.keys(codeToWorkflow).sort();
|
|
232
|
+
for (const file of sortedFiles) {
|
|
233
|
+
const workflows = codeToWorkflow[file].join(', ');
|
|
234
|
+
output += `| \`${file}\` | ${workflows} |\n`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (sortedFiles.length === 0) {
|
|
238
|
+
output += `| *No mappings yet* | - |\n`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fs.writeFileSync(mapPath, output);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Print sync summary
|
|
246
|
+
*/
|
|
247
|
+
function printSyncSummary(results, options) {
|
|
248
|
+
console.log('\n' + chalk.bold('Sync Summary:'));
|
|
249
|
+
console.log(` Files checked: ${results.filesChecked}`);
|
|
250
|
+
console.log(` Issues found: ${results.issuesFound}`);
|
|
251
|
+
|
|
252
|
+
if (options.fix) {
|
|
253
|
+
console.log(` Issues fixed: ${results.issuesFixed}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (results.success) {
|
|
257
|
+
console.log(chalk.green('\nā Sync check complete!\n'));
|
|
258
|
+
} else {
|
|
259
|
+
console.log(chalk.red('\nā Drift detected (strict mode).\n'));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = { sync };
|
package/lib/validate.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation module for Claude Context Engineering
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { glob } = require('glob');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate context engineering setup
|
|
12
|
+
*/
|
|
13
|
+
async function validate(projectRoot, options = {}) {
|
|
14
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
15
|
+
const results = {
|
|
16
|
+
success: true,
|
|
17
|
+
checks: [],
|
|
18
|
+
errors: [],
|
|
19
|
+
warnings: []
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
console.log(chalk.blue('\nš Validating context engineering setup...\n'));
|
|
23
|
+
|
|
24
|
+
// Determine which checks to run
|
|
25
|
+
const runAll = options.all || (!options.schema && !options.links && !options.placeholders && !options.structure && !options.lines);
|
|
26
|
+
|
|
27
|
+
// Schema validation
|
|
28
|
+
if (runAll || options.schema) {
|
|
29
|
+
const schemaResult = await validateSchemas(claudeDir, options);
|
|
30
|
+
results.checks.push(schemaResult);
|
|
31
|
+
if (!schemaResult.passed) {
|
|
32
|
+
results.errors.push(...schemaResult.errors);
|
|
33
|
+
results.success = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Link validation
|
|
38
|
+
if (runAll || options.links) {
|
|
39
|
+
const linkResult = await validateLinks(claudeDir, projectRoot, options);
|
|
40
|
+
results.checks.push(linkResult);
|
|
41
|
+
if (!linkResult.passed) {
|
|
42
|
+
results.errors.push(...linkResult.errors);
|
|
43
|
+
results.success = false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Placeholder validation
|
|
48
|
+
if (runAll || options.placeholders) {
|
|
49
|
+
const placeholderResult = await validatePlaceholders(claudeDir, options);
|
|
50
|
+
results.checks.push(placeholderResult);
|
|
51
|
+
if (!placeholderResult.passed) {
|
|
52
|
+
results.warnings.push(...placeholderResult.errors);
|
|
53
|
+
// Placeholders are warnings, not failures
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Structure validation
|
|
58
|
+
if (runAll || options.structure) {
|
|
59
|
+
const structureResult = await validateStructure(claudeDir, options);
|
|
60
|
+
results.checks.push(structureResult);
|
|
61
|
+
if (!structureResult.passed) {
|
|
62
|
+
results.errors.push(...structureResult.errors);
|
|
63
|
+
results.success = false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Line number validation
|
|
68
|
+
if (runAll || options.lines) {
|
|
69
|
+
const lineResult = await validateLineNumbers(claudeDir, projectRoot, options);
|
|
70
|
+
results.checks.push(lineResult);
|
|
71
|
+
if (!lineResult.passed) {
|
|
72
|
+
results.warnings.push(...lineResult.errors);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Print summary
|
|
77
|
+
printSummary(results);
|
|
78
|
+
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validate JSON schemas
|
|
84
|
+
*/
|
|
85
|
+
async function validateSchemas(claudeDir, options) {
|
|
86
|
+
const result = { name: 'Schema Validation', passed: true, errors: [], count: 0 };
|
|
87
|
+
|
|
88
|
+
const schemaDir = path.join(claudeDir, 'schemas');
|
|
89
|
+
if (!fs.existsSync(schemaDir)) {
|
|
90
|
+
result.errors.push('No schemas directory found');
|
|
91
|
+
result.passed = false;
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const jsonFiles = await glob('**/*.json', { cwd: claudeDir, nodir: true });
|
|
96
|
+
|
|
97
|
+
for (const file of jsonFiles) {
|
|
98
|
+
try {
|
|
99
|
+
const content = fs.readFileSync(path.join(claudeDir, file), 'utf8');
|
|
100
|
+
JSON.parse(content);
|
|
101
|
+
result.count++;
|
|
102
|
+
} catch (e) {
|
|
103
|
+
result.errors.push(`Invalid JSON in ${file}: ${e.message}`);
|
|
104
|
+
result.passed = false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(result.passed
|
|
109
|
+
? chalk.green(`ā Schema validation: ${result.count} JSON files valid`)
|
|
110
|
+
: chalk.red(`ā Schema validation: ${result.errors.length} errors`));
|
|
111
|
+
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Validate internal links
|
|
117
|
+
*/
|
|
118
|
+
async function validateLinks(claudeDir, projectRoot, options) {
|
|
119
|
+
const result = { name: 'Link Validation', passed: true, errors: [], count: 0 };
|
|
120
|
+
|
|
121
|
+
const mdFiles = await glob('**/*.md', { cwd: claudeDir, nodir: true });
|
|
122
|
+
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
123
|
+
|
|
124
|
+
for (const file of mdFiles) {
|
|
125
|
+
const content = fs.readFileSync(path.join(claudeDir, file), 'utf8');
|
|
126
|
+
let match;
|
|
127
|
+
|
|
128
|
+
while ((match = linkPattern.exec(content)) !== null) {
|
|
129
|
+
const linkPath = match[2];
|
|
130
|
+
|
|
131
|
+
// Skip external links and anchors
|
|
132
|
+
if (linkPath.startsWith('http') || linkPath.startsWith('#') || linkPath.startsWith('mailto:')) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
result.count++;
|
|
137
|
+
|
|
138
|
+
// Resolve relative path
|
|
139
|
+
const targetPath = path.resolve(path.dirname(path.join(claudeDir, file)), linkPath.split('#')[0]);
|
|
140
|
+
|
|
141
|
+
if (!fs.existsSync(targetPath)) {
|
|
142
|
+
result.errors.push(`Broken link in ${file}: ${linkPath}`);
|
|
143
|
+
result.passed = false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(result.passed
|
|
149
|
+
? chalk.green(`ā Link validation: ${result.count} links checked`)
|
|
150
|
+
: chalk.red(`ā Link validation: ${result.errors.length} broken links`));
|
|
151
|
+
|
|
152
|
+
if (options.verbose && result.errors.length > 0) {
|
|
153
|
+
result.errors.forEach(e => console.log(chalk.yellow(` - ${e}`)));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate placeholders are replaced
|
|
161
|
+
*/
|
|
162
|
+
async function validatePlaceholders(claudeDir, options) {
|
|
163
|
+
const result = { name: 'Placeholder Validation', passed: true, errors: [], count: 0 };
|
|
164
|
+
|
|
165
|
+
const mdFiles = await glob('**/*.md', { cwd: claudeDir, nodir: true });
|
|
166
|
+
const placeholderPattern = /\{\{([A-Z_]+)\}\}/g;
|
|
167
|
+
|
|
168
|
+
for (const file of mdFiles) {
|
|
169
|
+
const content = fs.readFileSync(path.join(claudeDir, file), 'utf8');
|
|
170
|
+
let match;
|
|
171
|
+
|
|
172
|
+
while ((match = placeholderPattern.exec(content)) !== null) {
|
|
173
|
+
result.errors.push(`Unreplaced placeholder in ${file}: ${match[0]}`);
|
|
174
|
+
result.count++;
|
|
175
|
+
result.passed = false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(result.passed
|
|
180
|
+
? chalk.green(`ā Placeholder validation: No unreplaced placeholders`)
|
|
181
|
+
: chalk.yellow(`ā Placeholder validation: ${result.count} unreplaced placeholders`));
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate directory structure
|
|
188
|
+
*/
|
|
189
|
+
async function validateStructure(claudeDir, options) {
|
|
190
|
+
const result = { name: 'Structure Validation', passed: true, errors: [], count: 0 };
|
|
191
|
+
|
|
192
|
+
const requiredDirs = [
|
|
193
|
+
'agents',
|
|
194
|
+
'commands',
|
|
195
|
+
'context',
|
|
196
|
+
'indexes'
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
for (const dir of requiredDirs) {
|
|
200
|
+
const dirPath = path.join(claudeDir, dir);
|
|
201
|
+
if (fs.existsSync(dirPath)) {
|
|
202
|
+
result.count++;
|
|
203
|
+
} else {
|
|
204
|
+
result.errors.push(`Missing required directory: ${dir}`);
|
|
205
|
+
result.passed = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check for CLAUDE.md at project root
|
|
210
|
+
const claudeMdPath = path.join(path.dirname(claudeDir), 'CLAUDE.md');
|
|
211
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
212
|
+
result.count++;
|
|
213
|
+
} else {
|
|
214
|
+
result.errors.push('Missing CLAUDE.md at project root');
|
|
215
|
+
result.passed = false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log(result.passed
|
|
219
|
+
? chalk.green(`ā Structure validation: ${result.count} required items present`)
|
|
220
|
+
: chalk.red(`ā Structure validation: ${result.errors.length} missing items`));
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Validate line number references
|
|
227
|
+
*/
|
|
228
|
+
async function validateLineNumbers(claudeDir, projectRoot, options) {
|
|
229
|
+
const result = { name: 'Line Number Validation', passed: true, errors: [], count: 0, accurate: 0 };
|
|
230
|
+
const threshold = parseInt(options.threshold || '60', 10);
|
|
231
|
+
|
|
232
|
+
const mdFiles = await glob('**/*.md', { cwd: claudeDir, nodir: true });
|
|
233
|
+
const lineRefPattern = /`([^`]+):(\d+)`|([a-zA-Z0-9_\-/.]+):(\d+)/g;
|
|
234
|
+
|
|
235
|
+
for (const file of mdFiles) {
|
|
236
|
+
const content = fs.readFileSync(path.join(claudeDir, file), 'utf8');
|
|
237
|
+
let match;
|
|
238
|
+
|
|
239
|
+
while ((match = lineRefPattern.exec(content)) !== null) {
|
|
240
|
+
const filePath = match[1] || match[3];
|
|
241
|
+
const lineNum = parseInt(match[2] || match[4], 10);
|
|
242
|
+
|
|
243
|
+
// Skip if file path doesn't look like a source file
|
|
244
|
+
if (!filePath || filePath.includes(' ') || !filePath.includes('.')) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
result.count++;
|
|
249
|
+
|
|
250
|
+
const targetPath = path.join(projectRoot, filePath);
|
|
251
|
+
if (fs.existsSync(targetPath)) {
|
|
252
|
+
try {
|
|
253
|
+
const lines = fs.readFileSync(targetPath, 'utf8').split('\n');
|
|
254
|
+
if (lineNum <= lines.length) {
|
|
255
|
+
result.accurate++;
|
|
256
|
+
} else {
|
|
257
|
+
result.errors.push(`Line ${lineNum} exceeds file length in ${filePath} (${lines.length} lines)`);
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
// Skip unreadable files
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const accuracy = result.count > 0 ? Math.round((result.accurate / result.count) * 100) : 100;
|
|
267
|
+
result.passed = accuracy >= threshold;
|
|
268
|
+
|
|
269
|
+
console.log(result.passed
|
|
270
|
+
? chalk.green(`ā Line number validation: ${accuracy}% accurate (threshold: ${threshold}%)`)
|
|
271
|
+
: chalk.yellow(`ā Line number validation: ${accuracy}% accurate (below ${threshold}% threshold)`));
|
|
272
|
+
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Print validation summary
|
|
278
|
+
*/
|
|
279
|
+
function printSummary(results) {
|
|
280
|
+
console.log('\n' + chalk.bold('Summary:'));
|
|
281
|
+
console.log(` Checks run: ${results.checks.length}`);
|
|
282
|
+
console.log(` Errors: ${results.errors.length}`);
|
|
283
|
+
console.log(` Warnings: ${results.warnings.length}`);
|
|
284
|
+
|
|
285
|
+
if (results.success) {
|
|
286
|
+
console.log(chalk.green('\nā All validations passed!\n'));
|
|
287
|
+
} else {
|
|
288
|
+
console.log(chalk.red('\nā Some validations failed.\n'));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = { validate };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-context",
|
|
3
|
+
"version": "1.2.4",
|
|
4
|
+
"description": "CLI tools for Claude Context Engineering - validate, sync, diagnose, and manage your context system",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"context-engineering",
|
|
9
|
+
"documentation",
|
|
10
|
+
"validation",
|
|
11
|
+
"cli"
|
|
12
|
+
],
|
|
13
|
+
"author": "SireJeff",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/SireJeff/claude-context-engineering-template.git",
|
|
18
|
+
"directory": "packages/claude-context"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/SireJeff/claude-context-engineering-template#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/SireJeff/claude-context-engineering-template/issues"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"claude-context": "./bin/claude-context.js"
|
|
26
|
+
},
|
|
27
|
+
"main": "lib/index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"lib"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^11.0.0",
|
|
37
|
+
"chalk": "^4.1.2",
|
|
38
|
+
"glob": "^10.3.0",
|
|
39
|
+
"minimatch": "^9.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"jest": "^29.7.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"test": "jest",
|
|
46
|
+
"test:coverage": "jest --coverage"
|
|
47
|
+
}
|
|
48
|
+
}
|