@mmnto/totem 0.16.1 → 0.18.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 +144 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +177 -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 +259 -0
- package/dist/compiler.test.js.map +1 -0
- package/dist/config-schema.d.ts +6 -6
- package/dist/drift-detector.d.ts +39 -0
- package/dist/drift-detector.d.ts.map +1 -0
- package/dist/drift-detector.js +168 -0
- package/dist/drift-detector.js.map +1 -0
- package/dist/drift-detector.test.d.ts +2 -0
- package/dist/drift-detector.test.d.ts.map +1 -0
- package/dist/drift-detector.test.js +263 -0
- package/dist/drift-detector.test.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
lessonHash: string;
|
|
17
|
+
lessonHeading: string;
|
|
18
|
+
pattern: string;
|
|
19
|
+
message: string;
|
|
20
|
+
engine: "regex";
|
|
21
|
+
compiledAt: string;
|
|
22
|
+
}, {
|
|
23
|
+
lessonHash: string;
|
|
24
|
+
lessonHeading: string;
|
|
25
|
+
pattern: string;
|
|
26
|
+
message: string;
|
|
27
|
+
engine: "regex";
|
|
28
|
+
compiledAt: string;
|
|
29
|
+
}>;
|
|
30
|
+
export type CompiledRule = z.infer<typeof CompiledRuleSchema>;
|
|
31
|
+
export declare const CompiledRulesFileSchema: z.ZodObject<{
|
|
32
|
+
version: z.ZodLiteral<1>;
|
|
33
|
+
rules: z.ZodArray<z.ZodObject<{
|
|
34
|
+
/** SHA-256 hash (first 16 hex chars) of heading + body — detects edits */
|
|
35
|
+
lessonHash: z.ZodString;
|
|
36
|
+
/** Human-readable heading from the lesson (for diagnostics) */
|
|
37
|
+
lessonHeading: z.ZodString;
|
|
38
|
+
/** Regex pattern to match against added diff lines */
|
|
39
|
+
pattern: z.ZodString;
|
|
40
|
+
/** Human-readable violation message shown when the pattern matches */
|
|
41
|
+
message: z.ZodString;
|
|
42
|
+
/** Engine type — only 'regex' for MVP */
|
|
43
|
+
engine: z.ZodLiteral<"regex">;
|
|
44
|
+
/** ISO timestamp of when this rule was compiled */
|
|
45
|
+
compiledAt: z.ZodString;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
lessonHash: string;
|
|
48
|
+
lessonHeading: string;
|
|
49
|
+
pattern: string;
|
|
50
|
+
message: string;
|
|
51
|
+
engine: "regex";
|
|
52
|
+
compiledAt: string;
|
|
53
|
+
}, {
|
|
54
|
+
lessonHash: string;
|
|
55
|
+
lessonHeading: string;
|
|
56
|
+
pattern: string;
|
|
57
|
+
message: string;
|
|
58
|
+
engine: "regex";
|
|
59
|
+
compiledAt: string;
|
|
60
|
+
}>, "many">;
|
|
61
|
+
}, "strip", z.ZodTypeAny, {
|
|
62
|
+
version: 1;
|
|
63
|
+
rules: {
|
|
64
|
+
lessonHash: string;
|
|
65
|
+
lessonHeading: string;
|
|
66
|
+
pattern: string;
|
|
67
|
+
message: string;
|
|
68
|
+
engine: "regex";
|
|
69
|
+
compiledAt: string;
|
|
70
|
+
}[];
|
|
71
|
+
}, {
|
|
72
|
+
version: 1;
|
|
73
|
+
rules: {
|
|
74
|
+
lessonHash: string;
|
|
75
|
+
lessonHeading: string;
|
|
76
|
+
pattern: string;
|
|
77
|
+
message: string;
|
|
78
|
+
engine: "regex";
|
|
79
|
+
compiledAt: string;
|
|
80
|
+
}[];
|
|
81
|
+
}>;
|
|
82
|
+
export type CompiledRulesFile = z.infer<typeof CompiledRulesFileSchema>;
|
|
83
|
+
export interface Violation {
|
|
84
|
+
/** The rule that was violated */
|
|
85
|
+
rule: CompiledRule;
|
|
86
|
+
/** The file path from the diff where the violation occurred */
|
|
87
|
+
file: string;
|
|
88
|
+
/** The matching line content */
|
|
89
|
+
line: string;
|
|
90
|
+
/** 1-based line number within the diff hunk (approximate) */
|
|
91
|
+
lineNumber: number;
|
|
92
|
+
}
|
|
93
|
+
/** Hash a lesson's heading + body to detect changes since compilation. */
|
|
94
|
+
export declare function hashLesson(heading: string, body: string): string;
|
|
95
|
+
export interface RegexValidation {
|
|
96
|
+
valid: boolean;
|
|
97
|
+
reason?: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Validate that a pattern string is a syntactically valid RegExp
|
|
101
|
+
* and is not vulnerable to ReDoS (catastrophic backtracking).
|
|
102
|
+
*/
|
|
103
|
+
export declare function validateRegex(pattern: string): RegexValidation;
|
|
104
|
+
interface DiffAddition {
|
|
105
|
+
file: string;
|
|
106
|
+
line: string;
|
|
107
|
+
lineNumber: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Extract added lines from a unified diff.
|
|
111
|
+
* Returns only lines that start with `+` (excluding `+++` file headers).
|
|
112
|
+
*/
|
|
113
|
+
export declare function extractAddedLines(diff: string): DiffAddition[];
|
|
114
|
+
/**
|
|
115
|
+
* Apply compiled rules against added lines from a diff.
|
|
116
|
+
* Returns all violations found.
|
|
117
|
+
*/
|
|
118
|
+
export declare function applyRules(rules: CompiledRule[], diff: string): Violation[];
|
|
119
|
+
/** Load compiled rules from a JSON file. Returns empty array if file missing or invalid. */
|
|
120
|
+
export declare function loadCompiledRules(rulesPath: string): CompiledRule[];
|
|
121
|
+
/** Save compiled rules to a JSON file. */
|
|
122
|
+
export declare function saveCompiledRules(rulesPath: string, rules: CompiledRule[]): void;
|
|
123
|
+
/** Schema for the structured JSON the LLM returns when compiling a lesson. */
|
|
124
|
+
export declare const CompilerOutputSchema: z.ZodObject<{
|
|
125
|
+
compilable: z.ZodBoolean;
|
|
126
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
127
|
+
message: z.ZodOptional<z.ZodString>;
|
|
128
|
+
}, "strip", z.ZodTypeAny, {
|
|
129
|
+
compilable: boolean;
|
|
130
|
+
pattern?: string | undefined;
|
|
131
|
+
message?: string | undefined;
|
|
132
|
+
}, {
|
|
133
|
+
compilable: boolean;
|
|
134
|
+
pattern?: string | undefined;
|
|
135
|
+
message?: string | undefined;
|
|
136
|
+
}>;
|
|
137
|
+
export type CompilerOutput = z.infer<typeof CompilerOutputSchema>;
|
|
138
|
+
/**
|
|
139
|
+
* Parse the LLM's compilation response. Extracts JSON from the response text,
|
|
140
|
+
* validates it, and returns the structured output or null if unparseable.
|
|
141
|
+
*/
|
|
142
|
+
export declare function parseCompilerResponse(response: string): CompilerOutput | null;
|
|
143
|
+
export {};
|
|
144
|
+
//# 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;;;;;;;;;;;;;;;;EAEnD,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,uBAAuB;;;QAhBlC,0EAA0E;;QAE1E,+DAA+D;;QAE/D,sDAAsD;;QAEtD,sEAAsE;;QAEtE,yCAAyC;;QAEzC,mDAAmD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASnD,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;AAID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CA4B3E;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;;;;;;;;;;;;EAI/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,177 @@
|
|
|
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
|
+
});
|
|
20
|
+
export const CompiledRulesFileSchema = z.object({
|
|
21
|
+
version: z.literal(1),
|
|
22
|
+
rules: z.array(CompiledRuleSchema),
|
|
23
|
+
});
|
|
24
|
+
// ─── Hashing ─────────────────────────────────────────
|
|
25
|
+
const HASH_SLICE_LEN = 16;
|
|
26
|
+
/** Hash a lesson's heading + body to detect changes since compilation. */
|
|
27
|
+
export function hashLesson(heading, body) {
|
|
28
|
+
return crypto
|
|
29
|
+
.createHash('sha256')
|
|
30
|
+
.update(`${heading}\n${body}`)
|
|
31
|
+
.digest('hex')
|
|
32
|
+
.slice(0, HASH_SLICE_LEN);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate that a pattern string is a syntactically valid RegExp
|
|
36
|
+
* and is not vulnerable to ReDoS (catastrophic backtracking).
|
|
37
|
+
*/
|
|
38
|
+
export function validateRegex(pattern) {
|
|
39
|
+
try {
|
|
40
|
+
new RegExp(pattern);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { valid: false, reason: 'invalid syntax' };
|
|
44
|
+
}
|
|
45
|
+
if (!safeRegex(pattern)) {
|
|
46
|
+
return { valid: false, reason: 'ReDoS vulnerability detected' };
|
|
47
|
+
}
|
|
48
|
+
return { valid: true };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract added lines from a unified diff.
|
|
52
|
+
* Returns only lines that start with `+` (excluding `+++` file headers).
|
|
53
|
+
*/
|
|
54
|
+
export function extractAddedLines(diff) {
|
|
55
|
+
const additions = [];
|
|
56
|
+
let currentFile = '';
|
|
57
|
+
let lineNum = 0;
|
|
58
|
+
for (const rawLine of diff.split('\n')) {
|
|
59
|
+
// Track current file from diff headers
|
|
60
|
+
// git quotes paths containing spaces: +++ "b/path with spaces/file.ts"
|
|
61
|
+
if (rawLine.startsWith('+++')) {
|
|
62
|
+
let pathPart = rawLine.slice(4); // strip "+++ "
|
|
63
|
+
// Strip surrounding quotes (git adds them for paths with spaces)
|
|
64
|
+
if (pathPart.startsWith('"') && pathPart.endsWith('"')) {
|
|
65
|
+
pathPart = pathPart.slice(1, -1);
|
|
66
|
+
}
|
|
67
|
+
// Strip the "b/" prefix git uses for the destination file
|
|
68
|
+
currentFile = pathPart.startsWith('b/') ? pathPart.slice(2) : pathPart;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Parse hunk header for line numbers: @@ -X,Y +Z,W @@
|
|
72
|
+
const hunkMatch = rawLine.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
|
|
73
|
+
if (hunkMatch) {
|
|
74
|
+
lineNum = parseInt(hunkMatch[1], 10) - 1; // will be incremented on first line
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Skip diff metadata lines
|
|
78
|
+
if (rawLine.startsWith('---') || rawLine.startsWith('diff ') || rawLine.startsWith('index ')) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Count lines for position tracking
|
|
82
|
+
if (rawLine.startsWith('+')) {
|
|
83
|
+
lineNum++;
|
|
84
|
+
additions.push({
|
|
85
|
+
file: currentFile,
|
|
86
|
+
line: rawLine.slice(1), // strip the leading +
|
|
87
|
+
lineNumber: lineNum,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
else if (!rawLine.startsWith('-')) {
|
|
91
|
+
// Context line (no prefix or space prefix) — increment line counter
|
|
92
|
+
lineNum++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return additions;
|
|
96
|
+
}
|
|
97
|
+
// ─── Rule execution ──────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* Apply compiled rules against added lines from a diff.
|
|
100
|
+
* Returns all violations found.
|
|
101
|
+
*/
|
|
102
|
+
export function applyRules(rules, diff) {
|
|
103
|
+
const additions = extractAddedLines(diff);
|
|
104
|
+
if (additions.length === 0 || rules.length === 0)
|
|
105
|
+
return [];
|
|
106
|
+
const violations = [];
|
|
107
|
+
for (const rule of rules) {
|
|
108
|
+
let re;
|
|
109
|
+
try {
|
|
110
|
+
re = new RegExp(rule.pattern);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Skip invalid patterns (shouldn't happen if validation gate works)
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
for (const addition of additions) {
|
|
117
|
+
if (re.test(addition.line)) {
|
|
118
|
+
violations.push({
|
|
119
|
+
rule,
|
|
120
|
+
file: addition.file,
|
|
121
|
+
line: addition.line,
|
|
122
|
+
lineNumber: addition.lineNumber,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return violations;
|
|
128
|
+
}
|
|
129
|
+
// ─── File I/O ────────────────────────────────────────
|
|
130
|
+
/** Load compiled rules from a JSON file. Returns empty array if file missing or invalid. */
|
|
131
|
+
export function loadCompiledRules(rulesPath) {
|
|
132
|
+
if (!fs.existsSync(rulesPath))
|
|
133
|
+
return [];
|
|
134
|
+
try {
|
|
135
|
+
const raw = fs.readFileSync(rulesPath, 'utf-8');
|
|
136
|
+
const parsed = CompiledRulesFileSchema.parse(JSON.parse(raw));
|
|
137
|
+
return parsed.rules;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Save compiled rules to a JSON file. */
|
|
144
|
+
export function saveCompiledRules(rulesPath, rules) {
|
|
145
|
+
const data = { version: 1, rules };
|
|
146
|
+
fs.writeFileSync(rulesPath, JSON.stringify(data, null, 2) + '\n', {
|
|
147
|
+
encoding: 'utf-8',
|
|
148
|
+
mode: 0o644,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// ─── LLM response parsing ───────────────────────────
|
|
152
|
+
/** Schema for the structured JSON the LLM returns when compiling a lesson. */
|
|
153
|
+
export const CompilerOutputSchema = z.object({
|
|
154
|
+
compilable: z.boolean(),
|
|
155
|
+
pattern: z.string().optional(),
|
|
156
|
+
message: z.string().optional(),
|
|
157
|
+
});
|
|
158
|
+
/**
|
|
159
|
+
* Parse the LLM's compilation response. Extracts JSON from the response text,
|
|
160
|
+
* validates it, and returns the structured output or null if unparseable.
|
|
161
|
+
*/
|
|
162
|
+
export function parseCompilerResponse(response) {
|
|
163
|
+
// Try to extract JSON from the response (LLMs often wrap in ```json blocks)
|
|
164
|
+
const jsonMatch = response.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
165
|
+
const jsonStr = jsonMatch ? jsonMatch[1] : response.trim();
|
|
166
|
+
try {
|
|
167
|
+
const parsed = JSON.parse(jsonStr);
|
|
168
|
+
const result = CompilerOutputSchema.safeParse(parsed);
|
|
169
|
+
if (!result.success)
|
|
170
|
+
return null;
|
|
171
|
+
return result.data;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# 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":""}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { applyRules, extractAddedLines, hashLesson, loadCompiledRules, parseCompilerResponse, saveCompiledRules, validateRegex, } from './compiler.js';
|
|
6
|
+
// ─── hashLesson ──────────────────────────────────────
|
|
7
|
+
describe('hashLesson', () => {
|
|
8
|
+
it('returns a 16-char hex string', () => {
|
|
9
|
+
const hash = hashLesson('heading', 'body');
|
|
10
|
+
expect(hash).toMatch(/^[0-9a-f]{16}$/);
|
|
11
|
+
});
|
|
12
|
+
it('returns different hashes for different inputs', () => {
|
|
13
|
+
const h1 = hashLesson('heading1', 'body');
|
|
14
|
+
const h2 = hashLesson('heading2', 'body');
|
|
15
|
+
expect(h1).not.toBe(h2);
|
|
16
|
+
});
|
|
17
|
+
it('returns stable hashes for the same input', () => {
|
|
18
|
+
const h1 = hashLesson('heading', 'body');
|
|
19
|
+
const h2 = hashLesson('heading', 'body');
|
|
20
|
+
expect(h1).toBe(h2);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
// ─── validateRegex ───────────────────────────────────
|
|
24
|
+
describe('validateRegex', () => {
|
|
25
|
+
it('accepts a valid regex', () => {
|
|
26
|
+
expect(validateRegex('\\bfoo\\b')).toEqual({ valid: true });
|
|
27
|
+
});
|
|
28
|
+
it('accepts a simple string pattern', () => {
|
|
29
|
+
expect(validateRegex('console.log')).toEqual({ valid: true });
|
|
30
|
+
});
|
|
31
|
+
it('accepts a complex but safe pattern', () => {
|
|
32
|
+
// Anchored API key format — complex but not vulnerable
|
|
33
|
+
expect(validateRegex('^[A-Za-z0-9]{32,}$')).toEqual({ valid: true });
|
|
34
|
+
});
|
|
35
|
+
it('rejects an invalid regex', () => {
|
|
36
|
+
const result = validateRegex('[invalid');
|
|
37
|
+
expect(result.valid).toBe(false);
|
|
38
|
+
expect(result.reason).toBe('invalid syntax');
|
|
39
|
+
});
|
|
40
|
+
it('rejects unbalanced parentheses', () => {
|
|
41
|
+
const result = validateRegex('(unclosed');
|
|
42
|
+
expect(result.valid).toBe(false);
|
|
43
|
+
expect(result.reason).toBe('invalid syntax');
|
|
44
|
+
});
|
|
45
|
+
it('rejects ReDoS pattern: nested quantifiers (a+)+', () => {
|
|
46
|
+
const result = validateRegex('(a+)+$');
|
|
47
|
+
expect(result.valid).toBe(false);
|
|
48
|
+
expect(result.reason).toBe('ReDoS vulnerability detected');
|
|
49
|
+
});
|
|
50
|
+
it('rejects ReDoS pattern: nested character class quantifiers ([a-zA-Z]+)*', () => {
|
|
51
|
+
const result = validateRegex('([a-zA-Z]+)*');
|
|
52
|
+
expect(result.valid).toBe(false);
|
|
53
|
+
expect(result.reason).toBe('ReDoS vulnerability detected');
|
|
54
|
+
});
|
|
55
|
+
it('rejects ReDoS pattern: nested repetition (.*a){10}', () => {
|
|
56
|
+
const result = validateRegex('(.*a){10}');
|
|
57
|
+
expect(result.valid).toBe(false);
|
|
58
|
+
expect(result.reason).toBe('ReDoS vulnerability detected');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
// ─── extractAddedLines ──────────────────────────────
|
|
62
|
+
describe('extractAddedLines', () => {
|
|
63
|
+
it('extracts added lines from a unified diff', () => {
|
|
64
|
+
const diff = `diff --git a/src/foo.ts b/src/foo.ts
|
|
65
|
+
index abc1234..def5678 100644
|
|
66
|
+
--- a/src/foo.ts
|
|
67
|
+
+++ b/src/foo.ts
|
|
68
|
+
@@ -1,3 +1,4 @@
|
|
69
|
+
const a = 1;
|
|
70
|
+
-const b = 2;
|
|
71
|
+
+const b = 3;
|
|
72
|
+
+const c = 4;
|
|
73
|
+
const d = 5;
|
|
74
|
+
`;
|
|
75
|
+
const additions = extractAddedLines(diff);
|
|
76
|
+
expect(additions).toHaveLength(2);
|
|
77
|
+
expect(additions[0]).toEqual({ file: 'src/foo.ts', line: 'const b = 3;', lineNumber: 2 });
|
|
78
|
+
expect(additions[1]).toEqual({ file: 'src/foo.ts', line: 'const c = 4;', lineNumber: 3 });
|
|
79
|
+
});
|
|
80
|
+
it('handles multiple files', () => {
|
|
81
|
+
const diff = `diff --git a/a.ts b/a.ts
|
|
82
|
+
--- a/a.ts
|
|
83
|
+
+++ b/a.ts
|
|
84
|
+
@@ -1,2 +1,3 @@
|
|
85
|
+
line1
|
|
86
|
+
+added in a
|
|
87
|
+
line2
|
|
88
|
+
diff --git a/b.ts b/b.ts
|
|
89
|
+
--- a/b.ts
|
|
90
|
+
+++ b/b.ts
|
|
91
|
+
@@ -1,2 +1,3 @@
|
|
92
|
+
line1
|
|
93
|
+
+added in b
|
|
94
|
+
line2
|
|
95
|
+
`;
|
|
96
|
+
const additions = extractAddedLines(diff);
|
|
97
|
+
expect(additions).toHaveLength(2);
|
|
98
|
+
expect(additions[0].file).toBe('a.ts');
|
|
99
|
+
expect(additions[1].file).toBe('b.ts');
|
|
100
|
+
});
|
|
101
|
+
it('returns empty array for empty diff', () => {
|
|
102
|
+
expect(extractAddedLines('')).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
it('handles quoted filenames with spaces', () => {
|
|
105
|
+
const diff = `diff --git "a/path with spaces/file.ts" "b/path with spaces/file.ts"
|
|
106
|
+
--- "a/path with spaces/file.ts"
|
|
107
|
+
+++ "b/path with spaces/file.ts"
|
|
108
|
+
@@ -1,2 +1,3 @@
|
|
109
|
+
line1
|
|
110
|
+
+added line
|
|
111
|
+
line2
|
|
112
|
+
`;
|
|
113
|
+
const additions = extractAddedLines(diff);
|
|
114
|
+
expect(additions).toHaveLength(1);
|
|
115
|
+
expect(additions[0].file).toBe('path with spaces/file.ts');
|
|
116
|
+
});
|
|
117
|
+
it('returns empty array for deletion-only diff', () => {
|
|
118
|
+
const diff = `diff --git a/foo.ts b/foo.ts
|
|
119
|
+
--- a/foo.ts
|
|
120
|
+
+++ b/foo.ts
|
|
121
|
+
@@ -1,3 +1,2 @@
|
|
122
|
+
line1
|
|
123
|
+
-removed
|
|
124
|
+
line2
|
|
125
|
+
`;
|
|
126
|
+
expect(extractAddedLines(diff)).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
// ─── applyRules ──────────────────────────────────────
|
|
130
|
+
describe('applyRules', () => {
|
|
131
|
+
const makeRule = (pattern, message) => ({
|
|
132
|
+
lessonHash: 'abc123',
|
|
133
|
+
lessonHeading: 'Test rule',
|
|
134
|
+
pattern,
|
|
135
|
+
message,
|
|
136
|
+
engine: 'regex',
|
|
137
|
+
compiledAt: new Date().toISOString(),
|
|
138
|
+
});
|
|
139
|
+
const diff = `diff --git a/src/app.ts b/src/app.ts
|
|
140
|
+
--- a/src/app.ts
|
|
141
|
+
+++ b/src/app.ts
|
|
142
|
+
@@ -1,3 +1,5 @@
|
|
143
|
+
import { foo } from './foo';
|
|
144
|
+
+import { error } from './errors';
|
|
145
|
+
+const result = npm.install('package');
|
|
146
|
+
export default foo;
|
|
147
|
+
`;
|
|
148
|
+
it('detects a simple pattern violation', () => {
|
|
149
|
+
const rules = [makeRule('\\bnpm\\.install\\b', 'Do not call npm.install directly')];
|
|
150
|
+
const violations = applyRules(rules, diff);
|
|
151
|
+
expect(violations).toHaveLength(1);
|
|
152
|
+
expect(violations[0].rule.message).toBe('Do not call npm.install directly');
|
|
153
|
+
expect(violations[0].file).toBe('src/app.ts');
|
|
154
|
+
});
|
|
155
|
+
it('returns no violations when patterns do not match', () => {
|
|
156
|
+
const rules = [makeRule('\\byarn\\b', 'Do not use yarn')];
|
|
157
|
+
const violations = applyRules(rules, diff);
|
|
158
|
+
expect(violations).toHaveLength(0);
|
|
159
|
+
});
|
|
160
|
+
it('applies multiple rules', () => {
|
|
161
|
+
const rules = [
|
|
162
|
+
makeRule('\\bnpm\\b', 'Do not use npm'),
|
|
163
|
+
makeRule('\\berror\\b', 'Use err, not error'),
|
|
164
|
+
];
|
|
165
|
+
const violations = applyRules(rules, diff);
|
|
166
|
+
expect(violations).toHaveLength(2);
|
|
167
|
+
});
|
|
168
|
+
it('skips rules with invalid patterns', () => {
|
|
169
|
+
const rules = [makeRule('[invalid', 'Bad pattern')];
|
|
170
|
+
const violations = applyRules(rules, diff);
|
|
171
|
+
expect(violations).toHaveLength(0);
|
|
172
|
+
});
|
|
173
|
+
it('returns empty for empty diff', () => {
|
|
174
|
+
const rules = [makeRule('anything', 'test')];
|
|
175
|
+
expect(applyRules(rules, '')).toEqual([]);
|
|
176
|
+
});
|
|
177
|
+
it('returns empty for empty rules', () => {
|
|
178
|
+
expect(applyRules([], diff)).toEqual([]);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
// ─── loadCompiledRules / saveCompiledRules ───────────
|
|
182
|
+
describe('compiled rules file I/O', () => {
|
|
183
|
+
let tmpDir;
|
|
184
|
+
beforeEach(() => {
|
|
185
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-compiler-'));
|
|
186
|
+
});
|
|
187
|
+
afterEach(() => {
|
|
188
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
189
|
+
});
|
|
190
|
+
it('round-trips rules through save and load', () => {
|
|
191
|
+
const rulesPath = path.join(tmpDir, 'compiled-rules.json');
|
|
192
|
+
const rules = [
|
|
193
|
+
{
|
|
194
|
+
lessonHash: 'abc123def456',
|
|
195
|
+
lessonHeading: 'Use err not error',
|
|
196
|
+
pattern: '\\berror\\b',
|
|
197
|
+
message: 'Use err instead of error in catch blocks',
|
|
198
|
+
engine: 'regex',
|
|
199
|
+
compiledAt: '2026-03-08T12:00:00Z',
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
saveCompiledRules(rulesPath, rules);
|
|
203
|
+
const loaded = loadCompiledRules(rulesPath);
|
|
204
|
+
expect(loaded).toEqual(rules);
|
|
205
|
+
});
|
|
206
|
+
it('returns empty array for missing file', () => {
|
|
207
|
+
const loaded = loadCompiledRules(path.join(tmpDir, 'nonexistent.json'));
|
|
208
|
+
expect(loaded).toEqual([]);
|
|
209
|
+
});
|
|
210
|
+
it('returns empty array for invalid JSON', () => {
|
|
211
|
+
const rulesPath = path.join(tmpDir, 'bad.json');
|
|
212
|
+
fs.writeFileSync(rulesPath, 'not valid json');
|
|
213
|
+
expect(loadCompiledRules(rulesPath)).toEqual([]);
|
|
214
|
+
});
|
|
215
|
+
it('returns empty array for wrong schema', () => {
|
|
216
|
+
const rulesPath = path.join(tmpDir, 'wrong.json');
|
|
217
|
+
fs.writeFileSync(rulesPath, JSON.stringify({ version: 99, rules: [] }));
|
|
218
|
+
expect(loadCompiledRules(rulesPath)).toEqual([]);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
// ─── parseCompilerResponse ──────────────────────────
|
|
222
|
+
describe('parseCompilerResponse', () => {
|
|
223
|
+
it('parses a valid compilable response', () => {
|
|
224
|
+
const response = JSON.stringify({
|
|
225
|
+
compilable: true,
|
|
226
|
+
pattern: '\\bnpm\\b',
|
|
227
|
+
message: 'Use pnpm instead of npm',
|
|
228
|
+
});
|
|
229
|
+
const result = parseCompilerResponse(response);
|
|
230
|
+
expect(result).toEqual({
|
|
231
|
+
compilable: true,
|
|
232
|
+
pattern: '\\bnpm\\b',
|
|
233
|
+
message: 'Use pnpm instead of npm',
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
it('parses a non-compilable response', () => {
|
|
237
|
+
const response = JSON.stringify({ compilable: false });
|
|
238
|
+
const result = parseCompilerResponse(response);
|
|
239
|
+
expect(result).toEqual({ compilable: false });
|
|
240
|
+
});
|
|
241
|
+
it('extracts JSON from a code fence', () => {
|
|
242
|
+
const response = `Here is the compiled rule:
|
|
243
|
+
\`\`\`json
|
|
244
|
+
{"compilable": true, "pattern": "console\\\\.log", "message": "Remove debug logging"}
|
|
245
|
+
\`\`\``;
|
|
246
|
+
const result = parseCompilerResponse(response);
|
|
247
|
+
expect(result).not.toBeNull();
|
|
248
|
+
expect(result.compilable).toBe(true);
|
|
249
|
+
expect(result.pattern).toBe('console\\.log');
|
|
250
|
+
});
|
|
251
|
+
it('returns null for completely invalid output', () => {
|
|
252
|
+
expect(parseCompilerResponse('I cannot compile this lesson.')).toBeNull();
|
|
253
|
+
});
|
|
254
|
+
it('returns null for JSON with wrong schema', () => {
|
|
255
|
+
const response = JSON.stringify({ foo: 'bar' });
|
|
256
|
+
expect(parseCompilerResponse(response)).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
//# sourceMappingURL=compiler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compiler.test.js","sourceRoot":"","sources":["../src/compiler.test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAErE,OAAO,EACL,UAAU,EAEV,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,wDAAwD;AAExD,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,EAAE,GAAG,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,EAAE,GAAG,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,EAAE,GAAG,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACzC,MAAM,EAAE,GAAG,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACzC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wDAAwD;AAExD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,uDAAuD;QACvD,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,MAAM,MAAM,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,uDAAuD;AAEvD,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,IAAI,GAAG;;;;;;;;;;CAUhB,CAAC;QAEE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1F,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;CAchB,CAAC;QAEE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,IAAI,GAAG;;;;;;;CAOhB,CAAC;QAEE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG;;;;;;;CAOhB,CAAC;QACE,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wDAAwD;AAExD,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAE,OAAe,EAAgB,EAAE,CAAC,CAAC;QACpE,UAAU,EAAE,QAAQ;QACpB,aAAa,EAAE,WAAW;QAC1B,OAAO;QACP,OAAO;QACP,MAAM,EAAE,OAAO;QACf,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG;;;;;;;;CAQd,CAAC;IAEA,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,qBAAqB,EAAE,kCAAkC,CAAC,CAAC,CAAC;QACpF,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QAC7E,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC1D,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,KAAK,GAAG;YACZ,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;YACvC,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;SAC9C,CAAC;QACF,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,wDAAwD;AAExD,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,IAAI,MAAc,CAAC;IAEnB,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAmB;YAC5B;gBACE,UAAU,EAAE,cAAc;gBAC1B,aAAa,EAAE,mBAAmB;gBAClC,OAAO,EAAE,aAAa;gBACtB,OAAO,EAAE,0CAA0C;gBACnD,MAAM,EAAE,OAAO;gBACf,UAAU,EAAE,sBAAsB;aACnC;SACF,CAAC;QAEF,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAChD,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;QAC9C,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAClD,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,uDAAuD;AAEvD,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;YAC9B,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,WAAW;YACpB,OAAO,EAAE,yBAAyB;SACnC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YACrB,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,WAAW;YACpB,OAAO,EAAE,yBAAyB;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,QAAQ,GAAG;;;OAGd,CAAC;QAEJ,MAAM,MAAM,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,MAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,qBAAqB,CAAC,+BAA+B,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/config-schema.d.ts
CHANGED
|
@@ -9,12 +9,12 @@ export declare const IngestTargetSchema: z.ZodObject<{
|
|
|
9
9
|
type: z.ZodEnum<["code", "session_log", "spec"]>;
|
|
10
10
|
strategy: z.ZodEnum<["typescript-ast", "markdown-heading", "session-log", "schema-file", "test-file"]>;
|
|
11
11
|
}, "strip", z.ZodTypeAny, {
|
|
12
|
-
glob: string;
|
|
13
12
|
type: "code" | "session_log" | "spec";
|
|
13
|
+
glob: string;
|
|
14
14
|
strategy: "typescript-ast" | "markdown-heading" | "session-log" | "schema-file" | "test-file";
|
|
15
15
|
}, {
|
|
16
|
-
glob: string;
|
|
17
16
|
type: "code" | "session_log" | "spec";
|
|
17
|
+
glob: string;
|
|
18
18
|
strategy: "typescript-ast" | "markdown-heading" | "session-log" | "schema-file" | "test-file";
|
|
19
19
|
}>;
|
|
20
20
|
export declare const OpenAIProviderSchema: z.ZodObject<{
|
|
@@ -154,12 +154,12 @@ export declare const TotemConfigSchema: z.ZodObject<{
|
|
|
154
154
|
type: z.ZodEnum<["code", "session_log", "spec"]>;
|
|
155
155
|
strategy: z.ZodEnum<["typescript-ast", "markdown-heading", "session-log", "schema-file", "test-file"]>;
|
|
156
156
|
}, "strip", z.ZodTypeAny, {
|
|
157
|
-
glob: string;
|
|
158
157
|
type: "code" | "session_log" | "spec";
|
|
158
|
+
glob: string;
|
|
159
159
|
strategy: "typescript-ast" | "markdown-heading" | "session-log" | "schema-file" | "test-file";
|
|
160
160
|
}, {
|
|
161
|
-
glob: string;
|
|
162
161
|
type: "code" | "session_log" | "spec";
|
|
162
|
+
glob: string;
|
|
163
163
|
strategy: "typescript-ast" | "markdown-heading" | "session-log" | "schema-file" | "test-file";
|
|
164
164
|
}>, "many">;
|
|
165
165
|
/** Embedding provider configuration (optional for Lite tier) */
|
|
@@ -246,8 +246,8 @@ export declare const TotemConfigSchema: z.ZodObject<{
|
|
|
246
246
|
}>, "many">>;
|
|
247
247
|
}, "strip", z.ZodTypeAny, {
|
|
248
248
|
targets: {
|
|
249
|
-
glob: string;
|
|
250
249
|
type: "code" | "session_log" | "spec";
|
|
250
|
+
glob: string;
|
|
251
251
|
strategy: "typescript-ast" | "markdown-heading" | "session-log" | "schema-file" | "test-file";
|
|
252
252
|
}[];
|
|
253
253
|
totemDir: string;
|
|
@@ -279,8 +279,8 @@ export declare const TotemConfigSchema: z.ZodObject<{
|
|
|
279
279
|
}[] | undefined;
|
|
280
280
|
}, {
|
|
281
281
|
targets: {
|
|
282
|
-
glob: string;
|
|
283
282
|
type: "code" | "session_log" | "spec";
|
|
283
|
+
glob: string;
|
|
284
284
|
strategy: "typescript-ast" | "markdown-heading" | "session-log" | "schema-file" | "test-file";
|
|
285
285
|
}[];
|
|
286
286
|
embedding?: {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ParsedLesson {
|
|
2
|
+
/** Heading text after "## Lesson — " */
|
|
3
|
+
heading: string;
|
|
4
|
+
/** Extracted tags from the **Tags:** line */
|
|
5
|
+
tags: string[];
|
|
6
|
+
/** The lesson body text (after heading + tags line) */
|
|
7
|
+
body: string;
|
|
8
|
+
/** The full raw text of this lesson section (heading through end) */
|
|
9
|
+
raw: string;
|
|
10
|
+
/** 0-based index in the parsed lessons array */
|
|
11
|
+
index: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DriftResult {
|
|
14
|
+
/** The parsed lesson that has orphaned references */
|
|
15
|
+
lesson: ParsedLesson;
|
|
16
|
+
/** File paths referenced in the lesson that no longer exist */
|
|
17
|
+
orphanedRefs: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse a lessons.md file into individual lesson entries.
|
|
21
|
+
* Splits on `## Lesson —` headings and extracts tags + body.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseLessonsFile(content: string): ParsedLesson[];
|
|
24
|
+
/**
|
|
25
|
+
* Extract file path references from a lesson body.
|
|
26
|
+
* Looks for backtick-wrapped content that looks like a real file path.
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractFileReferences(body: string): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Check parsed lessons for file references that no longer exist on disk.
|
|
31
|
+
* Returns only lessons that have at least one orphaned reference.
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectDrift(lessons: ParsedLesson[], projectRoot: string): DriftResult[];
|
|
34
|
+
/**
|
|
35
|
+
* Rewrite lessons.md content, removing lessons at the specified indices.
|
|
36
|
+
* Returns the new file content.
|
|
37
|
+
*/
|
|
38
|
+
export declare function rewriteLessonsFile(content: string, indicesToRemove: Set<number>): string;
|
|
39
|
+
//# sourceMappingURL=drift-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detector.d.ts","sourceRoot":"","sources":["../src/drift-detector.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,MAAM,EAAE,YAAY,CAAC;IACrB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AA8CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAyChE;AAID;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAuC5D;AAID;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,WAAW,EAAE,CAkBvF;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAqBxF"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
// ─── Constants ─────────────────────────────────────────
|
|
4
|
+
/** File extensions considered valid for drift detection */
|
|
5
|
+
const FILE_EXTENSIONS = new Set([
|
|
6
|
+
'.ts',
|
|
7
|
+
'.tsx',
|
|
8
|
+
'.js',
|
|
9
|
+
'.jsx',
|
|
10
|
+
'.mjs',
|
|
11
|
+
'.cjs',
|
|
12
|
+
'.json',
|
|
13
|
+
'.md',
|
|
14
|
+
'.mdx',
|
|
15
|
+
'.yaml',
|
|
16
|
+
'.yml',
|
|
17
|
+
'.toml',
|
|
18
|
+
'.css',
|
|
19
|
+
'.scss',
|
|
20
|
+
'.less',
|
|
21
|
+
'.html',
|
|
22
|
+
'.vue',
|
|
23
|
+
'.svelte',
|
|
24
|
+
'.py',
|
|
25
|
+
'.rs',
|
|
26
|
+
'.go',
|
|
27
|
+
'.java',
|
|
28
|
+
'.kt',
|
|
29
|
+
'.rb',
|
|
30
|
+
'.php',
|
|
31
|
+
'.sql',
|
|
32
|
+
'.graphql',
|
|
33
|
+
'.gql',
|
|
34
|
+
'.sh',
|
|
35
|
+
'.bash',
|
|
36
|
+
'.zsh',
|
|
37
|
+
'.env',
|
|
38
|
+
'.lock',
|
|
39
|
+
'.config',
|
|
40
|
+
]);
|
|
41
|
+
// ─── Lesson parser ─────────────────────────────────────
|
|
42
|
+
const LESSON_HEADING_RE = /^## Lesson — /m;
|
|
43
|
+
/**
|
|
44
|
+
* Parse a lessons.md file into individual lesson entries.
|
|
45
|
+
* Splits on `## Lesson —` headings and extracts tags + body.
|
|
46
|
+
*/
|
|
47
|
+
export function parseLessonsFile(content) {
|
|
48
|
+
const lessons = [];
|
|
49
|
+
// Split on lesson headings, keeping the delimiter
|
|
50
|
+
const parts = content.split(LESSON_HEADING_RE);
|
|
51
|
+
// parts[0] is the file header (before the first lesson)
|
|
52
|
+
for (let i = 1; i < parts.length; i++) {
|
|
53
|
+
const part = parts[i];
|
|
54
|
+
const lines = part.split('\n');
|
|
55
|
+
// First line is the heading (rest of the ## Lesson — line)
|
|
56
|
+
const heading = (lines[0] ?? '').trim();
|
|
57
|
+
// Find tags line: **Tags:** ...
|
|
58
|
+
let tags = [];
|
|
59
|
+
let bodyStartIdx = 1;
|
|
60
|
+
for (let j = 1; j < lines.length; j++) {
|
|
61
|
+
const line = lines[j].trim();
|
|
62
|
+
if (!line)
|
|
63
|
+
continue; // skip blank lines between heading and tags
|
|
64
|
+
if (line.startsWith('**Tags:**')) {
|
|
65
|
+
tags = line
|
|
66
|
+
.replace('**Tags:**', '')
|
|
67
|
+
.split(',')
|
|
68
|
+
.map((t) => t.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
bodyStartIdx = j + 1;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
// If we hit non-empty, non-tags content, no tags line exists
|
|
74
|
+
bodyStartIdx = j;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
const body = lines.slice(bodyStartIdx).join('\n').trim();
|
|
78
|
+
const raw = `## Lesson — ${part}`;
|
|
79
|
+
lessons.push({ heading, tags, body, raw, index: i - 1 });
|
|
80
|
+
}
|
|
81
|
+
return lessons;
|
|
82
|
+
}
|
|
83
|
+
// ─── File reference extraction ─────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* Extract file path references from a lesson body.
|
|
86
|
+
* Looks for backtick-wrapped content that looks like a real file path.
|
|
87
|
+
*/
|
|
88
|
+
export function extractFileReferences(body) {
|
|
89
|
+
const refs = new Set();
|
|
90
|
+
// Split by code fences and only process content outside them (even-indexed parts)
|
|
91
|
+
const segments = body.split('```');
|
|
92
|
+
for (let i = 0; i < segments.length; i += 2) {
|
|
93
|
+
const segment = segments[i];
|
|
94
|
+
const inlineCodeRe = /(?<!`)`([^`\n]+)`(?!`)/g;
|
|
95
|
+
let match;
|
|
96
|
+
while ((match = inlineCodeRe.exec(segment)) !== null) {
|
|
97
|
+
const candidate = match[1].trim();
|
|
98
|
+
// Must contain a forward slash (path separator)
|
|
99
|
+
if (!candidate.includes('/'))
|
|
100
|
+
continue;
|
|
101
|
+
// Exclude URLs
|
|
102
|
+
if (candidate.startsWith('http://') || candidate.startsWith('https://'))
|
|
103
|
+
continue;
|
|
104
|
+
// Exclude glob patterns
|
|
105
|
+
if (candidate.includes('*') || candidate.includes('?'))
|
|
106
|
+
continue;
|
|
107
|
+
// Exclude npm package names (@scope/name)
|
|
108
|
+
if (candidate.startsWith('@') && !candidate.startsWith('.'))
|
|
109
|
+
continue;
|
|
110
|
+
// Exclude shell commands with flags
|
|
111
|
+
if (candidate.includes(' -') || candidate.includes(' --'))
|
|
112
|
+
continue;
|
|
113
|
+
// Must have a recognized file extension
|
|
114
|
+
const ext = path.extname(candidate).toLowerCase();
|
|
115
|
+
if (!ext || !FILE_EXTENSIONS.has(ext))
|
|
116
|
+
continue;
|
|
117
|
+
// Normalize path separators
|
|
118
|
+
refs.add(candidate.replace(/\\/g, '/'));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return [...refs];
|
|
122
|
+
}
|
|
123
|
+
// ─── Drift detection ──────────────────────────────────
|
|
124
|
+
/**
|
|
125
|
+
* Check parsed lessons for file references that no longer exist on disk.
|
|
126
|
+
* Returns only lessons that have at least one orphaned reference.
|
|
127
|
+
*/
|
|
128
|
+
export function detectDrift(lessons, projectRoot) {
|
|
129
|
+
const results = [];
|
|
130
|
+
for (const lesson of lessons) {
|
|
131
|
+
const refs = extractFileReferences(lesson.body);
|
|
132
|
+
if (refs.length === 0)
|
|
133
|
+
continue;
|
|
134
|
+
const orphaned = refs.filter((ref) => {
|
|
135
|
+
const absPath = path.join(projectRoot, ref);
|
|
136
|
+
return !fs.existsSync(absPath);
|
|
137
|
+
});
|
|
138
|
+
if (orphaned.length > 0) {
|
|
139
|
+
results.push({ lesson, orphanedRefs: orphaned });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
// ─── Lesson file rewriting ────────────────────────────
|
|
145
|
+
/**
|
|
146
|
+
* Rewrite lessons.md content, removing lessons at the specified indices.
|
|
147
|
+
* Returns the new file content.
|
|
148
|
+
*/
|
|
149
|
+
export function rewriteLessonsFile(content, indicesToRemove) {
|
|
150
|
+
if (indicesToRemove.size === 0)
|
|
151
|
+
return content;
|
|
152
|
+
// Split on lesson headings to preserve the file header
|
|
153
|
+
const parts = content.split(LESSON_HEADING_RE);
|
|
154
|
+
const header = parts[0]; // Everything before the first lesson
|
|
155
|
+
// Reconstruct: header + kept lessons
|
|
156
|
+
const kept = [header];
|
|
157
|
+
for (let i = 1; i < parts.length; i++) {
|
|
158
|
+
if (!indicesToRemove.has(i - 1)) {
|
|
159
|
+
kept.push(`## Lesson — ${parts[i]}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Join, clean up excessive blank lines, and ensure single trailing newline
|
|
163
|
+
let result = kept.join('');
|
|
164
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
165
|
+
result = result.trimEnd() + '\n';
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=drift-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detector.js","sourceRoot":"","sources":["../src/drift-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detector.test.d.ts","sourceRoot":"","sources":["../src/drift-detector.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { detectDrift, extractFileReferences, parseLessonsFile, rewriteLessonsFile, } from './drift-detector.js';
|
|
6
|
+
// ─── parseLessonsFile ─────────────────────────────────
|
|
7
|
+
describe('parseLessonsFile', () => {
|
|
8
|
+
it('parses a standard lessons file with multiple entries', () => {
|
|
9
|
+
const content = `# Totem Lessons
|
|
10
|
+
|
|
11
|
+
Lessons learned from PR reviews and Shield checks.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Lesson — First heading
|
|
16
|
+
|
|
17
|
+
**Tags:** tag1, tag2
|
|
18
|
+
|
|
19
|
+
Body of the first lesson.
|
|
20
|
+
|
|
21
|
+
## Lesson — Second heading
|
|
22
|
+
|
|
23
|
+
**Tags:** tag3
|
|
24
|
+
|
|
25
|
+
Body of the second lesson with multiple lines.
|
|
26
|
+
And another line.
|
|
27
|
+
`;
|
|
28
|
+
const lessons = parseLessonsFile(content);
|
|
29
|
+
expect(lessons).toHaveLength(2);
|
|
30
|
+
expect(lessons[0].heading).toBe('First heading');
|
|
31
|
+
expect(lessons[0].tags).toEqual(['tag1', 'tag2']);
|
|
32
|
+
expect(lessons[0].body).toBe('Body of the first lesson.');
|
|
33
|
+
expect(lessons[0].index).toBe(0);
|
|
34
|
+
expect(lessons[1].heading).toBe('Second heading');
|
|
35
|
+
expect(lessons[1].tags).toEqual(['tag3']);
|
|
36
|
+
expect(lessons[1].body).toContain('Body of the second lesson');
|
|
37
|
+
expect(lessons[1].body).toContain('And another line.');
|
|
38
|
+
expect(lessons[1].index).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
it('handles lessons with timestamp headings', () => {
|
|
41
|
+
const content = `# Totem Lessons
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Lesson — 2026-03-06T03:27:22.818Z
|
|
46
|
+
|
|
47
|
+
**Tags:** lancedb, trap
|
|
48
|
+
|
|
49
|
+
Some lesson body.
|
|
50
|
+
`;
|
|
51
|
+
const lessons = parseLessonsFile(content);
|
|
52
|
+
expect(lessons).toHaveLength(1);
|
|
53
|
+
expect(lessons[0].heading).toBe('2026-03-06T03:27:22.818Z');
|
|
54
|
+
});
|
|
55
|
+
it('returns empty array for file with no lessons', () => {
|
|
56
|
+
const content = `# Totem Lessons
|
|
57
|
+
|
|
58
|
+
Lessons learned from PR reviews.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
`;
|
|
62
|
+
const lessons = parseLessonsFile(content);
|
|
63
|
+
expect(lessons).toHaveLength(0);
|
|
64
|
+
});
|
|
65
|
+
it('preserves raw text for reconstruction', () => {
|
|
66
|
+
const content = `# Header
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Lesson — Test
|
|
71
|
+
|
|
72
|
+
**Tags:** a
|
|
73
|
+
|
|
74
|
+
Body text.
|
|
75
|
+
`;
|
|
76
|
+
const lessons = parseLessonsFile(content);
|
|
77
|
+
expect(lessons[0].raw).toContain('## Lesson — Test');
|
|
78
|
+
expect(lessons[0].raw).toContain('Body text.');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// ─── extractFileReferences ────────────────────────────
|
|
82
|
+
describe('extractFileReferences', () => {
|
|
83
|
+
it('extracts backtick-wrapped file paths', () => {
|
|
84
|
+
const body = 'The issue is in `packages/core/src/sync.ts` and also affects `src/utils.ts`.';
|
|
85
|
+
const refs = extractFileReferences(body);
|
|
86
|
+
expect(refs).toEqual(['packages/core/src/sync.ts', 'src/utils.ts']);
|
|
87
|
+
});
|
|
88
|
+
it('ignores non-path backtick content', () => {
|
|
89
|
+
const body = 'Use `totem sync` to re-index. The `filePath` column uses `ENOBUFS` error code.';
|
|
90
|
+
const refs = extractFileReferences(body);
|
|
91
|
+
expect(refs).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
it('ignores URLs', () => {
|
|
94
|
+
const body = 'See `https://example.com/path/file.ts` for details.';
|
|
95
|
+
const refs = extractFileReferences(body);
|
|
96
|
+
expect(refs).toHaveLength(0);
|
|
97
|
+
});
|
|
98
|
+
it('ignores glob patterns', () => {
|
|
99
|
+
const body = 'Add `src/**/*.ts` to your config.';
|
|
100
|
+
const refs = extractFileReferences(body);
|
|
101
|
+
expect(refs).toHaveLength(0);
|
|
102
|
+
});
|
|
103
|
+
it('ignores npm package names', () => {
|
|
104
|
+
const body = 'Import from `@mmnto/totem` package.';
|
|
105
|
+
const refs = extractFileReferences(body);
|
|
106
|
+
expect(refs).toHaveLength(0);
|
|
107
|
+
});
|
|
108
|
+
it('ignores shell commands with flags', () => {
|
|
109
|
+
const body = 'Run `git diff --name-only HEAD~1` to see changes.';
|
|
110
|
+
const refs = extractFileReferences(body);
|
|
111
|
+
expect(refs).toHaveLength(0);
|
|
112
|
+
});
|
|
113
|
+
it('handles dotfile paths', () => {
|
|
114
|
+
const body = 'Check `.totem/lessons.md` for existing entries.';
|
|
115
|
+
const refs = extractFileReferences(body);
|
|
116
|
+
expect(refs).toEqual(['.totem/lessons.md']);
|
|
117
|
+
});
|
|
118
|
+
it('deduplicates references', () => {
|
|
119
|
+
const body = 'The file `src/index.ts` is important. Also see `src/index.ts` again.';
|
|
120
|
+
const refs = extractFileReferences(body);
|
|
121
|
+
expect(refs).toEqual(['src/index.ts']);
|
|
122
|
+
});
|
|
123
|
+
it('ignores content inside code blocks', () => {
|
|
124
|
+
// Code fences use triple backticks — our regex excludes them
|
|
125
|
+
const body = 'The path `src/real.ts` is referenced but ```\n`src/fake.ts`\n``` is in a block.';
|
|
126
|
+
const refs = extractFileReferences(body);
|
|
127
|
+
expect(refs).toContain('src/real.ts');
|
|
128
|
+
expect(refs).not.toContain('src/fake.ts');
|
|
129
|
+
});
|
|
130
|
+
it('handles paths without leading directory', () => {
|
|
131
|
+
const body = 'Edit `config/database.json` to add the new table.';
|
|
132
|
+
const refs = extractFileReferences(body);
|
|
133
|
+
expect(refs).toEqual(['config/database.json']);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
// ─── detectDrift ──────────────────────────────────────
|
|
137
|
+
describe('detectDrift', () => {
|
|
138
|
+
let tmpDir;
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-drift-'));
|
|
141
|
+
// Create some files
|
|
142
|
+
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
|
|
143
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'index.ts'), '');
|
|
144
|
+
fs.writeFileSync(path.join(tmpDir, 'src', 'utils.ts'), '');
|
|
145
|
+
});
|
|
146
|
+
afterEach(() => {
|
|
147
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
148
|
+
});
|
|
149
|
+
it('returns empty array when all references exist', () => {
|
|
150
|
+
const lessons = parseLessonsFile(`# Header
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Lesson — Test
|
|
155
|
+
|
|
156
|
+
**Tags:** test
|
|
157
|
+
|
|
158
|
+
The file \`src/index.ts\` works fine.
|
|
159
|
+
`);
|
|
160
|
+
const drift = detectDrift(lessons, tmpDir);
|
|
161
|
+
expect(drift).toHaveLength(0);
|
|
162
|
+
});
|
|
163
|
+
it('detects orphaned file references', () => {
|
|
164
|
+
const lessons = parseLessonsFile(`# Header
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Lesson — Test
|
|
169
|
+
|
|
170
|
+
**Tags:** test
|
|
171
|
+
|
|
172
|
+
The file \`src/deleted.ts\` was removed.
|
|
173
|
+
`);
|
|
174
|
+
const drift = detectDrift(lessons, tmpDir);
|
|
175
|
+
expect(drift).toHaveLength(1);
|
|
176
|
+
expect(drift[0].orphanedRefs).toEqual(['src/deleted.ts']);
|
|
177
|
+
expect(drift[0].lesson.heading).toBe('Test');
|
|
178
|
+
});
|
|
179
|
+
it('skips lessons with no file references', () => {
|
|
180
|
+
const lessons = parseLessonsFile(`# Header
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Lesson — General advice
|
|
185
|
+
|
|
186
|
+
**Tags:** general
|
|
187
|
+
|
|
188
|
+
Always use \`const\` instead of \`let\` when possible.
|
|
189
|
+
`);
|
|
190
|
+
const drift = detectDrift(lessons, tmpDir);
|
|
191
|
+
expect(drift).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
it('reports only orphaned refs, not valid ones', () => {
|
|
194
|
+
const lessons = parseLessonsFile(`# Header
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Lesson — Mixed
|
|
199
|
+
|
|
200
|
+
**Tags:** test
|
|
201
|
+
|
|
202
|
+
See \`src/index.ts\` (exists) and \`src/gone.ts\` (deleted).
|
|
203
|
+
`);
|
|
204
|
+
const drift = detectDrift(lessons, tmpDir);
|
|
205
|
+
expect(drift).toHaveLength(1);
|
|
206
|
+
expect(drift[0].orphanedRefs).toEqual(['src/gone.ts']);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
// ─── rewriteLessonsFile ───────────────────────────────
|
|
210
|
+
describe('rewriteLessonsFile', () => {
|
|
211
|
+
const SAMPLE = `# Totem Lessons
|
|
212
|
+
|
|
213
|
+
Lessons learned.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Lesson — First
|
|
218
|
+
|
|
219
|
+
**Tags:** a
|
|
220
|
+
|
|
221
|
+
Body one.
|
|
222
|
+
|
|
223
|
+
## Lesson — Second
|
|
224
|
+
|
|
225
|
+
**Tags:** b
|
|
226
|
+
|
|
227
|
+
Body two.
|
|
228
|
+
|
|
229
|
+
## Lesson — Third
|
|
230
|
+
|
|
231
|
+
**Tags:** c
|
|
232
|
+
|
|
233
|
+
Body three.
|
|
234
|
+
`;
|
|
235
|
+
it('removes specified lessons by index', () => {
|
|
236
|
+
const result = rewriteLessonsFile(SAMPLE, new Set([1]));
|
|
237
|
+
expect(result).toContain('## Lesson — First');
|
|
238
|
+
expect(result).not.toContain('## Lesson — Second');
|
|
239
|
+
expect(result).toContain('## Lesson — Third');
|
|
240
|
+
});
|
|
241
|
+
it('removes multiple lessons', () => {
|
|
242
|
+
const result = rewriteLessonsFile(SAMPLE, new Set([0, 2]));
|
|
243
|
+
expect(result).not.toContain('## Lesson — First');
|
|
244
|
+
expect(result).toContain('## Lesson — Second');
|
|
245
|
+
expect(result).not.toContain('## Lesson — Third');
|
|
246
|
+
});
|
|
247
|
+
it('preserves the file header', () => {
|
|
248
|
+
const result = rewriteLessonsFile(SAMPLE, new Set([0, 1, 2]));
|
|
249
|
+
expect(result).toContain('# Totem Lessons');
|
|
250
|
+
expect(result).toContain('Lessons learned.');
|
|
251
|
+
expect(result).not.toContain('## Lesson —');
|
|
252
|
+
});
|
|
253
|
+
it('returns unchanged content when no indices to remove', () => {
|
|
254
|
+
const result = rewriteLessonsFile(SAMPLE, new Set());
|
|
255
|
+
expect(result).toBe(SAMPLE);
|
|
256
|
+
});
|
|
257
|
+
it('ends with a single newline', () => {
|
|
258
|
+
const result = rewriteLessonsFile(SAMPLE, new Set([2]));
|
|
259
|
+
expect(result.endsWith('\n')).toBe(true);
|
|
260
|
+
expect(result.endsWith('\n\n')).toBe(false);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
//# sourceMappingURL=drift-detector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detector.test.js","sourceRoot":"","sources":["../src/drift-detector.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export { TOTEM_TABLE_NAME } from './store/lance-schema.js';
|
|
|
9
9
|
export { LanceStore } from './store/lance-store.js';
|
|
10
10
|
export type { ResolvedFile } from './ingest/sync.js';
|
|
11
11
|
export { getChangedFiles, getHeadSha, resolveFiles, runSync } from './ingest/sync.js';
|
|
12
|
+
export type { DriftResult, ParsedLesson } from './drift-detector.js';
|
|
13
|
+
export { detectDrift, extractFileReferences, parseLessonsFile, rewriteLessonsFile, } from './drift-detector.js';
|
|
14
|
+
export type { CompiledRule, CompiledRulesFile, CompilerOutput, Violation } from './compiler.js';
|
|
15
|
+
export { applyRules, CompiledRuleSchema, CompiledRulesFileSchema, CompilerOutputSchema, extractAddedLines, hashLesson, loadCompiledRules, parseCompilerResponse, type RegexValidation, saveCompiledRules, validateRegex, } from './compiler.js';
|
|
12
16
|
export { generateLessonHeading } from './lesson-format.js';
|
|
13
17
|
export { sanitize } from './sanitize.js';
|
|
14
18
|
export { wrapXml } from './xml-format.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,aAAa,EACb,UAAU,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EACV,KAAK,EACL,aAAa,EACb,YAAY,EACZ,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,YAAY,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGpD,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGtF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,aAAa,EACb,UAAU,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAG5B,YAAY,EACV,KAAK,EACL,aAAa,EACb,YAAY,EACZ,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,YAAY,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGpD,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGtF,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAG7B,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,eAAe,EACpB,iBAAiB,EACjB,aAAa,GACd,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export { createEmbedder } from './embedders/embedder.js';
|
|
|
5
5
|
export { TOTEM_TABLE_NAME } from './store/lance-schema.js';
|
|
6
6
|
export { LanceStore } from './store/lance-store.js';
|
|
7
7
|
export { getChangedFiles, getHeadSha, resolveFiles, runSync } from './ingest/sync.js';
|
|
8
|
+
export { detectDrift, extractFileReferences, parseLessonsFile, rewriteLessonsFile, } from './drift-detector.js';
|
|
9
|
+
export { applyRules, CompiledRuleSchema, CompiledRulesFileSchema, CompilerOutputSchema, extractAddedLines, hashLesson, loadCompiledRules, parseCompilerResponse, saveCompiledRules, validateRegex, } from './compiler.js';
|
|
8
10
|
// Utilities
|
|
9
11
|
export { generateLessonHeading } from './lesson-format.js';
|
|
10
12
|
export { sanitize } from './sanitize.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,QAAQ;AACR,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIpD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAc5B,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,QAAQ;AACR,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAIpD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAItF,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EAErB,iBAAiB,EACjB,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,YAAY;AACZ,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmnto/totem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Persistent memory and context layer for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"openai": "^4.77.0",
|
|
19
19
|
"remark-frontmatter": "^5.0.0",
|
|
20
20
|
"remark-parse": "^11.0.0",
|
|
21
|
+
"safe-regex2": "^5.0.0",
|
|
21
22
|
"typescript": "^5.7.0",
|
|
22
23
|
"unified": "^11.0.0",
|
|
23
24
|
"yaml": "^2.4.0",
|