micro-models-agent 0.51.1 → 0.51.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +358 -358
- package/dist/main.js +817 -573
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/package.json +48 -48
- package/dist/cli/commands.js +0 -323
- package/dist/cli/completer.js +0 -167
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -165
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/repl-commands.js +0 -661
- package/dist/cli/repl.js +0 -616
- package/dist/cli/run-result.js +0 -22
- package/dist/cli/security-commands.js +0 -164
- package/dist/cli/setup.js +0 -231
- package/dist/config/config.js +0 -249
- package/dist/config/defaults.js +0 -124
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -193
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -102
- package/dist/core/agent.js +0 -886
- package/dist/core/bootstrap.js +0 -404
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -76
- package/dist/core/session-logger.js +0 -197
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -24
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -598
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -598
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -143
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -359
- package/dist/llm/orchestrator.js +0 -198
- package/dist/llm/provider.js +0 -10
- package/dist/llm/response.js +0 -39
- package/dist/llm/token-counter.js +0 -39
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -143
- package/dist/logger/file-log.js +0 -151
- package/dist/logger/index.js +0 -1
- package/dist/migration/backup.js +0 -45
- package/dist/migration/detect.js +0 -50
- package/dist/migration/index.js +0 -2
- package/dist/modules/artifacts/store.js +0 -61
- package/dist/modules/browser/actions.js +0 -76
- package/dist/modules/browser/bridge-client.js +0 -199
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/driver.js +0 -136
- package/dist/modules/browser/index.js +0 -7
- package/dist/modules/browser/module.js +0 -29
- package/dist/modules/browser/session.js +0 -338
- package/dist/modules/browser/snapshot.js +0 -148
- package/dist/modules/browser/types.js +0 -12
- package/dist/modules/certification/cli.js +0 -174
- package/dist/modules/certification/fact-checker.js +0 -82
- package/dist/modules/certification/loader.js +0 -105
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -159
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/chunk-query.js +0 -100
- package/dist/modules/context/fact-extractor.js +0 -162
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -423
- package/dist/modules/execution/audit-runners.js +0 -152
- package/dist/modules/execution/auditor.js +0 -218
- package/dist/modules/execution/execution-plugin.js +0 -272
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -436
- package/dist/modules/execution/moe-executor.js +0 -291
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -157
- package/dist/modules/execution/plan-tool.js +0 -508
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -90
- package/dist/modules/execution/stuck-detector.js +0 -510
- package/dist/modules/execution/tracker.js +0 -67
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -222
- package/dist/modules/execution/windows-commands.js +0 -41
- package/dist/modules/hallucination/confidence.js +0 -66
- package/dist/modules/hallucination/consistency.js +0 -26
- package/dist/modules/hallucination/detector.js +0 -43
- package/dist/modules/hallucination/factual.js +0 -129
- package/dist/modules/hallucination/index.js +0 -5
- package/dist/modules/hallucination/js-identifiers.js +0 -262
- package/dist/modules/hallucination/llm-judge.js +0 -101
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -40
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -245
- package/dist/modules/indexer/project-profile.js +0 -183
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/lsp/check-tool.js +0 -58
- package/dist/modules/lsp/client.js +0 -278
- package/dist/modules/lsp/command.js +0 -60
- package/dist/modules/lsp/config.js +0 -135
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -232
- package/dist/modules/lsp/probe.js +0 -76
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -141
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -399
- package/dist/modules/mcp/index.js +0 -3
- package/dist/modules/mcp/module.js +0 -142
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -96
- package/dist/modules/memory/search.js +0 -42
- package/dist/modules/memory/store.js +0 -69
- package/dist/modules/pipelines/engine.js +0 -60
- package/dist/modules/pipelines/index.js +0 -3
- package/dist/modules/pipelines/parser.js +0 -56
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -231
- package/dist/modules/plugins/builtin/notify.js +0 -9
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -70
- package/dist/modules/plugins/manager.js +0 -217
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -2
- package/dist/modules/processes/registry.js +0 -327
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/registry.js +0 -47
- package/dist/modules/security/audit-log.js +0 -136
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -205
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -89
- package/dist/modules/security/encryption.js +0 -242
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -71
- package/dist/modules/security/path-validator.js +0 -207
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -531
- package/dist/modules/security/session-encryption.js +0 -210
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -172
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -222
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -129
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -96
- package/dist/modules/updater/index.js +0 -2
- package/dist/modules/updater/module.js +0 -116
- package/dist/modules/user-profile/compressor.js +0 -16
- package/dist/modules/user-profile/index.js +0 -1
- package/dist/modules/user-profile/profile.js +0 -68
- package/dist/tools/approve.js +0 -32
- package/dist/tools/attach-image.js +0 -89
- package/dist/tools/bash.js +0 -496
- package/dist/tools/browser.js +0 -114
- package/dist/tools/chunk-query.js +0 -99
- package/dist/tools/create-dir.js +0 -55
- package/dist/tools/delete-file.js +0 -62
- package/dist/tools/download-file.js +0 -116
- package/dist/tools/edit-file.js +0 -79
- package/dist/tools/enable-tools.js +0 -58
- package/dist/tools/executor.js +0 -144
- package/dist/tools/file-info.js +0 -46
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -26
- package/dist/tools/grep-tool.js +0 -84
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -78
- package/dist/tools/list-dir.js +0 -48
- package/dist/tools/load-skill.js +0 -42
- package/dist/tools/mcp-call.js +0 -68
- package/dist/tools/move-file.js +0 -85
- package/dist/tools/path-utils.js +0 -51
- package/dist/tools/pipeline-run.js +0 -144
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -29
- package/dist/tools/process-list.js +0 -36
- package/dist/tools/process-log.js +0 -45
- package/dist/tools/question.js +0 -140
- package/dist/tools/read-file.js +0 -91
- package/dist/tools/recall.js +0 -117
- package/dist/tools/registry.js +0 -47
- package/dist/tools/remember.js +0 -67
- package/dist/tools/scope-check.js +0 -30
- package/dist/tools/search-history.js +0 -84
- package/dist/tools/subagent.js +0 -196
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -86
- package/dist/tools/web-fetch.js +0 -98
- package/dist/tools/web-search.js +0 -78
- package/dist/tools/write-file.js +0 -81
- package/dist/ui/box.js +0 -77
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -178
- package/dist/ui/index.js +0 -6
- package/dist/ui/line-editor.js +0 -703
- package/dist/ui/line-math.js +0 -69
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/plan-view.js +0 -103
- package/dist/ui/renderer.js +0 -209
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
2
|
-
import { resolve, extname, join } from "path";
|
|
3
|
-
import { spawn } from "child_process";
|
|
4
|
-
import { t } from "../../i18n/index";
|
|
5
|
-
import { validateExpertConfig } from "../../config/experts";
|
|
6
|
-
import { findProjectRoot } from "../lsp/project-root";
|
|
7
|
-
import { extractFileLikeTokens, stripUrls } from "../hallucination/js-identifiers";
|
|
8
|
-
import { findExistingFile } from "./auditor";
|
|
9
|
-
export class StepVerifier {
|
|
10
|
-
baseDir;
|
|
11
|
-
constructor(baseDir) {
|
|
12
|
-
this.baseDir = baseDir;
|
|
13
|
-
}
|
|
14
|
-
async checkFileExists(path) {
|
|
15
|
-
const resolved = resolve(this.baseDir, path);
|
|
16
|
-
const exists = existsSync(resolved);
|
|
17
|
-
return {
|
|
18
|
-
passed: exists,
|
|
19
|
-
message: exists ? t("verify.file_exists", { path }) : t("verify.file_not_found", { path }),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
async runScript(scriptName) {
|
|
23
|
-
try {
|
|
24
|
-
await this.runAsync(`bun run ${scriptName}`, this.baseDir, 60_000);
|
|
25
|
-
return { passed: true, message: t("verify.script_passed", { script: scriptName }) };
|
|
26
|
-
}
|
|
27
|
-
catch (e) {
|
|
28
|
-
return {
|
|
29
|
-
passed: false,
|
|
30
|
-
message: t("verify.script_failed", { script: scriptName, message: e.message }),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async runTypeCheck() {
|
|
35
|
-
const tsconfigPath = resolve(this.baseDir, "tsconfig.json");
|
|
36
|
-
if (!existsSync(tsconfigPath)) {
|
|
37
|
-
return { passed: true, message: "No tsconfig.json found — skipping type check" };
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
await this.runAsync("npx tsc --noEmit", this.baseDir, 60_000);
|
|
41
|
-
return { passed: true, message: "TypeScript type check passed" };
|
|
42
|
-
}
|
|
43
|
-
catch (e) {
|
|
44
|
-
const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
|
|
45
|
-
return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Type-check a specific file, resolving the project root from the FILE's
|
|
50
|
-
* location (not the agent baseDir). For nested layouts (`baseDir/proj/` with
|
|
51
|
-
* its own tsconfig.json) the old baseDir-based check silently skipped every
|
|
52
|
-
* type error (observed in ses_mst2r0r5: zero type feedback for the whole
|
|
53
|
-
* session). Markers mirror the TypeScript LSP server config.
|
|
54
|
-
*/
|
|
55
|
-
async runTypeCheckForFile(filePath) {
|
|
56
|
-
const projectRoot = findProjectRoot(filePath, this.baseDir, ["tsconfig.json", "package.json"]);
|
|
57
|
-
const tsconfigPath = join(projectRoot, "tsconfig.json");
|
|
58
|
-
if (!existsSync(tsconfigPath)) {
|
|
59
|
-
return { passed: true, message: "No tsconfig.json found — skipping type check" };
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
await this.runAsync("npx tsc --noEmit --skipLibCheck", projectRoot, 60_000);
|
|
63
|
-
return { passed: true, message: "TypeScript type check passed" };
|
|
64
|
-
}
|
|
65
|
-
catch (e) {
|
|
66
|
-
const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
|
|
67
|
-
return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async runTests() {
|
|
71
|
-
const pkgPath = resolve(this.baseDir, "package.json");
|
|
72
|
-
if (!existsSync(pkgPath)) {
|
|
73
|
-
return { passed: true, message: "No package.json found — skipping tests" };
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
const pkg = JSON.parse(require("fs").readFileSync(pkgPath, "utf-8"));
|
|
77
|
-
if (!pkg.scripts?.test) {
|
|
78
|
-
return { passed: true, message: "No test script defined — skipping tests" };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
/* fall through — attempt to run */
|
|
83
|
-
}
|
|
84
|
-
return this.runScript("test");
|
|
85
|
-
}
|
|
86
|
-
async verifyArtifactFiles(files) {
|
|
87
|
-
return Promise.all(files.map((f) => this.checkFileExists(f)));
|
|
88
|
-
}
|
|
89
|
-
async verifyMoEManifest(plan, config, allToolTags) {
|
|
90
|
-
const errors = [];
|
|
91
|
-
const warnings = [];
|
|
92
|
-
const details = [];
|
|
93
|
-
const expertErrors = validateExpertConfig(config, allToolTags);
|
|
94
|
-
for (const err of expertErrors) {
|
|
95
|
-
errors.push(err);
|
|
96
|
-
details.push({ passed: false, message: err });
|
|
97
|
-
}
|
|
98
|
-
const tscResult = await this.runTypeCheck();
|
|
99
|
-
details.push(tscResult);
|
|
100
|
-
if (!tscResult.passed) {
|
|
101
|
-
errors.push(tscResult.message);
|
|
102
|
-
}
|
|
103
|
-
for (const sub of plan.subtasks) {
|
|
104
|
-
const fileChecks = await this.verifyArtifactFiles(sub.allowed_files || []);
|
|
105
|
-
for (const check of fileChecks) {
|
|
106
|
-
details.push(check);
|
|
107
|
-
if (!check.passed) {
|
|
108
|
-
errors.push(check.message);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
success: errors.length === 0,
|
|
114
|
-
errors,
|
|
115
|
-
warnings,
|
|
116
|
-
details,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
async verifyStep(stepDescription) {
|
|
120
|
-
const fileMatches = extractFileLikeTokens(stripUrls(stepDescription));
|
|
121
|
-
// No named files — this step cannot be verified by file existence. Do NOT
|
|
122
|
-
// trivially pass (empty every() = true); signal noFiles so the tool guides
|
|
123
|
-
// the model to run a real check instead.
|
|
124
|
-
if (fileMatches.length === 0) {
|
|
125
|
-
return { passed: false, syntaxValid: true, failed: [], noFiles: true };
|
|
126
|
-
}
|
|
127
|
-
const results = [];
|
|
128
|
-
let syntaxValid = true;
|
|
129
|
-
for (const filePath of fileMatches) {
|
|
130
|
-
const resolved = findExistingFile(this.baseDir, filePath);
|
|
131
|
-
if (!resolved) {
|
|
132
|
-
results.push({ passed: false, message: t("verify.file_not_found", { path: filePath }) });
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
results.push({ passed: true, message: t("verify.file_exists", { path: filePath }) });
|
|
136
|
-
if (!(await this.validateSyntax(resolved))) {
|
|
137
|
-
syntaxValid = false;
|
|
138
|
-
results.push({ passed: false, message: t("verify.syntax_error", { path: filePath }) });
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
passed: results.every((r) => r.passed),
|
|
143
|
-
syntaxValid,
|
|
144
|
-
failed: results.filter((r) => !r.passed),
|
|
145
|
-
noFiles: false,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
async validateSyntax(filePath) {
|
|
149
|
-
const ext = extname(filePath);
|
|
150
|
-
if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
|
|
151
|
-
try {
|
|
152
|
-
await this.runAsync(`npx tsc --noEmit --skipLibCheck ${filePath}`, this.baseDir, 10000);
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
catch (err) {
|
|
156
|
-
if (err.status === 127 ||
|
|
157
|
-
err.message.includes("not found") ||
|
|
158
|
-
err.message.includes("ENOENT")) {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
const stderr = err.stderr?.toString() || "";
|
|
162
|
-
if (stderr.includes("error TS") && !stderr.includes("Cannot find module")) {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (ext === ".js" || ext === ".jsx" || ext === ".cjs" || ext === ".mjs") {
|
|
169
|
-
try {
|
|
170
|
-
await this.runAsync(`node --check ${filePath}`, this.baseDir, 5000);
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Run a command asynchronously (non-blocking event loop) and resolve when it
|
|
181
|
-
* exits. Rejects with the captured stderr/stdout on non-zero exit or timeout.
|
|
182
|
-
*/
|
|
183
|
-
runAsync(command, cwd, timeoutMs) {
|
|
184
|
-
return new Promise((resolve, reject) => {
|
|
185
|
-
const child = spawn(command, {
|
|
186
|
-
cwd,
|
|
187
|
-
shell: true,
|
|
188
|
-
windowsHide: true,
|
|
189
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
190
|
-
});
|
|
191
|
-
let stdout = "";
|
|
192
|
-
let stderr = "";
|
|
193
|
-
child.stdout?.on("data", (d) => {
|
|
194
|
-
stdout += d.toString();
|
|
195
|
-
});
|
|
196
|
-
child.stderr?.on("data", (d) => {
|
|
197
|
-
stderr += d.toString();
|
|
198
|
-
});
|
|
199
|
-
const timer = setTimeout(() => {
|
|
200
|
-
child.kill();
|
|
201
|
-
reject(new Error(`Command timed out after ${timeoutMs}ms`));
|
|
202
|
-
}, timeoutMs);
|
|
203
|
-
child.on("error", (err) => {
|
|
204
|
-
clearTimeout(timer);
|
|
205
|
-
reject(err);
|
|
206
|
-
});
|
|
207
|
-
child.on("close", (code) => {
|
|
208
|
-
clearTimeout(timer);
|
|
209
|
-
if (code === 0) {
|
|
210
|
-
resolve({ stdout, stderr });
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
const err = new Error(`Command failed with exit code ${code}`);
|
|
214
|
-
err.stdout = stdout;
|
|
215
|
-
err.stderr = stderr;
|
|
216
|
-
err.status = code;
|
|
217
|
-
reject(err);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unix commands that do not exist (or have incompatible semantics) in Windows
|
|
3
|
-
* cmd.exe. The 9B model keeps reaching for grep/sed/ls/find via the bash tool;
|
|
4
|
-
* each failure returns a hint, but the model retries.
|
|
5
|
-
*
|
|
6
|
-
* This is a session-level, module-side companion to the bash tool's own
|
|
7
|
-
* per-command hard-stop (`UNIX_TO_WIN_HINTS` / `FAILING_FIRST_WORDS` /
|
|
8
|
-
* `HARD_BLOCK_THRESHOLD` in src/tools/bash.ts). That mechanism injects
|
|
9
|
-
* "STOP using X" INTO the tool output, which a 9B model tends to ignore; this
|
|
10
|
-
* one, gated to win32 like bash.ts, queues a <system-summary> that lands
|
|
11
|
-
* before the next model call. Keep the two command lists consistent.
|
|
12
|
-
*
|
|
13
|
-
* This is a command allow/deny list (like the security module's command
|
|
14
|
-
* blacklist), NOT task classification (rule #10).
|
|
15
|
-
*/
|
|
16
|
-
const FORBIDDEN_COMMANDS = new Set([
|
|
17
|
-
"grep",
|
|
18
|
-
"sed",
|
|
19
|
-
"ls",
|
|
20
|
-
"find",
|
|
21
|
-
"rm",
|
|
22
|
-
"touch",
|
|
23
|
-
"which",
|
|
24
|
-
"diff",
|
|
25
|
-
"cp",
|
|
26
|
-
"mv",
|
|
27
|
-
]);
|
|
28
|
-
function firstWord(command) {
|
|
29
|
-
const segs = command.trim().split(/[\s;|&]+/);
|
|
30
|
-
return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
|
|
31
|
-
}
|
|
32
|
-
/** The forbidden command name if `command` starts with one (handles `cd x && …`). */
|
|
33
|
-
export function forbiddenWindowsCommand(command) {
|
|
34
|
-
const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
|
|
35
|
-
if (m)
|
|
36
|
-
return forbiddenWindowsCommand(m[1]);
|
|
37
|
-
const word = firstWord(command);
|
|
38
|
-
if (word && FORBIDDEN_COMMANDS.has(word))
|
|
39
|
-
return word;
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { t } from "../../i18n/index";
|
|
2
|
-
const MIN_CHARS = 1;
|
|
3
|
-
const MIN_WORDS = 5;
|
|
4
|
-
export class ConfidenceCheck {
|
|
5
|
-
previousResponse = "";
|
|
6
|
-
setPreviousResponse(response) {
|
|
7
|
-
this.previousResponse = response;
|
|
8
|
-
}
|
|
9
|
-
validate(response) {
|
|
10
|
-
if (!response || response.length < MIN_CHARS) {
|
|
11
|
-
return {
|
|
12
|
-
status: "retry",
|
|
13
|
-
kind: "short",
|
|
14
|
-
reason: t("hall.short_response"),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
// Language-agnostic: very short response with no structured content
|
|
18
|
-
const wordCount = response.split(/\s+/).filter(Boolean).length;
|
|
19
|
-
const hasStructure = /```|^\s*[-*]\s|^\s*\d+\.\s|<[^>]+>/m.test(response);
|
|
20
|
-
if (wordCount < MIN_WORDS && !hasStructure) {
|
|
21
|
-
return {
|
|
22
|
-
status: "warn",
|
|
23
|
-
kind: "short",
|
|
24
|
-
reason: t("hall.short_response"),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
// Language-agnostic: repetition detection via word overlap
|
|
28
|
-
if (this.previousResponse) {
|
|
29
|
-
const overlap = this.calculateOverlap(response, this.previousResponse);
|
|
30
|
-
if (overlap > 0.5) {
|
|
31
|
-
return {
|
|
32
|
-
status: "retry",
|
|
33
|
-
kind: "repetition",
|
|
34
|
-
reason: t("hall.repetitive", { pct: Math.round(overlap * 100) }),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
// Language-agnostic: very low word diversity (same words repeated)
|
|
39
|
-
const words = response
|
|
40
|
-
.toLowerCase()
|
|
41
|
-
.split(/\s+/)
|
|
42
|
-
.filter((w) => w.length > 2);
|
|
43
|
-
if (words.length >= 10) {
|
|
44
|
-
const unique = new Set(words);
|
|
45
|
-
const diversity = unique.size / words.length;
|
|
46
|
-
if (diversity < 0.25) {
|
|
47
|
-
return {
|
|
48
|
-
status: "warn",
|
|
49
|
-
kind: "diversity",
|
|
50
|
-
reason: t("hall.repetitive", {
|
|
51
|
-
pct: Math.round((1 - diversity) * 100),
|
|
52
|
-
}),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return { status: "pass" };
|
|
57
|
-
}
|
|
58
|
-
calculateOverlap(a, b) {
|
|
59
|
-
const wordsA = new Set(a.toLowerCase().split(/\s+/));
|
|
60
|
-
const wordsB = b.toLowerCase().split(/\s+/);
|
|
61
|
-
if (wordsB.length === 0)
|
|
62
|
-
return 0;
|
|
63
|
-
const matches = wordsB.filter((w) => wordsA.has(w));
|
|
64
|
-
return matches.length / wordsB.length;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export class ConsistencyCheck {
|
|
2
|
-
decisions = [];
|
|
3
|
-
createdFiles = new Set();
|
|
4
|
-
deletedFiles = new Set();
|
|
5
|
-
trackDecision(decision, location) {
|
|
6
|
-
this.decisions.push({ decision, location });
|
|
7
|
-
}
|
|
8
|
-
trackCreatedFile(path) {
|
|
9
|
-
this.createdFiles.add(path);
|
|
10
|
-
}
|
|
11
|
-
trackDeletedFile(path) {
|
|
12
|
-
this.deletedFiles.add(path);
|
|
13
|
-
}
|
|
14
|
-
getCreatedFiles() {
|
|
15
|
-
return Array.from(this.createdFiles);
|
|
16
|
-
}
|
|
17
|
-
getDeletedFiles() {
|
|
18
|
-
return Array.from(this.deletedFiles);
|
|
19
|
-
}
|
|
20
|
-
getDecisions() {
|
|
21
|
-
return this.decisions;
|
|
22
|
-
}
|
|
23
|
-
hasDecisions() {
|
|
24
|
-
return this.decisions.length > 0;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { ConsistencyCheck } from "./consistency";
|
|
2
|
-
import { ConfidenceCheck } from "./confidence";
|
|
3
|
-
import { FactualCheck } from "./factual";
|
|
4
|
-
import { LLMJudge } from "./llm-judge";
|
|
5
|
-
export class HallucinationDetector {
|
|
6
|
-
consistency;
|
|
7
|
-
confidence;
|
|
8
|
-
factual;
|
|
9
|
-
judge;
|
|
10
|
-
constructor(baseDir, llmProvider) {
|
|
11
|
-
this.consistency = new ConsistencyCheck();
|
|
12
|
-
this.confidence = new ConfidenceCheck();
|
|
13
|
-
this.factual = baseDir ? new FactualCheck(baseDir) : null;
|
|
14
|
-
this.judge = llmProvider ? new LLMJudge(llmProvider) : null;
|
|
15
|
-
}
|
|
16
|
-
getConsistencyCheck() {
|
|
17
|
-
return this.consistency;
|
|
18
|
-
}
|
|
19
|
-
getConfidenceCheck() {
|
|
20
|
-
return this.confidence;
|
|
21
|
-
}
|
|
22
|
-
async validate(response) {
|
|
23
|
-
const confidenceResult = this.confidence.validate(response);
|
|
24
|
-
if (confidenceResult.status === "retry" || confidenceResult.status === "block") {
|
|
25
|
-
return confidenceResult;
|
|
26
|
-
}
|
|
27
|
-
const factualResult = this.factual?.validate(response);
|
|
28
|
-
if (factualResult && factualResult.status !== "pass") {
|
|
29
|
-
return factualResult;
|
|
30
|
-
}
|
|
31
|
-
// LLM-as-judge consistency: fast path (no decisions) skips any LLM call.
|
|
32
|
-
const judgeResult = this.judge ? await this.judge.validate(response, this.consistency) : null;
|
|
33
|
-
const warnings = [];
|
|
34
|
-
if (judgeResult && judgeResult.status === "warn")
|
|
35
|
-
warnings.push(judgeResult.reason || "");
|
|
36
|
-
if (confidenceResult.status === "warn")
|
|
37
|
-
warnings.push(confidenceResult.reason || "");
|
|
38
|
-
if (warnings.length > 0) {
|
|
39
|
-
return { status: "warn", reason: warnings.join("; ") };
|
|
40
|
-
}
|
|
41
|
-
return { status: "pass" };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from "fs";
|
|
2
|
-
import { resolve, isAbsolute, join } from "path";
|
|
3
|
-
import { t } from "../../i18n/index";
|
|
4
|
-
import { extractFileLikeTokens, stripUrls } from "./js-identifiers";
|
|
5
|
-
/** Generated/cached dirs to skip when scanning for bare file names. */
|
|
6
|
-
const IGNORED_DIRS = new Set([
|
|
7
|
-
"node_modules",
|
|
8
|
-
".git",
|
|
9
|
-
"dist",
|
|
10
|
-
"build",
|
|
11
|
-
"coverage",
|
|
12
|
-
".mma",
|
|
13
|
-
"_testing",
|
|
14
|
-
]);
|
|
15
|
-
export class FactualCheck {
|
|
16
|
-
baseDir;
|
|
17
|
-
nameCache = null;
|
|
18
|
-
nameCacheTime = 0;
|
|
19
|
-
lastRescan = 0;
|
|
20
|
-
static NAME_CACHE_TTL_MS = 5_000;
|
|
21
|
-
static RESCAN_COOLDOWN_MS = 2_000;
|
|
22
|
-
static MAX_INDEXED_FILES = 20_000;
|
|
23
|
-
constructor(baseDir) {
|
|
24
|
-
this.baseDir = baseDir;
|
|
25
|
-
}
|
|
26
|
-
validate(response) {
|
|
27
|
-
const filePaths = this.extractFilePaths(response);
|
|
28
|
-
if (filePaths.length === 0)
|
|
29
|
-
return { status: "pass" };
|
|
30
|
-
const nonExistent = [];
|
|
31
|
-
for (const fp of filePaths) {
|
|
32
|
-
if (!this.pathExists(fp)) {
|
|
33
|
-
nonExistent.push(fp);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (nonExistent.length > 0) {
|
|
37
|
-
return {
|
|
38
|
-
status: "warn",
|
|
39
|
-
reason: t("hall.unknown_paths", {
|
|
40
|
-
paths: nonExistent.slice(0, 5).join(", "),
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return { status: "pass" };
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Bare filenames ("bikes.json") and relative paths ("src/data/bikes.json")
|
|
48
|
-
* both land on the project tree. Absolute paths are checked as-is.
|
|
49
|
-
*/
|
|
50
|
-
pathExists(fp) {
|
|
51
|
-
if (isAbsolute(fp))
|
|
52
|
-
return existsSync(fp);
|
|
53
|
-
// "foo.json" has no directory part — treat it as a bare name that may
|
|
54
|
-
// live anywhere under baseDir (e.g. src/data/bikes.json). Resolving it
|
|
55
|
-
// against the workspace root alone produced false "unknown file" warnings.
|
|
56
|
-
if (!fp.includes("/") && !fp.includes("\\")) {
|
|
57
|
-
return this.bareNameExists(fp);
|
|
58
|
-
}
|
|
59
|
-
return existsSync(resolve(this.baseDir, fp));
|
|
60
|
-
}
|
|
61
|
-
bareNameExists(name) {
|
|
62
|
-
if (existsSync(resolve(this.baseDir, name)))
|
|
63
|
-
return true;
|
|
64
|
-
if (this.indexHas(name))
|
|
65
|
-
return true;
|
|
66
|
-
// A write_file from this session may have created the file after the
|
|
67
|
-
// index was built. Do a one-off rescan (rate-limited) before warning.
|
|
68
|
-
const now = Date.now();
|
|
69
|
-
if (now - this.lastRescan < FactualCheck.RESCAN_COOLDOWN_MS)
|
|
70
|
-
return false;
|
|
71
|
-
this.lastRescan = now;
|
|
72
|
-
this.nameCache = null;
|
|
73
|
-
this.nameCacheTime = 0;
|
|
74
|
-
return this.indexHas(name);
|
|
75
|
-
}
|
|
76
|
-
indexHas(name) {
|
|
77
|
-
return (this.getNameIndex().get(name)?.length ?? 0) > 0;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Basename → paths index, rebuilt every NAME_CACHE_TTL_MS so newly created
|
|
81
|
-
* files (write_file) are picked up without a full scan on every response.
|
|
82
|
-
*/
|
|
83
|
-
getNameIndex() {
|
|
84
|
-
const now = Date.now();
|
|
85
|
-
if (this.nameCache && now - this.nameCacheTime < FactualCheck.NAME_CACHE_TTL_MS) {
|
|
86
|
-
return this.nameCache;
|
|
87
|
-
}
|
|
88
|
-
const index = new Map();
|
|
89
|
-
const scanned = this.scanDir(this.baseDir, index, 0);
|
|
90
|
-
if (scanned > 0 || index.size > 0) {
|
|
91
|
-
this.nameCache = index;
|
|
92
|
-
this.nameCacheTime = now;
|
|
93
|
-
}
|
|
94
|
-
return index;
|
|
95
|
-
}
|
|
96
|
-
scanDir(dir, index, count) {
|
|
97
|
-
if (count >= FactualCheck.MAX_INDEXED_FILES)
|
|
98
|
-
return count;
|
|
99
|
-
let entries;
|
|
100
|
-
try {
|
|
101
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return count;
|
|
105
|
-
}
|
|
106
|
-
for (const entry of entries) {
|
|
107
|
-
if (count >= FactualCheck.MAX_INDEXED_FILES)
|
|
108
|
-
break;
|
|
109
|
-
const full = join(dir, entry.name);
|
|
110
|
-
if (entry.isDirectory()) {
|
|
111
|
-
if (!IGNORED_DIRS.has(entry.name)) {
|
|
112
|
-
count = this.scanDir(full, index, count);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else if (entry.isFile()) {
|
|
116
|
-
const list = index.get(entry.name);
|
|
117
|
-
if (list)
|
|
118
|
-
list.push(full);
|
|
119
|
-
else
|
|
120
|
-
index.set(entry.name, [full]);
|
|
121
|
-
count++;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return count;
|
|
125
|
-
}
|
|
126
|
-
extractFilePaths(text) {
|
|
127
|
-
return extractFileLikeTokens(stripUrls(text));
|
|
128
|
-
}
|
|
129
|
-
}
|