sentinel-verify 1.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.
- package/CHANGELOG.md +84 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/cli/commands/hook.js +133 -0
- package/dist/cli/commands/hook.js.map +1 -0
- package/dist/cli/commands/init.js +21 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/testHallucination.js +35 -0
- package/dist/cli/commands/testHallucination.js.map +1 -0
- package/dist/cli/commands/testSecurity.js +35 -0
- package/dist/cli/commands/testSecurity.js.map +1 -0
- package/dist/cli/commands/testStyle.js +41 -0
- package/dist/cli/commands/testStyle.js.map +1 -0
- package/dist/cli/commands/testVerify.js +36 -0
- package/dist/cli/commands/testVerify.js.map +1 -0
- package/dist/cli/diffInput.js +17 -0
- package/dist/cli/diffInput.js.map +1 -0
- package/dist/cli/index.js +93 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/env.js +16 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/sentinelrc.js +104 -0
- package/dist/config/sentinelrc.js.map +1 -0
- package/dist/dashboard/page.js +270 -0
- package/dist/dashboard/page.js.map +1 -0
- package/dist/dashboard/server.js +65 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/mcp/server.js +141 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/anthropic.js +72 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/index.js +37 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ollama.js +102 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/types.js +3 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/storage/db.js +123 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/verify/combine.js +42 -0
- package/dist/verify/combine.js.map +1 -0
- package/dist/verify/diff.js +27 -0
- package/dist/verify/diff.js.map +1 -0
- package/dist/verify/evidence.js +25 -0
- package/dist/verify/evidence.js.map +1 -0
- package/dist/verify/hallucination.js +299 -0
- package/dist/verify/hallucination.js.map +1 -0
- package/dist/verify/security.js +274 -0
- package/dist/verify/security.js.map +1 -0
- package/dist/verify/style.js +489 -0
- package/dist/verify/style.js.map +1 -0
- package/dist/verify/taskMatch.js +107 -0
- package/dist/verify/taskMatch.js.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
// Purpose: core logic for check_style_consistency — profiles the existing codebase's conventions, then flags diff lines that clearly deviate (deterministic checks first, model judgment second).
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { join, extname } from "node:path";
|
|
5
|
+
import { z } from "zod/v4";
|
|
6
|
+
import { resolveProvider } from "../providers/index.js";
|
|
7
|
+
import { openDatabase, insertVerification } from "../storage/db.js";
|
|
8
|
+
import { parseDiff } from "./diff.js";
|
|
9
|
+
import { dropDuplicateModelIssues } from "./combine.js";
|
|
10
|
+
import { loadSentinelrc, configPromptBlocks } from "../config/sentinelrc.js";
|
|
11
|
+
import { storeEvidence } from "./evidence.js";
|
|
12
|
+
export const STYLE_TOOL_NAME = "check_style_consistency";
|
|
13
|
+
export const STYLE_VERDICTS = ["no_issues", "style_mismatch"];
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Codebase style profiling
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const JS_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
18
|
+
const SKIP_DIRS = new Set([
|
|
19
|
+
"node_modules", ".git", "dist", "build", "out", "coverage",
|
|
20
|
+
".sentinel", ".next", "vendor", "target", "__pycache__",
|
|
21
|
+
]);
|
|
22
|
+
const MAX_SAMPLE_FILES = 30;
|
|
23
|
+
const MAX_FILE_BYTES = 200_000;
|
|
24
|
+
/** ≥80% of a meaningful sample wins; small or split samples are "unknown"/"mixed". */
|
|
25
|
+
function dominant(countA, countB, labelA, labelB) {
|
|
26
|
+
const total = countA + countB;
|
|
27
|
+
if (total < 5)
|
|
28
|
+
return "unknown";
|
|
29
|
+
if (countA / total >= 0.8)
|
|
30
|
+
return labelA;
|
|
31
|
+
if (countB / total >= 0.8)
|
|
32
|
+
return labelB;
|
|
33
|
+
return "mixed";
|
|
34
|
+
}
|
|
35
|
+
/** Recursively collects up to MAX_SAMPLE_FILES JS/TS files, pruning junk directories. */
|
|
36
|
+
function sampleFiles(projectDir, exclude) {
|
|
37
|
+
const collected = [];
|
|
38
|
+
const walk = (dir, rel) => {
|
|
39
|
+
if (collected.length >= MAX_SAMPLE_FILES)
|
|
40
|
+
return;
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = readdirSync(dir);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
for (const entry of entries.sort()) {
|
|
49
|
+
if (collected.length >= MAX_SAMPLE_FILES)
|
|
50
|
+
return;
|
|
51
|
+
const abs = join(dir, entry);
|
|
52
|
+
const relPath = rel ? `${rel}/${entry}` : entry;
|
|
53
|
+
let stats;
|
|
54
|
+
try {
|
|
55
|
+
stats = statSync(abs);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (stats.isDirectory()) {
|
|
61
|
+
if (!SKIP_DIRS.has(entry) && !entry.startsWith("."))
|
|
62
|
+
walk(abs, relPath);
|
|
63
|
+
}
|
|
64
|
+
else if (JS_EXTENSIONS.has(extname(entry)) &&
|
|
65
|
+
!entry.endsWith(".min.js") &&
|
|
66
|
+
!exclude.has(relPath) &&
|
|
67
|
+
stats.size <= MAX_FILE_BYTES) {
|
|
68
|
+
collected.push(abs);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
walk(projectDir, "");
|
|
73
|
+
return collected;
|
|
74
|
+
}
|
|
75
|
+
function emptyCounts() {
|
|
76
|
+
return {
|
|
77
|
+
tabLines: 0, spaceLines: 0, indentDeltas: new Map(),
|
|
78
|
+
singleQuotes: 0, doubleQuotes: 0,
|
|
79
|
+
semicolonEnd: 0, noSemicolonEnd: 0,
|
|
80
|
+
camelNames: 0, snakeNames: 0,
|
|
81
|
+
esmImports: 0, cjsRequires: 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const DECLARATION_RE = /(?:function\*?|const|let|var)\s+([A-Za-z_$][\w$]*)/g;
|
|
85
|
+
const STRING_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g;
|
|
86
|
+
/** Accumulates style statistics from one file or set of lines. */
|
|
87
|
+
function tallyLines(lines, counts) {
|
|
88
|
+
let previousIndent = null;
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
if (line.trim().length === 0) {
|
|
91
|
+
previousIndent = null;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Indentation
|
|
95
|
+
if (line.startsWith("\t")) {
|
|
96
|
+
counts.tabLines += 1;
|
|
97
|
+
previousIndent = null;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const indent = /^( *)/.exec(line)[1].length;
|
|
101
|
+
if (indent > 0)
|
|
102
|
+
counts.spaceLines += 1;
|
|
103
|
+
if (previousIndent !== null) {
|
|
104
|
+
const delta = indent - previousIndent;
|
|
105
|
+
if (delta > 0 && delta <= 8) {
|
|
106
|
+
counts.indentDeltas.set(delta, (counts.indentDeltas.get(delta) ?? 0) + 1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
previousIndent = indent;
|
|
110
|
+
}
|
|
111
|
+
const trimmed = line.trim();
|
|
112
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*"))
|
|
113
|
+
continue;
|
|
114
|
+
// Quotes (template literals excluded — they exist for interpolation, not preference)
|
|
115
|
+
for (const match of trimmed.match(STRING_RE) ?? []) {
|
|
116
|
+
if (match.startsWith("'"))
|
|
117
|
+
counts.singleQuotes += 1;
|
|
118
|
+
else
|
|
119
|
+
counts.doubleQuotes += 1;
|
|
120
|
+
}
|
|
121
|
+
// Semicolons: judge only clear statement endings
|
|
122
|
+
if (/[;]$/.test(trimmed))
|
|
123
|
+
counts.semicolonEnd += 1;
|
|
124
|
+
else if (/[\w$)\]'"`]$/.test(trimmed))
|
|
125
|
+
counts.noSemicolonEnd += 1;
|
|
126
|
+
// Naming of declared functions/variables (classes and CONSTANTS excluded)
|
|
127
|
+
for (const match of trimmed.matchAll(DECLARATION_RE)) {
|
|
128
|
+
const name = match[1];
|
|
129
|
+
if (/^[A-Z0-9_]+$/.test(name))
|
|
130
|
+
continue; // CONSTANT_CASE is fine everywhere
|
|
131
|
+
if (/^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(name))
|
|
132
|
+
counts.snakeNames += 1;
|
|
133
|
+
else if (/^[a-z][a-zA-Z0-9]*$/.test(name) && /[A-Z]/.test(name))
|
|
134
|
+
counts.camelNames += 1;
|
|
135
|
+
}
|
|
136
|
+
// Module style
|
|
137
|
+
if (/^import\s/.test(trimmed) || /^export\s.*\sfrom\s/.test(trimmed))
|
|
138
|
+
counts.esmImports += 1;
|
|
139
|
+
if (/\brequire\s*\(\s*["']/.test(trimmed))
|
|
140
|
+
counts.cjsRequires += 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function countsToProfile(counts, sampledFiles) {
|
|
144
|
+
let indentWidth = null;
|
|
145
|
+
const deltas = [...counts.indentDeltas.entries()].sort((a, b) => b[1] - a[1]);
|
|
146
|
+
if (deltas.length > 0 && deltas[0][1] >= 5)
|
|
147
|
+
indentWidth = deltas[0][0];
|
|
148
|
+
return {
|
|
149
|
+
sampledFiles,
|
|
150
|
+
indentChar: dominant(counts.spaceLines, counts.tabLines, "spaces", "tabs"),
|
|
151
|
+
indentWidth,
|
|
152
|
+
quotes: dominant(counts.singleQuotes, counts.doubleQuotes, "single", "double"),
|
|
153
|
+
semicolons: dominant(counts.semicolonEnd, counts.noSemicolonEnd, "always", "never"),
|
|
154
|
+
naming: dominant(counts.camelNames, counts.snakeNames, "camelCase", "snake_case"),
|
|
155
|
+
moduleStyle: dominant(counts.esmImports, counts.cjsRequires, "esm", "commonjs"),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Builds the style profile from existing project files, excluding files the diff touches. */
|
|
159
|
+
export function buildStyleProfile(projectDir, excludeFiles) {
|
|
160
|
+
const files = sampleFiles(projectDir, excludeFiles);
|
|
161
|
+
const counts = emptyCounts();
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
try {
|
|
164
|
+
const text = readFileSync(file, "utf8");
|
|
165
|
+
const lines = text.split("\n");
|
|
166
|
+
// Skip minified/generated content
|
|
167
|
+
if (text.length / Math.max(lines.length, 1) > 200)
|
|
168
|
+
continue;
|
|
169
|
+
tallyLines(lines, counts);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// unreadable file — skip
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return countsToProfile(counts, files.length);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Applies .sentinelrc style overrides on top of the inferred profile — an
|
|
179
|
+
* overridden dimension REPLACES inference and counts as confident even when
|
|
180
|
+
* the codebase sample was too small or mixed to infer anything.
|
|
181
|
+
*/
|
|
182
|
+
export function applyStyleOverrides(profile, config) {
|
|
183
|
+
const overridden = new Set();
|
|
184
|
+
if (!config?.style)
|
|
185
|
+
return { profile, overridden };
|
|
186
|
+
const merged = { ...profile };
|
|
187
|
+
const style = config.style;
|
|
188
|
+
if (style.indent !== undefined) {
|
|
189
|
+
if (style.indent === "tabs") {
|
|
190
|
+
merged.indentChar = "tabs";
|
|
191
|
+
merged.indentWidth = null;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
merged.indentChar = "spaces";
|
|
195
|
+
merged.indentWidth = style.indent;
|
|
196
|
+
}
|
|
197
|
+
overridden.add("indent");
|
|
198
|
+
}
|
|
199
|
+
if (style.quotes !== undefined) {
|
|
200
|
+
merged.quotes = style.quotes;
|
|
201
|
+
overridden.add("quotes");
|
|
202
|
+
}
|
|
203
|
+
if (style.semicolons !== undefined) {
|
|
204
|
+
merged.semicolons = style.semicolons;
|
|
205
|
+
overridden.add("semicolons");
|
|
206
|
+
}
|
|
207
|
+
if (style.naming !== undefined) {
|
|
208
|
+
merged.naming = style.naming;
|
|
209
|
+
overridden.add("naming");
|
|
210
|
+
}
|
|
211
|
+
if (style.moduleStyle !== undefined) {
|
|
212
|
+
merged.moduleStyle = style.moduleStyle;
|
|
213
|
+
overridden.add("moduleStyle");
|
|
214
|
+
}
|
|
215
|
+
return { profile: merged, overridden };
|
|
216
|
+
}
|
|
217
|
+
/** Renders the profile as plain text for the model prompt and CLI output. */
|
|
218
|
+
export function renderProfile(profile, overridden = new Set()) {
|
|
219
|
+
const from = (key) => (overridden.has(key) ? " (from .sentinelrc)" : "");
|
|
220
|
+
if (profile.sampledFiles === 0 && overridden.size === 0) {
|
|
221
|
+
return "(no existing JS/TS files to compare against)";
|
|
222
|
+
}
|
|
223
|
+
const lines = [];
|
|
224
|
+
if (profile.indentChar === "spaces" && profile.indentWidth !== null) {
|
|
225
|
+
lines.push(`Indentation: ${profile.indentWidth} spaces${from("indent")}`);
|
|
226
|
+
}
|
|
227
|
+
else if (profile.indentChar !== "unknown" && profile.indentChar !== "mixed") {
|
|
228
|
+
lines.push(`Indentation: ${profile.indentChar}${from("indent")}`);
|
|
229
|
+
}
|
|
230
|
+
if (profile.quotes !== "unknown" && profile.quotes !== "mixed")
|
|
231
|
+
lines.push(`Quotes: ${profile.quotes}${from("quotes")}`);
|
|
232
|
+
if (profile.semicolons !== "unknown" && profile.semicolons !== "mixed")
|
|
233
|
+
lines.push(`Semicolons: ${profile.semicolons}${from("semicolons")}`);
|
|
234
|
+
if (profile.naming !== "unknown" && profile.naming !== "mixed")
|
|
235
|
+
lines.push(`Function/variable naming: ${profile.naming}${from("naming")}`);
|
|
236
|
+
if (profile.moduleStyle !== "unknown" && profile.moduleStyle !== "mixed")
|
|
237
|
+
lines.push(`Module style: ${profile.moduleStyle === "esm" ? "ESM (import/export)" : "CommonJS (require)"}${from("moduleStyle")}`);
|
|
238
|
+
if (lines.length === 0)
|
|
239
|
+
return "(no strong conventions detected)";
|
|
240
|
+
return lines.join("\n");
|
|
241
|
+
}
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Deterministic diff-vs-profile checks
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
const JS_FILE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
|
|
246
|
+
/**
|
|
247
|
+
* Authoritative checks: fire only when the project convention is dominant AND
|
|
248
|
+
* the diff violates it repeatedly. Single occurrences are never flagged.
|
|
249
|
+
*/
|
|
250
|
+
export function staticStyleCheck(profile, added) {
|
|
251
|
+
const issues = [];
|
|
252
|
+
const jsLines = added.filter((line) => JS_FILE.test(line.file) && line.text.trim().length > 0);
|
|
253
|
+
if (jsLines.length === 0)
|
|
254
|
+
return issues;
|
|
255
|
+
const at = (line) => `${line.file}:${line.lineNo} — ${line.text.trim()}`;
|
|
256
|
+
// 1. Tabs vs spaces
|
|
257
|
+
if (profile.indentChar === "spaces" || profile.indentChar === "tabs") {
|
|
258
|
+
const wrongChar = profile.indentChar === "spaces" ? "\t" : " ";
|
|
259
|
+
const offenders = jsLines.filter((line) => line.text.startsWith(wrongChar) && line.text.trim().length > 0);
|
|
260
|
+
// For a tabs project, only flag space-indented lines at real indent depth (≥2 spaces).
|
|
261
|
+
const real = profile.indentChar === "tabs"
|
|
262
|
+
? offenders.filter((line) => /^ {2,}/.test(line.text))
|
|
263
|
+
: offenders;
|
|
264
|
+
if (real.length >= 2) {
|
|
265
|
+
const wrongLabel = profile.indentChar === "spaces" ? "tabs" : "spaces";
|
|
266
|
+
issues.push({
|
|
267
|
+
location: at(real[0]),
|
|
268
|
+
reason: `The project indents with "${profile.indentChar}" but ${real.length} added line(s) use "${wrongLabel}".`,
|
|
269
|
+
source: "static",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// 2. Indent width (space-indented projects only)
|
|
274
|
+
if (profile.indentChar === "spaces" && profile.indentWidth !== null) {
|
|
275
|
+
const diffCounts = emptyCounts();
|
|
276
|
+
// Group by file and require consecutive line numbers so deltas are meaningful.
|
|
277
|
+
const byFile = new Map();
|
|
278
|
+
for (const line of jsLines) {
|
|
279
|
+
byFile.set(line.file, [...(byFile.get(line.file) ?? []), line]);
|
|
280
|
+
}
|
|
281
|
+
for (const lines of byFile.values()) {
|
|
282
|
+
let run = [];
|
|
283
|
+
for (let i = 0; i < lines.length; i++) {
|
|
284
|
+
run.push(lines[i].text);
|
|
285
|
+
const next = lines[i + 1];
|
|
286
|
+
if (!next || next.lineNo !== lines[i].lineNo + 1) {
|
|
287
|
+
tallyLines(run, diffCounts);
|
|
288
|
+
run = [];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const deltas = [...diffCounts.indentDeltas.entries()].sort((a, b) => b[1] - a[1]);
|
|
293
|
+
const rightWidthCount = diffCounts.indentDeltas.get(profile.indentWidth) ?? 0;
|
|
294
|
+
if (deltas.length > 0 &&
|
|
295
|
+
deltas[0][0] !== profile.indentWidth &&
|
|
296
|
+
deltas[0][1] >= 2 &&
|
|
297
|
+
deltas[0][1] > rightWidthCount) {
|
|
298
|
+
issues.push({
|
|
299
|
+
location: `${jsLines[0].file} (added lines)`,
|
|
300
|
+
reason: `The project uses "${profile.indentWidth}-space" indentation but the diff indents by "${deltas[0][0]}" spaces.`,
|
|
301
|
+
source: "static",
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// 3. Quote style
|
|
306
|
+
if (profile.quotes === "single" || profile.quotes === "double") {
|
|
307
|
+
const wrong = profile.quotes === "single" ? '"' : "'";
|
|
308
|
+
let wrongCount = 0;
|
|
309
|
+
let rightCount = 0;
|
|
310
|
+
let firstOffender = null;
|
|
311
|
+
for (const line of jsLines) {
|
|
312
|
+
for (const match of line.text.match(STRING_RE) ?? []) {
|
|
313
|
+
if (match.startsWith(wrong)) {
|
|
314
|
+
wrongCount += 1;
|
|
315
|
+
firstOffender ??= line;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
rightCount += 1;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (wrongCount >= 3 && wrongCount > rightCount && firstOffender) {
|
|
323
|
+
const wrongLabel = profile.quotes === "single" ? "double quotes" : "single quotes";
|
|
324
|
+
issues.push({
|
|
325
|
+
location: at(firstOffender),
|
|
326
|
+
reason: `The project uses "${profile.quotes} quotes" but the diff adds ${wrongCount} strings in "${wrongLabel}".`,
|
|
327
|
+
source: "static",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// 4. Naming convention
|
|
332
|
+
if (profile.naming === "camelCase" || profile.naming === "snake_case") {
|
|
333
|
+
const wrongNames = [];
|
|
334
|
+
for (const line of jsLines) {
|
|
335
|
+
for (const match of line.text.matchAll(DECLARATION_RE)) {
|
|
336
|
+
const name = match[1];
|
|
337
|
+
if (/^[A-Z0-9_]+$/.test(name))
|
|
338
|
+
continue;
|
|
339
|
+
const isSnake = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/.test(name);
|
|
340
|
+
const isCamel = /^[a-z][a-zA-Z0-9]*$/.test(name) && /[A-Z]/.test(name);
|
|
341
|
+
if (profile.naming === "camelCase" && isSnake)
|
|
342
|
+
wrongNames.push({ line, name });
|
|
343
|
+
if (profile.naming === "snake_case" && isCamel)
|
|
344
|
+
wrongNames.push({ line, name });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (wrongNames.length >= 2) {
|
|
348
|
+
const wrongLabel = profile.naming === "camelCase" ? "snake_case" : "camelCase";
|
|
349
|
+
const shown = wrongNames.slice(0, 3).map((w) => w.name).join(", ");
|
|
350
|
+
issues.push({
|
|
351
|
+
location: at(wrongNames[0].line),
|
|
352
|
+
reason: `The project names functions/variables in "${profile.naming}" but the diff declares ${wrongNames.length} "${wrongLabel}" name(s): ${shown}.`,
|
|
353
|
+
source: "static",
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return issues;
|
|
358
|
+
}
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// Model judgment (advisory)
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
const modelIssuesSchema = z.object({
|
|
363
|
+
issues: z.array(z.object({
|
|
364
|
+
location: z.string().describe("File and line, or the offending code snippet from the diff."),
|
|
365
|
+
reason: z.string().describe("Which stated convention is violated and how, in one or two sentences."),
|
|
366
|
+
})),
|
|
367
|
+
});
|
|
368
|
+
// Deliberately short and neutral — long prompts make small local models
|
|
369
|
+
// fabricate findings and loop under schema constraints.
|
|
370
|
+
const SYSTEM_PROMPT = `You review a code diff for consistency with the project's existing style conventions, which are provided. Flag only clear violations of a STATED convention: indentation, quote style, semicolon usage, naming convention, or module import style. Do not flag code that follows the stated conventions, and do not give subjective opinions about quality, logic, or design. Most diffs are consistent; an empty issues list is a normal answer.`;
|
|
371
|
+
// Model style findings must reference an actual style topic — anything else
|
|
372
|
+
// (logic opinions, "unused function", etc.) is off-task noise from small models.
|
|
373
|
+
const STYLE_TOPIC = /indent|quote|semicolon|naming|case\b|camel|snake|import|require|module|convention|style|format|spacing|tab|space/i;
|
|
374
|
+
function buildPrompt(profileText, diff) {
|
|
375
|
+
return `<project_conventions>
|
|
376
|
+
${profileText}
|
|
377
|
+
</project_conventions>
|
|
378
|
+
|
|
379
|
+
<code_diff>
|
|
380
|
+
${diff}
|
|
381
|
+
</code_diff>
|
|
382
|
+
|
|
383
|
+
List added code that violates the stated conventions (empty list if none).`;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Agent-native (MCP) mode: profiles the codebase and runs the deterministic
|
|
387
|
+
* diff-vs-profile checks — no model call — and returns the evidence for the
|
|
388
|
+
* calling agent to judge.
|
|
389
|
+
*/
|
|
390
|
+
export function gatherStyleEvidence(projectDir, diff) {
|
|
391
|
+
if (diff.trim().length === 0) {
|
|
392
|
+
throw new Error("Diff is empty — there is no code change to check.");
|
|
393
|
+
}
|
|
394
|
+
const config = loadSentinelrc(projectDir);
|
|
395
|
+
const { added, touchedFiles } = parseDiff(diff);
|
|
396
|
+
const inferred = buildStyleProfile(projectDir, touchedFiles);
|
|
397
|
+
const { profile, overridden } = applyStyleOverrides(inferred, config);
|
|
398
|
+
const staticFindings = profile.sampledFiles > 0 || overridden.size > 0 ? staticStyleCheck(profile, added) : [];
|
|
399
|
+
storeEvidence(projectDir, STYLE_TOOL_NAME, diff, staticFindings);
|
|
400
|
+
return {
|
|
401
|
+
mode: "evidence",
|
|
402
|
+
instruction: "The profile below is this project's MEASURED style (entries marked 'from .sentinelrc' are pinned by " +
|
|
403
|
+
"config). The staticFindings are deterministic measurements of the diff against that profile — do not " +
|
|
404
|
+
"contradict them. Additionally judge dimensions that were not statically measured (semicolon usage, " +
|
|
405
|
+
"import ordering, non-JS files) against the profile. Flag only clear violations, not nitpicks. " +
|
|
406
|
+
"Honor projectRules if present.",
|
|
407
|
+
projectContext: config?.context ?? "",
|
|
408
|
+
projectRules: config?.rules ?? [],
|
|
409
|
+
profile: renderProfile(profile, overridden),
|
|
410
|
+
staticFindings,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Standalone (CLI) mode: profile the existing codebase, apply deterministic
|
|
415
|
+
* diff-vs-profile checks, then model judgment (advisory, deduplicated and
|
|
416
|
+
* topic-filtered). The result is stored locally.
|
|
417
|
+
*/
|
|
418
|
+
export async function checkStyleConsistency(projectDir, diff) {
|
|
419
|
+
if (diff.trim().length === 0) {
|
|
420
|
+
throw new Error("Diff is empty — there is no code change to check.");
|
|
421
|
+
}
|
|
422
|
+
const { added, touchedFiles } = parseDiff(diff);
|
|
423
|
+
const config = loadSentinelrc(projectDir);
|
|
424
|
+
const inferred = buildStyleProfile(projectDir, touchedFiles);
|
|
425
|
+
const { profile, overridden } = applyStyleOverrides(inferred, config);
|
|
426
|
+
const profileText = renderProfile(profile, overridden);
|
|
427
|
+
let issues = [];
|
|
428
|
+
let providerName = "none";
|
|
429
|
+
let modelName = "none";
|
|
430
|
+
// With nothing to compare against there is nothing to be inconsistent with —
|
|
431
|
+
// unless .sentinelrc pins conventions, which are enforceable from day one.
|
|
432
|
+
if (profile.sampledFiles > 0 || overridden.size > 0) {
|
|
433
|
+
const staticIssues = staticStyleCheck(profile, added);
|
|
434
|
+
// Dimensions the static pass confidently measured are OWNED by it: if it
|
|
435
|
+
// flagged the dimension, a model comment is redundant; if it passed the
|
|
436
|
+
// dimension, a model complaint is contradicted by measurement. Either
|
|
437
|
+
// way the model issue is dropped. The model's value is the dimensions
|
|
438
|
+
// static checks don't cover (semicolons, import style, other languages).
|
|
439
|
+
const ownedDimensions = [];
|
|
440
|
+
if (profile.indentChar === "spaces" || profile.indentChar === "tabs") {
|
|
441
|
+
ownedDimensions.push(/indent|tabs?\b/i);
|
|
442
|
+
}
|
|
443
|
+
if (profile.quotes === "single" || profile.quotes === "double") {
|
|
444
|
+
ownedDimensions.push(/quote/i);
|
|
445
|
+
}
|
|
446
|
+
if (profile.naming === "camelCase" || profile.naming === "snake_case") {
|
|
447
|
+
ownedDimensions.push(/camel|snake|naming|\bcase\b/i);
|
|
448
|
+
}
|
|
449
|
+
const provider = resolveProvider();
|
|
450
|
+
providerName = provider.name;
|
|
451
|
+
modelName = provider.model;
|
|
452
|
+
const modelAnswer = await provider.completeStructured({
|
|
453
|
+
system: SYSTEM_PROMPT,
|
|
454
|
+
prompt: configPromptBlocks(config) + buildPrompt(profileText, diff),
|
|
455
|
+
schema: modelIssuesSchema,
|
|
456
|
+
schemaName: "style_issues",
|
|
457
|
+
});
|
|
458
|
+
const modelIssues = dropDuplicateModelIssues(staticIssues, modelAnswer.issues.map((issue) => ({ ...issue, source: "model" }))).filter((issue) => {
|
|
459
|
+
const text = `${issue.location} ${issue.reason}`;
|
|
460
|
+
if (!STYLE_TOPIC.test(text))
|
|
461
|
+
return false; // off-task noise
|
|
462
|
+
return !ownedDimensions.some((re) => re.test(text)); // statically owned dimension
|
|
463
|
+
});
|
|
464
|
+
issues = [...staticIssues, ...modelIssues];
|
|
465
|
+
}
|
|
466
|
+
const verdict = issues.length > 0 ? "style_mismatch" : "no_issues";
|
|
467
|
+
const db = openDatabase(projectDir);
|
|
468
|
+
try {
|
|
469
|
+
insertVerification(db, {
|
|
470
|
+
tool: STYLE_TOOL_NAME,
|
|
471
|
+
task: "",
|
|
472
|
+
diffHash: createHash("sha256").update(diff).digest("hex"),
|
|
473
|
+
verdict,
|
|
474
|
+
reason: profile.sampledFiles === 0 && overridden.size === 0
|
|
475
|
+
? "No existing files to compare against — style not checked."
|
|
476
|
+
: issues.length === 0
|
|
477
|
+
? "Diff is consistent with the project's style."
|
|
478
|
+
: `${issues.length} style inconsistency(ies) found.`,
|
|
479
|
+
details: JSON.stringify(issues),
|
|
480
|
+
provider: providerName,
|
|
481
|
+
model: modelName,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
finally {
|
|
485
|
+
db.close();
|
|
486
|
+
}
|
|
487
|
+
return { verdict, issues, profile: profileText, provider: providerName, model: modelName };
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/verify/style.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Purpose: core logic for verify_task_match — asks the configured LLM whether a diff functionally accomplishes the stated task, and records the result locally.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { z } from "zod/v4";
|
|
4
|
+
import { resolveProvider } from "../providers/index.js";
|
|
5
|
+
import { openDatabase, insertVerification } from "../storage/db.js";
|
|
6
|
+
import { loadSentinelrc, configPromptBlocks } from "../config/sentinelrc.js";
|
|
7
|
+
import { parseDiff } from "./diff.js";
|
|
8
|
+
import { storeEvidence } from "./evidence.js";
|
|
9
|
+
export const TOOL_NAME = "verify_task_match";
|
|
10
|
+
/** The three possible verdicts, from best to worst. */
|
|
11
|
+
export const VERDICTS = ["match", "partial_match", "no_match"];
|
|
12
|
+
const verdictSchema = z.object({
|
|
13
|
+
verdict: z.enum(VERDICTS),
|
|
14
|
+
reason: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("One to three sentences explaining the verdict, citing specifics from the diff."),
|
|
17
|
+
});
|
|
18
|
+
const SYSTEM_PROMPT = `You are Sentinel, a local code-verification layer that reviews AI-generated code changes before a human sees them.
|
|
19
|
+
|
|
20
|
+
You will be given a stated task (what the change was supposed to do) and a code diff (what was actually changed). Judge whether the diff FUNCTIONALLY accomplishes the stated task — not whether it superficially mentions it.
|
|
21
|
+
|
|
22
|
+
Be skeptical. Common failure modes to catch:
|
|
23
|
+
- The diff touches related files or adds names matching the task, but the actual logic is missing, stubbed, or wrong.
|
|
24
|
+
- The diff solves a different or narrower problem than the one stated.
|
|
25
|
+
- The diff accomplishes part of the task but silently skips a stated requirement.
|
|
26
|
+
|
|
27
|
+
Verdicts:
|
|
28
|
+
- "match": the diff fully and functionally accomplishes the stated task.
|
|
29
|
+
- "partial_match": the diff makes real progress but misses part of the task, or its correctness is doubtful.
|
|
30
|
+
- "no_match": the diff does not accomplish the task (unrelated, superficial, or broken).
|
|
31
|
+
|
|
32
|
+
Judge only task accomplishment. Style, security, and hallucinated APIs are checked by other tools.`;
|
|
33
|
+
function buildPrompt(task, diff) {
|
|
34
|
+
return `<stated_task>
|
|
35
|
+
${task}
|
|
36
|
+
</stated_task>
|
|
37
|
+
|
|
38
|
+
<code_diff>
|
|
39
|
+
${diff}
|
|
40
|
+
</code_diff>
|
|
41
|
+
|
|
42
|
+
Does the diff functionally accomplish the stated task?`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Agent-native (MCP) mode: no model call. Task matching has no deterministic
|
|
46
|
+
* half, so the evidence is diff statistics plus project context, and the
|
|
47
|
+
* judgment instruction goes to the calling agent.
|
|
48
|
+
*/
|
|
49
|
+
export function gatherTaskMatchEvidence(projectDir, task, diff) {
|
|
50
|
+
if (task.trim().length === 0) {
|
|
51
|
+
throw new Error("Task description is empty — nothing to verify against.");
|
|
52
|
+
}
|
|
53
|
+
if (diff.trim().length === 0) {
|
|
54
|
+
throw new Error("Diff is empty — there is no code change to verify.");
|
|
55
|
+
}
|
|
56
|
+
const config = loadSentinelrc(projectDir);
|
|
57
|
+
const { added, touchedFiles } = parseDiff(diff);
|
|
58
|
+
storeEvidence(projectDir, TOOL_NAME, diff, []);
|
|
59
|
+
return {
|
|
60
|
+
mode: "evidence",
|
|
61
|
+
instruction: "Sentinel does not judge in agent mode — you do. Review the diff against the stated task: " +
|
|
62
|
+
"does it FUNCTIONALLY accomplish the task, not just superficially? Watch for missing or stubbed logic, " +
|
|
63
|
+
"a narrower problem than stated, or silently skipped requirements. " +
|
|
64
|
+
"Conclude match, partial_match, or no_match with a short reason. Honor projectRules if present.",
|
|
65
|
+
projectContext: config?.context ?? "",
|
|
66
|
+
projectRules: config?.rules ?? [],
|
|
67
|
+
diffFiles: [...touchedFiles],
|
|
68
|
+
addedLineCount: added.length,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Standalone (CLI) mode: calls the configured provider, stores the result in
|
|
73
|
+
* the project's local SQLite database, and returns the verdict.
|
|
74
|
+
*/
|
|
75
|
+
export async function verifyTaskMatch(projectDir, task, diff) {
|
|
76
|
+
if (task.trim().length === 0) {
|
|
77
|
+
throw new Error("Task description is empty — nothing to verify against.");
|
|
78
|
+
}
|
|
79
|
+
if (diff.trim().length === 0) {
|
|
80
|
+
throw new Error("Diff is empty — there is no code change to verify.");
|
|
81
|
+
}
|
|
82
|
+
const config = loadSentinelrc(projectDir);
|
|
83
|
+
const provider = resolveProvider();
|
|
84
|
+
const { verdict, reason } = await provider.completeStructured({
|
|
85
|
+
system: SYSTEM_PROMPT,
|
|
86
|
+
prompt: configPromptBlocks(config) + buildPrompt(task, diff),
|
|
87
|
+
schema: verdictSchema,
|
|
88
|
+
schemaName: "task_match_verdict",
|
|
89
|
+
});
|
|
90
|
+
const db = openDatabase(projectDir);
|
|
91
|
+
try {
|
|
92
|
+
insertVerification(db, {
|
|
93
|
+
tool: TOOL_NAME,
|
|
94
|
+
task,
|
|
95
|
+
diffHash: createHash("sha256").update(diff).digest("hex"),
|
|
96
|
+
verdict,
|
|
97
|
+
reason,
|
|
98
|
+
provider: provider.name,
|
|
99
|
+
model: provider.model,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
db.close();
|
|
104
|
+
}
|
|
105
|
+
return { verdict, reason, provider: provider.name, model: provider.model };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=taskMatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"taskMatch.js","sourceRoot":"","sources":["../../src/verify/taskMatch.ts"],"names":[],"mappings":"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"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAE9F,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC"}
|