@turri-dev/cli 0.1.3-next.e5599c1 → 0.1.3-next.f74e98a
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/dist/config.js +3 -2
- package/dist/extension.js +3 -2
- package/dist/loop-cli.js +1 -1
- package/dist/regenerate/conformance-agents.js +34 -0
- package/dist/regenerate/conformance.js +9 -14
- package/dist/regenerate/debugger.js +27 -3
- package/dist/regenerate/environment-error.js +17 -0
- package/dist/regenerate/regenerate.js +23 -6
- package/dist/regenerate/test-scripts.js +22 -1
- package/dist/regenerate-cli.js +1 -1
- package/docs/config.md +3 -2
- package/docs/headless.md +2 -2
- package/docs/regenerating.md +16 -3
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -189,8 +189,9 @@ export const ROLE_THINKING = {
|
|
|
189
189
|
diagnosis: "medium",
|
|
190
190
|
};
|
|
191
191
|
/** The roles on the conversation's side: without their own entry they take the chat's model, not `default`. */
|
|
192
|
-
// The release is not: it follows steps - a script, a start command - and runs on the coding model, thinking low.
|
|
193
|
-
|
|
192
|
+
// The release is not: it follows steps - a script, a start command - and runs on the coding model, thinking low. Nor
|
|
193
|
+
// is the diagnosis: a post-mortem of a job that failed, read by the person - it runs on the coding model, thinking medium.
|
|
194
|
+
const CHAT_SIDE = ["chat", "chooser", "planner"];
|
|
194
195
|
function isThinkingLevel(value) {
|
|
195
196
|
return typeof value === "string" && THINKING_LEVELS.includes(value);
|
|
196
197
|
}
|
package/dist/extension.js
CHANGED
|
@@ -9,7 +9,7 @@ import { allocateImageId, Box, getCapabilities, getPngDimensions, isKeyRelease,
|
|
|
9
9
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { Type } from "typebox";
|
|
11
11
|
import { CODING_MODEL_ENV } from "./chat-models.js";
|
|
12
|
-
import { AGENT_ROLES, CONFIG_FILE, configForSpec, hasProjectConfig, LEGACY_CONFIG_FILE, projectConfigFile, stackConfigFileIn, stackOf, stacksOf, withStack, modelSpec, projectModels, readProjectConfig, roleModel, splitModelSpec } from "./config.js";
|
|
12
|
+
import { AGENT_ROLES, CONFIG_FILE, configForSpec, hasProjectConfig, LEGACY_CONFIG_FILE, projectConfigFile, stackConfigFileIn, stackOf, stacksOf, withStack, modelOnly, modelSpec, projectModels, readProjectConfig, roleModel, splitModelSpec } from "./config.js";
|
|
13
13
|
import { skillRunDir } from "./regenerate/paths.js";
|
|
14
14
|
import { sweepOrphanedGroups } from "./regenerate/shell.js";
|
|
15
15
|
import { readScript, scriptAgainstMachine, stackOnMachine, toolchainsBlock } from "./stack.js";
|
|
@@ -186,7 +186,8 @@ export default function (pi) {
|
|
|
186
186
|
projectDir: cwd,
|
|
187
187
|
harnessDir: PACKAGE_ROOT,
|
|
188
188
|
jobLog,
|
|
189
|
-
model:
|
|
189
|
+
// The coding model the job ran on - its model alone: the diagnosis thinks as its role has it.
|
|
190
|
+
model: modelSpec(modelOnly(job.model ?? modelSpec(projectModels(readProjectConfig(cwd)).coding))),
|
|
190
191
|
});
|
|
191
192
|
return diagnosis !== undefined ? describeDiagnosis(diagnosis) : undefined;
|
|
192
193
|
},
|
package/dist/loop-cli.js
CHANGED
|
@@ -69,7 +69,7 @@ async function runLoopReporting(options) {
|
|
|
69
69
|
const registry = createJobRegistry({
|
|
70
70
|
retries: 1,
|
|
71
71
|
diagnose: async (job, failures, jobLog) => {
|
|
72
|
-
const diagnosis = await diagnoseFailure({ job, failures, projectDir, harnessDir: PACKAGE_ROOT, jobLog, model: modelSpec(
|
|
72
|
+
const diagnosis = await diagnoseFailure({ job, failures, projectDir, harnessDir: PACKAGE_ROOT, jobLog, model: modelSpec(modelOnly(job.model ?? model)) });
|
|
73
73
|
return diagnosis !== undefined ? describeDiagnosis(diagnosis) : undefined;
|
|
74
74
|
},
|
|
75
75
|
onChange: (job) => {
|
|
@@ -7,6 +7,7 @@ import { confinedTools } from "./confine.js";
|
|
|
7
7
|
import { ensureInModulesScratch, modulesScratchDir } from "./paths.js";
|
|
8
8
|
import { workspaceBlock } from "./workspace.js";
|
|
9
9
|
import { reportActivity } from "./activity.js";
|
|
10
|
+
import { runTiming } from "./test-scripts.js";
|
|
10
11
|
import { createWebTools, WEB_TOOLS } from "./web.js";
|
|
11
12
|
import { diffFiles, digestFiles } from "./codebase.js";
|
|
12
13
|
import { ERROR_CAUSE_RULES, fixMethod, INTEGRITY_RULES, specPrimer } from "./prompt.js";
|
|
@@ -882,6 +883,8 @@ export function judgePrompt(options) {
|
|
|
882
883
|
" the tests set up themselves - a credential, a variable or a fixture a test sets, overrides or names wrong: that is",
|
|
883
884
|
" tests; nor for a toolchain or package missing where the tests run: the agents install those. The run stops on it",
|
|
884
885
|
` once it has been judged so ${ENVIRONMENT_RAISES} times in a row; until then the tests are checked for whether they cause it.`,
|
|
886
|
+
" A run the time limit stopped is environment only when nothing hangs and it needs more time than a test run may",
|
|
887
|
+
" take - and then it stops at once.",
|
|
885
888
|
"",
|
|
886
889
|
"When the output does not show why the software failed - it answered with an error that names no cause, and",
|
|
887
890
|
"logged nothing about it - the code is what is wrong first: it hides the cause, and nobody can fix what they",
|
|
@@ -927,12 +930,37 @@ export function judgePrompt(options) {
|
|
|
927
930
|
`<how to invoke it>\n${options.invocation.trim()}\n</how to invoke it>`,
|
|
928
931
|
"",
|
|
929
932
|
...scripts.flatMap((script) => [`<conformance testing script path="${script.path}" run-as="${script.runs}">`, script.content.trimEnd(), "</conformance testing script>", ""]),
|
|
933
|
+
...(options.run.timedOut === true ? [...timedOutBlock(options.run), ""] : []),
|
|
930
934
|
`<failing run exit="${options.run.exitCode}" command="${options.run.command}">`,
|
|
935
|
+
runTiming(options.run),
|
|
936
|
+
"",
|
|
931
937
|
options.run.output.trimEnd(),
|
|
932
938
|
"</failing run>",
|
|
933
939
|
...(options.escalation !== undefined ? ["", ...escalationBlock(options.escalation)] : []),
|
|
934
940
|
].join("\n");
|
|
935
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* For the judge, of a run the time limit stopped: what kept it from finishing - something that hangs, the code's or a
|
|
944
|
+
* test's, or a suite that is only slow, which no agent can give more time.
|
|
945
|
+
*/
|
|
946
|
+
export function timedOutBlock(run) {
|
|
947
|
+
const limit = Math.round(run.timeoutMs / 1000);
|
|
948
|
+
return [
|
|
949
|
+
"<timed out>",
|
|
950
|
+
`This run did not end on its own: the time limit stopped it at ${limit}s, and it counts as failed. Its output ends where it`,
|
|
951
|
+
"was stopped - the step running then, or the one after the last it printed, is what did not finish. Decide what kept it:",
|
|
952
|
+
"- code: the software hangs - it waits on input, keeps a server in the foreground, never exits, loops, or blocks on a",
|
|
953
|
+
" lock or a call that never answers - when the tests reach it as the specification says.",
|
|
954
|
+
"- tests: a test waits for something that never comes - a process it started and never stops, output it waits for that",
|
|
955
|
+
" the software never prints, a port or a file nobody makes, a sleep or a retry with no end.",
|
|
956
|
+
"- environment: nothing hangs - the output shows the run making steady progress right up to the limit (installing,",
|
|
957
|
+
" building, test after test passing) and it needs more time than a test run may take. No agent can give it that: the",
|
|
958
|
+
" run stops at once, and the person is told to raise the limit. Say from the output how far it got, and what took",
|
|
959
|
+
" the time.",
|
|
960
|
+
"Judge from the output and the files, never from the time alone.",
|
|
961
|
+
"</timed out>",
|
|
962
|
+
];
|
|
963
|
+
}
|
|
936
964
|
/** For the judge: the tests were fixed for this failure, and it did not go away - is it the code's? */
|
|
937
965
|
function escalationBlock(escalation) {
|
|
938
966
|
const changed = escalation.changed.length === 0 ? "changed no file" : `changed ${escalation.changed.slice(0, 8).join(", ")}${escalation.changed.length > 8 ? ` and ${escalation.changed.length - 8} more` : ""}`;
|
|
@@ -1029,6 +1057,8 @@ export function codeFixPrompt(options) {
|
|
|
1029
1057
|
`<why the code>\n${options.reason.trim()}\n</why the code>`,
|
|
1030
1058
|
"",
|
|
1031
1059
|
`<failing run command="${options.run.command}">`,
|
|
1060
|
+
runTiming(options.run),
|
|
1061
|
+
"",
|
|
1032
1062
|
options.run.output.trimEnd(),
|
|
1033
1063
|
"</failing run>",
|
|
1034
1064
|
"",
|
|
@@ -1062,6 +1092,8 @@ export function escalatedFixPrompt(options) {
|
|
|
1062
1092
|
"</evidence>",
|
|
1063
1093
|
"",
|
|
1064
1094
|
`<failing run command="${options.run.command}">`,
|
|
1095
|
+
runTiming(options.run),
|
|
1096
|
+
"",
|
|
1065
1097
|
options.run.output.trimEnd(),
|
|
1066
1098
|
"</failing run>",
|
|
1067
1099
|
"",
|
|
@@ -1099,6 +1131,8 @@ export function testsFixPrompt(options) {
|
|
|
1099
1131
|
`<why the tests>\n${options.reason.trim()}\n</why the tests>`,
|
|
1100
1132
|
"",
|
|
1101
1133
|
"<failing run>",
|
|
1134
|
+
runTiming(options.run),
|
|
1135
|
+
"",
|
|
1102
1136
|
options.run.output.trimEnd(),
|
|
1103
1137
|
"</failing run>",
|
|
1104
1138
|
"",
|
|
@@ -14,7 +14,7 @@ import { createScriptTool, findTestScripts, projectRootFor, requireTestScripts,
|
|
|
14
14
|
import { createWebTools, WEB_TOOLS } from "./web.js";
|
|
15
15
|
import { createMyDiffTool } from "./self-check.js";
|
|
16
16
|
import { createServiceTools } from "./services.js";
|
|
17
|
-
import { missingToolchainError, toolchainError } from "./environment-error.js";
|
|
17
|
+
import { missingToolchainError, timeLimitError, toolchainError } from "./environment-error.js";
|
|
18
18
|
import { workspaceBlock } from "./workspace.js";
|
|
19
19
|
import { codeFixPrompt, conflictKey, escalatedFixPrompt, createCodeFixer, answerAsExplorer, createAskExplorerTool, exploreInvocation, judgeFailure, SPEC_CONFLICT_RAISES, ENVIRONMENT_RAISES, environmentCheckPrompt, createTestReviewer, testReviewRequest, reviewFeedbackPrompt, testsFixPrompt, sortedRefs, } from "./conformance-agents.js";
|
|
20
20
|
import { resolveRequiresChain } from "./requires.js";
|
|
@@ -494,19 +494,6 @@ export async function renderConformanceTests(options) {
|
|
|
494
494
|
idleFixes = 0;
|
|
495
495
|
continue;
|
|
496
496
|
}
|
|
497
|
-
// A run out of time is not a question of test or code: the tests' environment is the conformance agent's.
|
|
498
|
-
if (recheck.timedOut === true) {
|
|
499
|
-
const moved = await unmoved();
|
|
500
|
-
if (moved === "stuck")
|
|
501
|
-
break;
|
|
502
|
-
if (moved === "asked")
|
|
503
|
-
continue;
|
|
504
|
-
stage("the conformance run did not get to the tests - back to the conformance agent");
|
|
505
|
-
const fixed = await changing(() => askConformance(scriptFailure(recheck, "conformance testing"), options.onDelta));
|
|
506
|
-
response = fixed.result;
|
|
507
|
-
recordAttempt(rounds, recheck, `not a question of code or tests (${recheck.timedOut === true ? "the time limit" : `exit ${recheck.exitCode}`})`, { who: "conformance agent", files: fixed.files });
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
497
|
stage(`conformance tests failing - working out whether the code, the tests or the spec are wrong${judgeModel ? ` (${judgeModel})` : ""}`);
|
|
511
498
|
// A fix that changed no file leaves the next run as this one: IDLE_FIXES of them in a row, and nothing will change.
|
|
512
499
|
const idle = (files) => {
|
|
@@ -541,6 +528,14 @@ export async function renderConformanceTests(options) {
|
|
|
541
528
|
onDelta: options.onDelta,
|
|
542
529
|
});
|
|
543
530
|
const reason = verdict.reason.split("\n")[0].slice(0, 160);
|
|
531
|
+
// Stopped by the time limit while it was still getting on: no agent can give it more time. The pulse stops now -
|
|
532
|
+
// rolled back, no retry - and the person is told how to raise the limit.
|
|
533
|
+
if (verdict.culprit === "environment" && recheck.timedOut === true) {
|
|
534
|
+
stage(`the conformance run needs more than the ${Math.round(recheck.timeoutMs / 1000)}s a test run may take - stopping`);
|
|
535
|
+
if (start !== undefined)
|
|
536
|
+
abandonWork(start, moduleName, "the conformance testing script needs more time than a test run may take");
|
|
537
|
+
throw timeLimitError(recheck, { moduleName, script: scripts.conformance, kind: "conformance", reason: verdict.reason });
|
|
538
|
+
}
|
|
544
539
|
// The environment - what the run is given - is the person's to fix: once judged so twice in a row, the run stops.
|
|
545
540
|
if (verdict.culprit === "environment") {
|
|
546
541
|
environmentRaises += 1;
|
|
@@ -4,6 +4,7 @@ import { Type } from "typebox";
|
|
|
4
4
|
import { ask, BEGIN, createExecutor } from "../pi.js";
|
|
5
5
|
import { confinedTools } from "./confine.js";
|
|
6
6
|
import { createWebTools, WEB_TOOLS } from "./web.js";
|
|
7
|
+
import { runTiming } from "./test-scripts.js";
|
|
7
8
|
/**
|
|
8
9
|
* A fresh look at a failure the coding agent keeps hitting.
|
|
9
10
|
*
|
|
@@ -24,9 +25,12 @@ export function diagnosisText(diagnosis) {
|
|
|
24
25
|
}
|
|
25
26
|
function debugPrompt(input) {
|
|
26
27
|
const script = input.script !== undefined && existsSync(input.script) ? readFileSync(input.script, "utf8") : undefined;
|
|
28
|
+
const timedOut = input.run?.timedOut === true;
|
|
27
29
|
return [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
timedOut
|
|
31
|
+
? `The unit testing script of the codebase in ${input.buildDir} did not finish: the time limit stopped it. You look at it with fresh eyes.`
|
|
32
|
+
: `The unit tests of the codebase in ${input.buildDir} have failed the same way ${input.times} times in a row. The coding agent`,
|
|
33
|
+
...(timedOut ? [] : ["keeps changing things and the failure does not move: it has not seen the cause. You look at it with fresh eyes."]),
|
|
30
34
|
"",
|
|
31
35
|
"Find the root cause - the one thing that makes this failure happen - and the change that fixes it. Read the failing",
|
|
32
36
|
"run below, the tests it names, the code they exercise, the build files (pyproject.toml, package.json, go.mod, pom.xml,",
|
|
@@ -37,6 +41,21 @@ function debugPrompt(input) {
|
|
|
37
41
|
"",
|
|
38
42
|
"You change nothing - you only read. When you know, call submit_diagnosis: the root cause, with the evidence for it,",
|
|
39
43
|
"and the fix - which files, and exactly what to change in them.",
|
|
44
|
+
...(timedOut
|
|
45
|
+
? [
|
|
46
|
+
"",
|
|
47
|
+
"<timed out>",
|
|
48
|
+
"The run's output ends where the time limit stopped it: the test running then, or the step after the last one it",
|
|
49
|
+
"printed, is what did not finish. Find what kept it:",
|
|
50
|
+
"- something hangs - a test or the code waits on input, keeps a server in the foreground, never exits, loops, blocks",
|
|
51
|
+
" on a lock or a network call that never answers: the root cause and the fix, as for any failure;",
|
|
52
|
+
"- nothing hangs - the output shows the run making steady progress right up to the limit (installing, building, test",
|
|
53
|
+
" after test passing) and it needs more time than a test run may take: set too_slow, and say in root_cause from the",
|
|
54
|
+
" output how far it got and what took the time. No agent can give it more time: the work stops, and the person is",
|
|
55
|
+
" told to raise the limit. Judge from the output and the files, never from the time alone.",
|
|
56
|
+
"</timed out>",
|
|
57
|
+
]
|
|
58
|
+
: []),
|
|
40
59
|
"",
|
|
41
60
|
"<what the change is meant to do>",
|
|
42
61
|
input.task.trim(),
|
|
@@ -44,6 +63,7 @@ function debugPrompt(input) {
|
|
|
44
63
|
...(script !== undefined ? ["", `<testing script path="${input.script}">`, script.trimEnd(), "</testing script>"] : []),
|
|
45
64
|
"",
|
|
46
65
|
"<failing run>",
|
|
66
|
+
...(input.run !== undefined ? [runTiming(input.run), ""] : []),
|
|
47
67
|
input.failure.trimEnd().slice(-8000),
|
|
48
68
|
"</failing run>",
|
|
49
69
|
].join("\n");
|
|
@@ -58,10 +78,14 @@ export async function debugFailure(input) {
|
|
|
58
78
|
parameters: Type.Object({
|
|
59
79
|
root_cause: Type.String({ description: "The one thing that makes the failure happen, and the evidence for it: the lines of the run, the code, the docs" }),
|
|
60
80
|
fix: Type.String({ description: "The change that fixes it: which files, and exactly what to change in them" }),
|
|
81
|
+
too_slow: Type.Optional(Type.Boolean({
|
|
82
|
+
description: "Only for a run the time limit stopped: true when nothing hangs - it was making progress up to the limit and needs more time than a test run may take",
|
|
83
|
+
})),
|
|
61
84
|
}),
|
|
62
85
|
async execute(_id, params) {
|
|
63
86
|
const given = params;
|
|
64
|
-
|
|
87
|
+
// Too slow is a verdict on a run the limit stopped, and on no other.
|
|
88
|
+
found = { rootCause: given.root_cause, fix: given.fix, ...(given.too_slow === true && input.run?.timedOut === true ? { tooSlow: true } : {}) };
|
|
65
89
|
return { content: [{ type: "text", text: "Recorded." }], details: {}, terminate: true };
|
|
66
90
|
},
|
|
67
91
|
});
|
|
@@ -224,6 +224,23 @@ export function toolchainError(missing, details) {
|
|
|
224
224
|
source: "harness",
|
|
225
225
|
});
|
|
226
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* A testing script the time limit stopped while it was still making progress - judged so by the judge or the debugger,
|
|
229
|
+
* from where its output stops: nothing hangs, the suite needs more time than a test run may take. No agent can give it
|
|
230
|
+
* that, so the pulse stops and the person is told how to.
|
|
231
|
+
*/
|
|
232
|
+
export function timeLimitError(run, details) {
|
|
233
|
+
const limit = Math.round(run.timeoutMs / 1000);
|
|
234
|
+
return new EnvironmentError({
|
|
235
|
+
moduleName: details.moduleName,
|
|
236
|
+
missing: `more time for a test run than its ${limit}s limit`,
|
|
237
|
+
neededBy: `the ${details.kind} testing script, \`${basename(details.script)}\``,
|
|
238
|
+
shownBy: `it ran ${Math.round((run.elapsedMs ?? run.timeoutMs) / 1000)}s and was stopped at the limit, still making progress - ${details.reason.replace(/\s+/g, " ").trim().slice(0, 400)}`,
|
|
239
|
+
fix: `give a test run more time - set TURRI_COMMAND_TIMEOUT, in seconds, above ${limit} (e.g. TURRI_COMMAND_TIMEOUT=${limit * 3}) in the ` +
|
|
240
|
+
"environment turri runs in - or make the suite faster: what it installs or builds on every run, and the tests that take longest.",
|
|
241
|
+
source: "agent",
|
|
242
|
+
});
|
|
243
|
+
}
|
|
227
244
|
/** The harness's run of a testing script, when it could not find its toolchain: the error that stops the pulse. */
|
|
228
245
|
export function missingToolchainError(run, details) {
|
|
229
246
|
const missing = missingToolchain(run);
|
|
@@ -20,7 +20,7 @@ import { createWebTools, WEB_TOOLS } from "./web.js";
|
|
|
20
20
|
import { createCanItFailTool, createMyDiffTool, selfCheckNote } from "./self-check.js";
|
|
21
21
|
import { createServiceTools } from "./services.js";
|
|
22
22
|
import { createUnitGate } from "./unit-gate.js";
|
|
23
|
-
import { toolchainError } from "./environment-error.js";
|
|
23
|
+
import { timeLimitError, toolchainError } from "./environment-error.js";
|
|
24
24
|
import { workspaceBlock } from "./workspace.js";
|
|
25
25
|
import { ATTEMPTS, describeStop } from "./stop-report.js";
|
|
26
26
|
import { summarizeDiff } from "./job-panel.js";
|
|
@@ -333,6 +333,8 @@ export async function renderSpecDiff(options) {
|
|
|
333
333
|
let draftAsks = 0;
|
|
334
334
|
/** What the unit testing script needs and does not find, last run. */
|
|
335
335
|
let missing;
|
|
336
|
+
/** The unit testing script needs more time than a test run may take - the debugger's finding: the pulse stops on it. */
|
|
337
|
+
let slow;
|
|
336
338
|
/** The last round failed on the script's static checks alone: the unit tests passed. */
|
|
337
339
|
let staticOnly = false;
|
|
338
340
|
const testRemovals = [];
|
|
@@ -423,24 +425,35 @@ export async function renderSpecDiff(options) {
|
|
|
423
425
|
seenFailures.set(key, seen);
|
|
424
426
|
let prompt = verdict.prompt;
|
|
425
427
|
const circling = loop.stuck;
|
|
426
|
-
|
|
428
|
+
// A run the time limit stopped is looked at at once: whether something hangs, or the suite only needs more time.
|
|
429
|
+
const timedOut = verdict.run.timedOut === true;
|
|
430
|
+
if (circling || timedOut || (key !== undefined && seen >= STUCK_ROUNDS)) {
|
|
427
431
|
if (key !== undefined)
|
|
428
432
|
seenFailures.delete(key);
|
|
429
433
|
const times = circling ? Math.max(seen, loop.failing) : seen;
|
|
430
434
|
loop.reset();
|
|
431
435
|
stage(circling
|
|
432
436
|
? `the coding agent is going round in circles - ${times} failing runs - a debugger looks at it with fresh eyes`
|
|
433
|
-
:
|
|
437
|
+
: timedOut && seen < STUCK_ROUNDS
|
|
438
|
+
? `the unit testing script did not finish in ${Math.round(verdict.run.timeoutMs / 1000)}s - a debugger works out whether something hangs`
|
|
439
|
+
: `the same failure ${seen} times - a debugger looks at it with fresh eyes`);
|
|
434
440
|
const found = await debugFailure({
|
|
435
441
|
buildDir,
|
|
436
442
|
failure: verdict.run.output,
|
|
437
443
|
times,
|
|
438
444
|
task: steeredChange() ?? codingText ?? changeText ?? options.diff ?? "",
|
|
439
445
|
script: verdict.script,
|
|
446
|
+
run: verdict.run,
|
|
440
447
|
label: `${moduleName} spec change`,
|
|
441
448
|
...(debuggerModel !== undefined ? { model: debuggerModel } : {}),
|
|
442
449
|
onDelta: options.onDelta,
|
|
443
450
|
}).catch(() => undefined);
|
|
451
|
+
// Nothing hangs: the suite needs more time than a test run may take, and no agent can give it that.
|
|
452
|
+
if (found?.tooSlow === true) {
|
|
453
|
+
stage(`the unit testing script needs more than the ${Math.round(verdict.run.timeoutMs / 1000)}s a test run may take - stopping`);
|
|
454
|
+
slow = timeLimitError(verdict.run, { moduleName, script: verdict.script, kind: "unit", reason: found.rootCause });
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
444
457
|
if (found !== undefined) {
|
|
445
458
|
const cause = found.rootCause.split("\n")[0].slice(0, 240);
|
|
446
459
|
reportActivity(` the debugger: ${cause.slice(0, 160)}`);
|
|
@@ -543,13 +556,17 @@ export async function renderSpecDiff(options) {
|
|
|
543
556
|
stage(unitReviewStopped ? "the unit tests pass but still leave the change untested - undoing the change" : "unit tests NOT passing - undoing the change");
|
|
544
557
|
abandonWork(unit, moduleName, missing !== undefined
|
|
545
558
|
? "the unit testing script could not run"
|
|
546
|
-
:
|
|
547
|
-
? "the
|
|
548
|
-
:
|
|
559
|
+
: slow !== undefined
|
|
560
|
+
? "the unit testing script needs more time than a test run may take"
|
|
561
|
+
: staticOnly
|
|
562
|
+
? "the spec change did not pass its static checks"
|
|
563
|
+
: "the spec change did not pass its unit tests");
|
|
549
564
|
// The environment lacks what the testing script needs: the pulse stops, rolled back, and the person is told.
|
|
550
565
|
if (missing !== undefined) {
|
|
551
566
|
throw toolchainError(missing, { moduleName, script: scripts.unit, kind: "unit" });
|
|
552
567
|
}
|
|
568
|
+
if (slow !== undefined)
|
|
569
|
+
throw slow;
|
|
553
570
|
return { ...base, passed: false, stoppedAt: "unit tests", ...(staticOnly && !unitReviewStopped ? { staticChecks: true } : {}) };
|
|
554
571
|
}
|
|
555
572
|
stage("unit tests pass - landing the change on them");
|
|
@@ -335,7 +335,15 @@ async function runScript(cwd, script, args, logDir, options = {}) {
|
|
|
335
335
|
const noted = withScriptLineNote(text, script);
|
|
336
336
|
// Its failures, counts and end are what the agent is shown; the whole is kept, a read_output away.
|
|
337
337
|
const log = keepTestOutput(logDir, basename(script), noted);
|
|
338
|
-
return {
|
|
338
|
+
return {
|
|
339
|
+
command: shown(script),
|
|
340
|
+
exitCode: timedOut === true ? 124 : exitCode,
|
|
341
|
+
output: shownTestOutput(noted, log),
|
|
342
|
+
timedOut: timedOut === true,
|
|
343
|
+
timeoutMs,
|
|
344
|
+
elapsedMs: Date.now() - started,
|
|
345
|
+
...(log !== undefined ? { log } : {}),
|
|
346
|
+
};
|
|
339
347
|
}
|
|
340
348
|
/** What bash says when a line of the script itself cannot run: a command or file that is not there, or not runnable. */
|
|
341
349
|
const OWN_LINE_FAILURE = /: (No such file or directory|command not found|Permission denied)$/;
|
|
@@ -589,6 +597,19 @@ function loopNotice(loop) {
|
|
|
589
597
|
"the failure with fresh eyes, and you will be given what it finds.",
|
|
590
598
|
].join("\n");
|
|
591
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* How long a run took against its time limit, in a line: for whoever judges it or works out its cause - so a run that
|
|
602
|
+
* ended on its own is never taken for one the limit stopped, nor the other way.
|
|
603
|
+
*/
|
|
604
|
+
export function runTiming(run) {
|
|
605
|
+
const seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
|
|
606
|
+
const limit = seconds(run.timeoutMs);
|
|
607
|
+
if (run.timedOut === true) {
|
|
608
|
+
return `The time limit stopped this run: it had run ${seconds(run.elapsedMs ?? run.timeoutMs)} of the ${limit} a test run may take, and had not finished.`;
|
|
609
|
+
}
|
|
610
|
+
const took = run.elapsedMs !== undefined ? `after ${seconds(run.elapsedMs)} of the ${limit} a test run may take` : `well within the ${limit} a test run may take`;
|
|
611
|
+
return `This run ended on its own - exit ${run.exitCode}, ${took}. The time limit did not stop it and played no part in how it ended.`;
|
|
612
|
+
}
|
|
592
613
|
/** A run the time limit stopped: failed, and what most often keeps a test run from ending. */
|
|
593
614
|
function timedOutFailure(run, what) {
|
|
594
615
|
const limit = Math.round(run.timeoutMs / 1000);
|
package/dist/regenerate-cli.js
CHANGED
|
@@ -19,7 +19,7 @@ its conformance tests too when they are on.
|
|
|
19
19
|
--start-over start over, without asking, a codebase regenerated without a module it now requires -
|
|
20
20
|
with no terminal to ask and without it, the run stops there
|
|
21
21
|
--from-scratch delete the module's code and conformance tests (its lineage keeps them) and regenerate all of it
|
|
22
|
-
--harness-model <p/id> config.turri.yaml's harness-model for this run: the planner
|
|
22
|
+
--harness-model <p/id> config.turri.yaml's harness-model for this run: the planner and the conformance chooser
|
|
23
23
|
--coding-model <p/id> its coding-model, --model too: the agents that build; p/id:level for how hard a model thinks
|
|
24
24
|
--judge-model <p/id> its judge-model: the judge and the test review
|
|
25
25
|
--review <true|false> its review: whether the code and its tests are reviewed once they pass
|
package/docs/config.md
CHANGED
|
@@ -122,7 +122,7 @@ models: # each agent role's model, thinking and tur
|
|
|
122
122
|
| key | what it sets | left out |
|
|
123
123
|
| --- | --- | --- |
|
|
124
124
|
| `module` | the `.plain` file `turri regenerate` and the chat's `regenerate` build when none is named, from the project root | a file has to be named |
|
|
125
|
-
| `harness-model` | the model `turri` starts on - the one you talk to -
|
|
125
|
+
| `harness-model` | the model `turri` starts on - the one you talk to - and, in the conversation, the planner that cuts the work into jobs (on whatever model the chat is on when it plans); under `turri regenerate`, the planner too | pi's configured model |
|
|
126
126
|
| `coding-model` | every render: functionalities, spec diffs, conformance tests, repairs, planning spec diffs, the explorer | `harness-model` |
|
|
127
127
|
| `judge-model` | the judge and the test review of a conformance run | `coding-model` |
|
|
128
128
|
| `conformance-tests` | `all` (or `true`): conformance tests for every functional spec of every module with test reqs, written once each module's code is built, before anything is built on it. `chosen`: for the functional specs chosen - every one with acceptance tests, and those the chooser picks as each code pulse ends, from what that pulse regenerated. `module`: the same, but the chooser is asked once, when the whole module is regenerated, and plans the conformance pulses too. Under both, the choice is made inside the pulse, before anything is built on the module, and the pulse's report says what was chosen and why; those the person asks for with `conform` are written too; once chosen, a functional spec keeps them, and they are rewritten when it changes - see [who chooses, and when](regenerating.md#conformance-tests-in-the-work-loop). `false`: none written, and none of the existing ones run - until the conversation asks with `conform` | `false` |
|
|
@@ -158,7 +158,8 @@ wrong type, as if it were not there. `model:`, from before there were separate m
|
|
|
158
158
|
| --- | --- |
|
|
159
159
|
| `default` | every role not named below - but the conversation's side |
|
|
160
160
|
| `chat` | `turri`'s model - the same as `harness-model`, which it wins over |
|
|
161
|
-
| `chooser`, `planner
|
|
161
|
+
| `chooser`, `planner` | the conversation's side: the conformance chooser of `turri regenerate`, the planner's agents. Unnamed, the chat's model |
|
|
162
|
+
| `diagnosis` | the diagnosis of a job that failed for good: why it did not land, and what to do. Unnamed, the coding model the job ran on, thinking medium |
|
|
162
163
|
| `release` | `turri release`'s agent and release-after-pulse's: it follows steps - a script, a start command - so it runs on the coding model, thinking low |
|
|
163
164
|
| `coding` | the coding agent, the code fixer of a conformance run and the repair agent - the same as `coding-model`, which it wins over. `--model` on the command line, or a model chosen in the conversation (`engine`), wins over both |
|
|
164
165
|
| `conformance` | the conformance agent |
|
package/docs/headless.md
CHANGED
|
@@ -81,7 +81,7 @@ Useful options - `turri regenerate --help` lists all of them:
|
|
|
81
81
|
|
|
82
82
|
| | |
|
|
83
83
|
| --- | --- |
|
|
84
|
-
| `--harness-model <provider/id>` | `harness-model`: the planner
|
|
84
|
+
| `--harness-model <provider/id>` | `harness-model`: the planner and the conformance chooser |
|
|
85
85
|
| `--coding-model <provider/id>` | `coding-model` (`--model` too): the agents that build - with `:level` for how hard it thinks (`provider/id:high`); not given, the harness model's |
|
|
86
86
|
| `--judge-model <provider/id>` | `judge-model`: the judge and the test review; not given, the coding model |
|
|
87
87
|
| `--conformance-tests <all\|chosen\|module\|false>` | `conformance-tests`, with its values (`true` is `all`) |
|
|
@@ -151,7 +151,7 @@ changes meaning or goes; new fields may appear in the same version. Paths in `pu
|
|
|
151
151
|
"conformance_tests": "all", // all | chosen | module | false
|
|
152
152
|
"review": false,
|
|
153
153
|
"models": {
|
|
154
|
-
"harness": { "model": "…", "thinking": "medium" }, // the planner, the chooser
|
|
154
|
+
"harness": { "model": "…", "thinking": "medium" }, // the planner, the chooser
|
|
155
155
|
"coding": { "model": "…", "thinking": "low" },
|
|
156
156
|
"judge": { "model": "…", "thinking": "medium" },
|
|
157
157
|
"observed": { "coding": ["provider/id:low"], "planner": ["provider/id:medium"] } // what each role really ran on
|
package/docs/regenerating.md
CHANGED
|
@@ -160,6 +160,10 @@ runs the unit testing script itself**, and only that run counts (see
|
|
|
160
160
|
tried. An agent circles a cause it has not thought of - an in-memory database every connection opens
|
|
161
161
|
empty, a test runner that leaves the code off its path - and this is the way out. The rounds go on as
|
|
162
162
|
before, up to 20; should the same failure come round three times again, the debugger looks again.
|
|
163
|
+
**A run the time limit stopped** is looked at by the debugger at once, told how long the run had and that the limit
|
|
164
|
+
stopped it: it finds what hangs - the root cause and the fix, as above - or that nothing does, the run getting on up
|
|
165
|
+
to the limit and needing more time than a test run may take. Then the pulse stops, rolled back and not retried, and
|
|
166
|
+
the person is told to raise `TURRI_COMMAND_TIMEOUT` or make the suite faster.
|
|
163
167
|
- **It goes round in circles in its own runs.** An agent stuck on a failure can try the same few changes in
|
|
164
168
|
turn - three signatures of a stand-in function, say - and run the tests after each, dozens of times in one
|
|
165
169
|
turn. Its own `run_unit_tests` runs are watched (`createLoopWatch` in `test-scripts.ts`): the code folder back
|
|
@@ -978,9 +982,18 @@ the run failed with, how it was judged, and which files the agent sent to fix it
|
|
|
978
982
|
attempt's line says what came of it - and each judge reads them first, so it does not send a
|
|
979
983
|
failure the way that already failed. A run that does not pass is undone whole, its notes with it.
|
|
980
984
|
|
|
981
|
-
Then the suite runs again. A run
|
|
982
|
-
|
|
983
|
-
|
|
985
|
+
Then the suite runs again. A run that exits 80 goes back to the conformance agent without a judge - the tests pass and
|
|
986
|
+
only their static checks do not (see [the static checks](#the-static-checks)): the findings are the tests'.
|
|
987
|
+
|
|
988
|
+
**Timing.** Every run is shown to the judge, the debugger and whoever fixes it with how long it ran against its time
|
|
989
|
+
limit (`runTiming` in `test-scripts.ts`): `This run ended on its own - exit 1, after 37.2s of the 120.0s a test run
|
|
990
|
+
may take`, or `The time limit stopped this run: it had run 120.0s of the 120.0s ...` - so none takes a run that
|
|
991
|
+
ended on its own for one the limit stopped, or the other way. A run the time limit stopped is judged like any other,
|
|
992
|
+
and the judge is told what keeps a run from finishing: the code hangs (it waits on input, keeps a server in the
|
|
993
|
+
foreground, never exits) - the code's; a test waits for what never comes - the tests'; or nothing hangs, the output
|
|
994
|
+
showing the run getting on up to the limit, and it needs more time than a test run may take - the environment. That
|
|
995
|
+
verdict stops the pulse at once, rolled back and not retried, and the person is told to raise `TURRI_COMMAND_TIMEOUT`
|
|
996
|
+
or make the suite faster - no agent can give a run more time. A run whose script cannot find its
|
|
984
997
|
toolchain stops the pulse instead - see [when the environment lacks something](#when-the-environment-lacks-something).
|
|
985
998
|
Unit tests failing after the suite passes go to the coding agent. It goes round up to 20 times; still failing, the work is undone.
|
|
986
999
|
|