@mmnto/totem 0.17.0 → 0.19.0
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/dist/compiler.d.ts +158 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +213 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compiler.test.d.ts +2 -0
- package/dist/compiler.test.d.ts.map +1 -0
- package/dist/compiler.test.js +350 -0
- package/dist/compiler.test.js.map +1 -0
- package/dist/config-schema.d.ts +194 -36
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config-schema.js +41 -10
- package/dist/config-schema.js.map +1 -1
- package/dist/config-schema.test.js +131 -1
- package/dist/config-schema.test.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const CompiledRuleSchema: z.ZodObject<{
|
|
3
|
+
/** SHA-256 hash (first 16 hex chars) of heading + body — detects edits */
|
|
4
|
+
lessonHash: z.ZodString;
|
|
5
|
+
/** Human-readable heading from the lesson (for diagnostics) */
|
|
6
|
+
lessonHeading: z.ZodString;
|
|
7
|
+
/** Regex pattern to match against added diff lines */
|
|
8
|
+
pattern: z.ZodString;
|
|
9
|
+
/** Human-readable violation message shown when the pattern matches */
|
|
10
|
+
message: z.ZodString;
|
|
11
|
+
/** Engine type — only 'regex' for MVP */
|
|
12
|
+
engine: z.ZodLiteral<"regex">;
|
|
13
|
+
/** ISO timestamp of when this rule was compiled */
|
|
14
|
+
compiledAt: z.ZodString;
|
|
15
|
+
/** Optional file glob patterns — rule only applies to matching files (e.g., ["*.sh", "*.yml"]) */
|
|
16
|
+
fileGlobs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
lessonHash: string;
|
|
19
|
+
lessonHeading: string;
|
|
20
|
+
pattern: string;
|
|
21
|
+
message: string;
|
|
22
|
+
engine: "regex";
|
|
23
|
+
compiledAt: string;
|
|
24
|
+
fileGlobs?: string[] | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
lessonHash: string;
|
|
27
|
+
lessonHeading: string;
|
|
28
|
+
pattern: string;
|
|
29
|
+
message: string;
|
|
30
|
+
engine: "regex";
|
|
31
|
+
compiledAt: string;
|
|
32
|
+
fileGlobs?: string[] | undefined;
|
|
33
|
+
}>;
|
|
34
|
+
export type CompiledRule = z.infer<typeof CompiledRuleSchema>;
|
|
35
|
+
export declare const CompiledRulesFileSchema: z.ZodObject<{
|
|
36
|
+
version: z.ZodLiteral<1>;
|
|
37
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
38
|
+
/** SHA-256 hash (first 16 hex chars) of heading + body — detects edits */
|
|
39
|
+
lessonHash: z.ZodString;
|
|
40
|
+
/** Human-readable heading from the lesson (for diagnostics) */
|
|
41
|
+
lessonHeading: z.ZodString;
|
|
42
|
+
/** Regex pattern to match against added diff lines */
|
|
43
|
+
pattern: z.ZodString;
|
|
44
|
+
/** Human-readable violation message shown when the pattern matches */
|
|
45
|
+
message: z.ZodString;
|
|
46
|
+
/** Engine type — only 'regex' for MVP */
|
|
47
|
+
engine: z.ZodLiteral<"regex">;
|
|
48
|
+
/** ISO timestamp of when this rule was compiled */
|
|
49
|
+
compiledAt: z.ZodString;
|
|
50
|
+
/** Optional file glob patterns — rule only applies to matching files (e.g., ["*.sh", "*.yml"]) */
|
|
51
|
+
fileGlobs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
lessonHash: string;
|
|
54
|
+
lessonHeading: string;
|
|
55
|
+
pattern: string;
|
|
56
|
+
message: string;
|
|
57
|
+
engine: "regex";
|
|
58
|
+
compiledAt: string;
|
|
59
|
+
fileGlobs?: string[] | undefined;
|
|
60
|
+
}, {
|
|
61
|
+
lessonHash: string;
|
|
62
|
+
lessonHeading: string;
|
|
63
|
+
pattern: string;
|
|
64
|
+
message: string;
|
|
65
|
+
engine: "regex";
|
|
66
|
+
compiledAt: string;
|
|
67
|
+
fileGlobs?: string[] | undefined;
|
|
68
|
+
}>, "many">;
|
|
69
|
+
}, "strip", z.ZodTypeAny, {
|
|
70
|
+
version: 1;
|
|
71
|
+
rules: {
|
|
72
|
+
lessonHash: string;
|
|
73
|
+
lessonHeading: string;
|
|
74
|
+
pattern: string;
|
|
75
|
+
message: string;
|
|
76
|
+
engine: "regex";
|
|
77
|
+
compiledAt: string;
|
|
78
|
+
fileGlobs?: string[] | undefined;
|
|
79
|
+
}[];
|
|
80
|
+
}, {
|
|
81
|
+
version: 1;
|
|
82
|
+
rules: {
|
|
83
|
+
lessonHash: string;
|
|
84
|
+
lessonHeading: string;
|
|
85
|
+
pattern: string;
|
|
86
|
+
message: string;
|
|
87
|
+
engine: "regex";
|
|
88
|
+
compiledAt: string;
|
|
89
|
+
fileGlobs?: string[] | undefined;
|
|
90
|
+
}[];
|
|
91
|
+
}>;
|
|
92
|
+
export type CompiledRulesFile = z.infer<typeof CompiledRulesFileSchema>;
|
|
93
|
+
export interface Violation {
|
|
94
|
+
/** The rule that was violated */
|
|
95
|
+
rule: CompiledRule;
|
|
96
|
+
/** The file path from the diff where the violation occurred */
|
|
97
|
+
file: string;
|
|
98
|
+
/** The matching line content */
|
|
99
|
+
line: string;
|
|
100
|
+
/** 1-based line number within the diff hunk (approximate) */
|
|
101
|
+
lineNumber: number;
|
|
102
|
+
}
|
|
103
|
+
/** Hash a lesson's heading + body to detect changes since compilation. */
|
|
104
|
+
export declare function hashLesson(heading: string, body: string): string;
|
|
105
|
+
export interface RegexValidation {
|
|
106
|
+
valid: boolean;
|
|
107
|
+
reason?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate that a pattern string is a syntactically valid RegExp
|
|
111
|
+
* and is not vulnerable to ReDoS (catastrophic backtracking).
|
|
112
|
+
*/
|
|
113
|
+
export declare function validateRegex(pattern: string): RegexValidation;
|
|
114
|
+
interface DiffAddition {
|
|
115
|
+
file: string;
|
|
116
|
+
line: string;
|
|
117
|
+
lineNumber: number;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Extract added lines from a unified diff.
|
|
121
|
+
* Returns only lines that start with `+` (excluding `+++` file headers).
|
|
122
|
+
*/
|
|
123
|
+
export declare function extractAddedLines(diff: string): DiffAddition[];
|
|
124
|
+
/**
|
|
125
|
+
* Apply compiled rules against added lines from a diff.
|
|
126
|
+
* Returns all violations found.
|
|
127
|
+
* @param excludeFiles — file paths to skip (e.g., compiled-rules.json to avoid self-matches)
|
|
128
|
+
*/
|
|
129
|
+
export declare function applyRules(rules: CompiledRule[], diff: string, excludeFiles?: string[]): Violation[];
|
|
130
|
+
/** Load compiled rules from a JSON file. Returns empty array if file missing or invalid. */
|
|
131
|
+
export declare function loadCompiledRules(rulesPath: string): CompiledRule[];
|
|
132
|
+
/** Save compiled rules to a JSON file. */
|
|
133
|
+
export declare function saveCompiledRules(rulesPath: string, rules: CompiledRule[]): void;
|
|
134
|
+
/** Schema for the structured JSON the LLM returns when compiling a lesson. */
|
|
135
|
+
export declare const CompilerOutputSchema: z.ZodObject<{
|
|
136
|
+
compilable: z.ZodBoolean;
|
|
137
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
138
|
+
message: z.ZodOptional<z.ZodString>;
|
|
139
|
+
fileGlobs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
140
|
+
}, "strip", z.ZodTypeAny, {
|
|
141
|
+
compilable: boolean;
|
|
142
|
+
pattern?: string | undefined;
|
|
143
|
+
message?: string | undefined;
|
|
144
|
+
fileGlobs?: string[] | undefined;
|
|
145
|
+
}, {
|
|
146
|
+
compilable: boolean;
|
|
147
|
+
pattern?: string | undefined;
|
|
148
|
+
message?: string | undefined;
|
|
149
|
+
fileGlobs?: string[] | undefined;
|
|
150
|
+
}>;
|
|
151
|
+
export type CompilerOutput = z.infer<typeof CompilerOutputSchema>;
|
|
152
|
+
/**
|
|
153
|
+
* Parse the LLM's compilation response. Extracts JSON from the response text,
|
|
154
|
+
* validates it, and returns the structured output or null if unparseable.
|
|
155
|
+
*/
|
|
156
|
+
export declare function parseCompilerResponse(response: string): CompilerOutput | null;
|
|
157
|
+
export {};
|
|
158
|
+
//# sourceMappingURL=compiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,kBAAkB;IAC7B,0EAA0E;;IAE1E,+DAA+D;;IAE/D,sDAAsD;;IAEtD,sEAAsE;;IAEtE,yCAAyC;;IAEzC,mDAAmD;;IAEnD,kGAAkG;;;;;;;;;;;;;;;;;;EAElG,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,uBAAuB;;;QAlBlC,0EAA0E;;QAE1E,+DAA+D;;QAE/D,sDAAsD;;QAEtD,sEAAsE;;QAEtE,yCAAyC;;QAEzC,mDAAmD;;QAEnD,kGAAkG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASlG,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,IAAI,EAAE,YAAY,CAAC;IACnB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhE;AAID,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAY9D;AAID,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CA8C9D;AA+BD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,YAAY,EAAE,EACrB,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,SAAS,EAAE,CAsCb;AAID,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CAUnE;AAED,0CAA0C;AAC1C,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,IAAI,CAMhF;AAID,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;EAK/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAa7E"}
|
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import safeRegex from 'safe-regex2';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
// ─── Schemas ─────────────────────────────────────────
|
|
6
|
+
export const CompiledRuleSchema = z.object({
|
|
7
|
+
/** SHA-256 hash (first 16 hex chars) of heading + body — detects edits */
|
|
8
|
+
lessonHash: z.string(),
|
|
9
|
+
/** Human-readable heading from the lesson (for diagnostics) */
|
|
10
|
+
lessonHeading: z.string(),
|
|
11
|
+
/** Regex pattern to match against added diff lines */
|
|
12
|
+
pattern: z.string(),
|
|
13
|
+
/** Human-readable violation message shown when the pattern matches */
|
|
14
|
+
message: z.string(),
|
|
15
|
+
/** Engine type — only 'regex' for MVP */
|
|
16
|
+
engine: z.literal('regex'),
|
|
17
|
+
/** ISO timestamp of when this rule was compiled */
|
|
18
|
+
compiledAt: z.string(),
|
|
19
|
+
/** Optional file glob patterns — rule only applies to matching files (e.g., ["*.sh", "*.yml"]) */
|
|
20
|
+
fileGlobs: z.array(z.string()).optional(),
|
|
21
|
+
});
|
|
22
|
+
export const CompiledRulesFileSchema = z.object({
|
|
23
|
+
version: z.literal(1),
|
|
24
|
+
rules: z.array(CompiledRuleSchema),
|
|
25
|
+
});
|
|
26
|
+
// ─── Hashing ─────────────────────────────────────────
|
|
27
|
+
const HASH_SLICE_LEN = 16;
|
|
28
|
+
/** Hash a lesson's heading + body to detect changes since compilation. */
|
|
29
|
+
export function hashLesson(heading, body) {
|
|
30
|
+
return crypto
|
|
31
|
+
.createHash('sha256')
|
|
32
|
+
.update(`${heading}\n${body}`)
|
|
33
|
+
.digest('hex')
|
|
34
|
+
.slice(0, HASH_SLICE_LEN);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate that a pattern string is a syntactically valid RegExp
|
|
38
|
+
* and is not vulnerable to ReDoS (catastrophic backtracking).
|
|
39
|
+
*/
|
|
40
|
+
export function validateRegex(pattern) {
|
|
41
|
+
try {
|
|
42
|
+
new RegExp(pattern);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { valid: false, reason: 'invalid syntax' };
|
|
46
|
+
}
|
|
47
|
+
if (!safeRegex(pattern)) {
|
|
48
|
+
return { valid: false, reason: 'ReDoS vulnerability detected' };
|
|
49
|
+
}
|
|
50
|
+
return { valid: true };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract added lines from a unified diff.
|
|
54
|
+
* Returns only lines that start with `+` (excluding `+++` file headers).
|
|
55
|
+
*/
|
|
56
|
+
export function extractAddedLines(diff) {
|
|
57
|
+
const additions = [];
|
|
58
|
+
let currentFile = '';
|
|
59
|
+
let lineNum = 0;
|
|
60
|
+
for (const rawLine of diff.split('\n')) {
|
|
61
|
+
// Track current file from diff headers
|
|
62
|
+
// git quotes paths containing spaces: +++ "b/path with spaces/file.ts"
|
|
63
|
+
if (rawLine.startsWith('+++')) {
|
|
64
|
+
let pathPart = rawLine.slice(4); // strip "+++ "
|
|
65
|
+
// Strip surrounding quotes (git adds them for paths with spaces)
|
|
66
|
+
if (pathPart.startsWith('"') && pathPart.endsWith('"')) {
|
|
67
|
+
pathPart = pathPart.slice(1, -1);
|
|
68
|
+
}
|
|
69
|
+
// Strip the "b/" prefix git uses for the destination file
|
|
70
|
+
currentFile = pathPart.startsWith('b/') ? pathPart.slice(2) : pathPart;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Parse hunk header for line numbers: @@ -X,Y +Z,W @@
|
|
74
|
+
const hunkMatch = rawLine.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
|
|
75
|
+
if (hunkMatch) {
|
|
76
|
+
lineNum = parseInt(hunkMatch[1], 10) - 1; // will be incremented on first line
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// Skip diff metadata lines
|
|
80
|
+
if (rawLine.startsWith('---') || rawLine.startsWith('diff ') || rawLine.startsWith('index ')) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Count lines for position tracking
|
|
84
|
+
if (rawLine.startsWith('+')) {
|
|
85
|
+
lineNum++;
|
|
86
|
+
additions.push({
|
|
87
|
+
file: currentFile,
|
|
88
|
+
line: rawLine.slice(1), // strip the leading +
|
|
89
|
+
lineNumber: lineNum,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else if (!rawLine.startsWith('-')) {
|
|
93
|
+
// Context line (no prefix or space prefix) — increment line counter
|
|
94
|
+
lineNum++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return additions;
|
|
98
|
+
}
|
|
99
|
+
// ─── Rule execution ──────────────────────────────────
|
|
100
|
+
// ─── File glob matching ─────────────────────────────
|
|
101
|
+
/**
|
|
102
|
+
* Check if a file path matches any of the given glob patterns.
|
|
103
|
+
* Supports simple patterns: `*.ext`, `**\/*.ext`, literal filenames.
|
|
104
|
+
*/
|
|
105
|
+
function matchesGlob(filePath, glob) {
|
|
106
|
+
// Normalize separators
|
|
107
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
108
|
+
// *.ext — match file extension anywhere
|
|
109
|
+
if (glob.startsWith('*.')) {
|
|
110
|
+
return normalized.endsWith(glob.slice(1));
|
|
111
|
+
}
|
|
112
|
+
// **/*.ext — same as *.ext (match extension anywhere in path)
|
|
113
|
+
if (glob.startsWith('**/')) {
|
|
114
|
+
return matchesGlob(normalized, glob.slice(3));
|
|
115
|
+
}
|
|
116
|
+
// Literal filename match (e.g., "Dockerfile")
|
|
117
|
+
return normalized === glob || normalized.endsWith('/' + glob);
|
|
118
|
+
}
|
|
119
|
+
function fileMatchesGlobs(filePath, globs) {
|
|
120
|
+
return globs.some((g) => matchesGlob(filePath, g));
|
|
121
|
+
}
|
|
122
|
+
// ─── Rule execution ──────────────────────────────────
|
|
123
|
+
/**
|
|
124
|
+
* Apply compiled rules against added lines from a diff.
|
|
125
|
+
* Returns all violations found.
|
|
126
|
+
* @param excludeFiles — file paths to skip (e.g., compiled-rules.json to avoid self-matches)
|
|
127
|
+
*/
|
|
128
|
+
export function applyRules(rules, diff, excludeFiles) {
|
|
129
|
+
let additions = extractAddedLines(diff);
|
|
130
|
+
if (additions.length === 0 || rules.length === 0)
|
|
131
|
+
return [];
|
|
132
|
+
if (excludeFiles && excludeFiles.length > 0) {
|
|
133
|
+
const excluded = new Set(excludeFiles);
|
|
134
|
+
additions = additions.filter((a) => !excluded.has(a.file));
|
|
135
|
+
}
|
|
136
|
+
const violations = [];
|
|
137
|
+
for (const rule of rules) {
|
|
138
|
+
let re;
|
|
139
|
+
try {
|
|
140
|
+
re = new RegExp(rule.pattern);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Skip invalid patterns (shouldn't happen if validation gate works)
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
for (const addition of additions) {
|
|
147
|
+
// Skip if rule has fileGlobs and this file doesn't match
|
|
148
|
+
if (rule.fileGlobs && rule.fileGlobs.length > 0) {
|
|
149
|
+
if (!fileMatchesGlobs(addition.file, rule.fileGlobs))
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (re.test(addition.line)) {
|
|
153
|
+
violations.push({
|
|
154
|
+
rule,
|
|
155
|
+
file: addition.file,
|
|
156
|
+
line: addition.line,
|
|
157
|
+
lineNumber: addition.lineNumber,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return violations;
|
|
163
|
+
}
|
|
164
|
+
// ─── File I/O ────────────────────────────────────────
|
|
165
|
+
/** Load compiled rules from a JSON file. Returns empty array if file missing or invalid. */
|
|
166
|
+
export function loadCompiledRules(rulesPath) {
|
|
167
|
+
if (!fs.existsSync(rulesPath))
|
|
168
|
+
return [];
|
|
169
|
+
try {
|
|
170
|
+
const raw = fs.readFileSync(rulesPath, 'utf-8');
|
|
171
|
+
const parsed = CompiledRulesFileSchema.parse(JSON.parse(raw));
|
|
172
|
+
return parsed.rules;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Save compiled rules to a JSON file. */
|
|
179
|
+
export function saveCompiledRules(rulesPath, rules) {
|
|
180
|
+
const data = { version: 1, rules };
|
|
181
|
+
fs.writeFileSync(rulesPath, JSON.stringify(data, null, 2) + '\n', {
|
|
182
|
+
encoding: 'utf-8',
|
|
183
|
+
mode: 0o644,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// ─── LLM response parsing ───────────────────────────
|
|
187
|
+
/** Schema for the structured JSON the LLM returns when compiling a lesson. */
|
|
188
|
+
export const CompilerOutputSchema = z.object({
|
|
189
|
+
compilable: z.boolean(),
|
|
190
|
+
pattern: z.string().optional(),
|
|
191
|
+
message: z.string().optional(),
|
|
192
|
+
fileGlobs: z.array(z.string()).optional(),
|
|
193
|
+
});
|
|
194
|
+
/**
|
|
195
|
+
* Parse the LLM's compilation response. Extracts JSON from the response text,
|
|
196
|
+
* validates it, and returns the structured output or null if unparseable.
|
|
197
|
+
*/
|
|
198
|
+
export function parseCompilerResponse(response) {
|
|
199
|
+
// Try to extract JSON from the response (LLMs often wrap in ```json blocks)
|
|
200
|
+
const jsonMatch = response.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
201
|
+
const jsonStr = jsonMatch ? jsonMatch[1] : response.trim();
|
|
202
|
+
try {
|
|
203
|
+
const parsed = JSON.parse(jsonStr);
|
|
204
|
+
const result = CompilerOutputSchema.safeParse(parsed);
|
|
205
|
+
if (!result.success)
|
|
206
|
+
return null;
|
|
207
|
+
return result.data;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=compiler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compiler.js","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compiler.test.d.ts","sourceRoot":"","sources":["../src/compiler.test.ts"],"names":[],"mappings":""}
|