@elench/testkit 0.1.94 → 0.1.95
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/lib/cli/agents/providers/codex.mjs +30 -11
- package/lib/cli/assistant/context-pack.mjs +5 -4
- package/lib/cli/assistant/state.mjs +34 -3
- package/lib/database/template-steps.mjs +3 -3
- package/lib/runner/runtime-preparation.mjs +36 -0
- package/lib/runner/template.mjs +24 -3
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +5 -5
|
@@ -22,17 +22,14 @@ export function startCodexHostedSession({
|
|
|
22
22
|
} = {}) {
|
|
23
23
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-codex-"));
|
|
24
24
|
const outputFile = path.join(tempDir, "final-message.txt");
|
|
25
|
-
const args =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
args.push(...normalizeProviderArgs(providerArgs));
|
|
34
|
-
|
|
35
|
-
args.push(prompt);
|
|
25
|
+
const args = buildCodexArgs({
|
|
26
|
+
outputFile,
|
|
27
|
+
purpose,
|
|
28
|
+
model,
|
|
29
|
+
providerArgs,
|
|
30
|
+
prompt,
|
|
31
|
+
sandbox: process.env.TESTKIT_CODEX_SANDBOX,
|
|
32
|
+
});
|
|
36
33
|
|
|
37
34
|
const child = execa(command, args, {
|
|
38
35
|
cwd,
|
|
@@ -65,6 +62,28 @@ export function startCodexHostedSession({
|
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
64
|
|
|
65
|
+
export function buildCodexArgs({
|
|
66
|
+
outputFile,
|
|
67
|
+
purpose = "assistant",
|
|
68
|
+
model = null,
|
|
69
|
+
providerArgs = [],
|
|
70
|
+
prompt = "",
|
|
71
|
+
sandbox = null,
|
|
72
|
+
} = {}) {
|
|
73
|
+
const args = ["exec", "--json"];
|
|
74
|
+
if (outputFile) args.push("-o", outputFile);
|
|
75
|
+
|
|
76
|
+
if (purpose === "assistant") {
|
|
77
|
+
args.push("-s", String(sandbox || "workspace-write"));
|
|
78
|
+
}
|
|
79
|
+
if (model) {
|
|
80
|
+
args.push("--model", String(model));
|
|
81
|
+
}
|
|
82
|
+
args.push(...normalizeProviderArgs(providerArgs));
|
|
83
|
+
args.push(prompt);
|
|
84
|
+
return args;
|
|
85
|
+
}
|
|
86
|
+
|
|
68
87
|
function normalizeProviderArgs(providerArgs) {
|
|
69
88
|
if (!Array.isArray(providerArgs)) return [];
|
|
70
89
|
return providerArgs.flatMap((arg) => String(arg || "").split(/\s+/).filter(Boolean));
|
|
@@ -160,7 +160,8 @@ function buildContextMarkdown(productDir, snapshot, paths) {
|
|
|
160
160
|
lines.push(
|
|
161
161
|
"",
|
|
162
162
|
"## Guidance",
|
|
163
|
-
"- Use shell commands like `npm run testkit`, `npx testkit`, or `testkit run --dir
|
|
163
|
+
"- Use shell commands like `npm run testkit`, `npx testkit`, or `testkit run <type> --dir .` when you need to execute tests.",
|
|
164
|
+
"- Do not reinterpret CLI syntax after an execution failure unless `testkit run --help` confirms a syntax problem.",
|
|
164
165
|
"- Use the command log and focused context files before rereading artifacts manually.",
|
|
165
166
|
"- Prefer repo-local commands over guessing project-specific wrappers.",
|
|
166
167
|
""
|
|
@@ -173,15 +174,15 @@ function buildCommandsMarkdown() {
|
|
|
173
174
|
return [
|
|
174
175
|
"# Testkit Commands",
|
|
175
176
|
"",
|
|
176
|
-
"- `testkit run --dir
|
|
177
|
-
"- `testkit run --dir
|
|
177
|
+
"- `testkit run int --dir .`",
|
|
178
|
+
"- `testkit run e2e --dir .`",
|
|
178
179
|
"- `testkit run --dir . --file path/to/file.testkit.ts`",
|
|
179
180
|
"- `testkit discover --dir .`",
|
|
180
181
|
"- `testkit status --dir .`",
|
|
181
182
|
"- `testkit doctor --dir .`",
|
|
182
183
|
"- `testkit destroy --dir .`",
|
|
183
184
|
"- `npm run testkit`",
|
|
184
|
-
"- `npx testkit
|
|
185
|
+
"- `npx testkit run e2e --dir .`",
|
|
185
186
|
"",
|
|
186
187
|
].join("\n");
|
|
187
188
|
}
|
|
@@ -177,11 +177,19 @@ export function createAssistantState({
|
|
|
177
177
|
setProvider(nextProvider) {
|
|
178
178
|
settings = mergeAssistantSettings(settings, { provider: nextProvider || DEFAULT_ASSISTANT_SETTINGS.provider });
|
|
179
179
|
resolvedProviderName = null;
|
|
180
|
+
if (settings.model && getModelProviderMismatch(resolveInitialProvider(settings.provider, env), settings.model)) {
|
|
181
|
+
settings = mergeAssistantSettings(settings, { model: null });
|
|
182
|
+
}
|
|
180
183
|
saveAssistantSettings(productDir, settings);
|
|
181
184
|
notify();
|
|
182
185
|
},
|
|
183
186
|
|
|
184
187
|
setModel(nextModel) {
|
|
188
|
+
const resolvedProvider = resolveInitialProvider(settings.provider, env);
|
|
189
|
+
const mismatch = getModelProviderMismatch(resolvedProvider, nextModel);
|
|
190
|
+
if (mismatch) {
|
|
191
|
+
throw new Error(mismatch);
|
|
192
|
+
}
|
|
185
193
|
settings = mergeAssistantSettings(settings, { model: nextModel || null });
|
|
186
194
|
saveAssistantSettings(productDir, settings);
|
|
187
195
|
notify();
|
|
@@ -345,6 +353,22 @@ function resolveInitialProvider(provider, env) {
|
|
|
345
353
|
return null;
|
|
346
354
|
}
|
|
347
355
|
|
|
356
|
+
function getModelProviderMismatch(provider, model) {
|
|
357
|
+
const normalizedModel = String(model || "").trim().toLowerCase();
|
|
358
|
+
if (!provider || !normalizedModel) return null;
|
|
359
|
+
|
|
360
|
+
const looksClaude = /\b(?:opus|sonnet|haiku|claude)\b/.test(normalizedModel);
|
|
361
|
+
const looksCodex = /\b(?:gpt|codex|o[1-9]|chatgpt)\b/.test(normalizedModel);
|
|
362
|
+
|
|
363
|
+
if (provider === "codex" && looksClaude) {
|
|
364
|
+
return `Model "${model}" looks like a Claude model, but the assistant is using Codex. Run /provider claude or /model default.`;
|
|
365
|
+
}
|
|
366
|
+
if (provider === "claude" && looksCodex) {
|
|
367
|
+
return `Model "${model}" looks like a Codex/OpenAI model, but the assistant is using Claude. Run /provider codex or /model default.`;
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
348
372
|
async function executeSlashCommand({
|
|
349
373
|
slash,
|
|
350
374
|
state,
|
|
@@ -496,9 +520,16 @@ async function executeSlashTool(slash, context) {
|
|
|
496
520
|
}
|
|
497
521
|
|
|
498
522
|
function buildRunSlashCommand(options = {}) {
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
523
|
+
const types = options.type || [];
|
|
524
|
+
const parts = ["testkit", "run"];
|
|
525
|
+
if (types.length === 1) {
|
|
526
|
+
parts.push(types[0]);
|
|
527
|
+
}
|
|
528
|
+
parts.push("--dir", ".");
|
|
529
|
+
if (types.length !== 1) {
|
|
530
|
+
for (const type of types) {
|
|
531
|
+
parts.push("--type", type);
|
|
532
|
+
}
|
|
502
533
|
}
|
|
503
534
|
for (const suite of options.suite || []) {
|
|
504
535
|
parts.push("--suite", suite);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { execa } from "execa";
|
|
4
|
-
import {
|
|
4
|
+
import { buildTemplateExecutionEnv } from "../runner/template.mjs";
|
|
5
5
|
import {
|
|
6
6
|
collectConfiguredInputs,
|
|
7
7
|
runConfiguredSteps,
|
|
@@ -13,7 +13,7 @@ export async function runTemplateStage(config, stageName, databaseUrl, options =
|
|
|
13
13
|
if (steps.length === 0) return;
|
|
14
14
|
|
|
15
15
|
const env = {
|
|
16
|
-
...
|
|
16
|
+
...buildTemplateExecutionEnv(config, {}, process.env),
|
|
17
17
|
DATABASE_URL: databaseUrl,
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -54,7 +54,7 @@ export async function captureTemplateSnapshot(config, outputPath, databaseUrl, o
|
|
|
54
54
|
{
|
|
55
55
|
cwd: config.productDir,
|
|
56
56
|
env: {
|
|
57
|
-
...
|
|
57
|
+
...buildTemplateExecutionEnv(config, {}, process.env),
|
|
58
58
|
DATABASE_URL: templateDbUrl,
|
|
59
59
|
},
|
|
60
60
|
stdout: "pipe",
|
|
@@ -127,6 +127,7 @@ export async function computeRuntimePrepareFingerprint(config) {
|
|
|
127
127
|
: null,
|
|
128
128
|
})
|
|
129
129
|
);
|
|
130
|
+
hash.update(JSON.stringify(collectRuntimeDatabaseFingerprintInputs(config)));
|
|
130
131
|
|
|
131
132
|
for (const envFile of config.testkit.envFiles || []) {
|
|
132
133
|
appendFileToHash(hash, config.productDir, resolveServiceCwd(config.productDir, envFile));
|
|
@@ -138,6 +139,41 @@ export async function computeRuntimePrepareFingerprint(config) {
|
|
|
138
139
|
return hash.digest("hex");
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
function collectRuntimeDatabaseFingerprintInputs(config) {
|
|
143
|
+
const inputs = [];
|
|
144
|
+
const ownDatabaseUrl = readDatabaseUrl(config.stateDir);
|
|
145
|
+
if (ownDatabaseUrl) {
|
|
146
|
+
inputs.push({ service: config.name, url: ownDatabaseUrl });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const referencedServices = new Set();
|
|
150
|
+
for (const value of Object.values(config.testkit.serviceEnv || {})) {
|
|
151
|
+
collectDatabasePlaceholderServices(value, referencedServices, config.name);
|
|
152
|
+
}
|
|
153
|
+
for (const value of Object.values(config.testkit.local?.env || {})) {
|
|
154
|
+
collectDatabasePlaceholderServices(value, referencedServices, config.name);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const stateDirByService = config.testkit.templateContext?.stateDirByService;
|
|
158
|
+
for (const serviceName of [...referencedServices].sort()) {
|
|
159
|
+
const stateDir = stateDirByService?.get?.(serviceName);
|
|
160
|
+
const databaseUrl = stateDir ? readDatabaseUrl(stateDir) : null;
|
|
161
|
+
inputs.push({ service: serviceName, url: databaseUrl || null });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return inputs;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function collectDatabasePlaceholderServices(value, out, defaultServiceName) {
|
|
168
|
+
if (typeof value !== "string") return;
|
|
169
|
+
const matcher = /\{db(?:Url|Host|Port|Name|User|Password)(?::([a-zA-Z0-9_-]+))?\}/g;
|
|
170
|
+
let match = matcher.exec(value);
|
|
171
|
+
while (match) {
|
|
172
|
+
out.add(match[1] || defaultServiceName);
|
|
173
|
+
match = matcher.exec(value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
141
177
|
function appendResolvedInputToHash(hash, productDir, absPath) {
|
|
142
178
|
const relative = path.relative(productDir, absPath);
|
|
143
179
|
appendInputToHash(hash, productDir, relative);
|
package/lib/runner/template.mjs
CHANGED
|
@@ -204,17 +204,29 @@ export function buildExecutionEnv(config, extraEnv = {}, processEnv = process.en
|
|
|
204
204
|
return buildExecutionEnvWithContext(config, null, extraEnv, processEnv);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
export function buildTemplateExecutionEnv(config, extraEnv = {}, processEnv = process.env) {
|
|
208
|
+
return buildExecutionEnvWithContext(config, null, extraEnv, processEnv, {
|
|
209
|
+
omitRuntimeDatabaseBindings: true,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
207
213
|
export function buildTaskExecutionEnv(config, lease, extraEnv = {}, processEnv = process.env) {
|
|
208
214
|
return buildExecutionEnvWithContext(config, lease, extraEnv, processEnv);
|
|
209
215
|
}
|
|
210
216
|
|
|
211
|
-
function buildExecutionEnvWithContext(config, lease, extraEnv, processEnv) {
|
|
217
|
+
function buildExecutionEnvWithContext(config, lease, extraEnv, processEnv, options = {}) {
|
|
212
218
|
const inheritedEnv = { ...processEnv };
|
|
213
219
|
const templateContext = buildTemplateContext(config, lease);
|
|
220
|
+
const serviceEnv = options.omitRuntimeDatabaseBindings
|
|
221
|
+
? omitRuntimeDatabaseBindings(config.testkit.serviceEnv || {})
|
|
222
|
+
: config.testkit.serviceEnv || {};
|
|
223
|
+
const localEnv = options.omitRuntimeDatabaseBindings
|
|
224
|
+
? omitRuntimeDatabaseBindings(config.testkit.local?.env || {})
|
|
225
|
+
: config.testkit.local?.env || {};
|
|
214
226
|
const env = {
|
|
215
227
|
...inheritedEnv,
|
|
216
|
-
...resolveEnvTemplates(
|
|
217
|
-
...resolveEnvTemplates(
|
|
228
|
+
...resolveEnvTemplates(serviceEnv, templateContext),
|
|
229
|
+
...resolveEnvTemplates(localEnv, templateContext),
|
|
218
230
|
...resolveEnvTemplates(extraEnv, templateContext),
|
|
219
231
|
TESTKIT_ACTIVE: "1",
|
|
220
232
|
...(config.runtimeId ? { TESTKIT_RUNTIME_ID: String(config.runtimeId) } : {}),
|
|
@@ -340,6 +352,15 @@ function resolveEnvTemplates(values, templateContext) {
|
|
|
340
352
|
);
|
|
341
353
|
}
|
|
342
354
|
|
|
355
|
+
function omitRuntimeDatabaseBindings(values = {}) {
|
|
356
|
+
return Object.fromEntries(
|
|
357
|
+
Object.entries(values).filter(([_key, value]) => {
|
|
358
|
+
if (typeof value !== "string") return true;
|
|
359
|
+
return !/\{db(?:Url|Host|Port|Name|User|Password)(?::[a-zA-Z0-9_-]+)?\}/.test(value);
|
|
360
|
+
})
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
343
364
|
function finalizeRuntimePrepare(prepare, context) {
|
|
344
365
|
if (!prepare) {
|
|
345
366
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.95",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@elench/testkit-protocol": "0.1.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.95"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.95",
|
|
4
4
|
"description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@babel/code-frame": "^7.29.0",
|
|
86
|
-
"@elench/next-analysis": "0.1.
|
|
87
|
-
"@elench/testkit-bridge": "0.1.
|
|
88
|
-
"@elench/testkit-protocol": "0.1.
|
|
89
|
-
"@elench/ts-analysis": "0.1.
|
|
86
|
+
"@elench/next-analysis": "0.1.95",
|
|
87
|
+
"@elench/testkit-bridge": "0.1.95",
|
|
88
|
+
"@elench/testkit-protocol": "0.1.95",
|
|
89
|
+
"@elench/ts-analysis": "0.1.95",
|
|
90
90
|
"@oclif/core": "^4.10.6",
|
|
91
91
|
"esbuild": "^0.25.11",
|
|
92
92
|
"execa": "^9.5.0",
|