@kernlang/review 2.0.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.
Files changed (47) hide show
  1. package/LICENSE +661 -0
  2. package/dist/differ.d.ts +11 -0
  3. package/dist/differ.js +132 -0
  4. package/dist/differ.js.map +1 -0
  5. package/dist/external-tools.d.ts +32 -0
  6. package/dist/external-tools.js +173 -0
  7. package/dist/external-tools.js.map +1 -0
  8. package/dist/index.d.ts +33 -0
  9. package/dist/index.js +98 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/inferrer.d.ts +15 -0
  12. package/dist/inferrer.js +502 -0
  13. package/dist/inferrer.js.map +1 -0
  14. package/dist/llm-review.d.ts +24 -0
  15. package/dist/llm-review.js +197 -0
  16. package/dist/llm-review.js.map +1 -0
  17. package/dist/quality-rules.d.ts +12 -0
  18. package/dist/quality-rules.js +28 -0
  19. package/dist/quality-rules.js.map +1 -0
  20. package/dist/reporter.d.ts +15 -0
  21. package/dist/reporter.js +217 -0
  22. package/dist/reporter.js.map +1 -0
  23. package/dist/rules/base.d.ts +10 -0
  24. package/dist/rules/base.js +556 -0
  25. package/dist/rules/base.js.map +1 -0
  26. package/dist/rules/express.d.ts +9 -0
  27. package/dist/rules/express.js +107 -0
  28. package/dist/rules/express.js.map +1 -0
  29. package/dist/rules/index.d.ts +16 -0
  30. package/dist/rules/index.js +38 -0
  31. package/dist/rules/index.js.map +1 -0
  32. package/dist/rules/nextjs.d.ts +9 -0
  33. package/dist/rules/nextjs.js +128 -0
  34. package/dist/rules/nextjs.js.map +1 -0
  35. package/dist/rules/react.d.ts +9 -0
  36. package/dist/rules/react.js +252 -0
  37. package/dist/rules/react.js.map +1 -0
  38. package/dist/rules/vue.d.ts +9 -0
  39. package/dist/rules/vue.js +198 -0
  40. package/dist/rules/vue.js.map +1 -0
  41. package/dist/template-detector.d.ts +12 -0
  42. package/dist/template-detector.js +225 -0
  43. package/dist/template-detector.js.map +1 -0
  44. package/dist/types.d.ts +152 -0
  45. package/dist/types.js +17 -0
  46. package/dist/types.js.map +1 -0
  47. package/package.json +23 -0
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Vue review rules — active when target = vue | nuxt.
3
+ *
4
+ * Catches Vue 3 Composition API pitfalls.
5
+ */
6
+ import { SyntaxKind } from 'ts-morph';
7
+ import { createFingerprint } from '../types.js';
8
+ function span(file, line, col = 1) {
9
+ return { file, startLine: line, startCol: col, endLine: line, endCol: col };
10
+ }
11
+ function finding(ruleId, severity, category, message, file, line, extra) {
12
+ return {
13
+ source: 'kern',
14
+ ruleId,
15
+ severity,
16
+ category,
17
+ message,
18
+ primarySpan: span(file, line),
19
+ fingerprint: createFingerprint(ruleId, line, 1),
20
+ ...extra,
21
+ };
22
+ }
23
+ // ── Rule 17: missing-ref-value ───────────────────────────────────────────
24
+ // Using ref() result without .value in script setup
25
+ function missingRefValue(ctx) {
26
+ const findings = [];
27
+ // AST-based: find ref() declarations via variable statements
28
+ const refVarNames = new Map(); // name → declaration line
29
+ for (const stmt of ctx.sourceFile.getVariableStatements()) {
30
+ for (const decl of stmt.getDeclarations()) {
31
+ const init = decl.getInitializer();
32
+ if (!init)
33
+ continue;
34
+ // Match ref() or ref<T>() calls
35
+ if (init.getKind() === SyntaxKind.CallExpression) {
36
+ const call = init;
37
+ const calleeName = call.getExpression().getText();
38
+ if (calleeName === 'ref') {
39
+ refVarNames.set(decl.getName(), stmt.getStartLineNumber());
40
+ }
41
+ }
42
+ }
43
+ }
44
+ if (refVarNames.size === 0)
45
+ return findings;
46
+ // Walk all identifiers and check if ref vars are used without .value
47
+ for (const ident of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {
48
+ const name = ident.getText();
49
+ if (!refVarNames.has(name))
50
+ continue;
51
+ // Skip the declaration itself
52
+ if (ident.getStartLineNumber() === refVarNames.get(name))
53
+ continue;
54
+ const parent = ident.getParent();
55
+ if (!parent)
56
+ continue;
57
+ // If parent is PropertyAccessExpression and ident is the object, check if accessing .value
58
+ if (parent.getKind() === SyntaxKind.PropertyAccessExpression) {
59
+ const propAccess = parent;
60
+ if (propAccess.getExpression() === ident) {
61
+ if (propAccess.getName() === 'value')
62
+ continue; // correct: ref.value
63
+ // Accessing some other property on ref without .value — still a bug
64
+ }
65
+ }
66
+ // Skip if ref is passed as a function argument (intentional: watch(myRef), toRef(myRef))
67
+ if (parent.getKind() === SyntaxKind.CallExpression)
68
+ continue;
69
+ // Also skip if it's an argument in a call's argument list
70
+ const grandparent = parent.getParent();
71
+ if (grandparent?.getKind() === SyntaxKind.CallExpression)
72
+ continue;
73
+ // Skip type contexts
74
+ if (parent.getKind() === SyntaxKind.TypeReference)
75
+ continue;
76
+ if (parent.getKind() === SyntaxKind.TypeQuery)
77
+ continue;
78
+ // Skip shorthand property assignments: { count } in object literals
79
+ if (parent.getKind() === SyntaxKind.ShorthandPropertyAssignment)
80
+ continue;
81
+ // Skip imports and variable declarations
82
+ if (parent.getKind() === SyntaxKind.ImportSpecifier)
83
+ continue;
84
+ if (parent.getKind() === SyntaxKind.VariableDeclaration)
85
+ continue;
86
+ // Likely a bug: ref used in expression context without .value
87
+ if (parent.getKind() === SyntaxKind.BinaryExpression ||
88
+ parent.getKind() === SyntaxKind.ConditionalExpression ||
89
+ parent.getKind() === SyntaxKind.TemplateSpan ||
90
+ parent.getKind() === SyntaxKind.ReturnStatement ||
91
+ parent.getKind() === SyntaxKind.ElementAccessExpression) {
92
+ findings.push(finding('missing-ref-value', 'warning', 'bug', `'${name}' is a ref — did you mean '${name}.value'?`, ctx.filePath, ident.getStartLineNumber(), { suggestion: `${name}.value` }));
93
+ // One finding per ref variable to avoid noise
94
+ refVarNames.delete(name);
95
+ }
96
+ }
97
+ return findings;
98
+ }
99
+ // ── Rule 18: missing-onUnmounted ─────────────────────────────────────────
100
+ // watch/addEventListener without cleanup in onUnmounted
101
+ function missingOnUnmounted(ctx) {
102
+ const findings = [];
103
+ // AST pre-check: find onUnmounted/onBeforeUnmount CallExpressions
104
+ let hasLifecycleCleanup = false;
105
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
106
+ const callee = call.getExpression();
107
+ if (callee.getKind() === SyntaxKind.Identifier) {
108
+ const name = callee.getText();
109
+ if (name === 'onUnmounted' || name === 'onBeforeUnmount') {
110
+ hasLifecycleCleanup = true;
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ // Check for watch() calls via AST
116
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
117
+ const callee = call.getExpression();
118
+ if (callee.getKind() !== SyntaxKind.Identifier || callee.getText() !== 'watch')
119
+ continue;
120
+ // Check if parent is VariableDeclaration (stop handle assigned)
121
+ const parent = call.getParent();
122
+ const hasStopHandle = parent?.getKind() === SyntaxKind.VariableDeclaration;
123
+ if (!hasStopHandle && !hasLifecycleCleanup) {
124
+ findings.push(finding('missing-onUnmounted', 'error', 'bug', 'watch() without stop handle or onUnmounted cleanup — potential memory leak', ctx.filePath, call.getStartLineNumber(), { suggestion: 'Assign watch to a variable and call stop() in onUnmounted, or use watchEffect (auto-stops)' }));
125
+ }
126
+ }
127
+ // Check for addEventListener via AST
128
+ let hasRemoveListener = false;
129
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
130
+ const callee = call.getExpression();
131
+ if (callee.getKind() === SyntaxKind.PropertyAccessExpression) {
132
+ const pa = callee;
133
+ if (pa.getName() === 'removeEventListener') {
134
+ hasRemoveListener = true;
135
+ break;
136
+ }
137
+ }
138
+ }
139
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
140
+ const callee = call.getExpression();
141
+ if (callee.getKind() !== SyntaxKind.PropertyAccessExpression)
142
+ continue;
143
+ const pa = callee;
144
+ if (pa.getName() !== 'addEventListener')
145
+ continue;
146
+ if (!hasRemoveListener && !hasLifecycleCleanup) {
147
+ findings.push(finding('missing-onUnmounted', 'error', 'bug', 'addEventListener without removeEventListener in onUnmounted — memory leak', ctx.filePath, call.getStartLineNumber(), { suggestion: 'Clean up event listeners in onUnmounted()' }));
148
+ }
149
+ }
150
+ return findings;
151
+ }
152
+ // ── Rule 19: setup-side-effect ───────────────────────────────────────────
153
+ // Async call in setup() without onMounted wrapper
154
+ function setupSideEffect(ctx) {
155
+ const findings = [];
156
+ const fullText = ctx.sourceFile.getFullText();
157
+ // Detect <script setup> or setup() function
158
+ const isScriptSetup = fullText.includes('<script setup') || fullText.includes('defineComponent');
159
+ if (!isScriptSetup)
160
+ return findings;
161
+ // Check for top-level await without onMounted
162
+ const hasOnMounted = fullText.includes('onMounted');
163
+ const awaitRegex = /(?:^|\n)\s*(?:const|let|var)?\s*\w*\s*=?\s*await\s/g;
164
+ let match;
165
+ while ((match = awaitRegex.exec(fullText)) !== null) {
166
+ const line = fullText.substring(0, match.index).split('\n').length;
167
+ // Skip if inside a function body
168
+ const lineText = fullText.split('\n')[line - 1] || '';
169
+ if (lineText.trim().startsWith('//') || lineText.trim().startsWith('*'))
170
+ continue;
171
+ if (!hasOnMounted) {
172
+ findings.push(finding('setup-side-effect', 'warning', 'pattern', 'Top-level await in setup — consider wrapping in onMounted() for SSR compatibility', ctx.filePath, line, { suggestion: 'onMounted(async () => { ... })' }));
173
+ }
174
+ }
175
+ return findings;
176
+ }
177
+ // ── Rule 20: reactive-destructure ────────────────────────────────────────
178
+ // Destructuring reactive() loses reactivity
179
+ function reactiveDestructure(ctx) {
180
+ const findings = [];
181
+ const fullText = ctx.sourceFile.getFullText();
182
+ // Find const { ... } = reactive(...)
183
+ const destructRegex = /(?:const|let)\s*\{[^}]+\}\s*=\s*reactive\s*\(/g;
184
+ let match;
185
+ while ((match = destructRegex.exec(fullText)) !== null) {
186
+ const line = fullText.substring(0, match.index).split('\n').length;
187
+ findings.push(finding('reactive-destructure', 'warning', 'bug', 'Destructuring reactive() loses reactivity — use toRefs() or access properties directly', ctx.filePath, line, { suggestion: 'const state = reactive({...}); use state.prop, or const { prop } = toRefs(state)' }));
188
+ }
189
+ return findings;
190
+ }
191
+ // ── Exported Vue Rules ───────────────────────────────────────────────────
192
+ export const vueRules = [
193
+ missingRefValue,
194
+ missingOnUnmounted,
195
+ setupSideEffect,
196
+ reactiveDestructure,
197
+ ];
198
+ //# sourceMappingURL=vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vue.js","sourceRoot":"","sources":["../../src/rules/vue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Template Detector — matches code against known library patterns.
3
+ *
4
+ * Import-anchored detection: look at import sources to identify libraries,
5
+ * then pattern-match the usage and extract slot values for .kern rewrites.
6
+ *
7
+ * When registeredTemplates are provided (from kern.config.ts), the detector
8
+ * generates suggested .kern rewrites with actual slot values filled in.
9
+ */
10
+ import { SourceFile } from 'ts-morph';
11
+ import type { TemplateMatch, ReviewConfig } from './types.js';
12
+ export declare function detectTemplates(sourceFile: SourceFile, config?: ReviewConfig): TemplateMatch[];
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Template Detector — matches code against known library patterns.
3
+ *
4
+ * Import-anchored detection: look at import sources to identify libraries,
5
+ * then pattern-match the usage and extract slot values for .kern rewrites.
6
+ *
7
+ * When registeredTemplates are provided (from kern.config.ts), the detector
8
+ * generates suggested .kern rewrites with actual slot values filled in.
9
+ */
10
+ import { countTokens } from '@kernlang/core';
11
+ // ── Slot Extractors ──────────────────────────────────────────────────────
12
+ function extractZustandSlots(sourceFile, fullText) {
13
+ // Pattern: const useXStore = create<StateType>((set, get) => ({...}))
14
+ // or: export const useXStore = create<StateType>()(...)
15
+ const match = fullText.match(/(?:export\s+)?const\s+use(\w+)Store\s*=\s*create\s*<\s*(\w+)\s*>/);
16
+ if (match) {
17
+ return { storeName: match[1], stateType: match[2] };
18
+ }
19
+ // Fallback: look for create<X> without the useXStore naming
20
+ const match2 = fullText.match(/create\s*<\s*(\w+)\s*>/);
21
+ if (match2) {
22
+ // Try to find the variable name
23
+ const varMatch = fullText.match(/(?:export\s+)?const\s+(\w+)\s*=\s*create/);
24
+ const storeName = varMatch ? varMatch[1].replace(/^use/, '').replace(/Store$/, '') : match2[1];
25
+ return { storeName, stateType: match2[1] };
26
+ }
27
+ return null;
28
+ }
29
+ function extractSwrSlots(sourceFile, fullText) {
30
+ // Pattern: function useX() { ... useSWR(key, fetcher) ... }
31
+ const fnMatch = fullText.match(/function\s+(use\w+)\s*\(/);
32
+ const swrMatch = fullText.match(/useSWR\s*\(\s*([^,)]+)/);
33
+ if (fnMatch && swrMatch) {
34
+ return { hookName: fnMatch[1], cacheKey: swrMatch[1].trim() };
35
+ }
36
+ return null;
37
+ }
38
+ function extractQuerySlots(sourceFile, fullText) {
39
+ const fnMatch = fullText.match(/function\s+(use\w+)\s*\(/);
40
+ const keyMatch = fullText.match(/queryKey\s*:\s*\[([^\]]+)\]/);
41
+ const fnBodyMatch = fullText.match(/queryFn\s*:\s*(\w+)/);
42
+ if (fnMatch && keyMatch) {
43
+ return {
44
+ hookName: fnMatch[1],
45
+ queryKey: keyMatch[1].trim(),
46
+ ...(fnBodyMatch ? { queryFn: fnBodyMatch[1] } : {}),
47
+ };
48
+ }
49
+ return null;
50
+ }
51
+ function extractJotaiSlots(sourceFile, fullText) {
52
+ const match = fullText.match(/(?:export\s+)?const\s+(\w+)Atom\s*=\s*atom\s*<\s*(\w+)\s*>\s*\(\s*([^)]+)\s*\)/);
53
+ if (match) {
54
+ return { atomName: match[1], atomType: match[2], initialValue: match[3].trim() };
55
+ }
56
+ return null;
57
+ }
58
+ function extractZodSlots(sourceFile, fullText) {
59
+ const match = fullText.match(/(?:export\s+)?const\s+(\w+)(?:Schema)?\s*=\s*z\.object\s*\(/);
60
+ if (match) {
61
+ return { schemaName: match[1] };
62
+ }
63
+ return null;
64
+ }
65
+ // ── Patterns ─────────────────────────────────────────────────────────────
66
+ const PATTERNS = [
67
+ {
68
+ templateName: 'zustand-store',
69
+ libraryName: 'zustand',
70
+ importSource: 'zustand',
71
+ anchorImport: 'create',
72
+ bodyHint: /create\s*(<|[\s(])/,
73
+ confidencePct: 92,
74
+ extractSlots: extractZustandSlots,
75
+ },
76
+ {
77
+ templateName: 'zustand-selector',
78
+ libraryName: 'zustand',
79
+ importSource: 'zustand',
80
+ anchorImport: 'create',
81
+ bodyHint: /useStore\s*\(\s*\(\s*state\s*\)/,
82
+ confidencePct: 85,
83
+ },
84
+ {
85
+ templateName: 'swr-hook',
86
+ libraryName: 'SWR',
87
+ importSource: 'swr',
88
+ anchorImport: 'useSWR',
89
+ bodyHint: /useSWR\s*\(/,
90
+ confidencePct: 90,
91
+ extractSlots: extractSwrSlots,
92
+ },
93
+ {
94
+ templateName: 'query-hook',
95
+ libraryName: 'TanStack Query',
96
+ importSource: /tanstack\/.*query/,
97
+ anchorImport: 'useQuery',
98
+ bodyHint: /useQuery\s*\(\s*\{/,
99
+ confidencePct: 90,
100
+ extractSlots: extractQuerySlots,
101
+ },
102
+ {
103
+ templateName: 'mutation-hook',
104
+ libraryName: 'TanStack Query',
105
+ importSource: /tanstack\/.*query/,
106
+ anchorImport: 'useMutation',
107
+ bodyHint: /useMutation\s*\(\s*\{/,
108
+ confidencePct: 88,
109
+ },
110
+ {
111
+ templateName: 'xstate-machine',
112
+ libraryName: 'XState',
113
+ importSource: 'xstate',
114
+ anchorImport: 'createMachine',
115
+ bodyHint: /createMachine\s*\(/,
116
+ confidencePct: 90,
117
+ },
118
+ {
119
+ templateName: 'jotai-atom',
120
+ libraryName: 'Jotai',
121
+ importSource: 'jotai',
122
+ anchorImport: 'atom',
123
+ bodyHint: /atom\s*(<|\()/,
124
+ confidencePct: 88,
125
+ extractSlots: extractJotaiSlots,
126
+ },
127
+ {
128
+ templateName: 'trpc-router',
129
+ libraryName: 'tRPC',
130
+ importSource: /@trpc/,
131
+ anchorImport: 'router',
132
+ bodyHint: /\.router\s*\(/,
133
+ confidencePct: 85,
134
+ },
135
+ {
136
+ templateName: 'zod-schema',
137
+ libraryName: 'Zod',
138
+ importSource: 'zod',
139
+ anchorImport: 'z',
140
+ bodyHint: /z\.object\s*\(/,
141
+ confidencePct: 88,
142
+ extractSlots: extractZodSlots,
143
+ },
144
+ ];
145
+ // ── Build .kern suggestion ───────────────────────────────────────────────
146
+ function buildKernSuggestion(templateName, slots) {
147
+ const parts = [templateName];
148
+ for (const [key, value] of Object.entries(slots)) {
149
+ if (value.includes(' ')) {
150
+ parts.push(`${key}="${value}"`);
151
+ }
152
+ else {
153
+ parts.push(`${key}=${value}`);
154
+ }
155
+ }
156
+ return parts.join(' ');
157
+ }
158
+ // ── Main Detector ────────────────────────────────────────────────────────
159
+ export function detectTemplates(sourceFile, config) {
160
+ const results = [];
161
+ const fullText = sourceFile.getFullText();
162
+ const totalTokens = countTokens(fullText);
163
+ const imports = sourceFile.getImportDeclarations();
164
+ const registeredTemplates = new Set(config?.registeredTemplates || []);
165
+ for (const pattern of PATTERNS) {
166
+ const matchingImport = imports.find(imp => {
167
+ const source = imp.getModuleSpecifierValue();
168
+ if (typeof pattern.importSource === 'string') {
169
+ return source === pattern.importSource || source.startsWith(pattern.importSource + '/');
170
+ }
171
+ return pattern.importSource.test(source);
172
+ });
173
+ if (!matchingImport)
174
+ continue;
175
+ const namedImports = matchingImport.getNamedImports().map(n => n.getName());
176
+ const defaultImport = matchingImport.getDefaultImport()?.getText();
177
+ const hasAnchor = namedImports.includes(pattern.anchorImport) ||
178
+ defaultImport === pattern.anchorImport;
179
+ if (!hasAnchor)
180
+ continue;
181
+ let confidence = pattern.confidencePct;
182
+ if (pattern.bodyHint) {
183
+ const bodyMatches = typeof pattern.bodyHint === 'string'
184
+ ? fullText.includes(pattern.bodyHint)
185
+ : pattern.bodyHint.test(fullText);
186
+ if (!bodyMatches) {
187
+ confidence -= 15;
188
+ }
189
+ }
190
+ if (confidence < 50)
191
+ continue;
192
+ const startLine = matchingImport.getStartLineNumber();
193
+ const endLine = sourceFile.getEndLineNumber();
194
+ // Extract slot values if extractor exists
195
+ const slotValues = pattern.extractSlots
196
+ ? pattern.extractSlots(sourceFile, fullText) ?? undefined
197
+ : undefined;
198
+ // Build .kern suggestion if template is registered and slots extracted
199
+ let suggestedKern;
200
+ let kernTokens;
201
+ const isRegistered = registeredTemplates.has(pattern.templateName);
202
+ if (slotValues) {
203
+ suggestedKern = buildKernSuggestion(pattern.templateName, slotValues);
204
+ kernTokens = countTokens(suggestedKern);
205
+ // Boost confidence when template is registered
206
+ if (isRegistered) {
207
+ confidence = Math.min(confidence + 5, 99);
208
+ }
209
+ }
210
+ results.push({
211
+ templateName: pattern.templateName,
212
+ libraryName: pattern.libraryName,
213
+ anchorImport: pattern.anchorImport,
214
+ confidencePct: confidence,
215
+ startLine,
216
+ endLine,
217
+ slotValues,
218
+ suggestedKern,
219
+ kernTokens,
220
+ tsTokens: totalTokens,
221
+ });
222
+ }
223
+ return results;
224
+ }
225
+ //# sourceMappingURL=template-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-detector.js","sourceRoot":"","sources":["../src/template-detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Types for @kernlang/review — TS → .kern inference, review pipeline, unified findings.
3
+ *
4
+ * v2: Unified ReviewFinding replaces QualityFinding + DiffFinding.
5
+ * InferResult gains stable nodeId + promptAlias + sourceSpans.
6
+ */
7
+ import type { IRNode } from '@kernlang/core';
8
+ /** Exact location in a source file */
9
+ export interface SourceSpan {
10
+ file: string;
11
+ startLine: number;
12
+ startCol: number;
13
+ endLine: number;
14
+ endCol: number;
15
+ }
16
+ /** Unified finding from any review layer */
17
+ export interface ReviewFinding {
18
+ /** Which layer produced this finding */
19
+ source: 'kern' | 'eslint' | 'tsc' | 'llm';
20
+ /** Rule identifier (e.g., 'memory-leak', 'floating-promise') */
21
+ ruleId: string;
22
+ /** Severity level */
23
+ severity: 'error' | 'warning' | 'info';
24
+ /** Finding category */
25
+ category: 'bug' | 'type' | 'pattern' | 'style' | 'structure';
26
+ /** Human-readable message */
27
+ message: string;
28
+ /** Primary source location */
29
+ primarySpan: SourceSpan;
30
+ /** Related locations (e.g., definition + usage) */
31
+ relatedSpans?: SourceSpan[];
32
+ /** Associated KERN nodeIds */
33
+ nodeIds?: string[];
34
+ /** Fix suggestion */
35
+ suggestion?: string;
36
+ /** Confidence (0-1, for LLM findings) */
37
+ confidence?: number;
38
+ /** Stable fingerprint for dedup across sources */
39
+ fingerprint: string;
40
+ }
41
+ /** Confidence level for an inference match */
42
+ export type Confidence = 'high' | 'medium' | 'low';
43
+ /** Result of inferring a single TS construct as a KERN node */
44
+ export interface InferResult {
45
+ /** The inferred KERN IR node */
46
+ node: IRNode;
47
+ /** Stable internal ID: file#type:name@offset */
48
+ nodeId: string;
49
+ /** Short alias for LLM prompts: "N1", "N2", etc. (assigned after sort) */
50
+ promptAlias: string;
51
+ /** Source location in the original TS file */
52
+ startLine: number;
53
+ endLine: number;
54
+ /** Exact TS source spans for this construct */
55
+ sourceSpans: SourceSpan[];
56
+ /** What was detected (human-readable summary) */
57
+ summary: string;
58
+ /** Confidence of the inference */
59
+ confidence: Confidence;
60
+ /** Confidence percentage (0-100) */
61
+ confidencePct: number;
62
+ /** KERN token count for this construct */
63
+ kernTokens: number;
64
+ /** Original TS token count for this construct */
65
+ tsTokens: number;
66
+ }
67
+ /** A template pattern match */
68
+ export interface TemplateMatch {
69
+ /** Template name (e.g., 'zustand-store', 'swr-hook') */
70
+ templateName: string;
71
+ /** Library name (e.g., 'zustand', 'swr') */
72
+ libraryName: string;
73
+ /** Import that anchored the detection */
74
+ anchorImport: string;
75
+ /** Confidence percentage */
76
+ confidencePct: number;
77
+ /** Source location */
78
+ startLine: number;
79
+ endLine: number;
80
+ /** Suggested .kern rewrite (when template is registered) */
81
+ suggestedKern?: string;
82
+ /** Extracted slot values from code analysis */
83
+ slotValues?: Record<string, string>;
84
+ /** Estimated KERN tokens for the suggested rewrite */
85
+ kernTokens?: number;
86
+ /** Original TS tokens covered by this match */
87
+ tsTokens?: number;
88
+ }
89
+ /** Full review report for a single file */
90
+ export interface ReviewReport {
91
+ /** File path that was reviewed */
92
+ filePath: string;
93
+ /** Inferred KERN constructs */
94
+ inferred: InferResult[];
95
+ /** Template pattern matches */
96
+ templateMatches: TemplateMatch[];
97
+ /** All findings from every review layer (unified) */
98
+ findings: ReviewFinding[];
99
+ /** Summary stats */
100
+ stats: ReviewStats;
101
+ }
102
+ /** Summary statistics for a review */
103
+ export interface ReviewStats {
104
+ /** Total lines in the original file */
105
+ totalLines: number;
106
+ /** Lines covered by KERN inferences */
107
+ coveredLines: number;
108
+ /** Coverage percentage */
109
+ coveragePct: number;
110
+ /** Total TS tokens */
111
+ totalTsTokens: number;
112
+ /** Total KERN tokens (if re-expressed as .kern) */
113
+ totalKernTokens: number;
114
+ /** Token reduction percentage */
115
+ reductionPct: number;
116
+ /** Number of KERN-expressible constructs */
117
+ constructCount: number;
118
+ }
119
+ /** Enforcement result for CI */
120
+ export interface EnforceResult {
121
+ /** Whether enforcement passed */
122
+ passed: boolean;
123
+ /** Minimum coverage threshold */
124
+ minCoverage: number;
125
+ /** Actual coverage */
126
+ actualCoverage: number;
127
+ /** Template violations (detected pattern but no KERN template used) */
128
+ templateViolations: string[];
129
+ }
130
+ /** Configuration for the review pipeline */
131
+ export interface ReviewConfig {
132
+ /** Registered template names (from kern.config.ts templates) */
133
+ registeredTemplates?: string[];
134
+ /** Minimum coverage for enforcement */
135
+ minCoverage?: number;
136
+ /** Require detected library patterns to use KERN templates */
137
+ enforceTemplates?: boolean;
138
+ /** Build target — activates framework-specific rules */
139
+ target?: string;
140
+ }
141
+ /** Context passed to each review rule */
142
+ export interface RuleContext {
143
+ sourceFile: import('ts-morph').SourceFile;
144
+ inferred: InferResult[];
145
+ templateMatches: TemplateMatch[];
146
+ config?: ReviewConfig;
147
+ filePath: string;
148
+ }
149
+ /** A review rule function */
150
+ export type ReviewRule = (ctx: RuleContext) => ReviewFinding[];
151
+ /** Create a stable fingerprint for dedup across sources and runs */
152
+ export declare function createFingerprint(ruleId: string, startLine: number, startCol: number): string;
package/dist/types.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Types for @kernlang/review — TS → .kern inference, review pipeline, unified findings.
3
+ *
4
+ * v2: Unified ReviewFinding replaces QualityFinding + DiffFinding.
5
+ * InferResult gains stable nodeId + promptAlias + sourceSpans.
6
+ */
7
+ // ── Helpers ──────────────────────────────────────────────────────────────
8
+ /** Create a stable fingerprint for dedup across sources and runs */
9
+ export function createFingerprint(ruleId, startLine, startCol) {
10
+ const input = `${ruleId}:${startLine}:${startCol}`;
11
+ let hash = 0;
12
+ for (let i = 0; i < input.length; i++) {
13
+ hash = ((hash << 5) - hash + input.charCodeAt(i)) | 0;
14
+ }
15
+ return Math.abs(hash).toString(36);
16
+ }
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA8KH,4EAA4E;AAE5E,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,SAAiB,EAAE,QAAgB;IACnF,MAAM,KAAK,GAAG,GAAG,MAAM,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;IACnD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACrC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@kernlang/review",
3
+ "version": "2.0.0",
4
+ "description": "Kern Review — scan TS, infer .kern IR, roundtrip diff, report",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "license": "AGPL-3.0",
15
+ "dependencies": {
16
+ "ts-morph": "^24.0.0",
17
+ "@kernlang/core": "2.0.0"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -b",
21
+ "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --forceExit --config jest.config.js"
22
+ }
23
+ }