@zebralabs/context-cli 0.1.3 → 0.1.5
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 +472 -0
- 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 +274 -10
- 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,125 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { FileReader } from "../../infrastructure/file-system/file-reader.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Stage 1: Discovery & Collection
|
|
8
|
+
* Gathers all sources of knowledge (packs + custom docs)
|
|
9
|
+
*/
|
|
10
|
+
export class Stage1Discovery {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.fileReader = new FileReader();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discover and collect source files
|
|
17
|
+
* @param {string} repoRoot - Repository root directory
|
|
18
|
+
* @param {Object} contextYaml - Parsed context.yaml
|
|
19
|
+
* @returns {Object} Discovery result with source files and metadata
|
|
20
|
+
*/
|
|
21
|
+
execute(repoRoot, contextYaml) {
|
|
22
|
+
const sourceFiles = [];
|
|
23
|
+
const packs = [];
|
|
24
|
+
const precedence = contextYaml.precedence || [];
|
|
25
|
+
|
|
26
|
+
// Order packs by precedence
|
|
27
|
+
const installedPacks = this.orderPacksByPrecedence(
|
|
28
|
+
contextYaml.installed_packs || [],
|
|
29
|
+
precedence
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Process each pack
|
|
33
|
+
for (let i = 0; i < installedPacks.length; i++) {
|
|
34
|
+
const pack = installedPacks[i];
|
|
35
|
+
const precedenceIndex = precedence.indexOf(pack.id);
|
|
36
|
+
const finalPrecedence = precedenceIndex !== -1 ? precedenceIndex : 1000 + i;
|
|
37
|
+
|
|
38
|
+
// Load pack manifest
|
|
39
|
+
const manifestPath = path.join(repoRoot, pack.manifest);
|
|
40
|
+
if (!this.fileReader.exists(manifestPath)) {
|
|
41
|
+
console.warn(`Pack manifest not found: ${manifestPath}`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Parse pack.yaml to get contributes.roots
|
|
46
|
+
const packManifest = YAML.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
47
|
+
const packId = pack.id;
|
|
48
|
+
const packVersion = pack.version;
|
|
49
|
+
const roots = packManifest?.contributes?.roots || [];
|
|
50
|
+
|
|
51
|
+
packs.push({
|
|
52
|
+
id: packId,
|
|
53
|
+
version: packVersion,
|
|
54
|
+
precedence: finalPrecedence
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Collect markdown files from each root
|
|
58
|
+
for (const root of roots) {
|
|
59
|
+
// Try installed location first (docs/practices-and-standards/...)
|
|
60
|
+
let rootPath = path.join(repoRoot, root);
|
|
61
|
+
|
|
62
|
+
// If not found and root starts with "docs/", try source location
|
|
63
|
+
if (!this.fileReader.exists(rootPath) && root.startsWith("docs/")) {
|
|
64
|
+
const sourceRoot = root.replace(/^docs\//, "");
|
|
65
|
+
rootPath = path.join(repoRoot, sourceRoot);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!this.fileReader.exists(rootPath)) {
|
|
69
|
+
console.warn(`Pack ${packId} root not found: ${root} (tried: ${path.join(repoRoot, root)} and ${rootPath})`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const markdownFiles = this.fileReader.listMarkdownFiles(rootPath);
|
|
74
|
+
|
|
75
|
+
for (const filePath of markdownFiles) {
|
|
76
|
+
const relativePath = path.relative(repoRoot, filePath).replace(/\\/g, "/");
|
|
77
|
+
|
|
78
|
+
sourceFiles.push({
|
|
79
|
+
path: relativePath,
|
|
80
|
+
absolutePath: filePath,
|
|
81
|
+
packId: packId,
|
|
82
|
+
packVersion: packVersion,
|
|
83
|
+
precedence: finalPrecedence,
|
|
84
|
+
isCustom: false
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
sourceFiles,
|
|
92
|
+
packs,
|
|
93
|
+
precedenceOrder: precedence
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Order packs by precedence
|
|
99
|
+
* @param {Object[]} installedPacks - Installed packs
|
|
100
|
+
* @param {string[]} precedence - Precedence order
|
|
101
|
+
* @returns {Object[]} Ordered packs
|
|
102
|
+
*/
|
|
103
|
+
orderPacksByPrecedence(installedPacks, precedence) {
|
|
104
|
+
const ordered = [];
|
|
105
|
+
const unordered = [];
|
|
106
|
+
|
|
107
|
+
// Add packs in precedence order
|
|
108
|
+
for (const packId of precedence) {
|
|
109
|
+
const pack = installedPacks.find(p => p.id === packId);
|
|
110
|
+
if (pack) {
|
|
111
|
+
ordered.push(pack);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add remaining packs
|
|
116
|
+
for (const pack of installedPacks) {
|
|
117
|
+
if (!precedence.includes(pack.id)) {
|
|
118
|
+
unordered.push(pack);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return [...ordered, ...unordered];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { FileReader } from "../../infrastructure/file-system/file-reader.js";
|
|
2
|
+
import { RuleExtractor } from "../../infrastructure/parsing/rule-extractor.js";
|
|
3
|
+
import { Compilation } from "../../domain/compilation.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Stage 2: Rule Extraction & Parsing
|
|
7
|
+
* Extracts structured rules from markdown documents
|
|
8
|
+
*/
|
|
9
|
+
export class Stage2Extraction {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.fileReader = new FileReader();
|
|
12
|
+
this.ruleExtractor = new RuleExtractor();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract rules from source files
|
|
17
|
+
* @param {Object[]} sourceFiles - Array of source file metadata
|
|
18
|
+
* @returns {Object} Extraction result with compilation and report
|
|
19
|
+
*/
|
|
20
|
+
execute(sourceFiles) {
|
|
21
|
+
const compilation = new Compilation();
|
|
22
|
+
const report = {
|
|
23
|
+
filesProcessed: 0,
|
|
24
|
+
rulesExtracted: 0,
|
|
25
|
+
preferencesExtracted: 0,
|
|
26
|
+
scopesExtracted: 0,
|
|
27
|
+
errors: [],
|
|
28
|
+
warnings: []
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
for (const sourceFile of sourceFiles) {
|
|
32
|
+
try {
|
|
33
|
+
report.filesProcessed++;
|
|
34
|
+
|
|
35
|
+
const content = this.fileReader.readFile(sourceFile.absolutePath);
|
|
36
|
+
|
|
37
|
+
// Extract rules
|
|
38
|
+
const rules = this.ruleExtractor.extractRules(content, {
|
|
39
|
+
pack: sourceFile.packId,
|
|
40
|
+
packVersion: sourceFile.packVersion,
|
|
41
|
+
file: sourceFile.path,
|
|
42
|
+
precedence: sourceFile.precedence
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
for (const rule of rules) {
|
|
46
|
+
try {
|
|
47
|
+
compilation.addRule(rule);
|
|
48
|
+
report.rulesExtracted++;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
report.errors.push({
|
|
51
|
+
file: sourceFile.path,
|
|
52
|
+
error: error.message
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Extract preferences
|
|
58
|
+
const preferences = this.ruleExtractor.extractPreferences(content, {
|
|
59
|
+
pack: sourceFile.packId,
|
|
60
|
+
packVersion: sourceFile.packVersion,
|
|
61
|
+
file: sourceFile.path,
|
|
62
|
+
precedence: sourceFile.precedence
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
for (const preference of preferences) {
|
|
66
|
+
compilation.addPreference(preference);
|
|
67
|
+
report.preferencesExtracted++;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Extract scope
|
|
71
|
+
const scope = this.ruleExtractor.extractScope(content, {
|
|
72
|
+
pack: sourceFile.packId,
|
|
73
|
+
packVersion: sourceFile.packVersion,
|
|
74
|
+
file: sourceFile.path,
|
|
75
|
+
precedence: sourceFile.precedence
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (scope) {
|
|
79
|
+
compilation.addScope(scope);
|
|
80
|
+
report.scopesExtracted++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
report.errors.push({
|
|
85
|
+
file: sourceFile.path,
|
|
86
|
+
error: error.message
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
compilation,
|
|
93
|
+
report
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { FileWriter } from "../../infrastructure/file-system/file-writer.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stage 4: Consolidation & Standard Generation
|
|
6
|
+
* Creates a single, coherent, human-readable standard document
|
|
7
|
+
*/
|
|
8
|
+
export class Stage4Consolidation {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.fileWriter = new FileWriter();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate consolidated outputs
|
|
15
|
+
* @param {Compilation} compilation - The compilation with extracted rules
|
|
16
|
+
* @param {string} outputDir - Output directory
|
|
17
|
+
* @returns {Object} Consolidation result
|
|
18
|
+
*/
|
|
19
|
+
execute(compilation, outputDir) {
|
|
20
|
+
const consolidatedDir = outputDir;
|
|
21
|
+
|
|
22
|
+
// Generate consolidated standard
|
|
23
|
+
const consolidatedStandard = this.generateConsolidatedStandard(compilation);
|
|
24
|
+
this.fileWriter.writeFile(
|
|
25
|
+
path.join(consolidatedDir, "CONSOLIDATED-STANDARDS.md"),
|
|
26
|
+
consolidatedStandard
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Generate rule index
|
|
30
|
+
const rulesIndex = this.generateRulesIndex(compilation);
|
|
31
|
+
this.fileWriter.writeFile(
|
|
32
|
+
path.join(consolidatedDir, "RULES-INDEX.md"),
|
|
33
|
+
rulesIndex
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// Generate rules by scope
|
|
37
|
+
const rulesByScope = this.generateRulesByScope(compilation);
|
|
38
|
+
this.fileWriter.writeFile(
|
|
39
|
+
path.join(consolidatedDir, "RULES-BY-SCOPE.md"),
|
|
40
|
+
rulesByScope
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Generate JSON outputs
|
|
44
|
+
this.fileWriter.writeJson(
|
|
45
|
+
path.join(consolidatedDir, "rules.json"),
|
|
46
|
+
compilation.rules.map(r => ({
|
|
47
|
+
id: r.id,
|
|
48
|
+
level: r.level,
|
|
49
|
+
category: r.category,
|
|
50
|
+
appliesTo: r.appliesTo,
|
|
51
|
+
rule: r.rule,
|
|
52
|
+
rationale: r.rationale,
|
|
53
|
+
source: r.source
|
|
54
|
+
}))
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
this.fileWriter.writeJson(
|
|
58
|
+
path.join(consolidatedDir, "preferences.json"),
|
|
59
|
+
compilation.preferences.map(p => ({
|
|
60
|
+
name: p.name,
|
|
61
|
+
description: p.description,
|
|
62
|
+
source: p.source
|
|
63
|
+
}))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
this.fileWriter.writeJson(
|
|
67
|
+
path.join(consolidatedDir, "metadata.json"),
|
|
68
|
+
{
|
|
69
|
+
...compilation.metadata,
|
|
70
|
+
rulesCount: compilation.rules.length,
|
|
71
|
+
preferencesCount: compilation.preferences.length,
|
|
72
|
+
scopesCount: compilation.scopes.length,
|
|
73
|
+
categories: compilation.getCategories()
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
outputDir: consolidatedDir,
|
|
79
|
+
filesGenerated: [
|
|
80
|
+
"CONSOLIDATED-STANDARDS.md",
|
|
81
|
+
"RULES-INDEX.md",
|
|
82
|
+
"RULES-BY-SCOPE.md",
|
|
83
|
+
"rules.json",
|
|
84
|
+
"preferences.json",
|
|
85
|
+
"metadata.json"
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate consolidated standard markdown
|
|
92
|
+
* @param {Compilation} compilation - Compilation object
|
|
93
|
+
* @returns {string} Markdown content
|
|
94
|
+
*/
|
|
95
|
+
generateConsolidatedStandard(compilation) {
|
|
96
|
+
const lines = [];
|
|
97
|
+
lines.push("# Consolidated Standards");
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push(`Generated: ${compilation.metadata.timestamp}`);
|
|
100
|
+
lines.push(`Total Rules: ${compilation.rules.length}`);
|
|
101
|
+
lines.push(`Total Preferences: ${compilation.preferences.length}`);
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push("---");
|
|
104
|
+
lines.push("");
|
|
105
|
+
|
|
106
|
+
// Group by category
|
|
107
|
+
const categories = compilation.getCategories();
|
|
108
|
+
for (const category of categories) {
|
|
109
|
+
lines.push(`## ${category}`);
|
|
110
|
+
lines.push("");
|
|
111
|
+
|
|
112
|
+
const rules = compilation.getRulesByCategory(category);
|
|
113
|
+
// Sort by level, then by ID
|
|
114
|
+
const levelOrder = { must: 0, should: 1, prefer: 2, avoid: 3 };
|
|
115
|
+
rules.sort((a, b) => {
|
|
116
|
+
const levelDiff = levelOrder[a.level] - levelOrder[b.level];
|
|
117
|
+
if (levelDiff !== 0) return levelDiff;
|
|
118
|
+
return a.id.localeCompare(b.id);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
for (const rule of rules) {
|
|
122
|
+
lines.push(`### ${rule.id}: ${this.extractTitle(rule.rule)}`);
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push(`- **Level:** ${rule.level}`);
|
|
125
|
+
lines.push(`- **Applies to:** ${rule.appliesTo.join(", ")}`);
|
|
126
|
+
lines.push(`- **Rule:** ${rule.rule}`);
|
|
127
|
+
if (rule.rationale) {
|
|
128
|
+
lines.push(`- **Rationale:** ${rule.rationale}`);
|
|
129
|
+
}
|
|
130
|
+
lines.push(`- **Source:** [${rule.source.pack}@${rule.source.packVersion}](${rule.source.file})`);
|
|
131
|
+
lines.push("");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add preferences section
|
|
136
|
+
if (compilation.preferences.length > 0) {
|
|
137
|
+
lines.push("---");
|
|
138
|
+
lines.push("");
|
|
139
|
+
lines.push("## Preferences");
|
|
140
|
+
lines.push("");
|
|
141
|
+
|
|
142
|
+
for (const pref of compilation.preferences) {
|
|
143
|
+
lines.push(`- **${pref.name}**`);
|
|
144
|
+
if (pref.description) {
|
|
145
|
+
lines.push(` - ${pref.description}`);
|
|
146
|
+
}
|
|
147
|
+
lines.push(` - Source: [${pref.source.pack}@${pref.source.packVersion}](${pref.source.file})`);
|
|
148
|
+
lines.push("");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate rules index
|
|
157
|
+
* @param {Compilation} compilation - Compilation object
|
|
158
|
+
* @returns {string} Markdown content
|
|
159
|
+
*/
|
|
160
|
+
generateRulesIndex(compilation) {
|
|
161
|
+
const lines = [];
|
|
162
|
+
lines.push("# Rules Index");
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push("Quick reference of all rules.");
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push("| ID | Level | Category | Applies To |");
|
|
167
|
+
lines.push("|----|-------|----------|------------|");
|
|
168
|
+
|
|
169
|
+
const allRules = [...compilation.rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
170
|
+
for (const rule of allRules) {
|
|
171
|
+
lines.push(`| ${rule.id} | ${rule.level} | ${rule.category} | ${rule.appliesTo.join(", ")} |`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return lines.join("\n");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generate rules by scope
|
|
179
|
+
* @param {Compilation} compilation - Compilation object
|
|
180
|
+
* @returns {string} Markdown content
|
|
181
|
+
*/
|
|
182
|
+
generateRulesByScope(compilation) {
|
|
183
|
+
const lines = [];
|
|
184
|
+
lines.push("# Rules by Scope");
|
|
185
|
+
lines.push("");
|
|
186
|
+
lines.push("Rules organized by what they apply to.");
|
|
187
|
+
lines.push("");
|
|
188
|
+
|
|
189
|
+
// Collect all unique scopes
|
|
190
|
+
const scopes = new Set();
|
|
191
|
+
for (const rule of compilation.rules) {
|
|
192
|
+
if (rule.appliesTo.includes("all")) {
|
|
193
|
+
scopes.add("all");
|
|
194
|
+
} else {
|
|
195
|
+
rule.appliesTo.forEach(s => scopes.add(s));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const sortedScopes = Array.from(scopes).sort();
|
|
200
|
+
|
|
201
|
+
for (const scope of sortedScopes) {
|
|
202
|
+
lines.push(`## ${scope}`);
|
|
203
|
+
lines.push("");
|
|
204
|
+
|
|
205
|
+
const applicableRules = compilation.rules.filter(r => r.appliesToScope(scope));
|
|
206
|
+
const levelOrder = { must: 0, should: 1, prefer: 2, avoid: 3 };
|
|
207
|
+
applicableRules.sort((a, b) => {
|
|
208
|
+
const levelDiff = levelOrder[a.level] - levelOrder[b.level];
|
|
209
|
+
if (levelDiff !== 0) return levelDiff;
|
|
210
|
+
return a.id.localeCompare(b.id);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
for (const rule of applicableRules) {
|
|
214
|
+
lines.push(`- **${rule.id}** (${rule.level}): ${this.extractTitle(rule.rule)}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
lines.push("");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return lines.join("\n");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Extract a short title from a rule statement
|
|
225
|
+
* @param {string} rule - Rule statement
|
|
226
|
+
* @returns {string} Short title
|
|
227
|
+
*/
|
|
228
|
+
extractTitle(rule) {
|
|
229
|
+
// Take first sentence or first 80 characters
|
|
230
|
+
const firstSentence = rule.split(/[.!?]/)[0];
|
|
231
|
+
if (firstSentence.length <= 80) return firstSentence;
|
|
232
|
+
return rule.substring(0, 77) + "...";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CursorRulesGenerator } from "../../infrastructure/assets/cursor/cursor-rules-generator.js";
|
|
2
|
+
import { CursorSkillsGenerator } from "../../infrastructure/assets/cursor/cursor-skills-generator.js";
|
|
3
|
+
import { ClaudeGenerator } from "../../infrastructure/assets/claude/claude-generator.js";
|
|
4
|
+
import { SkillExtractor } from "../../infrastructure/parsing/skill-extractor.js";
|
|
5
|
+
import { FileReader } from "../../infrastructure/file-system/file-reader.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stage 5: Integration Asset Generation
|
|
9
|
+
* Generates tool-specific assets from consolidated standard
|
|
10
|
+
*/
|
|
11
|
+
export class Stage5Assets {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.cursorRulesGenerator = new CursorRulesGenerator();
|
|
14
|
+
this.cursorSkillsGenerator = new CursorSkillsGenerator();
|
|
15
|
+
this.claudeGenerator = new ClaudeGenerator();
|
|
16
|
+
this.skillExtractor = new SkillExtractor();
|
|
17
|
+
this.fileReader = new FileReader();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate integration assets
|
|
22
|
+
* @param {Compilation} compilation - The compilation with extracted rules
|
|
23
|
+
* @param {Object[]} sourceFiles - Source files (for skill extraction)
|
|
24
|
+
* @param {string} outputPath - Output directory
|
|
25
|
+
* @returns {Object} Asset generation result
|
|
26
|
+
*/
|
|
27
|
+
async execute(compilation, sourceFiles, outputPath) {
|
|
28
|
+
const results = {};
|
|
29
|
+
|
|
30
|
+
// Extract skills from source files
|
|
31
|
+
const skills = [];
|
|
32
|
+
for (const sourceFile of sourceFiles) {
|
|
33
|
+
const content = this.fileReader.readFile(sourceFile.absolutePath);
|
|
34
|
+
const extractedSkills = this.skillExtractor.extractSkills(content, {
|
|
35
|
+
pack: sourceFile.packId,
|
|
36
|
+
packVersion: sourceFile.packVersion,
|
|
37
|
+
file: sourceFile.path
|
|
38
|
+
});
|
|
39
|
+
skills.push(...extractedSkills);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Generate Cursor rules
|
|
43
|
+
results.cursorRules = await this.cursorRulesGenerator.generate(compilation, outputPath);
|
|
44
|
+
|
|
45
|
+
// Generate Cursor skills
|
|
46
|
+
if (skills.length > 0) {
|
|
47
|
+
results.cursorSkills = await this.cursorSkillsGenerator.generate(compilation, outputPath, skills);
|
|
48
|
+
} else {
|
|
49
|
+
results.cursorSkills = { filesGenerated: [], outputDir: null };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Generate Claude.md
|
|
53
|
+
results.claude = await this.claudeGenerator.generate(compilation, outputPath);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
results,
|
|
57
|
+
skillsExtracted: skills.length
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
@@ -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
|
+
|