semantic-js-mcp 0.8.1 → 0.9.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/CHANGELOG.md +25 -0
- package/CONTRIBUTING.md +17 -1
- package/README.md +33 -68
- package/ROADMAP.md +1 -1
- package/docs/architecture-decisions.md +35 -22
- package/docs/distribution.md +11 -0
- package/docs/result-schema-migration.md +56 -0
- package/package.json +21 -2
- package/protocol.mjs +84 -3
- package/scripts/agent-evaluation-contract.mjs +142 -0
- package/scripts/agent-evaluation-smoke.mjs +44 -0
- package/scripts/agent-evaluation.mjs +37 -0
- package/scripts/check-protocol-literals.mjs +20 -0
- package/scripts/ci-smoke.mjs +19 -4
- package/scripts/codex-plugin-verification.mjs +129 -0
- package/scripts/distribution-policy.mjs +28 -0
- package/scripts/distribution-smoke.mjs +2 -1
- package/scripts/generate-protocol-reference.mjs +22 -3
- package/scripts/postpublication-smoke.mjs +246 -0
- package/scripts/release-contract.mjs +70 -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 +5 -7
- package/scripts/smoke.mjs +223 -36
- package/scripts/vue-smoke.mjs +320 -5
- package/server.mjs +447 -208
- package/skills/semantic-navigation/SKILL.md +21 -10
- package/skills/semantic-navigation/references/protocol-literals.md +82 -4
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,34 @@ 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 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 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
|
+
PUBLISH_DIAGNOSTICS: "textDocument/publishDiagnostics",
|
|
223
|
+
WORKSPACE_DIAGNOSTIC_REFRESH: "workspace/diagnostic/refresh",
|
|
224
|
+
});
|
|
225
|
+
|
|
158
226
|
export const LIMIT_MODE = Object.freeze({
|
|
159
227
|
UNLIMITED: "unlimited",
|
|
160
228
|
MAXIMUM: "maximum",
|
|
@@ -170,6 +238,12 @@ export const DEFINITION_MATCH = Object.freeze({
|
|
|
170
238
|
UNRESOLVED: "unresolved",
|
|
171
239
|
});
|
|
172
240
|
|
|
241
|
+
export const DEFINITION_SELECTION_STATUS = Object.freeze({
|
|
242
|
+
NONE: "no-definition-selected",
|
|
243
|
+
ONE: "one-definition-selected",
|
|
244
|
+
MULTIPLE: "multiple-definitions-selected",
|
|
245
|
+
});
|
|
246
|
+
|
|
173
247
|
export const SIGNATURE_SOURCE = Object.freeze({
|
|
174
248
|
QUERY_POSITION_HOVER: "query-position-hover",
|
|
175
249
|
RESOLVED_DEFINITION_HOVER: "resolved-definition-hover",
|
|
@@ -178,6 +252,7 @@ export const SIGNATURE_SOURCE = Object.freeze({
|
|
|
178
252
|
|
|
179
253
|
export const DIAGNOSTIC_FRESHNESS = Object.freeze({
|
|
180
254
|
CURRENT: "current-document-version",
|
|
255
|
+
DIFFERENT_CONTENT: "different-document-content",
|
|
181
256
|
VERSION_NOT_REPORTED: "version-not-reported-by-language-server",
|
|
182
257
|
DIFFERENT_VERSION: "different-document-version",
|
|
183
258
|
NOT_REPORTED_FOR_CURRENT_DOCUMENT: "not-reported-for-current-document",
|
|
@@ -198,6 +273,8 @@ export const EVIDENCE_STATUS = Object.freeze({
|
|
|
198
273
|
|
|
199
274
|
export const DIAGNOSTIC_EVIDENCE_REASON = Object.freeze({
|
|
200
275
|
CURRENT_DOCUMENT_VERSION_CONFIRMED: "current-document-version-confirmed",
|
|
276
|
+
CURRENT_DOCUMENT_SNAPSHOT_CONFIRMED: "current-document-snapshot-confirmed",
|
|
277
|
+
DOCUMENT_CONTENT_CHANGED_DURING_ACQUISITION: "document-content-changed-during-diagnostic-acquisition",
|
|
201
278
|
LANGUAGE_SERVER_VERSION_NOT_REPORTED: "language-server-version-not-reported",
|
|
202
279
|
LANGUAGE_SERVER_REPORTED_DIFFERENT_VERSION: "language-server-reported-different-version",
|
|
203
280
|
LANGUAGE_SERVER_DID_NOT_REPORT_CURRENT_DOCUMENT: "language-server-did-not-report-current-document",
|
|
@@ -230,6 +307,10 @@ export const FINGERPRINT_ALGORITHM = Object.freeze({
|
|
|
230
307
|
SHA_256: "sha256",
|
|
231
308
|
});
|
|
232
309
|
|
|
310
|
+
export const FINGERPRINT_FORMAT = Object.freeze({
|
|
311
|
+
SHA_256_PREFIX: `${FINGERPRINT_ALGORITHM.SHA_256}:`,
|
|
312
|
+
});
|
|
313
|
+
|
|
233
314
|
export const REFERENCE_DISCOVERY_METHOD = Object.freeze({
|
|
234
315
|
OWNING_WORKSPACE_LANGUAGE_SERVER: "owning-workspace-language-server",
|
|
235
316
|
DEFINITION_MATCH_FROM_ANOTHER_WORKSPACE: "definition-match-from-another-workspace",
|
|
@@ -302,10 +383,10 @@ export const INTERNAL_RESOLUTION_SOURCE = Object.freeze({
|
|
|
302
383
|
|
|
303
384
|
export const RESULT_SCHEMA = Object.freeze({
|
|
304
385
|
NAME: PRODUCT.NAME,
|
|
305
|
-
VERSION:
|
|
386
|
+
VERSION: 6,
|
|
306
387
|
});
|
|
307
388
|
|
|
308
|
-
export const SERVER_VERSION = "0.
|
|
389
|
+
export const SERVER_VERSION = "0.9.0";
|
|
309
390
|
|
|
310
391
|
export const REQUIRED_RUNTIME_COMPONENT = Object.freeze({
|
|
311
392
|
TYPESCRIPT_LANGUAGE_SERVER: "typescript-language-server/lib/cli.mjs",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {ACCOUNTING_STATUS, CI_EXIT_CODE, CI_STATUS, COLLECTION_STATUS, DEFINITION_SELECTION_STATUS, TOOL} from "../protocol.mjs";
|
|
2
|
+
|
|
3
|
+
export const AGENT_EVALUATION_ARGUMENT = Object.freeze({
|
|
4
|
+
ANSWERS: "--answers",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export const AGENT_EVALUATION_COVERAGE = Object.freeze({
|
|
8
|
+
COMPLETE: "complete",
|
|
9
|
+
INCOMPLETE: "incomplete",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const AGENT_EVALUATION_REASON = Object.freeze({
|
|
13
|
+
ALL_DECISIONS_CORRECT: "all-decisions-correct",
|
|
14
|
+
DECISIONS_INCORRECT: "decisions-incorrect",
|
|
15
|
+
ANSWERS_INVALID: "answers-invalid",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const AGENT_EVALUATION_CASES = Object.freeze([
|
|
19
|
+
Object.freeze({
|
|
20
|
+
id: "inspect-named-symbol-identity",
|
|
21
|
+
question: "Which tool should inspect identity and signature before changing this named symbol?",
|
|
22
|
+
compactResult: Object.freeze({
|
|
23
|
+
tool: TOOL.COUNT_NAMED_SYMBOL,
|
|
24
|
+
result: Object.freeze({
|
|
25
|
+
requestedSymbol: "calculateTotal",
|
|
26
|
+
exactDefinitionsFound: 1,
|
|
27
|
+
definitionSelectionStatus: DEFINITION_SELECTION_STATUS.ONE,
|
|
28
|
+
references: Object.freeze({verifiedTotal: 18}),
|
|
29
|
+
unresolvedReferences: Object.freeze({count: 0}),
|
|
30
|
+
}),
|
|
31
|
+
collection: Object.freeze({status: COLLECTION_STATUS.COMPLETE, stoppedByLimit: false}),
|
|
32
|
+
continueWith: Object.freeze([TOOL.AUDIT_NAMED_SYMBOL, TOOL.REFERENCE_PAGE]),
|
|
33
|
+
}),
|
|
34
|
+
expected: Object.freeze({nextTool: TOOL.AUDIT_NAMED_SYMBOL, coverage: AGENT_EVALUATION_COVERAGE.COMPLETE}),
|
|
35
|
+
}),
|
|
36
|
+
Object.freeze({
|
|
37
|
+
id: "inspect-unresolved-references",
|
|
38
|
+
question: "Which tool should inspect the unresolved candidates before claiming full impact coverage?",
|
|
39
|
+
compactResult: Object.freeze({
|
|
40
|
+
tool: TOOL.AUDIT_NAMED_SYMBOL,
|
|
41
|
+
result: Object.freeze({
|
|
42
|
+
requestedSymbol: "RequestHandler",
|
|
43
|
+
exactDefinitionsFound: 1,
|
|
44
|
+
references: Object.freeze({verifiedTotal: 52}),
|
|
45
|
+
unresolvedReferences: Object.freeze({count: 1}),
|
|
46
|
+
}),
|
|
47
|
+
collection: Object.freeze({status: COLLECTION_STATUS.PARTIAL, stoppedByLimit: false}),
|
|
48
|
+
continueWith: Object.freeze([TOOL.UNRESOLVED_REFERENCE_PAGE, TOOL.REFERENCE_PAGE]),
|
|
49
|
+
}),
|
|
50
|
+
expected: Object.freeze({nextTool: TOOL.UNRESOLVED_REFERENCE_PAGE, coverage: AGENT_EVALUATION_COVERAGE.INCOMPLETE}),
|
|
51
|
+
}),
|
|
52
|
+
Object.freeze({
|
|
53
|
+
id: "disambiguate-homonymous-definitions",
|
|
54
|
+
question: "Which tool should identify the intended declaration from an exact source position?",
|
|
55
|
+
compactResult: Object.freeze({
|
|
56
|
+
tool: TOOL.COUNT_NAMED_SYMBOL,
|
|
57
|
+
result: Object.freeze({
|
|
58
|
+
requestedSymbol: "createClient",
|
|
59
|
+
exactDefinitionsFound: 3,
|
|
60
|
+
definitionSelectionStatus: DEFINITION_SELECTION_STATUS.MULTIPLE,
|
|
61
|
+
unresolvedReferences: Object.freeze({count: 0}),
|
|
62
|
+
}),
|
|
63
|
+
collection: Object.freeze({status: COLLECTION_STATUS.COMPLETE, stoppedByLimit: false}),
|
|
64
|
+
continueWith: Object.freeze([TOOL.AUDIT_SYMBOL, TOOL.AUDIT_NAMED_SYMBOL]),
|
|
65
|
+
}),
|
|
66
|
+
expected: Object.freeze({nextTool: TOOL.AUDIT_SYMBOL, coverage: AGENT_EVALUATION_COVERAGE.COMPLETE}),
|
|
67
|
+
}),
|
|
68
|
+
Object.freeze({
|
|
69
|
+
id: "reuse-file-hint-binding-position",
|
|
70
|
+
question: "Which tool should reuse the verified source position without inventing a filename declaration?",
|
|
71
|
+
compactResult: Object.freeze({
|
|
72
|
+
tool: TOOL.AUDIT_NAMED_SYMBOL,
|
|
73
|
+
result: Object.freeze({
|
|
74
|
+
requestedSymbol: "RenamedPanel",
|
|
75
|
+
exactDefinitionsFound: 0,
|
|
76
|
+
definitionSelectionStatus: DEFINITION_SELECTION_STATUS.NONE,
|
|
77
|
+
fileHintResolution: Object.freeze({
|
|
78
|
+
textMatchesFound: 2,
|
|
79
|
+
textMatchesChecked: 2,
|
|
80
|
+
textMatchesResolvingToFileFilter: 2,
|
|
81
|
+
textMatchesWhoseDefinitionCouldNotBeResolved: 0,
|
|
82
|
+
accountingStatus: ACCOUNTING_STATUS.COMPLETE,
|
|
83
|
+
sourcePositionForAudit: Object.freeze({file: "src/Parent.vue", line: 2, column: 8}),
|
|
84
|
+
}),
|
|
85
|
+
}),
|
|
86
|
+
collection: Object.freeze({status: COLLECTION_STATUS.COMPLETE, stoppedByLimit: false}),
|
|
87
|
+
continueWith: Object.freeze([TOOL.AUDIT_SYMBOL]),
|
|
88
|
+
}),
|
|
89
|
+
expected: Object.freeze({nextTool: TOOL.AUDIT_SYMBOL, coverage: AGENT_EVALUATION_COVERAGE.COMPLETE}),
|
|
90
|
+
}),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
function publicCase(evaluationCase) {
|
|
94
|
+
return {
|
|
95
|
+
id: evaluationCase.id,
|
|
96
|
+
question: evaluationCase.question,
|
|
97
|
+
compactResult: evaluationCase.compactResult,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function evaluationPrompt() {
|
|
102
|
+
return {
|
|
103
|
+
instructions: "For each case, choose one exact tool from continueWith and classify collection coverage as complete or incomplete.",
|
|
104
|
+
answerFormat: {
|
|
105
|
+
answers: "array with one object per case",
|
|
106
|
+
answer: {
|
|
107
|
+
id: "exact case id",
|
|
108
|
+
nextTool: "one exact tool from that case's continueWith list",
|
|
109
|
+
coverage: {enum: Object.values(AGENT_EVALUATION_COVERAGE)},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
cases: AGENT_EVALUATION_CASES.map(publicCase),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function gradeAgentAnswers(input) {
|
|
117
|
+
const suppliedAnswers = Array.isArray(input?.answers) ? input.answers : [];
|
|
118
|
+
const answerById = new Map(suppliedAnswers.map((answer) => [answer.id, answer]));
|
|
119
|
+
const cases = AGENT_EVALUATION_CASES.map((evaluationCase) => {
|
|
120
|
+
const answer = answerById.get(evaluationCase.id);
|
|
121
|
+
const nextToolCorrect = answer?.nextTool === evaluationCase.expected.nextTool;
|
|
122
|
+
const coverageCorrect = answer?.coverage === evaluationCase.expected.coverage;
|
|
123
|
+
return {
|
|
124
|
+
id: evaluationCase.id,
|
|
125
|
+
correct: nextToolCorrect && coverageCorrect,
|
|
126
|
+
nextToolCorrect,
|
|
127
|
+
coverageCorrect,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
const correctCases = cases.filter((item) => item.correct).length;
|
|
131
|
+
const complete = suppliedAnswers.length === AGENT_EVALUATION_CASES.length && correctCases === AGENT_EVALUATION_CASES.length;
|
|
132
|
+
const status = complete ? CI_STATUS.PASS : CI_STATUS.FAIL;
|
|
133
|
+
return {
|
|
134
|
+
status,
|
|
135
|
+
exitCode: CI_EXIT_CODE[status.toUpperCase()],
|
|
136
|
+
reason: complete ? AGENT_EVALUATION_REASON.ALL_DECISIONS_CORRECT : AGENT_EVALUATION_REASON.DECISIONS_INCORRECT,
|
|
137
|
+
casesAvailable: AGENT_EVALUATION_CASES.length,
|
|
138
|
+
answersSupplied: suppliedAnswers.length,
|
|
139
|
+
correctCases,
|
|
140
|
+
cases,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -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`);
|
|
@@ -11,13 +11,18 @@ import {
|
|
|
11
11
|
CLI_ARGUMENT,
|
|
12
12
|
CLI_COMMAND,
|
|
13
13
|
CLI_MESSAGE,
|
|
14
|
+
COMMON_VALUE,
|
|
14
15
|
CONFIGURATION_FILE,
|
|
15
16
|
CONTENT_FRESHNESS,
|
|
16
17
|
DEFAULT,
|
|
17
18
|
DEFINITION_MATCH,
|
|
18
19
|
DEFINITION_RESOLUTION_METHOD,
|
|
20
|
+
DEFINITION_SELECTION_STATUS,
|
|
19
21
|
DIAGNOSTIC_EVIDENCE_REASON,
|
|
20
22
|
DIAGNOSTIC_FRESHNESS,
|
|
23
|
+
DIAGNOSTIC_LANGUAGE,
|
|
24
|
+
DIAGNOSTIC_PROVIDER,
|
|
25
|
+
DIAGNOSTIC_REGION,
|
|
21
26
|
DIAGNOSTIC_SEVERITY,
|
|
22
27
|
DOCTOR_CHECK,
|
|
23
28
|
DOCTOR_DISTRIBUTION_ACCEPTED_STATUS,
|
|
@@ -28,8 +33,10 @@ import {
|
|
|
28
33
|
ERROR_CODE,
|
|
29
34
|
EVIDENCE_TYPE,
|
|
30
35
|
FINGERPRINT_ALGORITHM,
|
|
36
|
+
FINGERPRINT_FORMAT,
|
|
31
37
|
INTERNAL_RESOLUTION_SOURCE,
|
|
32
38
|
LIMIT_MODE,
|
|
39
|
+
LSP_METHOD,
|
|
33
40
|
NODE_EVENT,
|
|
34
41
|
PRESENTATION_MODE,
|
|
35
42
|
PACKAGE_PATH,
|
|
@@ -38,10 +45,13 @@ import {
|
|
|
38
45
|
REFERENCE_SET_CHANGE_TYPE,
|
|
39
46
|
REQUIRED_RUNTIME_COMPONENT,
|
|
40
47
|
RUNTIME_COMMAND,
|
|
48
|
+
RUNTIME_PACKAGE,
|
|
41
49
|
RUNTIME_REQUIREMENT,
|
|
42
50
|
RUNTIME_REQUIREMENT_KIND,
|
|
43
51
|
RESULT_SCHEMA,
|
|
52
|
+
SEARCH_SCOPE,
|
|
44
53
|
TOOL,
|
|
54
|
+
TOOL_DESCRIPTION,
|
|
45
55
|
TYPESCRIPT_PROJECT_KIND,
|
|
46
56
|
SOURCE_EXCLUDED_GLOBS,
|
|
47
57
|
SOURCE_FILE_GLOBS,
|
|
@@ -68,13 +78,21 @@ if (semanticServer?.tool_timeout_sec !== DEFAULT.MCP_TOOL_TIMEOUT_SECONDS) {
|
|
|
68
78
|
throw new Error(".mcp.json tool_timeout_sec differs from protocol.mjs");
|
|
69
79
|
}
|
|
70
80
|
const groups = [
|
|
81
|
+
COMMON_VALUE,
|
|
82
|
+
SEARCH_SCOPE,
|
|
83
|
+
RUNTIME_PACKAGE,
|
|
71
84
|
TOOL,
|
|
85
|
+
TOOL_DESCRIPTION,
|
|
72
86
|
COLLECTION_STATUS,
|
|
73
87
|
PRESENTATION_MODE,
|
|
74
88
|
LIMIT_MODE,
|
|
75
89
|
ACCOUNTING_STATUS,
|
|
76
90
|
DEFINITION_MATCH,
|
|
91
|
+
DEFINITION_SELECTION_STATUS,
|
|
77
92
|
DIAGNOSTIC_FRESHNESS,
|
|
93
|
+
DIAGNOSTIC_PROVIDER,
|
|
94
|
+
DIAGNOSTIC_REGION,
|
|
95
|
+
DIAGNOSTIC_LANGUAGE,
|
|
78
96
|
DIAGNOSTIC_SEVERITY,
|
|
79
97
|
DIAGNOSTIC_EVIDENCE_REASON,
|
|
80
98
|
EVIDENCE_STATUS,
|
|
@@ -82,10 +100,12 @@ const groups = [
|
|
|
82
100
|
ERROR_CODE,
|
|
83
101
|
EVIDENCE_TYPE,
|
|
84
102
|
FINGERPRINT_ALGORITHM,
|
|
103
|
+
FINGERPRINT_FORMAT,
|
|
85
104
|
REFERENCE_DISCOVERY_METHOD,
|
|
86
105
|
REFERENCE_SET_CHANGE_TYPE,
|
|
87
106
|
DEFINITION_RESOLUTION_METHOD,
|
|
88
107
|
INTERNAL_RESOLUTION_SOURCE,
|
|
108
|
+
LSP_METHOD,
|
|
89
109
|
UNRESOLVED_REFERENCE_REASON,
|
|
90
110
|
TYPESCRIPT_PROJECT_KIND,
|
|
91
111
|
CI_STATUS,
|
package/scripts/ci-smoke.mjs
CHANGED
|
@@ -51,8 +51,7 @@ async function evaluate(name, input, expectedStatus, expectedExitCode) {
|
|
|
51
51
|
|
|
52
52
|
function canonicalResult(tool, collectionStatus, result, extra = {}) {
|
|
53
53
|
return {
|
|
54
|
-
|
|
55
|
-
resultSchema: {name: RESULT_SCHEMA.NAME, version: RESULT_SCHEMA.VERSION},
|
|
54
|
+
producer: {name: PRODUCT.NAME, version: SERVER_VERSION, resultSchemaVersion: RESULT_SCHEMA.VERSION},
|
|
56
55
|
tool,
|
|
57
56
|
request: {},
|
|
58
57
|
result,
|
|
@@ -109,7 +108,23 @@ try {
|
|
|
109
108
|
"wrong-result-schema-version",
|
|
110
109
|
{
|
|
111
110
|
...canonicalResult(TOOL.COUNT_REFERENCES, COLLECTION_STATUS.COMPLETE, {}),
|
|
112
|
-
|
|
111
|
+
producer: {name: PRODUCT.NAME, version: SERVER_VERSION, resultSchemaVersion: RESULT_SCHEMA.VERSION + 1},
|
|
112
|
+
},
|
|
113
|
+
CI_STATUS.BLOCKED,
|
|
114
|
+
CI_EXIT_CODE.BLOCKED,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
await evaluate(
|
|
118
|
+
"schema-5-envelope",
|
|
119
|
+
{
|
|
120
|
+
server: {name: PRODUCT.NAME, version: SERVER_VERSION},
|
|
121
|
+
resultSchema: {name: RESULT_SCHEMA.NAME, version: RESULT_SCHEMA.VERSION - 1},
|
|
122
|
+
tool: TOOL.COUNT_REFERENCES,
|
|
123
|
+
request: {},
|
|
124
|
+
result: {},
|
|
125
|
+
collection: {status: COLLECTION_STATUS.COMPLETE},
|
|
126
|
+
presentation: {mode: PRESENTATION_MODE.COUNT_ONLY},
|
|
127
|
+
continueWith: [{tool: TOOL.AUDIT_SYMBOL}],
|
|
113
128
|
},
|
|
114
129
|
CI_STATUS.BLOCKED,
|
|
115
130
|
CI_EXIT_CODE.BLOCKED,
|
|
@@ -136,7 +151,7 @@ try {
|
|
|
136
151
|
"unknown-continuation-tool",
|
|
137
152
|
{
|
|
138
153
|
...canonicalResult(TOOL.COUNT_REFERENCES, COLLECTION_STATUS.COMPLETE, {}),
|
|
139
|
-
continueWith: [
|
|
154
|
+
continueWith: ["unknown_tool"],
|
|
140
155
|
},
|
|
141
156
|
CI_STATUS.BLOCKED,
|
|
142
157
|
CI_EXIT_CODE.BLOCKED,
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {mkdir} from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {CI_EXIT_CODE, CI_STATUS, NODE_EVENT, PRODUCT} from "../protocol.mjs";
|
|
4
|
+
import {CODEX_DISTRIBUTION} from "./distribution-policy.mjs";
|
|
5
|
+
import {RELEASE_CHECK, RELEASE_REASON} from "./release-contract.mjs";
|
|
6
|
+
|
|
7
|
+
function check(name, status, reason, details) {
|
|
8
|
+
return {name, status, reason, ...(details ? {details} : {})};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function run(command, args, options) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const child = options.spawn(command, args, {cwd: options.cwd, env: options.env, stdio: ["ignore", "pipe", "pipe"]});
|
|
14
|
+
const stdout = [];
|
|
15
|
+
const stderr = [];
|
|
16
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
17
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
18
|
+
child.on(NODE_EVENT.ERROR, (error) => resolve({exitCode: undefined, stdout: "", stderr: error.message}));
|
|
19
|
+
child.on(NODE_EVENT.CLOSE, (exitCode) =>
|
|
20
|
+
resolve({exitCode, stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8")}),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function failureMessage(result) {
|
|
26
|
+
return result.stderr.trim() || result.stdout.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function networkUnavailable(result) {
|
|
30
|
+
const message = `${result.stdout}\n${result.stderr}`.toLowerCase();
|
|
31
|
+
return CODEX_DISTRIBUTION.NETWORK_UNAVAILABLE_TEXT.some((text) => message.includes(text.toLowerCase()));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function verifyCodexPlugin({version, workspace, spawn, runCommand}) {
|
|
35
|
+
const codexHome = path.join(workspace, CODEX_DISTRIBUTION.HOME_DIRECTORY);
|
|
36
|
+
await mkdir(codexHome, {recursive: true});
|
|
37
|
+
const environment = {...process.env, [CODEX_DISTRIBUTION.HOME_ENVIRONMENT_VARIABLE]: codexHome};
|
|
38
|
+
const execute = runCommand
|
|
39
|
+
? (command, args) => runCommand(command, args, {cwd: workspace, env: environment})
|
|
40
|
+
: (command, args) => run(command, args, {cwd: workspace, env: environment, spawn});
|
|
41
|
+
const marketplaceRef = `${CODEX_DISTRIBUTION.VERSION_REF_PREFIX}${version}`;
|
|
42
|
+
|
|
43
|
+
const marketplace = await execute(CODEX_DISTRIBUTION.EXECUTABLE, [
|
|
44
|
+
CODEX_DISTRIBUTION.PLUGIN_COMMAND,
|
|
45
|
+
CODEX_DISTRIBUTION.MARKETPLACE_COMMAND,
|
|
46
|
+
CODEX_DISTRIBUTION.ADD_COMMAND,
|
|
47
|
+
CODEX_DISTRIBUTION.MARKETPLACE_SOURCE,
|
|
48
|
+
CODEX_DISTRIBUTION.REF_ARGUMENT,
|
|
49
|
+
marketplaceRef,
|
|
50
|
+
CODEX_DISTRIBUTION.JSON_ARGUMENT,
|
|
51
|
+
]);
|
|
52
|
+
if (marketplace.exitCode === undefined) {
|
|
53
|
+
return [
|
|
54
|
+
check(RELEASE_CHECK.CODEX_MARKETPLACE, CI_STATUS.BLOCKED, RELEASE_REASON.CODEX_CLI_UNAVAILABLE, {
|
|
55
|
+
message: failureMessage(marketplace),
|
|
56
|
+
}),
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
if (marketplace.exitCode !== CI_EXIT_CODE.PASS) {
|
|
60
|
+
const unavailable = networkUnavailable(marketplace);
|
|
61
|
+
return [
|
|
62
|
+
check(
|
|
63
|
+
RELEASE_CHECK.CODEX_MARKETPLACE,
|
|
64
|
+
unavailable ? CI_STATUS.BLOCKED : CI_STATUS.FAIL,
|
|
65
|
+
unavailable ? RELEASE_REASON.CODEX_MARKETPLACE_UNAVAILABLE : RELEASE_REASON.CODEX_MARKETPLACE_FAILED,
|
|
66
|
+
{
|
|
67
|
+
marketplaceRef,
|
|
68
|
+
message: failureMessage(marketplace),
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const checks = [check(RELEASE_CHECK.CODEX_MARKETPLACE, CI_STATUS.PASS, RELEASE_REASON.CHECK_COMPLETED, {marketplaceRef})];
|
|
75
|
+
const installation = await execute(CODEX_DISTRIBUTION.EXECUTABLE, [
|
|
76
|
+
CODEX_DISTRIBUTION.PLUGIN_COMMAND,
|
|
77
|
+
CODEX_DISTRIBUTION.ADD_COMMAND,
|
|
78
|
+
CODEX_DISTRIBUTION.PLUGIN_SELECTOR,
|
|
79
|
+
CODEX_DISTRIBUTION.JSON_ARGUMENT,
|
|
80
|
+
]);
|
|
81
|
+
if (installation.exitCode !== CI_EXIT_CODE.PASS) {
|
|
82
|
+
const unavailable = installation.exitCode === undefined || networkUnavailable(installation);
|
|
83
|
+
checks.push(
|
|
84
|
+
check(
|
|
85
|
+
RELEASE_CHECK.CODEX_PLUGIN_INSTALLATION,
|
|
86
|
+
unavailable ? CI_STATUS.BLOCKED : CI_STATUS.FAIL,
|
|
87
|
+
unavailable ? RELEASE_REASON.CODEX_PLUGIN_SOURCE_UNAVAILABLE : RELEASE_REASON.CODEX_PLUGIN_INSTALLATION_FAILED,
|
|
88
|
+
{message: failureMessage(installation)},
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
return checks;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const listed = await execute(CODEX_DISTRIBUTION.EXECUTABLE, [
|
|
95
|
+
CODEX_DISTRIBUTION.PLUGIN_COMMAND,
|
|
96
|
+
CODEX_DISTRIBUTION.LIST_COMMAND,
|
|
97
|
+
CODEX_DISTRIBUTION.JSON_ARGUMENT,
|
|
98
|
+
]);
|
|
99
|
+
let listResult;
|
|
100
|
+
try {
|
|
101
|
+
listResult = JSON.parse(listed.stdout);
|
|
102
|
+
} catch {
|
|
103
|
+
listResult = undefined;
|
|
104
|
+
}
|
|
105
|
+
const installedPlugin = listResult?.installed?.find(
|
|
106
|
+
(plugin) => plugin.name === PRODUCT.NAME && plugin.marketplaceName === CODEX_DISTRIBUTION.MARKETPLACE_NAME,
|
|
107
|
+
);
|
|
108
|
+
if (listed.exitCode === undefined || networkUnavailable(listed)) {
|
|
109
|
+
checks.push(
|
|
110
|
+
check(RELEASE_CHECK.CODEX_PLUGIN_INSTALLATION, CI_STATUS.BLOCKED, RELEASE_REASON.CODEX_PLUGIN_SOURCE_UNAVAILABLE, {
|
|
111
|
+
message: failureMessage(listed),
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
return checks;
|
|
115
|
+
}
|
|
116
|
+
const installedVersionMatches =
|
|
117
|
+
listed.exitCode === CI_EXIT_CODE.PASS && installedPlugin?.version === version && installedPlugin?.enabled === true;
|
|
118
|
+
checks.push(
|
|
119
|
+
check(
|
|
120
|
+
RELEASE_CHECK.CODEX_PLUGIN_INSTALLATION,
|
|
121
|
+
installedVersionMatches ? CI_STATUS.PASS : CI_STATUS.FAIL,
|
|
122
|
+
installedVersionMatches ? RELEASE_REASON.CHECK_COMPLETED : RELEASE_REASON.CODEX_PLUGIN_VERSION_DIFFERENT,
|
|
123
|
+
installedVersionMatches
|
|
124
|
+
? {version}
|
|
125
|
+
: {expectedVersion: version, installedVersion: installedPlugin?.version, message: failureMessage(listed)},
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
return checks;
|
|
129
|
+
}
|
|
@@ -19,6 +19,34 @@ export const NPM_AUTOMATIC_PACKAGE_FILE = Object.freeze([
|
|
|
19
19
|
REQUIRED_PACKAGE_FILE.LICENSE,
|
|
20
20
|
]);
|
|
21
21
|
|
|
22
|
+
export const CODEX_DISTRIBUTION = Object.freeze({
|
|
23
|
+
EXECUTABLE: "codex",
|
|
24
|
+
HOME_ENVIRONMENT_VARIABLE: "CODEX_HOME",
|
|
25
|
+
MARKETPLACE_NAME: "elnonathan",
|
|
26
|
+
MARKETPLACE_SOURCE: "elnonathan/semantic-js-mcp",
|
|
27
|
+
PLUGIN_SELECTOR: "semantic-js-mcp@elnonathan",
|
|
28
|
+
VERSION_REF_PREFIX: "v",
|
|
29
|
+
HOME_DIRECTORY: "codex-home",
|
|
30
|
+
PLUGIN_COMMAND: "plugin",
|
|
31
|
+
MARKETPLACE_COMMAND: "marketplace",
|
|
32
|
+
ADD_COMMAND: "add",
|
|
33
|
+
LIST_COMMAND: "list",
|
|
34
|
+
REF_ARGUMENT: "--ref",
|
|
35
|
+
JSON_ARGUMENT: "--json",
|
|
36
|
+
NETWORK_UNAVAILABLE_TEXT: Object.freeze([
|
|
37
|
+
"Could not resolve host",
|
|
38
|
+
"failed to lookup address information",
|
|
39
|
+
"network is unreachable",
|
|
40
|
+
"network timeout",
|
|
41
|
+
"timed out",
|
|
42
|
+
"unable to access",
|
|
43
|
+
]),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const NPM_DISTRIBUTION = Object.freeze({
|
|
47
|
+
CACHE_ENVIRONMENT_VARIABLE: "npm_config_cache",
|
|
48
|
+
});
|
|
49
|
+
|
|
22
50
|
const WINDOWS_PLATFORM = "win32";
|
|
23
51
|
const WINDOWS_COMMAND_SUFFIX = ".cmd";
|
|
24
52
|
const DEPENDENCY_DIRECTORY_PREFIX = "node_modules/";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "../protocol.mjs";
|
|
17
17
|
import {
|
|
18
18
|
REQUIRED_PACKAGE_FILE,
|
|
19
|
+
NPM_DISTRIBUTION,
|
|
19
20
|
allProductionDependenciesAreBundled,
|
|
20
21
|
npmExecutableName,
|
|
21
22
|
packagePathIsAllowed,
|
|
@@ -27,7 +28,7 @@ const packageOutput = path.join(workspace, "package");
|
|
|
27
28
|
const consumer = path.join(workspace, "consumer");
|
|
28
29
|
const npmCache = path.join(workspace, "npm-cache");
|
|
29
30
|
const npmCli = process.env.npm_execpath;
|
|
30
|
-
const npmEnvironment = {...process.env,
|
|
31
|
+
const npmEnvironment = {...process.env, [NPM_DISTRIBUTION.CACHE_ENVIRONMENT_VARIABLE]: npmCache};
|
|
31
32
|
|
|
32
33
|
function run(command, args, options = {}) {
|
|
33
34
|
return new Promise((resolve, reject) => {
|