micro-models-agent 0.59.0 → 0.60.1
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 +584 -564
- package/README.md +358 -358
- package/dist/certification/certifications.json +493 -493
- package/dist/i18n/en.json +746 -718
- package/dist/i18n/ru.json +746 -718
- package/dist/main.js +566 -70
- package/dist/modules/browser/bridge-server.mjs +219 -219
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/package.json +51 -51
- package/dist/cli/commands.js +0 -447
- package/dist/cli/completer.js +0 -167
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -153
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/repl-commands.js +0 -761
- package/dist/cli/repl.js +0 -702
- package/dist/cli/run-result.js +0 -33
- package/dist/cli/security-commands.js +0 -164
- package/dist/cli/setup.js +0 -237
- package/dist/config/config.js +0 -276
- package/dist/config/defaults.js +0 -141
- package/dist/config/domains.js +0 -179
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -4
- package/dist/config/security.js +0 -213
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -102
- package/dist/core/agent.js +0 -1018
- package/dist/core/bootstrap.js +0 -481
- package/dist/core/crash-handler.js +0 -51
- package/dist/core/environment.js +0 -199
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -76
- package/dist/core/session-logger.js +0 -251
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -26
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/index.js +0 -46
- 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 -497
- package/dist/llm/orchestrator.js +0 -200
- 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 -189
- 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 -342
- package/dist/modules/browser/snapshot.js +0 -148
- package/dist/modules/browser/types.js +0 -12
- package/dist/modules/certification/cli.js +0 -213
- package/dist/modules/certification/fact-checker.js +0 -82
- package/dist/modules/certification/loader.js +0 -106
- package/dist/modules/certification/manifest.js +0 -58
- package/dist/modules/certification/runner.js +0 -245
- package/dist/modules/certification/scenarios.js +0 -407
- 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 -168
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -440
- package/dist/modules/execution/audit-runners.js +0 -206
- package/dist/modules/execution/auditor.js +0 -218
- package/dist/modules/execution/execution-plugin.js +0 -431
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -625
- package/dist/modules/execution/moe-executor.js +0 -304
- 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 -196
- package/dist/modules/execution/plan-tool.js +0 -677
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -94
- package/dist/modules/execution/stuck-detector.js +0 -746
- package/dist/modules/execution/tracker.js +0 -69
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -235
- 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 -47
- package/dist/modules/hallucination/factual.js +0 -169
- 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 -246
- 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 -389
- 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 -260
- package/dist/modules/lsp/probe.js +0 -86
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -144
- 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 -334
- 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 -261
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/pricing/index.js +0 -61
- package/dist/modules/pricing/prices.js +0 -129
- 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/providers/create.js +0 -22
- package/dist/modules/providers/fallback.js +0 -79
- package/dist/modules/providers/health.js +0 -46
- package/dist/modules/providers/index.js +0 -5
- package/dist/modules/providers/manager.js +0 -161
- package/dist/modules/providers/presets.js +0 -128
- package/dist/modules/providers/registry.js +0 -22
- package/dist/modules/providers/types.js +0 -1
- package/dist/modules/registry.js +0 -48
- 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 -219
- 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 -88
- package/dist/modules/security/path-validator.js +0 -203
- 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 -33
- package/dist/tools/attach-image.js +0 -101
- package/dist/tools/bash.js +0 -519
- package/dist/tools/browser.js +0 -115
- package/dist/tools/chunk-query.js +0 -100
- package/dist/tools/create-dir.js +0 -56
- package/dist/tools/delete-file.js +0 -63
- package/dist/tools/download-file.js +0 -117
- package/dist/tools/edit-file.js +0 -80
- package/dist/tools/enable-tools.js +0 -59
- package/dist/tools/executor.js +0 -154
- package/dist/tools/file-info.js +0 -47
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -27
- package/dist/tools/grep-tool.js +0 -125
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -78
- package/dist/tools/list-dir.js +0 -49
- package/dist/tools/load-skill.js +0 -43
- package/dist/tools/mcp-call.js +0 -69
- package/dist/tools/move-file.js +0 -86
- package/dist/tools/path-utils.js +0 -101
- package/dist/tools/pipeline-run.js +0 -145
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -40
- package/dist/tools/process-list.js +0 -37
- package/dist/tools/process-log.js +0 -54
- package/dist/tools/question.js +0 -141
- package/dist/tools/read-file.js +0 -179
- package/dist/tools/recall.js +0 -118
- package/dist/tools/registry.js +0 -47
- package/dist/tools/remember.js +0 -68
- package/dist/tools/scope-check.js +0 -32
- package/dist/tools/search-history.js +0 -85
- 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 -87
- package/dist/tools/web-fetch.js +0 -119
- package/dist/tools/web-search.js +0 -105
- package/dist/tools/write-file.js +0 -82
- 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 -822
- package/dist/ui/line-math.js +0 -73
- 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 -259
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { PlanCreator, getPlanProgress } from "./planner";
|
|
2
|
-
export class PlanTracker {
|
|
3
|
-
plan;
|
|
4
|
-
currentStepIndex = 0;
|
|
5
|
-
constructor(plan) {
|
|
6
|
-
this.plan = plan;
|
|
7
|
-
}
|
|
8
|
-
getPlan() {
|
|
9
|
-
return this.plan;
|
|
10
|
-
}
|
|
11
|
-
getCurrentStepIndex() {
|
|
12
|
-
return this.currentStepIndex;
|
|
13
|
-
}
|
|
14
|
-
getCurrentStep() {
|
|
15
|
-
return this.plan.steps[this.currentStepIndex];
|
|
16
|
-
}
|
|
17
|
-
getStep(id) {
|
|
18
|
-
return this.plan.steps.find((s) => s.id === id);
|
|
19
|
-
}
|
|
20
|
-
updateStepStatus(id, status) {
|
|
21
|
-
const step = this.getStep(id);
|
|
22
|
-
if (step)
|
|
23
|
-
step.status = status;
|
|
24
|
-
}
|
|
25
|
-
addNote(id, note) {
|
|
26
|
-
const step = this.getStep(id);
|
|
27
|
-
if (step)
|
|
28
|
-
step.note = note;
|
|
29
|
-
}
|
|
30
|
-
advance() {
|
|
31
|
-
if (this.currentStepIndex < this.plan.steps.length - 1) {
|
|
32
|
-
this.currentStepIndex++;
|
|
33
|
-
this.plan.steps[this.currentStepIndex].status = "in_progress";
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Advance the pointer past any done/skipped steps without mutating their
|
|
40
|
-
* status. Keeps currentStepIndex in sync when the model marks steps done via
|
|
41
|
-
* the `plan update` tool (which never calls advance()). Without this,
|
|
42
|
-
* getCurrentStep() stays on the first step forever and the stuck detector
|
|
43
|
-
* accumulates false-positive "no progress" warnings.
|
|
44
|
-
*/
|
|
45
|
-
syncCurrentStep() {
|
|
46
|
-
while (this.currentStepIndex < this.plan.steps.length - 1 &&
|
|
47
|
-
(this.plan.steps[this.currentStepIndex].status === "done" ||
|
|
48
|
-
this.plan.steps[this.currentStepIndex].status === "skipped")) {
|
|
49
|
-
this.currentStepIndex++;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
isComplete() {
|
|
53
|
-
return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
|
|
54
|
-
}
|
|
55
|
-
getProgressString() {
|
|
56
|
-
const p = getPlanProgress(this.plan);
|
|
57
|
-
const total = p.total;
|
|
58
|
-
const settled = p.settled;
|
|
59
|
-
const pct = total > 0 ? Math.round((settled / total) * 100) : 0;
|
|
60
|
-
const barWidth = 10;
|
|
61
|
-
const filled = Math.round((settled / total) * barWidth);
|
|
62
|
-
const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
|
|
63
|
-
const skippedPart = p.skipped > 0 ? ` (${p.skipped} skipped)` : "";
|
|
64
|
-
return `[${this.plan.id}] ${this.plan.title} ${settled}/${total}${skippedPart} ${bar} ${pct}%`;
|
|
65
|
-
}
|
|
66
|
-
toPromptBlock() {
|
|
67
|
-
return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,235 +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, kind) {
|
|
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
|
-
const exists = !!resolved;
|
|
132
|
-
if (kind === "delete") {
|
|
133
|
-
// For delete steps: file NOT existing is a pass
|
|
134
|
-
results.push({
|
|
135
|
-
passed: !exists,
|
|
136
|
-
message: exists
|
|
137
|
-
? t("verify.file_still_exists", { path: filePath })
|
|
138
|
-
: t("verify.file_not_found", { path: filePath }),
|
|
139
|
-
});
|
|
140
|
-
// Don't syntax-check a deleted file
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
// Create steps: file must exist + valid syntax
|
|
144
|
-
if (!exists) {
|
|
145
|
-
results.push({ passed: false, message: t("verify.file_not_found", { path: filePath }) });
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
results.push({ passed: true, message: t("verify.file_exists", { path: filePath }) });
|
|
149
|
-
if (!(await this.validateSyntax(resolved))) {
|
|
150
|
-
syntaxValid = false;
|
|
151
|
-
results.push({ passed: false, message: t("verify.syntax_error", { path: filePath }) });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
passed: results.every((r) => r.passed),
|
|
156
|
-
syntaxValid,
|
|
157
|
-
failed: results.filter((r) => !r.passed),
|
|
158
|
-
noFiles: false,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
async validateSyntax(filePath) {
|
|
162
|
-
const ext = extname(filePath);
|
|
163
|
-
if (ext === ".ts" || ext === ".tsx" || ext === ".cts" || ext === ".mts") {
|
|
164
|
-
try {
|
|
165
|
-
await this.runAsync(`npx tsc --noEmit --skipLibCheck ${filePath}`, this.baseDir, 10000);
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
catch (err) {
|
|
169
|
-
if (err.status === 127 ||
|
|
170
|
-
err.message.includes("not found") ||
|
|
171
|
-
err.message.includes("ENOENT")) {
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
const stderr = err.stderr?.toString() || "";
|
|
175
|
-
if (stderr.includes("error TS") && !stderr.includes("Cannot find module")) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (ext === ".js" || ext === ".jsx" || ext === ".cjs" || ext === ".mjs") {
|
|
182
|
-
try {
|
|
183
|
-
await this.runAsync(`node --check ${filePath}`, this.baseDir, 5000);
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Run a command asynchronously (non-blocking event loop) and resolve when it
|
|
194
|
-
* exits. Rejects with the captured stderr/stdout on non-zero exit or timeout.
|
|
195
|
-
*/
|
|
196
|
-
runAsync(command, cwd, timeoutMs) {
|
|
197
|
-
return new Promise((resolve, reject) => {
|
|
198
|
-
const child = spawn(command, {
|
|
199
|
-
cwd,
|
|
200
|
-
shell: true,
|
|
201
|
-
windowsHide: true,
|
|
202
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
203
|
-
});
|
|
204
|
-
let stdout = "";
|
|
205
|
-
let stderr = "";
|
|
206
|
-
child.stdout?.on("data", (d) => {
|
|
207
|
-
stdout += d.toString();
|
|
208
|
-
});
|
|
209
|
-
child.stderr?.on("data", (d) => {
|
|
210
|
-
stderr += d.toString();
|
|
211
|
-
});
|
|
212
|
-
const timer = setTimeout(() => {
|
|
213
|
-
child.kill();
|
|
214
|
-
reject(new Error(`Command timed out after ${timeoutMs}ms`));
|
|
215
|
-
}, timeoutMs);
|
|
216
|
-
child.on("error", (err) => {
|
|
217
|
-
clearTimeout(timer);
|
|
218
|
-
reject(err);
|
|
219
|
-
});
|
|
220
|
-
child.on("close", (code) => {
|
|
221
|
-
clearTimeout(timer);
|
|
222
|
-
if (code === 0) {
|
|
223
|
-
resolve({ stdout, stderr });
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
const err = new Error(`Command failed with exit code ${code}`);
|
|
227
|
-
err.stdout = stdout;
|
|
228
|
-
err.stderr = stderr;
|
|
229
|
-
err.status = code;
|
|
230
|
-
reject(err);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
@@ -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,47 +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
|
-
/** Add files known from compaction summaries to the factual checker. */
|
|
23
|
-
addKnownFiles(files) {
|
|
24
|
-
this.factual?.addKnownFiles(files);
|
|
25
|
-
}
|
|
26
|
-
async validate(response) {
|
|
27
|
-
const confidenceResult = this.confidence.validate(response);
|
|
28
|
-
if (confidenceResult.status === "retry" || confidenceResult.status === "block") {
|
|
29
|
-
return confidenceResult;
|
|
30
|
-
}
|
|
31
|
-
const factualResult = this.factual?.validate(response);
|
|
32
|
-
if (factualResult && factualResult.status !== "pass") {
|
|
33
|
-
return factualResult;
|
|
34
|
-
}
|
|
35
|
-
// LLM-as-judge consistency: fast path (no decisions) skips any LLM call.
|
|
36
|
-
const judgeResult = this.judge ? await this.judge.validate(response, this.consistency) : null;
|
|
37
|
-
const warnings = [];
|
|
38
|
-
if (judgeResult && judgeResult.status === "warn")
|
|
39
|
-
warnings.push(judgeResult.reason || "");
|
|
40
|
-
if (confidenceResult.status === "warn")
|
|
41
|
-
warnings.push(confidenceResult.reason || "");
|
|
42
|
-
if (warnings.length > 0) {
|
|
43
|
-
return { status: "warn", reason: warnings.join("; ") };
|
|
44
|
-
}
|
|
45
|
-
return { status: "pass" };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,169 +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
|
-
/** Files known from compaction summaries — survive context resets. */
|
|
24
|
-
knownFiles = new Set();
|
|
25
|
-
constructor(baseDir) {
|
|
26
|
-
this.baseDir = baseDir;
|
|
27
|
-
}
|
|
28
|
-
/** Add file paths known from compaction summaries. These are checked
|
|
29
|
-
* before the filesystem scan so bare filenames from compacted turns
|
|
30
|
-
* are recognized even when the directory is in IGNORED_DIRS. */
|
|
31
|
-
addKnownFiles(files) {
|
|
32
|
-
for (const f of files)
|
|
33
|
-
this.knownFiles.add(f);
|
|
34
|
-
}
|
|
35
|
-
validate(response) {
|
|
36
|
-
const filePaths = this.extractFilePaths(response);
|
|
37
|
-
if (filePaths.length === 0)
|
|
38
|
-
return { status: "pass" };
|
|
39
|
-
const nonExistent = [];
|
|
40
|
-
for (const fp of filePaths) {
|
|
41
|
-
if (!this.pathExists(fp)) {
|
|
42
|
-
nonExistent.push(fp);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (nonExistent.length > 0) {
|
|
46
|
-
return {
|
|
47
|
-
status: "warn",
|
|
48
|
-
reason: t("hall.unknown_paths", {
|
|
49
|
-
paths: nonExistent.slice(0, 5).join(", "),
|
|
50
|
-
}),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return { status: "pass" };
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Bare filenames ("bikes.json") and relative paths ("src/data/bikes.json")
|
|
57
|
-
* both land on the project tree. Absolute paths are checked as-is.
|
|
58
|
-
*/
|
|
59
|
-
pathExists(fp) {
|
|
60
|
-
if (isAbsolute(fp))
|
|
61
|
-
return existsSync(fp);
|
|
62
|
-
// Check known files from compaction summaries first
|
|
63
|
-
if (this.knownFiles.has(fp))
|
|
64
|
-
return true;
|
|
65
|
-
for (const cand of this.dotfileVariants(fp)) {
|
|
66
|
-
if (this.knownFiles.has(cand))
|
|
67
|
-
return true;
|
|
68
|
-
if (existsSync(resolve(this.baseDir, cand)))
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
// "foo.json" has no directory part — treat it as a bare name that may
|
|
72
|
-
// live anywhere under baseDir (e.g. src/data/bikes.json). Resolving it
|
|
73
|
-
// against the workspace root alone produced false "unknown file" warnings.
|
|
74
|
-
if (!fp.includes("/") && !fp.includes("\\")) {
|
|
75
|
-
return this.bareNameExists(fp);
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* The token regex cannot anchor a leading dot (`\b` never matches between a
|
|
81
|
-
* non-word char and `.`), so dotfiles (.prettierrc.json, .env, …) are
|
|
82
|
-
* extracted as "prettierrc.json". Try the dot-prefixed basename too before
|
|
83
|
-
* declaring the file missing. "src/config.json" also yields
|
|
84
|
-
* "src/.config.json".
|
|
85
|
-
*/
|
|
86
|
-
dotfileVariants(fp) {
|
|
87
|
-
const idx = Math.max(fp.lastIndexOf("/"), fp.lastIndexOf("\\"));
|
|
88
|
-
const base = idx >= 0 ? fp.slice(idx + 1) : fp;
|
|
89
|
-
if (base.startsWith("."))
|
|
90
|
-
return [fp];
|
|
91
|
-
return idx >= 0 ? [fp, `${fp.slice(0, idx + 1)}.${base}`] : [fp, `.${fp}`];
|
|
92
|
-
}
|
|
93
|
-
bareNameExists(name) {
|
|
94
|
-
for (const cand of this.dotfileVariants(name)) {
|
|
95
|
-
if (existsSync(resolve(this.baseDir, cand)))
|
|
96
|
-
return true;
|
|
97
|
-
if (this.indexHas(cand))
|
|
98
|
-
return true;
|
|
99
|
-
// Check known files — a bare name may match a full path from compaction
|
|
100
|
-
for (const known of this.knownFiles) {
|
|
101
|
-
if (known.endsWith(`/${cand}`) || known.endsWith(`\\${cand}`) || known === cand) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// A write_file from this session may have created the file after the
|
|
107
|
-
// index was built. Do a one-off rescan (rate-limited) before warning.
|
|
108
|
-
const now = Date.now();
|
|
109
|
-
if (now - this.lastRescan < FactualCheck.RESCAN_COOLDOWN_MS)
|
|
110
|
-
return false;
|
|
111
|
-
this.lastRescan = now;
|
|
112
|
-
this.nameCache = null;
|
|
113
|
-
this.nameCacheTime = 0;
|
|
114
|
-
return this.indexHas(name);
|
|
115
|
-
}
|
|
116
|
-
indexHas(name) {
|
|
117
|
-
return (this.getNameIndex().get(name)?.length ?? 0) > 0;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Basename → paths index, rebuilt every NAME_CACHE_TTL_MS so newly created
|
|
121
|
-
* files (write_file) are picked up without a full scan on every response.
|
|
122
|
-
*/
|
|
123
|
-
getNameIndex() {
|
|
124
|
-
const now = Date.now();
|
|
125
|
-
if (this.nameCache && now - this.nameCacheTime < FactualCheck.NAME_CACHE_TTL_MS) {
|
|
126
|
-
return this.nameCache;
|
|
127
|
-
}
|
|
128
|
-
const index = new Map();
|
|
129
|
-
const scanned = this.scanDir(this.baseDir, index, 0);
|
|
130
|
-
if (scanned > 0 || index.size > 0) {
|
|
131
|
-
this.nameCache = index;
|
|
132
|
-
this.nameCacheTime = now;
|
|
133
|
-
}
|
|
134
|
-
return index;
|
|
135
|
-
}
|
|
136
|
-
scanDir(dir, index, count) {
|
|
137
|
-
if (count >= FactualCheck.MAX_INDEXED_FILES)
|
|
138
|
-
return count;
|
|
139
|
-
let entries;
|
|
140
|
-
try {
|
|
141
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
return count;
|
|
145
|
-
}
|
|
146
|
-
for (const entry of entries) {
|
|
147
|
-
if (count >= FactualCheck.MAX_INDEXED_FILES)
|
|
148
|
-
break;
|
|
149
|
-
const full = join(dir, entry.name);
|
|
150
|
-
if (entry.isDirectory()) {
|
|
151
|
-
if (!IGNORED_DIRS.has(entry.name)) {
|
|
152
|
-
count = this.scanDir(full, index, count);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else if (entry.isFile()) {
|
|
156
|
-
const list = index.get(entry.name);
|
|
157
|
-
if (list)
|
|
158
|
-
list.push(full);
|
|
159
|
-
else
|
|
160
|
-
index.set(entry.name, [full]);
|
|
161
|
-
count++;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return count;
|
|
165
|
-
}
|
|
166
|
-
extractFilePaths(text) {
|
|
167
|
-
return extractFileLikeTokens(stripUrls(text));
|
|
168
|
-
}
|
|
169
|
-
}
|