@zebralabs/context-cli 0.1.2 → 0.1.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/package.json +1 -1
- package/src/application/compile/compile-context.js +119 -0
- package/src/application/compile/stage1-discovery.js +125 -0
- package/src/application/compile/stage2-extraction.js +97 -0
- package/src/application/compile/stage4-consolidation.js +235 -0
- package/src/application/compile/stage5-assets.js +61 -0
- package/src/application/compile/stage6-validation.js +133 -0
- package/src/application/ports/asset-generator.js +33 -0
- package/src/context.js +280 -15
- package/src/domain/compilation.js +77 -0
- package/src/domain/preference.js +23 -0
- package/src/domain/rule.js +71 -0
- package/src/domain/scope.js +30 -0
- package/src/infrastructure/assets/claude/claude-generator.js +95 -0
- package/src/infrastructure/assets/cursor/cursor-rules-generator.js +119 -0
- package/src/infrastructure/assets/cursor/cursor-skills-generator.js +115 -0
- package/src/infrastructure/file-system/file-reader.js +67 -0
- package/src/infrastructure/file-system/file-writer.js +40 -0
- package/src/infrastructure/parsing/markdown-parser.js +95 -0
- package/src/infrastructure/parsing/rule-extractor.js +219 -0
- package/src/infrastructure/parsing/skill-extractor.js +74 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { FileWriter } from "../../infrastructure/file-system/file-writer.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stage 6: Validation & Reporting
|
|
6
|
+
* Validates compilation and generates comprehensive reports
|
|
7
|
+
*/
|
|
8
|
+
export class Stage6Validation {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.fileWriter = new FileWriter();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate and generate reports
|
|
15
|
+
* @param {Compilation} compilation - The compilation
|
|
16
|
+
* @param {Object} stageReports - Reports from all stages
|
|
17
|
+
* @param {string} outputDir - Output directory
|
|
18
|
+
* @returns {Object} Validation result
|
|
19
|
+
*/
|
|
20
|
+
execute(compilation, stageReports, outputDir) {
|
|
21
|
+
const reportsDir = path.join(outputDir, "reports");
|
|
22
|
+
this.fileWriter.ensureDir(reportsDir);
|
|
23
|
+
|
|
24
|
+
// Validation checks
|
|
25
|
+
const validation = {
|
|
26
|
+
rulesValid: true,
|
|
27
|
+
noDuplicateIds: true,
|
|
28
|
+
allRulesHaveIds: true,
|
|
29
|
+
errors: []
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Check for duplicate rule IDs
|
|
33
|
+
const ruleIds = new Set();
|
|
34
|
+
for (const rule of compilation.rules) {
|
|
35
|
+
if (ruleIds.has(rule.id)) {
|
|
36
|
+
validation.noDuplicateIds = false;
|
|
37
|
+
validation.errors.push(`Duplicate rule ID: ${rule.id}`);
|
|
38
|
+
}
|
|
39
|
+
ruleIds.add(rule.id);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check all rules have required fields
|
|
43
|
+
for (const rule of compilation.rules) {
|
|
44
|
+
if (!rule.id || !rule.level || !rule.rule) {
|
|
45
|
+
validation.allRulesHaveIds = false;
|
|
46
|
+
validation.errors.push(`Rule missing required fields: ${rule.id || "unknown"}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
validation.rulesValid = validation.noDuplicateIds && validation.allRulesHaveIds;
|
|
51
|
+
|
|
52
|
+
// Generate compilation report
|
|
53
|
+
const compilationReport = this.generateCompilationReport(compilation, stageReports, validation);
|
|
54
|
+
this.fileWriter.writeFile(
|
|
55
|
+
path.join(reportsDir, "COMPILATION-REPORT.md"),
|
|
56
|
+
compilationReport
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
validation,
|
|
61
|
+
reportsGenerated: ["COMPILATION-REPORT.md"]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate compilation report
|
|
67
|
+
*/
|
|
68
|
+
generateCompilationReport(compilation, stageReports, validation) {
|
|
69
|
+
const lines = [];
|
|
70
|
+
lines.push("# Compilation Report");
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(`Generated: ${compilation.metadata.timestamp}`);
|
|
73
|
+
lines.push("");
|
|
74
|
+
|
|
75
|
+
// Summary
|
|
76
|
+
lines.push("## Summary");
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(`- Files Processed: ${stageReports.stage2?.filesProcessed || 0}`);
|
|
79
|
+
lines.push(`- Rules Extracted: ${compilation.rules.length}`);
|
|
80
|
+
lines.push(`- Preferences Extracted: ${compilation.preferences.length}`);
|
|
81
|
+
lines.push(`- Scopes Extracted: ${compilation.scopes.length}`);
|
|
82
|
+
lines.push(`- Categories: ${compilation.getCategories().length}`);
|
|
83
|
+
lines.push("");
|
|
84
|
+
|
|
85
|
+
// Validation
|
|
86
|
+
lines.push("## Validation");
|
|
87
|
+
lines.push("");
|
|
88
|
+
if (validation.rulesValid) {
|
|
89
|
+
lines.push("✅ All rules are valid");
|
|
90
|
+
} else {
|
|
91
|
+
lines.push("❌ Validation errors found:");
|
|
92
|
+
for (const error of validation.errors) {
|
|
93
|
+
lines.push(` - ${error}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
lines.push("");
|
|
97
|
+
|
|
98
|
+
// Rules by category
|
|
99
|
+
lines.push("## Rules by Category");
|
|
100
|
+
lines.push("");
|
|
101
|
+
const categories = compilation.getCategories();
|
|
102
|
+
for (const category of categories) {
|
|
103
|
+
const rules = compilation.getRulesByCategory(category);
|
|
104
|
+
lines.push(`- **${category}**: ${rules.length} rules`);
|
|
105
|
+
}
|
|
106
|
+
lines.push("");
|
|
107
|
+
|
|
108
|
+
// Rules by level
|
|
109
|
+
lines.push("## Rules by Level");
|
|
110
|
+
lines.push("");
|
|
111
|
+
const levels = ["must", "should", "prefer", "avoid"];
|
|
112
|
+
for (const level of levels) {
|
|
113
|
+
const rules = compilation.getRulesByLevel(level);
|
|
114
|
+
if (rules.length > 0) {
|
|
115
|
+
lines.push(`- **${level}**: ${rules.length} rules`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
lines.push("");
|
|
119
|
+
|
|
120
|
+
// Errors and warnings
|
|
121
|
+
if (stageReports.stage2?.errors?.length > 0) {
|
|
122
|
+
lines.push("## Errors");
|
|
123
|
+
lines.push("");
|
|
124
|
+
for (const error of stageReports.stage2.errors) {
|
|
125
|
+
lines.push(`- ${error.file}: ${error.error}`);
|
|
126
|
+
}
|
|
127
|
+
lines.push("");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Generator Port (Interface)
|
|
3
|
+
* Defines the contract for tool-specific asset generators
|
|
4
|
+
*/
|
|
5
|
+
export class IAssetGenerator {
|
|
6
|
+
/**
|
|
7
|
+
* Generate tool-specific assets from consolidated rules
|
|
8
|
+
* @param {Compilation} compilation - The compiled context
|
|
9
|
+
* @param {string} outputPath - Where to write assets
|
|
10
|
+
* @returns {Promise<Object>} Generation result
|
|
11
|
+
*/
|
|
12
|
+
async generate(compilation, outputPath) {
|
|
13
|
+
throw new Error("Not implemented");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Merge pre-generated assets from packs
|
|
18
|
+
* @param {Object[]} assets - Assets from packs
|
|
19
|
+
* @returns {Object[]} Merged assets
|
|
20
|
+
*/
|
|
21
|
+
merge(assets) {
|
|
22
|
+
throw new Error("Not implemented");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the tool name this generator is for
|
|
27
|
+
* @returns {string} Tool name (e.g., "cursor", "claude")
|
|
28
|
+
*/
|
|
29
|
+
getToolName() {
|
|
30
|
+
throw new Error("Not implemented");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
package/src/context.js
CHANGED
|
@@ -17,9 +17,9 @@ Context-as-Code CLI
|
|
|
17
17
|
|
|
18
18
|
Usage:
|
|
19
19
|
ctx help
|
|
20
|
-
ctx list
|
|
21
|
-
ctx compile
|
|
22
|
-
ctx validate
|
|
20
|
+
ctx list [--context-yaml <path>]
|
|
21
|
+
ctx compile [--pack <packId>] [--context-yaml <path>]
|
|
22
|
+
ctx validate [--context-yaml <path>]
|
|
23
23
|
|
|
24
24
|
Pack install:
|
|
25
25
|
ctx pack install <packId> [--repo-root <path>] [--mode SkipExisting|Overwrite]
|
|
@@ -32,23 +32,74 @@ Examples (registry download):
|
|
|
32
32
|
ctx pack install pack-01-documentation-management --registry https://example.com --token YOURTOKEN
|
|
33
33
|
ctx pack install pack-01-documentation-management --registry https://example.com --token YOURTOKEN --version 0.1.0
|
|
34
34
|
|
|
35
|
+
Examples (compile):
|
|
36
|
+
ctx compile --pack pack-01-documentation-management
|
|
37
|
+
|
|
35
38
|
Notes:
|
|
36
39
|
- Zip must contain: practices-and-standards/install.ps1
|
|
37
|
-
- Installer merges into <repo-root>/practices-and-standards/
|
|
40
|
+
- Installer merges into <repo-root>/docs/practices-and-standards/
|
|
38
41
|
`.trim() + "\n");
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
function findRepoContextRoot(startDir) {
|
|
42
45
|
let dir = startDir;
|
|
43
46
|
while (true) {
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
// Check installed location
|
|
48
|
+
const candidate1 = path.join(dir, "docs", "practices-and-standards", "context.yaml");
|
|
49
|
+
if (fs.existsSync(candidate1)) return dir;
|
|
50
|
+
|
|
51
|
+
// Check source/development location
|
|
52
|
+
const candidate2 = path.join(dir, "practices-and-standards", "context.yaml");
|
|
53
|
+
if (fs.existsSync(candidate2)) return dir;
|
|
54
|
+
|
|
46
55
|
const parent = path.dirname(dir);
|
|
47
56
|
if (parent === dir) return null;
|
|
48
57
|
dir = parent;
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
function deriveRepoRootFromContextPath(contextPath) {
|
|
62
|
+
const normalized = path.normalize(contextPath);
|
|
63
|
+
|
|
64
|
+
// Check if path ends with docs/practices-and-standards/context.yaml
|
|
65
|
+
const docsSuffix = path.join("docs", "practices-and-standards", "context.yaml");
|
|
66
|
+
if (normalized.endsWith(docsSuffix)) {
|
|
67
|
+
return normalized.substring(0, normalized.length - docsSuffix.length);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check if path ends with practices-and-standards/context.yaml
|
|
71
|
+
const psSuffix = path.join("practices-and-standards", "context.yaml");
|
|
72
|
+
if (normalized.endsWith(psSuffix)) {
|
|
73
|
+
return normalized.substring(0, normalized.length - psSuffix.length);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fallback: go up 3 levels from context.yaml
|
|
77
|
+
// context.yaml -> practices-and-standards -> (repo root)
|
|
78
|
+
let current = path.dirname(contextPath);
|
|
79
|
+
if (path.basename(current) === "practices-and-standards") {
|
|
80
|
+
return path.dirname(current);
|
|
81
|
+
}
|
|
82
|
+
// Or if in docs/practices-and-standards
|
|
83
|
+
if (path.basename(current) === "practices-and-standards" && path.basename(path.dirname(current)) === "docs") {
|
|
84
|
+
return path.dirname(path.dirname(current));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Last resort: assume parent of practices-and-standards or docs
|
|
88
|
+
return path.dirname(path.dirname(contextPath));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function findContextYamlPath(repoRoot) {
|
|
92
|
+
// Try installed location first
|
|
93
|
+
const installedPath = path.join(repoRoot, "docs", "practices-and-standards", "context.yaml");
|
|
94
|
+
if (fs.existsSync(installedPath)) return installedPath;
|
|
95
|
+
|
|
96
|
+
// Try source location
|
|
97
|
+
const sourcePath = path.join(repoRoot, "practices-and-standards", "context.yaml");
|
|
98
|
+
if (fs.existsSync(sourcePath)) return sourcePath;
|
|
99
|
+
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
52
103
|
function readYamlFile(filePath) {
|
|
53
104
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
54
105
|
return YAML.parse(raw);
|
|
@@ -197,9 +248,192 @@ function cmdValidate(repoRoot, ctx) {
|
|
|
197
248
|
process.exit(errors.length > 0 ? 1 : 0);
|
|
198
249
|
}
|
|
199
250
|
|
|
251
|
+
// Helper function to test Stage5Assets dependencies individually
|
|
252
|
+
async function testStage5Dependencies() {
|
|
253
|
+
const dependencies = [
|
|
254
|
+
{ name: "CursorRulesGenerator", path: "./infrastructure/assets/cursor/cursor-rules-generator.js" },
|
|
255
|
+
{ name: "CursorSkillsGenerator", path: "./infrastructure/assets/cursor/cursor-skills-generator.js" },
|
|
256
|
+
{ name: "ClaudeGenerator", path: "./infrastructure/assets/claude/claude-generator.js" },
|
|
257
|
+
{ name: "SkillExtractor", path: "./infrastructure/parsing/skill-extractor.js" },
|
|
258
|
+
{ name: "FileReader", path: "./infrastructure/file-system/file-reader.js" },
|
|
259
|
+
{ name: "IAssetGenerator", path: "./application/ports/asset-generator.js" },
|
|
260
|
+
{ name: "FileWriter", path: "./infrastructure/file-system/file-writer.js" },
|
|
261
|
+
{ name: "MarkdownParser", path: "./infrastructure/parsing/markdown-parser.js" },
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
console.log(" Testing Stage5Assets dependencies...");
|
|
265
|
+
for (const dep of dependencies) {
|
|
266
|
+
try {
|
|
267
|
+
console.log(` Testing dependency: ${dep.name} from ${dep.path}`);
|
|
268
|
+
const module = await import(dep.path);
|
|
269
|
+
const exportNames = Object.keys(module);
|
|
270
|
+
console.log(` ✓ Success - exports: ${exportNames.join(", ")}`);
|
|
271
|
+
if (!exportNames.includes(dep.name)) {
|
|
272
|
+
console.warn(` ⚠ Warning: Expected export '${dep.name}' not found`);
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error(` ✗ FAILED: ${error.message}`);
|
|
276
|
+
console.error(` Error type: ${error.constructor.name}`);
|
|
277
|
+
if (error.code) {
|
|
278
|
+
console.error(` Error code: ${error.code}`);
|
|
279
|
+
}
|
|
280
|
+
if (error.stack) {
|
|
281
|
+
const stackLines = error.stack.split("\n").slice(0, 8);
|
|
282
|
+
console.error(` Stack trace:\n${stackLines.map(l => ` ${l}`).join("\n")}`);
|
|
283
|
+
}
|
|
284
|
+
throw new Error(`Failed to import ${dep.name} from ${dep.path}: ${error.message}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
console.log(" ✓ All Stage5Assets dependencies successful");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Helper function to test imports individually
|
|
291
|
+
async function testImports() {
|
|
292
|
+
const imports = [
|
|
293
|
+
{ name: "Stage1Discovery", path: "./application/compile/stage1-discovery.js" },
|
|
294
|
+
{ name: "Stage2Extraction", path: "./application/compile/stage2-extraction.js" },
|
|
295
|
+
{ name: "Stage4Consolidation", path: "./application/compile/stage4-consolidation.js" },
|
|
296
|
+
{ name: "Stage5Assets", path: "./application/compile/stage5-assets.js", testDependencies: true },
|
|
297
|
+
{ name: "Stage6Validation", path: "./application/compile/stage6-validation.js" },
|
|
298
|
+
{ name: "CompileContext", path: "./application/compile/compile-context.js" },
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
console.log("Testing module imports...");
|
|
302
|
+
for (const imp of imports) {
|
|
303
|
+
try {
|
|
304
|
+
console.log(` Testing: ${imp.name} from ${imp.path}`);
|
|
305
|
+
|
|
306
|
+
// For Stage5Assets, test dependencies first
|
|
307
|
+
if (imp.testDependencies) {
|
|
308
|
+
try {
|
|
309
|
+
await testStage5Dependencies();
|
|
310
|
+
} catch (depError) {
|
|
311
|
+
console.error(` ✗ Dependency test failed before importing ${imp.name}`);
|
|
312
|
+
throw depError;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const module = await import(imp.path);
|
|
317
|
+
const exportNames = Object.keys(module);
|
|
318
|
+
console.log(` ✓ Success - exports: ${exportNames.join(", ")}`);
|
|
319
|
+
if (!exportNames.includes(imp.name)) {
|
|
320
|
+
console.warn(` ⚠ Warning: Expected export '${imp.name}' not found`);
|
|
321
|
+
}
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error(` ✗ FAILED: ${error.message}`);
|
|
324
|
+
console.error(` Error type: ${error.constructor.name}`);
|
|
325
|
+
if (error.code) {
|
|
326
|
+
console.error(` Error code: ${error.code}`);
|
|
327
|
+
}
|
|
328
|
+
if (error.stack) {
|
|
329
|
+
const stackLines = error.stack.split("\n").slice(0, 10);
|
|
330
|
+
console.error(` Stack trace:\n${stackLines.map(l => ` ${l}`).join("\n")}`);
|
|
331
|
+
}
|
|
332
|
+
throw new Error(`Failed to import ${imp.name} from ${imp.path}: ${error.message}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
console.log(" ✓ All imports successful\n");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function cmdCompilePack(repoRoot, packId) {
|
|
339
|
+
// Test all imports first to isolate the failing module
|
|
340
|
+
try {
|
|
341
|
+
await testImports();
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.error("\n❌ Import test failed - this indicates a syntax or module resolution error");
|
|
344
|
+
die(`Import test failed: ${error.message}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Now try to import the main module with detailed error handling
|
|
348
|
+
let CompileContext;
|
|
349
|
+
try {
|
|
350
|
+
console.log("Loading compilation module...");
|
|
351
|
+
const modulePath = "./application/compile/compile-context.js";
|
|
352
|
+
console.log(` Import path: ${modulePath}`);
|
|
353
|
+
console.log(` Current working directory: ${process.cwd()}`);
|
|
354
|
+
|
|
355
|
+
const module = await import(modulePath);
|
|
356
|
+
CompileContext = module.CompileContext;
|
|
357
|
+
|
|
358
|
+
if (!CompileContext) {
|
|
359
|
+
const availableExports = Object.keys(module).join(", ");
|
|
360
|
+
throw new Error(`CompileContext not exported from ${modulePath}. Available exports: ${availableExports || "(none)"}`);
|
|
361
|
+
}
|
|
362
|
+
console.log(" ✓ Module loaded successfully");
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error("\n❌ Failed to load compilation module:");
|
|
365
|
+
console.error(` Error type: ${error.constructor.name}`);
|
|
366
|
+
console.error(` Error message: ${error.message}`);
|
|
367
|
+
if (error.code) {
|
|
368
|
+
console.error(` Error code: ${error.code}`);
|
|
369
|
+
if (error.code === "ERR_MODULE_NOT_FOUND") {
|
|
370
|
+
console.error(` This indicates the module file was not found at the specified path.`);
|
|
371
|
+
console.error(` Check if the file exists and the relative path is correct.`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (error.stack) {
|
|
375
|
+
const stackLines = error.stack.split("\n").slice(0, 15);
|
|
376
|
+
console.error(` Stack trace:\n${stackLines.map(l => ` ${l}`).join("\n")}`);
|
|
377
|
+
}
|
|
378
|
+
die(`Failed to load compilation module: ${error.message}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Try to create compiler instance
|
|
382
|
+
let compiler;
|
|
383
|
+
try {
|
|
384
|
+
compiler = new CompileContext();
|
|
385
|
+
console.log(" ✓ Compiler instance created\n");
|
|
386
|
+
} catch (error) {
|
|
387
|
+
console.error("\n❌ Failed to create compiler instance:");
|
|
388
|
+
console.error(` Error: ${error.message}`);
|
|
389
|
+
if (error.stack) {
|
|
390
|
+
const stackLines = error.stack.split("\n").slice(0, 10);
|
|
391
|
+
console.error(` Stack trace:\n${stackLines.map(l => ` ${l}`).join("\n")}`);
|
|
392
|
+
}
|
|
393
|
+
die(`Failed to create compiler: ${error.message}`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Execute compilation
|
|
397
|
+
try {
|
|
398
|
+
const result = await compiler.compilePack(repoRoot, packId);
|
|
399
|
+
|
|
400
|
+
console.log("\n✅ Compilation successful!");
|
|
401
|
+
console.log(`\nOutput: ${result.outputPath}`);
|
|
402
|
+
console.log(`\nRules by category:`);
|
|
403
|
+
const categories = result.compilation.getCategories();
|
|
404
|
+
for (const category of categories) {
|
|
405
|
+
const rules = result.compilation.getRulesByCategory(category);
|
|
406
|
+
console.log(` ${category}: ${rules.length} rules`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log(`\nTotal: ${result.compilation.rules.length} rules`);
|
|
410
|
+
console.log(` ${result.compilation.preferences.length} preferences`);
|
|
411
|
+
console.log(` ${result.compilation.scopes.length} scopes`);
|
|
412
|
+
|
|
413
|
+
console.log(`\nGenerated files:`);
|
|
414
|
+
console.log(` Consolidated: ${result.stageReports.stage4.filesGenerated.length} files`);
|
|
415
|
+
console.log(` Cursor rules: ${result.stageReports.stage5.results.cursorRules.filesGenerated.length} files`);
|
|
416
|
+
if (result.stageReports.stage5.skillsExtracted > 0) {
|
|
417
|
+
console.log(` Cursor skills: ${result.stageReports.stage5.results.cursorSkills.filesGenerated.length} files`);
|
|
418
|
+
}
|
|
419
|
+
console.log(` Claude.md: 1 file`);
|
|
420
|
+
console.log(` Reports: ${result.stageReports.stage6.reportsGenerated.length} files`);
|
|
421
|
+
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error("\n❌ Compilation execution failed:");
|
|
424
|
+
console.error(` Error: ${error.message}`);
|
|
425
|
+
if (error.stack) {
|
|
426
|
+
const stackLines = error.stack.split("\n").slice(0, 15);
|
|
427
|
+
console.error(` Stack trace:\n${stackLines.map(l => ` ${l}`).join("\n")}`);
|
|
428
|
+
}
|
|
429
|
+
die(error.message);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
200
433
|
function cmdCompile(repoRoot, ctx) {
|
|
201
434
|
const installed = orderInstalledPacks(ctx.installed_packs ?? [], ctx.precedence ?? []);
|
|
202
|
-
const
|
|
435
|
+
const psRoot = path.join(repoRoot, "docs", "practices-and-standards");
|
|
436
|
+
const outDir = path.join(psRoot, ".compiled");
|
|
203
437
|
fs.mkdirSync(outDir, { recursive: true });
|
|
204
438
|
|
|
205
439
|
const promptPath = path.join(outDir, "system-prompt.md");
|
|
@@ -351,7 +585,7 @@ async function fetchJson(url, token) {
|
|
|
351
585
|
}
|
|
352
586
|
|
|
353
587
|
function ensureContextInitialized(repoRoot, registryUrlMaybe) {
|
|
354
|
-
const psRoot = path.join(repoRoot, "practices-and-standards");
|
|
588
|
+
const psRoot = path.join(repoRoot, "docs", "practices-and-standards");
|
|
355
589
|
ensureDir(psRoot);
|
|
356
590
|
|
|
357
591
|
const ctxPath = path.join(psRoot, "context.yaml");
|
|
@@ -395,7 +629,7 @@ function upsertInstalledPack(ctxObj, packId, version) {
|
|
|
395
629
|
if (!Array.isArray(ctxObj.installed_packs)) ctxObj.installed_packs = [];
|
|
396
630
|
if (!Array.isArray(ctxObj.precedence)) ctxObj.precedence = [];
|
|
397
631
|
|
|
398
|
-
const manifest = `practices-and-standards/packs/${packId}/pack.yaml`;
|
|
632
|
+
const manifest = `docs/practices-and-standards/packs/${packId}/pack.yaml`;
|
|
399
633
|
const existing = ctxObj.installed_packs.find(p => p.id === packId);
|
|
400
634
|
|
|
401
635
|
if (existing) {
|
|
@@ -436,7 +670,7 @@ async function cmdPackInstall(repoRoot, packId, opts) {
|
|
|
436
670
|
if (!token) die("Missing required --token for registry install.");
|
|
437
671
|
|
|
438
672
|
const registry = opts.registry || ctx.registry;
|
|
439
|
-
if (!registry) die("No registry configured. Provide --registry <url> or set registry: in practices-and-standards/context.yaml");
|
|
673
|
+
if (!registry) die("No registry configured. Provide --registry <url> or set registry: in docs/practices-and-standards/context.yaml");
|
|
440
674
|
|
|
441
675
|
if (!version) {
|
|
442
676
|
const latestUrl = `${registry.replace(/\/$/, "")}/packs/${encodeURIComponent(packId)}/latest`;
|
|
@@ -538,7 +772,7 @@ async function main() {
|
|
|
538
772
|
|
|
539
773
|
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
540
774
|
// Make sure package.json has version (or hardcode a constant)
|
|
541
|
-
console.log("0.1.
|
|
775
|
+
console.log("0.1.4");
|
|
542
776
|
return;
|
|
543
777
|
}
|
|
544
778
|
|
|
@@ -564,10 +798,35 @@ async function main() {
|
|
|
564
798
|
}
|
|
565
799
|
|
|
566
800
|
// Existing behavior: these require an existing context.yaml
|
|
567
|
-
const
|
|
568
|
-
|
|
801
|
+
const contextYamlIndex = process.argv.indexOf("--context-yaml");
|
|
802
|
+
|
|
803
|
+
let repoRoot;
|
|
804
|
+
let contextPath;
|
|
805
|
+
|
|
806
|
+
if (contextYamlIndex !== -1 && process.argv[contextYamlIndex + 1]) {
|
|
807
|
+
// Explicit context.yaml path provided
|
|
808
|
+
contextPath = path.isAbsolute(process.argv[contextYamlIndex + 1])
|
|
809
|
+
? process.argv[contextYamlIndex + 1]
|
|
810
|
+
: path.join(process.cwd(), process.argv[contextYamlIndex + 1]);
|
|
811
|
+
|
|
812
|
+
if (!fs.existsSync(contextPath)) {
|
|
813
|
+
die(`context.yaml not found at ${contextPath}`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
repoRoot = deriveRepoRootFromContextPath(contextPath);
|
|
817
|
+
} else {
|
|
818
|
+
// Auto-detect
|
|
819
|
+
repoRoot = findRepoContextRoot(process.cwd());
|
|
820
|
+
if (!repoRoot) {
|
|
821
|
+
die("Could not find context.yaml. Use --context-yaml to specify the path.");
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
contextPath = findContextYamlPath(repoRoot);
|
|
825
|
+
if (!contextPath) {
|
|
826
|
+
die("Could not find context.yaml in expected locations.");
|
|
827
|
+
}
|
|
828
|
+
}
|
|
569
829
|
|
|
570
|
-
const contextPath = path.join(repoRoot, "practices-and-standards", "context.yaml");
|
|
571
830
|
const ctx = readYamlFile(contextPath);
|
|
572
831
|
|
|
573
832
|
if (!ctx?.schema || ctx.schema !== "context-install/v1") {
|
|
@@ -576,7 +835,13 @@ async function main() {
|
|
|
576
835
|
|
|
577
836
|
switch (cmd) {
|
|
578
837
|
case "list": return cmdList(repoRoot, ctx);
|
|
579
|
-
case "compile":
|
|
838
|
+
case "compile":
|
|
839
|
+
// Check for --pack flag
|
|
840
|
+
const packIndex = process.argv.indexOf("--pack");
|
|
841
|
+
if (packIndex !== -1 && process.argv[packIndex + 1]) {
|
|
842
|
+
return cmdCompilePack(repoRoot, process.argv[packIndex + 1]);
|
|
843
|
+
}
|
|
844
|
+
return cmdCompile(repoRoot, ctx);
|
|
580
845
|
case "validate": return cmdValidate(repoRoot, ctx);
|
|
581
846
|
default:
|
|
582
847
|
console.log(`Unknown command: ${cmd}\n`);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Rule } from "./rule.js";
|
|
2
|
+
import { Preference } from "./preference.js";
|
|
3
|
+
import { Scope } from "./scope.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Compilation aggregate root
|
|
7
|
+
* Orchestrates the compilation process and holds all extracted data
|
|
8
|
+
*/
|
|
9
|
+
export class Compilation {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.rules = [];
|
|
12
|
+
this.preferences = [];
|
|
13
|
+
this.scopes = [];
|
|
14
|
+
this.sourceFiles = [];
|
|
15
|
+
this.packs = [];
|
|
16
|
+
this.metadata = {
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
version: "1.0.0"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Add a rule to the compilation
|
|
24
|
+
* @param {Rule} rule - Rule to add
|
|
25
|
+
*/
|
|
26
|
+
addRule(rule) {
|
|
27
|
+
// Check for duplicate IDs
|
|
28
|
+
const existing = this.rules.find(r => r.id === rule.id);
|
|
29
|
+
if (existing) {
|
|
30
|
+
throw new Error(`Duplicate rule ID: ${rule.id} in ${rule.source.file} (already exists from ${existing.source.file})`);
|
|
31
|
+
}
|
|
32
|
+
this.rules.push(rule);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add a preference to the compilation
|
|
37
|
+
* @param {Preference} preference - Preference to add
|
|
38
|
+
*/
|
|
39
|
+
addPreference(preference) {
|
|
40
|
+
this.preferences.push(preference);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Add a scope to the compilation
|
|
45
|
+
* @param {Scope} scope - Scope to add
|
|
46
|
+
*/
|
|
47
|
+
addScope(scope) {
|
|
48
|
+
this.scopes.push(scope);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get rules by category
|
|
53
|
+
* @param {string} category - Category to filter by
|
|
54
|
+
* @returns {Rule[]} Rules in that category
|
|
55
|
+
*/
|
|
56
|
+
getRulesByCategory(category) {
|
|
57
|
+
return this.rules.filter(r => r.category === category);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get all categories
|
|
62
|
+
* @returns {string[]} Unique categories
|
|
63
|
+
*/
|
|
64
|
+
getCategories() {
|
|
65
|
+
return [...new Set(this.rules.map(r => r.category))].sort();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get rules by level
|
|
70
|
+
* @param {string} level - Level to filter by
|
|
71
|
+
* @returns {Rule[]} Rules with that level
|
|
72
|
+
*/
|
|
73
|
+
getRulesByLevel(level) {
|
|
74
|
+
return this.rules.filter(r => r.level === level);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preference value object
|
|
3
|
+
* Represents a preference extracted from documentation
|
|
4
|
+
*/
|
|
5
|
+
export class Preference {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Object} data
|
|
8
|
+
* @param {string} data.name - Preference name
|
|
9
|
+
* @param {string} [data.description] - Preference description
|
|
10
|
+
* @param {Object} data.source - Source information
|
|
11
|
+
* @param {string} data.source.pack - Pack ID
|
|
12
|
+
* @param {string} data.source.packVersion - Pack version
|
|
13
|
+
* @param {string} data.source.file - Source file path
|
|
14
|
+
* @param {number} data.source.line - Line number
|
|
15
|
+
* @param {number} data.source.precedence - Precedence index
|
|
16
|
+
*/
|
|
17
|
+
constructor(data) {
|
|
18
|
+
this.name = data.name;
|
|
19
|
+
this.description = data.description;
|
|
20
|
+
this.source = data.source;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|