semantic-js-mcp 0.8.1 → 0.10.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/.codex-plugin/plugin.json +1 -1
- package/.prettierrc.json +1 -0
- package/CHANGELOG.md +43 -0
- package/CONTRIBUTING.md +23 -3
- package/README.md +44 -69
- package/ROADMAP.md +2 -2
- package/docs/architecture-decisions.md +35 -22
- package/docs/distribution.md +19 -0
- package/docs/getting-started.md +72 -0
- package/docs/result-schema-migration.md +67 -0
- package/lib/doctor.mjs +3 -2
- package/lib/file-identity.mjs +18 -0
- package/lib/pending-requests.mjs +38 -0
- package/lib/runtime.mjs +4 -2
- package/lib/semantic-evidence.mjs +44 -0
- package/lib/stable-collection.mjs +10 -0
- package/lib/temporary-directory.mjs +15 -0
- package/package.json +28 -4
- package/protocol.mjs +112 -3
- package/scripts/agent-evaluation-contract.mjs +165 -0
- package/scripts/agent-evaluation-smoke.mjs +44 -0
- package/scripts/agent-evaluation.mjs +37 -0
- package/scripts/check-protocol-literals.mjs +30 -0
- package/scripts/ci-smoke.mjs +77 -5
- package/scripts/codex-plugin-verification.mjs +129 -0
- package/scripts/distribution-policy.mjs +28 -7
- package/scripts/distribution-smoke.mjs +19 -14
- package/scripts/doctor-smoke.mjs +40 -0
- package/scripts/documentation-contract.mjs +39 -0
- package/scripts/documentation-gate-smoke.mjs +55 -0
- package/scripts/documentation-gate.mjs +110 -0
- package/scripts/generate-protocol-reference.mjs +32 -3
- package/scripts/lifecycle-memory-benchmark.mjs +300 -0
- package/scripts/lifecycle-memory-contract.mjs +53 -0
- package/scripts/lifecycle-memory-observer.mjs +29 -0
- package/scripts/lifecycle-smoke.mjs +309 -0
- package/scripts/negative-verification-smoke.mjs +243 -0
- package/scripts/postpublication-smoke.mjs +245 -0
- package/scripts/release-contract.mjs +78 -0
- package/scripts/release-smoke.mjs +123 -0
- package/scripts/release-verify.mjs +64 -0
- package/scripts/repository-matrix-contract.mjs +80 -0
- package/scripts/repository-matrix-smoke.mjs +153 -0
- package/scripts/repository-matrix.mjs +276 -0
- package/scripts/semantic-js-mcp-ci.mjs +40 -26
- package/scripts/smoke.mjs +307 -47
- package/scripts/vue-smoke.mjs +352 -7
- package/server.mjs +736 -315
- package/skills/semantic-navigation/SKILL.md +23 -10
- package/skills/semantic-navigation/references/protocol-literals.md +110 -4
package/lib/doctor.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {mkdtemp, mkdir, realpath,
|
|
2
|
+
import {mkdtemp, mkdir, realpath, writeFile} from "node:fs/promises";
|
|
3
3
|
import {tmpdir} from "node:os";
|
|
4
4
|
import {Client} from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
5
|
import {StdioClientTransport} from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
TOOL_ORDER,
|
|
25
25
|
} from "../protocol.mjs";
|
|
26
26
|
import {PACKAGE_ROOT, inspectExternalCommand, inspectNodeRuntime, inspectRuntimeComponents} from "./runtime.mjs";
|
|
27
|
+
import {removeTemporaryDirectory} from "./temporary-directory.mjs";
|
|
27
28
|
|
|
28
29
|
const STATUS_EXIT_CODE = Object.freeze({
|
|
29
30
|
[CI_STATUS.PASS]: CI_EXIT_CODE.PASS,
|
|
@@ -312,7 +313,7 @@ export async function runDoctor({packageRoot = PACKAGE_ROOT} = {}) {
|
|
|
312
313
|
}
|
|
313
314
|
} finally {
|
|
314
315
|
await client.close().catch(() => undefined);
|
|
315
|
-
await
|
|
316
|
+
await removeTemporaryDirectory(fixture.workspace);
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
return doctorResult(packageRoot, checks, runtime);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {OPERATING_SYSTEM} from "../protocol.mjs";
|
|
3
|
+
|
|
4
|
+
export function fileIdentity(file, operatingSystem = process.platform) {
|
|
5
|
+
const pathImplementation = operatingSystem === OPERATING_SYSTEM.WINDOWS ? path.win32 : path;
|
|
6
|
+
const resolved = pathImplementation.resolve(file);
|
|
7
|
+
return operatingSystem === OPERATING_SYSTEM.WINDOWS ? resolved.toLowerCase() : resolved;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function locationKeyAt(file, line, column, operatingSystem = process.platform) {
|
|
11
|
+
return `${fileIdentity(file, operatingSystem)}:${line}:${column}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function locationKeyForOperatingSystem(operatingSystem) {
|
|
15
|
+
return (location) => locationKeyAt(location.file, location.range.start.line, location.range.start.column, operatingSystem);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const locationKey = locationKeyForOperatingSystem(process.platform);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class PendingRequestRegistry {
|
|
2
|
+
constructor({timeoutMilliseconds, timeoutMessage}) {
|
|
3
|
+
this.timeoutMilliseconds = timeoutMilliseconds;
|
|
4
|
+
this.timeoutMessage = timeoutMessage;
|
|
5
|
+
this.entries = new Map();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get size() {
|
|
9
|
+
return this.entries.size;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
create(key, operation) {
|
|
13
|
+
if (this.entries.has(key)) throw new Error(`Pending request already exists: ${key}`);
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
this.entries.delete(key);
|
|
17
|
+
reject(new Error(this.timeoutMessage(operation)));
|
|
18
|
+
}, this.timeoutMilliseconds);
|
|
19
|
+
this.entries.set(key, {resolve, reject, timer});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
take(key) {
|
|
24
|
+
const entry = this.entries.get(key);
|
|
25
|
+
if (!entry) return undefined;
|
|
26
|
+
clearTimeout(entry.timer);
|
|
27
|
+
this.entries.delete(key);
|
|
28
|
+
return entry;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
rejectAll(error) {
|
|
32
|
+
for (const entry of this.entries.values()) {
|
|
33
|
+
clearTimeout(entry.timer);
|
|
34
|
+
entry.reject(error);
|
|
35
|
+
}
|
|
36
|
+
this.entries.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
package/lib/runtime.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
import {fileURLToPath} from "node:url";
|
|
6
6
|
import {CONFIGURATION_FILE, NODE_EVENT, PROCESS_EXIT_CODE, REQUIRED_RUNTIME_COMPONENT, RUNTIME_REQUIREMENT} from "../protocol.mjs";
|
|
7
7
|
|
|
8
|
+
const STREAM_EVENT = Object.freeze({DATA: "data"});
|
|
9
|
+
|
|
8
10
|
export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
11
|
|
|
10
12
|
export function inspectNodeRuntime() {
|
|
@@ -50,10 +52,10 @@ export function inspectExternalCommand(command, args) {
|
|
|
50
52
|
const child = spawn(command, args, {stdio: ["ignore", "pipe", "pipe"]});
|
|
51
53
|
let stdout = "";
|
|
52
54
|
let stderr = "";
|
|
53
|
-
child.stdout.on(
|
|
55
|
+
child.stdout.on(STREAM_EVENT.DATA, (chunk) => {
|
|
54
56
|
stdout += chunk;
|
|
55
57
|
});
|
|
56
|
-
child.stderr.on(
|
|
58
|
+
child.stderr.on(STREAM_EVENT.DATA, (chunk) => {
|
|
57
59
|
stderr += chunk;
|
|
58
60
|
});
|
|
59
61
|
child.on(NODE_EVENT.ERROR, (error) => resolve({command, available: false, error: error.message}));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COLLECTION_STATUS,
|
|
3
|
+
DEFINITION_SELECTION_STATUS,
|
|
4
|
+
SEMANTIC_EVIDENCE_FOLLOW_UP_REASON,
|
|
5
|
+
SEMANTIC_EVIDENCE_STATUS,
|
|
6
|
+
TOOL,
|
|
7
|
+
} from "../protocol.mjs";
|
|
8
|
+
|
|
9
|
+
const NAMED_SYMBOL_TOOLS = new Set([TOOL.COUNT_NAMED_SYMBOL, TOOL.AUDIT_NAMED_SYMBOL]);
|
|
10
|
+
const DEFINITION_SELECTION_STATUSES = new Set(Object.values(DEFINITION_SELECTION_STATUS));
|
|
11
|
+
|
|
12
|
+
export function isNamedSymbolTool(tool) {
|
|
13
|
+
return NAMED_SYMBOL_TOOLS.has(tool);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function namedSemanticEvidence(selectionStatus, collectionStatus) {
|
|
17
|
+
const followUpReasons = [];
|
|
18
|
+
if (collectionStatus === COLLECTION_STATUS.LIMITED) {
|
|
19
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.COLLECTION_LIMITED);
|
|
20
|
+
}
|
|
21
|
+
if (collectionStatus === COLLECTION_STATUS.PARTIAL) {
|
|
22
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.COLLECTION_PARTIAL);
|
|
23
|
+
}
|
|
24
|
+
if (selectionStatus === DEFINITION_SELECTION_STATUS.NONE) {
|
|
25
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.NO_DEFINITION_SELECTED);
|
|
26
|
+
}
|
|
27
|
+
if (selectionStatus === DEFINITION_SELECTION_STATUS.MULTIPLE) {
|
|
28
|
+
followUpReasons.push(SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.MULTIPLE_DEFINITIONS_SELECTED);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
status: followUpReasons.length === 0 ? SEMANTIC_EVIDENCE_STATUS.USABLE : SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
|
|
32
|
+
followUpReasons,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function namedSemanticEvidenceMatches(result, collectionStatus) {
|
|
37
|
+
const actual = result?.semanticEvidence;
|
|
38
|
+
if (!actual || !Array.isArray(actual.followUpReasons)) return false;
|
|
39
|
+
if (!DEFINITION_SELECTION_STATUSES.has(result.definitionSelectionStatus)) return false;
|
|
40
|
+
const expected = namedSemanticEvidence(result.definitionSelectionStatus, collectionStatus);
|
|
41
|
+
if (actual.status !== expected.status) return false;
|
|
42
|
+
if (actual.followUpReasons.length !== expected.followUpReasons.length) return false;
|
|
43
|
+
return actual.followUpReasons.every((reason, index) => reason === expected.followUpReasons[index]);
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export async function collectStableSnapshot({attempts, collect, inventory, sameInventory, fingerprint}) {
|
|
2
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
3
|
+
const inventoryBefore = await inventory();
|
|
4
|
+
const value = await collect();
|
|
5
|
+
const [fingerprints, inventoryAfter] = await Promise.all([fingerprint(value), inventory()]);
|
|
6
|
+
if (!sameInventory(inventoryBefore, inventoryAfter)) continue;
|
|
7
|
+
return {value, fingerprints, inventory: inventoryAfter, attempt};
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {rm} from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
const CLEANUP = Object.freeze({
|
|
4
|
+
MAXIMUM_RETRIES: 10,
|
|
5
|
+
RETRY_DELAY_MILLISECONDS: 100,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export async function removeTemporaryDirectory(directory) {
|
|
9
|
+
await rm(directory, {
|
|
10
|
+
recursive: true,
|
|
11
|
+
force: true,
|
|
12
|
+
maxRetries: CLEANUP.MAXIMUM_RETRIES,
|
|
13
|
+
retryDelay: CLEANUP.RETRY_DELAY_MILLISECONDS,
|
|
14
|
+
});
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semantic-js-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Semantic understanding of JavaScript codebases for AI agents",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"lsp",
|
|
9
|
+
"language-server-protocol",
|
|
10
|
+
"javascript",
|
|
11
|
+
"typescript",
|
|
12
|
+
"vue",
|
|
13
|
+
"semantic-analysis",
|
|
14
|
+
"code-navigation",
|
|
15
|
+
"ai-agents"
|
|
16
|
+
],
|
|
5
17
|
"author": {
|
|
6
18
|
"name": "Jonathan Muñoz Lucas",
|
|
7
19
|
"email": "git@jonathanmunoz.dev",
|
|
@@ -32,17 +44,29 @@
|
|
|
32
44
|
"node": ">=22"
|
|
33
45
|
},
|
|
34
46
|
"scripts": {
|
|
35
|
-
"check": "npm run format:check && node --check protocol.mjs && node --check server.mjs && node --check cli.mjs && node --check lib/runtime.mjs && node --check lib/doctor.mjs && node --check scripts/generate-protocol-reference.mjs && node --check scripts/check-protocol-literals.mjs && node --check scripts/check-runtime.mjs && node --check scripts/semantic-js-mcp-ci.mjs && node --check scripts/ci-smoke.mjs && node --check scripts/smoke.mjs && node --check scripts/vue-smoke.mjs && node --check scripts/benchmark.mjs && node --check scripts/distribution-policy.mjs && node --check scripts/distribution-smoke.mjs && node scripts/generate-protocol-reference.mjs --check && node scripts/check-protocol-literals.mjs",
|
|
47
|
+
"check": "npm run format:check && node --check protocol.mjs && node --check server.mjs && node --check cli.mjs && node --check lib/runtime.mjs && node --check lib/doctor.mjs && node --check lib/semantic-evidence.mjs && node --check lib/pending-requests.mjs && node --check lib/stable-collection.mjs && node --check scripts/generate-protocol-reference.mjs && node --check scripts/check-protocol-literals.mjs && node --check scripts/check-runtime.mjs && node --check scripts/documentation-contract.mjs && node --check scripts/documentation-gate.mjs && node --check scripts/documentation-gate-smoke.mjs && node --check scripts/semantic-js-mcp-ci.mjs && node --check scripts/ci-smoke.mjs && node --check scripts/negative-verification-smoke.mjs && node --check scripts/doctor-smoke.mjs && node --check scripts/smoke.mjs && node --check scripts/vue-smoke.mjs && node --check scripts/lifecycle-smoke.mjs && node --check scripts/lifecycle-memory-contract.mjs && node --check scripts/lifecycle-memory-observer.mjs && node --check scripts/lifecycle-memory-benchmark.mjs && node --check scripts/benchmark.mjs && node --check scripts/distribution-policy.mjs && node --check scripts/distribution-smoke.mjs && node --check scripts/release-contract.mjs && node --check scripts/release-verify.mjs && node --check scripts/release-smoke.mjs && node --check scripts/postpublication-smoke.mjs && node --check scripts/codex-plugin-verification.mjs && node --check scripts/agent-evaluation-contract.mjs && node --check scripts/agent-evaluation.mjs && node --check scripts/agent-evaluation-smoke.mjs && node --check scripts/repository-matrix-contract.mjs && node --check scripts/repository-matrix.mjs && node --check scripts/repository-matrix-smoke.mjs && node scripts/generate-protocol-reference.mjs --check && node scripts/check-protocol-literals.mjs && node scripts/documentation-gate-smoke.mjs",
|
|
36
48
|
"check:runtime": "node scripts/check-runtime.mjs",
|
|
49
|
+
"check:documentation": "node scripts/documentation-gate.mjs",
|
|
37
50
|
"doctor": "node cli.mjs doctor",
|
|
38
51
|
"ci:evaluate": "node scripts/semantic-js-mcp-ci.mjs",
|
|
39
52
|
"smoke": "node scripts/smoke.mjs",
|
|
40
53
|
"smoke:ci": "node scripts/ci-smoke.mjs",
|
|
54
|
+
"smoke:negative": "node scripts/negative-verification-smoke.mjs",
|
|
55
|
+
"smoke:doctor": "node scripts/doctor-smoke.mjs",
|
|
41
56
|
"smoke:vue": "node scripts/vue-smoke.mjs",
|
|
57
|
+
"smoke:lifecycle": "node scripts/lifecycle-smoke.mjs",
|
|
42
58
|
"smoke:distribution": "node scripts/distribution-smoke.mjs",
|
|
59
|
+
"smoke:release": "node scripts/release-smoke.mjs",
|
|
60
|
+
"smoke:evaluation": "node scripts/agent-evaluation-smoke.mjs",
|
|
61
|
+
"smoke:matrix": "node scripts/repository-matrix-smoke.mjs",
|
|
62
|
+
"evaluate:agent": "node scripts/agent-evaluation.mjs",
|
|
63
|
+
"validate:repositories": "node scripts/repository-matrix.mjs",
|
|
64
|
+
"release:verify": "node scripts/release-verify.mjs",
|
|
65
|
+
"verify:published": "node scripts/postpublication-smoke.mjs",
|
|
43
66
|
"benchmark": "node scripts/benchmark.mjs",
|
|
44
|
-
"
|
|
45
|
-
"format
|
|
67
|
+
"benchmark:lifecycle-memory": "node scripts/lifecycle-memory-benchmark.mjs",
|
|
68
|
+
"format": "prettier --write \".codex-plugin/**/*.json\" \".agents/plugins/marketplace.json\" \".github/**/*.yml\" \".mcp.json\" \".prettierrc.json\" \"package.json\" \"package-lock.json\" \"CHANGELOG.md\" \"CONTRIBUTING.md\" \"README.md\" \"ROADMAP.md\" \"SECURITY.md\" \"*.mjs\" \"docs/**/*.md\" \"lib/**/*.mjs\" \"scripts/**/*.mjs\" \"skills/**/SKILL.md\" \"skills/**/agents/*.yaml\"",
|
|
69
|
+
"format:check": "prettier --check \".codex-plugin/**/*.json\" \".agents/plugins/marketplace.json\" \".github/**/*.yml\" \".mcp.json\" \".prettierrc.json\" \"package.json\" \"package-lock.json\" \"CHANGELOG.md\" \"CONTRIBUTING.md\" \"README.md\" \"ROADMAP.md\" \"SECURITY.md\" \"*.mjs\" \"docs/**/*.md\" \"lib/**/*.mjs\" \"scripts/**/*.mjs\" \"skills/**/SKILL.md\" \"skills/**/agents/*.yaml\""
|
|
46
70
|
},
|
|
47
71
|
"dependencies": {
|
|
48
72
|
"@modelcontextprotocol/sdk": "1.27.1",
|
package/protocol.mjs
CHANGED
|
@@ -125,9 +125,53 @@ export const LANGUAGE_ID = Object.freeze({
|
|
|
125
125
|
VUE: "vue",
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
export const COMMON_VALUE = Object.freeze({
|
|
129
|
+
UNKNOWN: "unknown",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const SEARCH_SCOPE = Object.freeze({
|
|
133
|
+
DOCUMENT: "document",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const RUNTIME_PACKAGE = Object.freeze({
|
|
137
|
+
TYPESCRIPT: "typescript",
|
|
138
|
+
});
|
|
139
|
+
|
|
128
140
|
export const VUE_SCRIPT_LANGUAGE = Object.freeze({
|
|
129
141
|
JAVASCRIPT: "js",
|
|
130
142
|
JAVASCRIPT_REACT: "jsx",
|
|
143
|
+
TYPESCRIPT: "ts",
|
|
144
|
+
TYPESCRIPT_REACT: "tsx",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export const DIAGNOSTIC_PROVIDER = Object.freeze({
|
|
148
|
+
TYPESCRIPT_LANGUAGE_SERVER: "typescript-language-server",
|
|
149
|
+
VUE_LANGUAGE_SERVER: "vue-language-server",
|
|
150
|
+
UNKNOWN: COMMON_VALUE.UNKNOWN,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export const DIAGNOSTIC_REGION = Object.freeze({
|
|
154
|
+
DOCUMENT: SEARCH_SCOPE.DOCUMENT,
|
|
155
|
+
SCRIPT: "script",
|
|
156
|
+
SCRIPT_SETUP: "script-setup",
|
|
157
|
+
TEMPLATE: "template",
|
|
158
|
+
STYLE: "style",
|
|
159
|
+
CUSTOM_BLOCK: "custom-block",
|
|
160
|
+
UNKNOWN: COMMON_VALUE.UNKNOWN,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export const DIAGNOSTIC_LANGUAGE = Object.freeze({
|
|
164
|
+
TYPESCRIPT: LANGUAGE_ID.TYPESCRIPT,
|
|
165
|
+
TYPESCRIPT_REACT: LANGUAGE_ID.TYPESCRIPT_REACT,
|
|
166
|
+
JAVASCRIPT: LANGUAGE_ID.JAVASCRIPT,
|
|
167
|
+
JAVASCRIPT_REACT: LANGUAGE_ID.JAVASCRIPT_REACT,
|
|
168
|
+
VUE: LANGUAGE_ID.VUE,
|
|
169
|
+
HTML: "html",
|
|
170
|
+
PUG: "pug",
|
|
171
|
+
CSS: "css",
|
|
172
|
+
SCSS: "scss",
|
|
173
|
+
LESS: "less",
|
|
174
|
+
UNKNOWN: COMMON_VALUE.UNKNOWN,
|
|
131
175
|
});
|
|
132
176
|
|
|
133
177
|
export const FORBIDDEN_PUBLIC_FIELD = Object.freeze([
|
|
@@ -151,10 +195,43 @@ export const PRESENTATION_MODE = Object.freeze({
|
|
|
151
195
|
ALL_ITEMS: "all-items",
|
|
152
196
|
SUBSET: "subset",
|
|
153
197
|
COUNT_ONLY: "count-only",
|
|
154
|
-
|
|
198
|
+
COMPACT_SUMMARY: "compact-summary",
|
|
155
199
|
PAGE: "page",
|
|
156
200
|
});
|
|
157
201
|
|
|
202
|
+
export const TOOL_DESCRIPTION = Object.freeze({
|
|
203
|
+
[TOOL.DOCUMENT_SYMBOLS]: `Returns declarations and nested members for one file. Use ${TOOL.DEFINITION} or ${TOOL.AUDIT_SYMBOL} with an exact position for semantic identity.`,
|
|
204
|
+
[TOOL.WORKSPACE_SYMBOLS]: `Returns declaration-shaped symbols whose names contain a query. Use ${TOOL.COUNT_NAMED_SYMBOL} or ${TOOL.AUDIT_NAMED_SYMBOL} with an exact name.`,
|
|
205
|
+
[TOOL.DEFINITION]: `Resolves definitions for one source position. Use ${TOOL.HOVER} for type information or ${TOOL.COUNT_REFERENCES} for impact scope.`,
|
|
206
|
+
[TOOL.HOVER]: `Returns inferred type information and documentation for one source position. Use ${TOOL.DEFINITION} for declaration identity.`,
|
|
207
|
+
[TOOL.DIAGNOSTICS]:
|
|
208
|
+
"Returns diagnostics for one file and marks evidence untrusted when the current document snapshot cannot be confirmed.",
|
|
209
|
+
[TOOL.COUNT_TEXT_MATCHES]: `Counts exact identifier text matches without semantic verification. Use ${TOOL.COUNT_NAMED_SYMBOL} or ${TOOL.AUDIT_NAMED_SYMBOL} to verify identity.`,
|
|
210
|
+
[TOOL.COUNT_NAMED_SYMBOL]: `Returns exact-definition and verified-reference counts plus explicit semantic follow-up status for a symbol name. Use ${TOOL.AUDIT_NAMED_SYMBOL} for identity, signature, or file-hint binding verification and ${TOOL.REFERENCE_PAGE} with a returned referenceSetId for locations.`,
|
|
211
|
+
[TOOL.COUNT_REFERENCES]: `Returns verified-reference counts for the symbol at one source position. Use ${TOOL.AUDIT_SYMBOL} for identity and signature or ${TOOL.REFERENCE_PAGE} with the returned referenceSetId for locations.`,
|
|
212
|
+
[TOOL.AUDIT_NAMED_SYMBOL]: `Returns a compact exact-name audit with explicit semantic follow-up status and verifies source bindings to fileHint when no declaration is selected. Use ${TOOL.REFERENCE_PAGE} with a returned referenceSetId for locations.`,
|
|
213
|
+
[TOOL.AUDIT_SYMBOL]: `Returns a compact definition, signature, coverage, and freshness summary for one source position. Use ${TOOL.REFERENCE_PAGE} with the returned referenceSetId for locations.`,
|
|
214
|
+
[TOOL.REFERENCES]: `Returns the first page of verified source locations and detailed collection evidence. Use ${TOOL.REFERENCE_PAGE} for later pages.`,
|
|
215
|
+
[TOOL.REFERENCE_PAGE]: `Returns a page from a freshness-checked reference set created by a count, audit, or ${TOOL.REFERENCES} call.`,
|
|
216
|
+
[TOOL.UNRESOLVED_REFERENCE_PAGE]:
|
|
217
|
+
"Returns freshness-checked text-match candidates whose definitions could not be resolved, including literal failure reasons.",
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
export const LSP_METHOD = Object.freeze({
|
|
221
|
+
DOCUMENT_DIAGNOSTIC: "textDocument/diagnostic",
|
|
222
|
+
EXECUTE_COMMAND: "workspace/executeCommand",
|
|
223
|
+
PUBLISH_DIAGNOSTICS: "textDocument/publishDiagnostics",
|
|
224
|
+
WORKSPACE_DIAGNOSTIC_REFRESH: "workspace/diagnostic/refresh",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const LSP_COMMAND = Object.freeze({
|
|
228
|
+
GO_TO_SOURCE_DEFINITION: "_typescript.goToSourceDefinition",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
export const TYPESCRIPT_SERVER_COMMAND = Object.freeze({
|
|
232
|
+
DEFINITION_AND_BOUND_SPAN: "definitionAndBoundSpan",
|
|
233
|
+
});
|
|
234
|
+
|
|
158
235
|
export const LIMIT_MODE = Object.freeze({
|
|
159
236
|
UNLIMITED: "unlimited",
|
|
160
237
|
MAXIMUM: "maximum",
|
|
@@ -170,6 +247,24 @@ export const DEFINITION_MATCH = Object.freeze({
|
|
|
170
247
|
UNRESOLVED: "unresolved",
|
|
171
248
|
});
|
|
172
249
|
|
|
250
|
+
export const DEFINITION_SELECTION_STATUS = Object.freeze({
|
|
251
|
+
NONE: "no-definition-selected",
|
|
252
|
+
ONE: "one-definition-selected",
|
|
253
|
+
MULTIPLE: "multiple-definitions-selected",
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
export const SEMANTIC_EVIDENCE_STATUS = Object.freeze({
|
|
257
|
+
USABLE: "usable-as-requested",
|
|
258
|
+
FOLLOW_UP_REQUIRED: "follow-up-required",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
export const SEMANTIC_EVIDENCE_FOLLOW_UP_REASON = Object.freeze({
|
|
262
|
+
COLLECTION_LIMITED: "collection-is-limited",
|
|
263
|
+
COLLECTION_PARTIAL: "collection-is-partial",
|
|
264
|
+
NO_DEFINITION_SELECTED: DEFINITION_SELECTION_STATUS.NONE,
|
|
265
|
+
MULTIPLE_DEFINITIONS_SELECTED: DEFINITION_SELECTION_STATUS.MULTIPLE,
|
|
266
|
+
});
|
|
267
|
+
|
|
173
268
|
export const SIGNATURE_SOURCE = Object.freeze({
|
|
174
269
|
QUERY_POSITION_HOVER: "query-position-hover",
|
|
175
270
|
RESOLVED_DEFINITION_HOVER: "resolved-definition-hover",
|
|
@@ -178,6 +273,7 @@ export const SIGNATURE_SOURCE = Object.freeze({
|
|
|
178
273
|
|
|
179
274
|
export const DIAGNOSTIC_FRESHNESS = Object.freeze({
|
|
180
275
|
CURRENT: "current-document-version",
|
|
276
|
+
DIFFERENT_CONTENT: "different-document-content",
|
|
181
277
|
VERSION_NOT_REPORTED: "version-not-reported-by-language-server",
|
|
182
278
|
DIFFERENT_VERSION: "different-document-version",
|
|
183
279
|
NOT_REPORTED_FOR_CURRENT_DOCUMENT: "not-reported-for-current-document",
|
|
@@ -198,6 +294,8 @@ export const EVIDENCE_STATUS = Object.freeze({
|
|
|
198
294
|
|
|
199
295
|
export const DIAGNOSTIC_EVIDENCE_REASON = Object.freeze({
|
|
200
296
|
CURRENT_DOCUMENT_VERSION_CONFIRMED: "current-document-version-confirmed",
|
|
297
|
+
CURRENT_DOCUMENT_SNAPSHOT_CONFIRMED: "current-document-snapshot-confirmed",
|
|
298
|
+
DOCUMENT_CONTENT_CHANGED_DURING_ACQUISITION: "document-content-changed-during-diagnostic-acquisition",
|
|
201
299
|
LANGUAGE_SERVER_VERSION_NOT_REPORTED: "language-server-version-not-reported",
|
|
202
300
|
LANGUAGE_SERVER_REPORTED_DIFFERENT_VERSION: "language-server-reported-different-version",
|
|
203
301
|
LANGUAGE_SERVER_DID_NOT_REPORT_CURRENT_DOCUMENT: "language-server-did-not-report-current-document",
|
|
@@ -230,6 +328,10 @@ export const FINGERPRINT_ALGORITHM = Object.freeze({
|
|
|
230
328
|
SHA_256: "sha256",
|
|
231
329
|
});
|
|
232
330
|
|
|
331
|
+
export const FINGERPRINT_FORMAT = Object.freeze({
|
|
332
|
+
SHA_256_PREFIX: `${FINGERPRINT_ALGORITHM.SHA_256}:`,
|
|
333
|
+
});
|
|
334
|
+
|
|
233
335
|
export const REFERENCE_DISCOVERY_METHOD = Object.freeze({
|
|
234
336
|
OWNING_WORKSPACE_LANGUAGE_SERVER: "owning-workspace-language-server",
|
|
235
337
|
DEFINITION_MATCH_FROM_ANOTHER_WORKSPACE: "definition-match-from-another-workspace",
|
|
@@ -288,6 +390,7 @@ export const CI_REASON = Object.freeze({
|
|
|
288
390
|
VERIFIED_DIAGNOSTIC_ERRORS: "verified-current-document-has-error-diagnostics",
|
|
289
391
|
INCOMPLETE_EVIDENCE: "collection-is-limited-or-partial",
|
|
290
392
|
UNTRUSTED_DIAGNOSTICS: "diagnostics-for-current-document-are-untrusted",
|
|
393
|
+
SEMANTIC_FOLLOW_UP_REQUIRED: "semantic-evidence-requires-follow-up",
|
|
291
394
|
TOOL_EXECUTION_FAILED: "tool-execution-failed",
|
|
292
395
|
INVALID_INPUT: "input-is-not-a-semantic-js-mcp-result",
|
|
293
396
|
});
|
|
@@ -302,10 +405,10 @@ export const INTERNAL_RESOLUTION_SOURCE = Object.freeze({
|
|
|
302
405
|
|
|
303
406
|
export const RESULT_SCHEMA = Object.freeze({
|
|
304
407
|
NAME: PRODUCT.NAME,
|
|
305
|
-
VERSION:
|
|
408
|
+
VERSION: 7,
|
|
306
409
|
});
|
|
307
410
|
|
|
308
|
-
export const SERVER_VERSION = "0.
|
|
411
|
+
export const SERVER_VERSION = "0.10.0";
|
|
309
412
|
|
|
310
413
|
export const REQUIRED_RUNTIME_COMPONENT = Object.freeze({
|
|
311
414
|
TYPESCRIPT_LANGUAGE_SERVER: "typescript-language-server/lib/cli.mjs",
|
|
@@ -330,6 +433,12 @@ export const NODE_EVENT = Object.freeze({
|
|
|
330
433
|
CLOSE: "close",
|
|
331
434
|
});
|
|
332
435
|
|
|
436
|
+
export const OPERATING_SYSTEM = Object.freeze({
|
|
437
|
+
LINUX: "linux",
|
|
438
|
+
MACOS: "darwin",
|
|
439
|
+
WINDOWS: "win32",
|
|
440
|
+
});
|
|
441
|
+
|
|
333
442
|
export const DEFAULT = Object.freeze({
|
|
334
443
|
REQUEST_TIMEOUT_MS: 30_000,
|
|
335
444
|
DIAGNOSTIC_WAIT_MS: 2_000,
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCOUNTING_STATUS,
|
|
3
|
+
CI_EXIT_CODE,
|
|
4
|
+
CI_STATUS,
|
|
5
|
+
COLLECTION_STATUS,
|
|
6
|
+
DEFINITION_SELECTION_STATUS,
|
|
7
|
+
SEMANTIC_EVIDENCE_FOLLOW_UP_REASON,
|
|
8
|
+
SEMANTIC_EVIDENCE_STATUS,
|
|
9
|
+
TOOL,
|
|
10
|
+
} from "../protocol.mjs";
|
|
11
|
+
|
|
12
|
+
export const AGENT_EVALUATION_ARGUMENT = Object.freeze({
|
|
13
|
+
ANSWERS: "--answers",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const AGENT_EVALUATION_COVERAGE = Object.freeze({
|
|
17
|
+
COMPLETE: "complete",
|
|
18
|
+
INCOMPLETE: "incomplete",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const AGENT_EVALUATION_REASON = Object.freeze({
|
|
22
|
+
ALL_DECISIONS_CORRECT: "all-decisions-correct",
|
|
23
|
+
DECISIONS_INCORRECT: "decisions-incorrect",
|
|
24
|
+
ANSWERS_INVALID: "answers-invalid",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const AGENT_EVALUATION_CASES = Object.freeze([
|
|
28
|
+
Object.freeze({
|
|
29
|
+
id: "inspect-named-symbol-identity",
|
|
30
|
+
question: "Which tool should inspect identity and signature before changing this named symbol?",
|
|
31
|
+
compactResult: Object.freeze({
|
|
32
|
+
tool: TOOL.COUNT_NAMED_SYMBOL,
|
|
33
|
+
result: Object.freeze({
|
|
34
|
+
requestedSymbol: "calculateTotal",
|
|
35
|
+
exactDefinitionsFound: 1,
|
|
36
|
+
definitionSelectionStatus: DEFINITION_SELECTION_STATUS.ONE,
|
|
37
|
+
semanticEvidence: Object.freeze({status: SEMANTIC_EVIDENCE_STATUS.USABLE, followUpReasons: Object.freeze([])}),
|
|
38
|
+
references: Object.freeze({verifiedTotal: 18}),
|
|
39
|
+
unresolvedReferences: Object.freeze({count: 0}),
|
|
40
|
+
}),
|
|
41
|
+
collection: Object.freeze({status: COLLECTION_STATUS.COMPLETE, stoppedByLimit: false}),
|
|
42
|
+
continueWith: Object.freeze([TOOL.AUDIT_NAMED_SYMBOL, TOOL.REFERENCE_PAGE]),
|
|
43
|
+
}),
|
|
44
|
+
expected: Object.freeze({nextTool: TOOL.AUDIT_NAMED_SYMBOL, coverage: AGENT_EVALUATION_COVERAGE.COMPLETE}),
|
|
45
|
+
}),
|
|
46
|
+
Object.freeze({
|
|
47
|
+
id: "inspect-unresolved-references",
|
|
48
|
+
question: "Which tool should inspect the unresolved candidates before claiming full impact coverage?",
|
|
49
|
+
compactResult: Object.freeze({
|
|
50
|
+
tool: TOOL.AUDIT_NAMED_SYMBOL,
|
|
51
|
+
result: Object.freeze({
|
|
52
|
+
requestedSymbol: "RequestHandler",
|
|
53
|
+
exactDefinitionsFound: 1,
|
|
54
|
+
definitionSelectionStatus: DEFINITION_SELECTION_STATUS.ONE,
|
|
55
|
+
semanticEvidence: Object.freeze({
|
|
56
|
+
status: SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
|
|
57
|
+
followUpReasons: Object.freeze([SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.COLLECTION_PARTIAL]),
|
|
58
|
+
}),
|
|
59
|
+
references: Object.freeze({verifiedTotal: 52}),
|
|
60
|
+
unresolvedReferences: Object.freeze({count: 1}),
|
|
61
|
+
}),
|
|
62
|
+
collection: Object.freeze({status: COLLECTION_STATUS.PARTIAL, stoppedByLimit: false}),
|
|
63
|
+
continueWith: Object.freeze([TOOL.UNRESOLVED_REFERENCE_PAGE, TOOL.REFERENCE_PAGE]),
|
|
64
|
+
}),
|
|
65
|
+
expected: Object.freeze({nextTool: TOOL.UNRESOLVED_REFERENCE_PAGE, coverage: AGENT_EVALUATION_COVERAGE.INCOMPLETE}),
|
|
66
|
+
}),
|
|
67
|
+
Object.freeze({
|
|
68
|
+
id: "disambiguate-homonymous-definitions",
|
|
69
|
+
question: "Which tool should identify the intended declaration from an exact source position?",
|
|
70
|
+
compactResult: Object.freeze({
|
|
71
|
+
tool: TOOL.COUNT_NAMED_SYMBOL,
|
|
72
|
+
result: Object.freeze({
|
|
73
|
+
requestedSymbol: "createClient",
|
|
74
|
+
exactDefinitionsFound: 3,
|
|
75
|
+
definitionSelectionStatus: DEFINITION_SELECTION_STATUS.MULTIPLE,
|
|
76
|
+
semanticEvidence: Object.freeze({
|
|
77
|
+
status: SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
|
|
78
|
+
followUpReasons: Object.freeze([SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.MULTIPLE_DEFINITIONS_SELECTED]),
|
|
79
|
+
}),
|
|
80
|
+
unresolvedReferences: Object.freeze({count: 0}),
|
|
81
|
+
}),
|
|
82
|
+
collection: Object.freeze({status: COLLECTION_STATUS.COMPLETE, stoppedByLimit: false}),
|
|
83
|
+
continueWith: Object.freeze([TOOL.AUDIT_SYMBOL, TOOL.AUDIT_NAMED_SYMBOL]),
|
|
84
|
+
}),
|
|
85
|
+
expected: Object.freeze({nextTool: TOOL.AUDIT_SYMBOL, coverage: AGENT_EVALUATION_COVERAGE.COMPLETE}),
|
|
86
|
+
}),
|
|
87
|
+
Object.freeze({
|
|
88
|
+
id: "reuse-file-hint-binding-position",
|
|
89
|
+
question: "Which tool should reuse the verified source position without inventing a filename declaration?",
|
|
90
|
+
compactResult: Object.freeze({
|
|
91
|
+
tool: TOOL.AUDIT_NAMED_SYMBOL,
|
|
92
|
+
result: Object.freeze({
|
|
93
|
+
requestedSymbol: "RenamedPanel",
|
|
94
|
+
exactDefinitionsFound: 0,
|
|
95
|
+
definitionSelectionStatus: DEFINITION_SELECTION_STATUS.NONE,
|
|
96
|
+
semanticEvidence: Object.freeze({
|
|
97
|
+
status: SEMANTIC_EVIDENCE_STATUS.FOLLOW_UP_REQUIRED,
|
|
98
|
+
followUpReasons: Object.freeze([SEMANTIC_EVIDENCE_FOLLOW_UP_REASON.NO_DEFINITION_SELECTED]),
|
|
99
|
+
}),
|
|
100
|
+
fileHintResolution: Object.freeze({
|
|
101
|
+
textMatchesFound: 2,
|
|
102
|
+
textMatchesChecked: 2,
|
|
103
|
+
textMatchesResolvingToFileFilter: 2,
|
|
104
|
+
textMatchesWhoseDefinitionCouldNotBeResolved: 0,
|
|
105
|
+
accountingStatus: ACCOUNTING_STATUS.COMPLETE,
|
|
106
|
+
sourcePositionForAudit: Object.freeze({file: "src/Parent.vue", line: 2, column: 8}),
|
|
107
|
+
}),
|
|
108
|
+
}),
|
|
109
|
+
collection: Object.freeze({status: COLLECTION_STATUS.COMPLETE, stoppedByLimit: false}),
|
|
110
|
+
continueWith: Object.freeze([TOOL.AUDIT_SYMBOL]),
|
|
111
|
+
}),
|
|
112
|
+
expected: Object.freeze({nextTool: TOOL.AUDIT_SYMBOL, coverage: AGENT_EVALUATION_COVERAGE.COMPLETE}),
|
|
113
|
+
}),
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
function publicCase(evaluationCase) {
|
|
117
|
+
return {
|
|
118
|
+
id: evaluationCase.id,
|
|
119
|
+
question: evaluationCase.question,
|
|
120
|
+
compactResult: evaluationCase.compactResult,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function evaluationPrompt() {
|
|
125
|
+
return {
|
|
126
|
+
instructions: "For each case, choose one exact tool from continueWith and classify collection coverage as complete or incomplete.",
|
|
127
|
+
answerFormat: {
|
|
128
|
+
answers: "array with one object per case",
|
|
129
|
+
answer: {
|
|
130
|
+
id: "exact case id",
|
|
131
|
+
nextTool: "one exact tool from that case's continueWith list",
|
|
132
|
+
coverage: {enum: Object.values(AGENT_EVALUATION_COVERAGE)},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
cases: AGENT_EVALUATION_CASES.map(publicCase),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function gradeAgentAnswers(input) {
|
|
140
|
+
const suppliedAnswers = Array.isArray(input?.answers) ? input.answers : [];
|
|
141
|
+
const answerById = new Map(suppliedAnswers.map((answer) => [answer.id, answer]));
|
|
142
|
+
const cases = AGENT_EVALUATION_CASES.map((evaluationCase) => {
|
|
143
|
+
const answer = answerById.get(evaluationCase.id);
|
|
144
|
+
const nextToolCorrect = answer?.nextTool === evaluationCase.expected.nextTool;
|
|
145
|
+
const coverageCorrect = answer?.coverage === evaluationCase.expected.coverage;
|
|
146
|
+
return {
|
|
147
|
+
id: evaluationCase.id,
|
|
148
|
+
correct: nextToolCorrect && coverageCorrect,
|
|
149
|
+
nextToolCorrect,
|
|
150
|
+
coverageCorrect,
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
const correctCases = cases.filter((item) => item.correct).length;
|
|
154
|
+
const complete = suppliedAnswers.length === AGENT_EVALUATION_CASES.length && correctCases === AGENT_EVALUATION_CASES.length;
|
|
155
|
+
const status = complete ? CI_STATUS.PASS : CI_STATUS.FAIL;
|
|
156
|
+
return {
|
|
157
|
+
status,
|
|
158
|
+
exitCode: CI_EXIT_CODE[status.toUpperCase()],
|
|
159
|
+
reason: complete ? AGENT_EVALUATION_REASON.ALL_DECISIONS_CORRECT : AGENT_EVALUATION_REASON.DECISIONS_INCORRECT,
|
|
160
|
+
casesAvailable: AGENT_EVALUATION_CASES.length,
|
|
161
|
+
answersSupplied: suppliedAnswers.length,
|
|
162
|
+
correctCases,
|
|
163
|
+
cases,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {CI_STATUS} from "../protocol.mjs";
|
|
4
|
+
import {AGENT_EVALUATION_CASES, evaluationPrompt, gradeAgentAnswers} from "./agent-evaluation-contract.mjs";
|
|
5
|
+
|
|
6
|
+
function assert(condition, message) {
|
|
7
|
+
if (!condition) throw new Error(message);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const prompt = evaluationPrompt();
|
|
11
|
+
assert(prompt.cases.length === AGENT_EVALUATION_CASES.length, "Evaluation prompt omitted cases");
|
|
12
|
+
assert(
|
|
13
|
+
prompt.cases.every((item) => !("expected" in item)),
|
|
14
|
+
"Evaluation prompt exposed expected answers",
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const correctAnswers = {
|
|
18
|
+
answers: AGENT_EVALUATION_CASES.map((evaluationCase) => ({id: evaluationCase.id, ...evaluationCase.expected})),
|
|
19
|
+
};
|
|
20
|
+
const passing = gradeAgentAnswers(correctAnswers);
|
|
21
|
+
assert(passing.status === CI_STATUS.PASS, "Correct agent answers did not pass");
|
|
22
|
+
assert(passing.correctCases === AGENT_EVALUATION_CASES.length, "Correct answer count was wrong");
|
|
23
|
+
|
|
24
|
+
const incorrectAnswers = {
|
|
25
|
+
answers: correctAnswers.answers.map((answer, index) =>
|
|
26
|
+
index === 0 ? {...answer, nextTool: AGENT_EVALUATION_CASES[1].expected.nextTool} : answer,
|
|
27
|
+
),
|
|
28
|
+
};
|
|
29
|
+
const failing = gradeAgentAnswers(incorrectAnswers);
|
|
30
|
+
assert(failing.status === CI_STATUS.FAIL, "Incorrect agent answer did not fail");
|
|
31
|
+
assert(failing.correctCases === AGENT_EVALUATION_CASES.length - 1, "Incorrect answer count was wrong");
|
|
32
|
+
|
|
33
|
+
process.stdout.write(
|
|
34
|
+
`${JSON.stringify(
|
|
35
|
+
{
|
|
36
|
+
evaluationCases: AGENT_EVALUATION_CASES.length,
|
|
37
|
+
hiddenExpectedAnswers: "ok",
|
|
38
|
+
deterministicGrading: "ok",
|
|
39
|
+
incorrectDecisionRejected: "ok",
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2,
|
|
43
|
+
)}\n`,
|
|
44
|
+
);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {readFile} from "node:fs/promises";
|
|
4
|
+
import {parse as parseYaml, stringify as stringifyYaml} from "yaml";
|
|
5
|
+
import {CI_EXIT_CODE, CI_STATUS, CLI_ARGUMENT} from "../protocol.mjs";
|
|
6
|
+
import {AGENT_EVALUATION_ARGUMENT, AGENT_EVALUATION_REASON, evaluationPrompt, gradeAgentAnswers} from "./agent-evaluation-contract.mjs";
|
|
7
|
+
|
|
8
|
+
const answersArgumentIndex = process.argv.indexOf(AGENT_EVALUATION_ARGUMENT.ANSWERS);
|
|
9
|
+
const answersFile = answersArgumentIndex >= 0 ? process.argv[answersArgumentIndex + 1] : undefined;
|
|
10
|
+
const useYaml = process.argv.includes(CLI_ARGUMENT.YAML);
|
|
11
|
+
|
|
12
|
+
let output;
|
|
13
|
+
if (!answersFile) {
|
|
14
|
+
output = evaluationPrompt();
|
|
15
|
+
} else {
|
|
16
|
+
try {
|
|
17
|
+
const text = await readFile(answersFile, "utf8");
|
|
18
|
+
let answers;
|
|
19
|
+
try {
|
|
20
|
+
answers = JSON.parse(text);
|
|
21
|
+
} catch {
|
|
22
|
+
answers = parseYaml(text);
|
|
23
|
+
}
|
|
24
|
+
output = gradeAgentAnswers(answers);
|
|
25
|
+
process.exitCode = output.exitCode;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
output = {
|
|
28
|
+
status: CI_STATUS.BLOCKED,
|
|
29
|
+
exitCode: CI_EXIT_CODE.BLOCKED,
|
|
30
|
+
reason: AGENT_EVALUATION_REASON.ANSWERS_INVALID,
|
|
31
|
+
message: error instanceof Error ? error.message : String(error),
|
|
32
|
+
};
|
|
33
|
+
process.exitCode = output.exitCode;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.stdout.write(useYaml ? stringifyYaml(output, {lineWidth: 0}) : `${JSON.stringify(output, null, 2)}\n`);
|