@turri-dev/cli 0.1.3-next.2babf06 → 0.1.3-next.420ce7e

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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/config.js +3 -2
  3. package/dist/extension.js +86 -35
  4. package/dist/identity.js +1 -1
  5. package/dist/loop-cli.js +1 -1
  6. package/dist/loop-tools.js +1 -1
  7. package/dist/pi-launch.js +5 -0
  8. package/dist/question-dialog.js +8 -3
  9. package/dist/questioning.js +1 -1
  10. package/dist/regenerate/activity.js +2 -0
  11. package/dist/regenerate/confine.js +1 -0
  12. package/dist/regenerate/conform.js +5 -5
  13. package/dist/regenerate/conformance-agents.js +220 -11
  14. package/dist/regenerate/conformance.js +91 -35
  15. package/dist/regenerate/debugger.js +27 -3
  16. package/dist/regenerate/environment-error.js +63 -9
  17. package/dist/regenerate/loop.js +22 -4
  18. package/dist/regenerate/notes.js +66 -17
  19. package/dist/regenerate/panel-dock.js +31 -2
  20. package/dist/regenerate/prompt.js +3 -1
  21. package/dist/regenerate/regenerate.js +35 -14
  22. package/dist/regenerate/regression.js +2 -1
  23. package/dist/regenerate/requires.js +55 -2
  24. package/dist/regenerate/side-panel.js +1 -1
  25. package/dist/regenerate/stop-report.js +13 -0
  26. package/dist/regenerate/test-scripts.js +22 -1
  27. package/dist/regenerate/unit-review.js +11 -0
  28. package/dist/regenerate-cli.js +1 -1
  29. package/dist/resume-line.js +4 -0
  30. package/dist/spec-tools.js +8 -44
  31. package/docs/authoring-plain.md +9 -6
  32. package/docs/config.md +3 -2
  33. package/docs/headless.md +2 -2
  34. package/docs/regenerating.md +75 -18
  35. package/docs/skills-and-gates.md +2 -2
  36. package/docs/using.md +5 -3
  37. package/genome/skills/bud/SKILL.md +67 -36
  38. package/genome/skills/bud/references/intent.md +4 -0
  39. package/package.json +3 -3
  40. package/genome/skills/bud/references/checklist.md +0 -44
@@ -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, withTestingScripts } 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";
@@ -377,9 +378,17 @@ export function explorePrompt(buildDir, spec, scope, testingScripts, question, p
377
378
  ...(testingScripts !== undefined && testingScripts.length > 0
378
379
  ? [
379
380
  "",
380
- "This is the project's conformance testing script, which the harness runs to decide whether the",
381
- "tests pass - read it for what it builds or installs before the tests run, and so what the tests can depend on:",
382
- ...testingScripts.flatMap((script) => ["", `<script path="${script.path}" run-as="${script.runs}">`, script.content.trimEnd(), "</script>"]),
381
+ ...(testingScripts.some((script) => script.prepare === true)
382
+ ? [
383
+ "These are the project's prepare script, which the harness runs on the build first, and its conformance testing",
384
+ "script, which runs only once the prepare script passes and decides whether the tests pass - read both for what",
385
+ "they build or install before the tests run, and so what the tests can depend on:",
386
+ ]
387
+ : [
388
+ "This is the project's conformance testing script, which the harness runs to decide whether the",
389
+ "tests pass - read it for what it builds or installs before the tests run, and so what the tests can depend on:",
390
+ ]),
391
+ ...testingScripts.flatMap((script) => ["", `<script${script.prepare === true ? ' kind="prepare environment"' : ""} path="${script.path}" run-as="${script.runs}">`, script.content.trimEnd(), "</script>"]),
383
392
  ]
384
393
  : []),
385
394
  ...(previous !== undefined && question === undefined
@@ -659,15 +668,28 @@ function words(text) {
659
668
  * in the specification - or when it hides why the software failed. A test asserting what the
660
669
  * specification leaves open is the test's mistake, however plausible the assertion.
661
670
  */
662
- export function unfoundedCodeVerdict(verdict, specText) {
671
+ export function unfoundedCodeVerdict(verdict, specText, scriptText) {
663
672
  if (verdict.causeHidden === true)
664
673
  return undefined;
674
+ // What the conformance testing script needs of the build - a file, an entry point, a module - binds the code as the
675
+ // specification does: neither agent can change the script. Its own words, as they are in it.
676
+ const needs = verdict.scriptNeeds?.trim() ?? "";
677
+ if (needs !== "" && scriptText !== undefined) {
678
+ const flat = (text) => text.replace(/\s+/g, " ");
679
+ if (flat(needs).length >= 4 && flat(scriptText).includes(flat(needs)))
680
+ return undefined;
681
+ if (verdict.specSays === undefined) {
682
+ return (`Your verdict blames the code for what the conformance testing script needs, but "${needs}" is not in the script. ` +
683
+ "Call submit_verdict again: copy into script_needs the script's own words for what it needs of the build, exactly as they are in it.");
684
+ }
685
+ }
665
686
  const quote = verdict.specSays?.trim() ?? "";
666
687
  if (quote === "") {
667
688
  return ("Your verdict blames the code, but quotes no words of the specification it does not meet. Call submit_verdict " +
668
689
  "again: for code, put the specification's own words the code does not meet in spec_says, copied exactly from " +
669
- "<specification>; or set cause_hidden when the output does not show why the software failed. If the specification " +
670
- "does not say what the failing test asserts, the test is what is wrong: culprit tests.");
690
+ "<specification>; or, when the run fails on something the conformance testing script needs the build to have, " +
691
+ "its own words in script_needs; or set cause_hidden when the output does not show why the software failed. If " +
692
+ "the specification does not say what the failing test asserts, the test is what is wrong: culprit tests.");
671
693
  }
672
694
  const quoted = words(quote);
673
695
  if (quoted.trim().split(" ").length < 4 || !words(specText).includes(quoted)) {
@@ -677,6 +699,31 @@ export function unfoundedCodeVerdict(verdict, specText) {
677
699
  }
678
700
  return undefined;
679
701
  }
702
+ /**
703
+ * Why a verdict's evidence cannot be taken as it is, or nothing when it can. Asked to weigh an escalation, the judge has
704
+ * to rest its verdict on what it found itself: each piece of evidence from a file it opened, the testing script or the
705
+ * failing run - and at least one not the run alone, which every judge before it read too. What the explorer's account
706
+ * says is not evidence: it was written before the run, and may be wrong.
707
+ */
708
+ export function unfoundedEvidence(evidence, opened) {
709
+ const given = (evidence ?? []).filter((item) => item.source.trim() !== "" && item.shows.trim() !== "");
710
+ if (given.length === 0) {
711
+ return ("Your verdict gives no evidence. Call submit_verdict again with `evidence`: what you found yourself - in a file you " +
712
+ "opened, the testing script, or the failing run - each with where. What <how to invoke it> says is not evidence: it " +
713
+ "may be wrong, so check it against the testing script and the files.");
714
+ }
715
+ const inPrompt = (source) => /^(the )?(conformance )?testing script$|^(the )?prepare script$|^(the )?failing run$/i.test(source.trim());
716
+ const unread = given.filter((item) => !inPrompt(item.source) && !opened(item.source.trim()));
717
+ if (unread.length > 0) {
718
+ return (`Your evidence cites ${unread.map((item) => item.source.trim()).join(", ")}, which you did not open. Open each file you cite ` +
719
+ "and call submit_verdict again - evidence is what you found yourself, never what <how to invoke it> says.");
720
+ }
721
+ if (given.every((item) => /failing run$/i.test(item.source.trim()))) {
722
+ return ("Your evidence is only the failing run, which the judges before you read too. Check what it points at - the testing " +
723
+ "script, the code, the tests - and call submit_verdict again with what you found there.");
724
+ }
725
+ return undefined;
726
+ }
680
727
  export async function judgeFailure(options) {
681
728
  let verdict;
682
729
  const submit = defineTool({
@@ -692,6 +739,13 @@ export async function judgeFailure(options) {
692
739
  description: "For code: the specification's own words the code does not meet, copied exactly from <specification> - the sentence, or the part of it that says what the test checks",
693
740
  })),
694
741
  cause_hidden: Type.Optional(Type.Boolean({ description: "For code: true when it is wrong because the output does not show why the software failed - it hides the cause" })),
742
+ script_needs: Type.Optional(Type.String({
743
+ description: "For code: the conformance testing script's own words for what it needs the build to have - the file, entry point or module the run failed on - copied exactly from the script",
744
+ })),
745
+ evidence: Type.Optional(Type.Array(Type.Object({
746
+ source: Type.String({ description: "Where: the path of a file you opened, `testing script`, `prepare script` where there is one, or `failing run`" }),
747
+ shows: Type.String({ description: "What it shows, e.g. `the script runs node dist/cli.js` or `tsconfig.json emits src/cli.ts as dist/src/cli.js`" }),
748
+ }), { description: "What you found yourself, each with where - never what <how to invoke it> claims unchecked" })),
695
749
  functionalities: Type.Optional(Type.Array(Type.Object({
696
750
  module: Type.String({ description: "The module the functionality is in" }),
697
751
  id: Type.String({ description: "Its id in that module" }),
@@ -704,6 +758,8 @@ export async function judgeFailure(options) {
704
758
  reason: given.reason,
705
759
  ...(given.spec_says?.trim() ? { specSays: given.spec_says.trim() } : {}),
706
760
  ...(given.cause_hidden === true ? { causeHidden: true } : {}),
761
+ ...(given.script_needs?.trim() ? { scriptNeeds: given.script_needs.trim() } : {}),
762
+ ...(given.evidence !== undefined && given.evidence.length > 0 ? { evidence: given.evidence } : {}),
707
763
  ...(given.functionalities ? { functionalities: conflictRefs(given.functionalities) } : {}),
708
764
  };
709
765
  return { content: [{ type: "text", text: "Recorded." }], details: {}, terminate: true };
@@ -712,10 +768,27 @@ export async function judgeFailure(options) {
712
768
  // It reads both, and changes neither: the file tools and the web - to look up what a service it calls answers -
713
769
  // and no shell. It judges from the code, the tests, the spec and the failing run's output, which it is given.
714
770
  const fence = confinedTools({ workdir: options.conformanceDir, read: [options.buildDir, options.conformanceDir], write: [], noBash: true });
771
+ // What it opens - read, listed, searched - is what its evidence can cite.
772
+ const openedPaths = new Set();
773
+ const tools = fence.tools.map((tool) => ({
774
+ ...tool,
775
+ execute: (async (...args) => {
776
+ const path = args[1]?.path;
777
+ if (typeof path === "string" && path !== "")
778
+ openedPaths.add(resolve(options.conformanceDir, path));
779
+ return tool.execute(...args);
780
+ }),
781
+ }));
782
+ const opened = (cited) => {
783
+ // A file it opened, or one in a folder it listed or searched; cited as it was opened, or relative to the code or the tests.
784
+ const candidates = [resolve(options.conformanceDir, cited), resolve(options.buildDir, cited)];
785
+ return candidates.some((path) => [...openedPaths].some((seen) => path === seen || path.startsWith(`${seen}/`)));
786
+ };
787
+ const scriptText = options.testingScripts?.map((script) => script.content).join("\n");
715
788
  const before = { code: digestFiles(options.buildDir), tests: digestFiles(options.conformanceDir) };
716
789
  const session = await createExecutor({
717
790
  cwd: options.conformanceDir,
718
- customTools: [submit, ...createWebTools(), ...fence.tools],
791
+ customTools: [submit, ...createWebTools(), ...tools],
719
792
  tools: [...fence.names, ...WEB_TOOLS, "submit_verdict"],
720
793
  model: options.model,
721
794
  label: `${options.label} judge`, role: "judge",
@@ -730,8 +803,15 @@ export async function judgeFailure(options) {
730
803
  await ask(session, "You have not called submit_verdict. Call it now: code, tests, spec or environment, and why.", options.onDelta);
731
804
  continue;
732
805
  }
806
+ // Asked to weigh an escalation, a verdict of code or tests rests on what it found itself.
807
+ const unproven = options.escalation !== undefined && (verdict.culprit === "code" || verdict.culprit === "tests") ? unfoundedEvidence(verdict.evidence, opened) : undefined;
808
+ if (unproven !== undefined) {
809
+ verdict = undefined;
810
+ await ask(session, unproven, options.onDelta);
811
+ continue;
812
+ }
733
813
  if (verdict.culprit === "code") {
734
- const unfounded = unfoundedCodeVerdict(verdict, options.spec.text);
814
+ const unfounded = unfoundedCodeVerdict(verdict, options.spec.text, scriptText);
735
815
  if (unfounded === undefined)
736
816
  break;
737
817
  verdict = undefined;
@@ -759,18 +839,26 @@ export async function judgeFailure(options) {
759
839
  }
760
840
  if (verdict?.culprit === "spec" && specConflictProblem(verdict.functionalities, options.scope) !== undefined)
761
841
  verdict = undefined;
762
- if (verdict?.culprit === "code" && unfoundedCodeVerdict(verdict, options.spec.text) !== undefined) {
842
+ // An escalation weighed without evidence escalates nothing: the tests stay the conformance agent's, as before it.
843
+ if (options.escalation !== undefined && verdict?.culprit === "code" && unfoundedEvidence(verdict.evidence, opened) !== undefined) {
844
+ return { culprit: "tests", reason: `${verdict.reason}\n\nBut the judge gave no evidence it found itself that the code is what is wrong, so the tests are looked at again.` };
845
+ }
846
+ if (verdict?.culprit === "code" && unfoundedCodeVerdict(verdict, options.spec.text, scriptText) !== undefined) {
763
847
  // Blaming the code takes the specification's own words; without them, the test asserts what the spec does not say.
764
848
  return { culprit: "tests", reason: `${verdict.reason}\n\nBut no words of the specification say so: a test that asserts what the specification does not say is what is wrong. Check each failing assertion against the specification, and assert only what it says.` };
765
849
  }
766
850
  if (verdict?.culprit === "code" && verdict.specSays !== undefined) {
767
851
  verdict = { ...verdict, reason: `${verdict.reason}\n\nThe specification says: "${verdict.specSays}"` };
768
852
  }
853
+ if (verdict?.culprit === "code" && verdict.scriptNeeds !== undefined) {
854
+ verdict = { ...verdict, reason: `${verdict.reason}\n\nThe conformance testing script needs: "${verdict.scriptNeeds}"` };
855
+ }
769
856
  // No verdict is no evidence the code is wrong: the tests are the newer work, and looked at first.
770
857
  return verdict ?? { culprit: "tests", reason: "The judge gave no verdict that could be used; check the failing tests against the specification first." };
771
858
  }
772
859
  export function judgePrompt(options) {
773
860
  const tested = options.scope.tested;
861
+ const scripts = options.testingScripts ?? [];
774
862
  return [
775
863
  specPrimer({ testReqs: true }),
776
864
  `The conformance tests of the \`${options.spec.moduleName}\` specification failed. Decide what is wrong: the code, the tests, the specification itself, or the run's environment.`,
@@ -780,7 +868,9 @@ export function judgePrompt(options) {
780
868
  "The specification is the judge, never either of them:",
781
869
  "- tests: a test asserts something the specification does not say, misreads it, or reaches the software the",
782
870
  " wrong way - a wrong command, route or setup (see <how to invoke it>).",
783
- "- code: the code does not do what the specification says, and the test checks exactly that.",
871
+ "- code: the code does not do what the specification says, and the test checks exactly that. Or the run fails",
872
+ " on something the conformance testing script needs the code to provide - a build output, an entry point, a",
873
+ " module - and the code does not provide it: quote the script's own words for it in script_needs.",
784
874
  " Only what the specification says counts: anything it leaves open - a name, coordinates, a version, a path,",
785
875
  " a package, a port, a file layout - is what the code already has. The tests follow it, and it is never",
786
876
  " code - changing the code to fit a test's guess moves what every other test points at.",
@@ -801,6 +891,8 @@ export function judgePrompt(options) {
801
891
  " the tests set up themselves - a credential, a variable or a fixture a test sets, overrides or names wrong: that is",
802
892
  " tests; nor for a toolchain or package missing where the tests run: the agents install those. The run stops on it",
803
893
  ` once it has been judged so ${ENVIRONMENT_RAISES} times in a row; until then the tests are checked for whether they cause it.`,
894
+ " A run the time limit stopped is environment only when nothing hangs and it needs more time than a test run may",
895
+ " take - and then it stops at once.",
804
896
  "",
805
897
  "When the output does not show why the software failed - it answered with an error that names no cause, and",
806
898
  "logged nothing about it - the code is what is wrong first: it hides the cause, and nobody can fix what they",
@@ -810,6 +902,25 @@ export function judgePrompt(options) {
810
902
  "verdict of code without them is not taken. What the specification leaves open is never the code's fault: a test",
811
903
  "that asserts it - a status code, a message, a format the specification does not name - is what is wrong.",
812
904
  "",
905
+ ...(scripts.length > 0
906
+ ? [
907
+ "The conformance testing script below is how every run is made, and neither agent can change it: it is never",
908
+ "what is wrong. What it does with the tests, the tests have to fit; what it needs of the code - a file it runs or",
909
+ "checks for, a command it builds with - the code has to provide. A verdict that the script is wrong is neither",
910
+ "code nor tests: say which of the two has to change to get past it.",
911
+ ...(scripts.some((script) => script.prepare === true)
912
+ ? [
913
+ "The prepare script below it runs on the build before every run, and the conformance testing script only once it",
914
+ "passes: it binds the same way - neither agent can change it, and what it needs of the code the code provides.",
915
+ ]
916
+ : []),
917
+ "",
918
+ ]
919
+ : []),
920
+ "<how to invoke it> is the explorer's account, written before the run, and it can be wrong - a folder, a path, a",
921
+ "command. Never rest a verdict on it: check each claim you use against the testing script and the files, and give",
922
+ "in `evidence` what you found yourself, each with where.",
923
+ "",
813
924
  `You have ${JUDGE_CALLS} tool calls to decide in: read what the failure points at, not the whole codebase.`,
814
925
  "",
815
926
  "When the code and the tests could both be what is wrong, choose tests. When both are wrong, say which to fix",
@@ -832,11 +943,63 @@ export function judgePrompt(options) {
832
943
  "",
833
944
  `<how to invoke it>\n${options.invocation.trim()}\n</how to invoke it>`,
834
945
  "",
946
+ ...scripts.flatMap((script) => {
947
+ const tag = script.prepare === true ? "prepare script" : "conformance testing script";
948
+ return [`<${tag} path="${script.path}" run-as="${script.runs}">`, script.content.trimEnd(), `</${tag}>`, ""];
949
+ }),
950
+ ...(options.run.timedOut === true ? [...timedOutBlock(options.run), ""] : []),
835
951
  `<failing run exit="${options.run.exitCode}" command="${options.run.command}">`,
952
+ runTiming(options.run),
953
+ "",
836
954
  options.run.output.trimEnd(),
837
955
  "</failing run>",
956
+ ...(options.escalation !== undefined ? ["", ...escalationBlock(options.escalation)] : []),
838
957
  ].join("\n");
839
958
  }
959
+ /**
960
+ * For the judge, of a run the time limit stopped: what kept it from finishing - something that hangs, the code's or a
961
+ * test's, or a suite that is only slow, which no agent can give more time.
962
+ */
963
+ export function timedOutBlock(run) {
964
+ const limit = Math.round(run.timeoutMs / 1000);
965
+ return [
966
+ "<timed out>",
967
+ `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`,
968
+ "was stopped - the step running then, or the one after the last it printed, is what did not finish. Decide what kept it:",
969
+ "- code: the software hangs - it waits on input, keeps a server in the foreground, never exits, loops, or blocks on a",
970
+ " lock or a call that never answers - when the tests reach it as the specification says.",
971
+ "- tests: a test waits for something that never comes - a process it started and never stops, output it waits for that",
972
+ " the software never prints, a port or a file nobody makes, a sleep or a retry with no end.",
973
+ "- environment: nothing hangs - the output shows the run making steady progress right up to the limit (installing,",
974
+ " building, test after test passing) and it needs more time than a test run may take. No agent can give it that: the",
975
+ " run stops at once, and the person is told to raise the limit. Say from the output how far it got, and what took",
976
+ " the time.",
977
+ "Judge from the output and the files, never from the time alone.",
978
+ "</timed out>",
979
+ ];
980
+ }
981
+ /** For the judge: the tests were fixed for this failure, and it did not go away - is it the code's? */
982
+ function escalationBlock(escalation) {
983
+ 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` : ""}`;
984
+ return [
985
+ "<escalation>",
986
+ "This failure was judged the tests' before, and the conformance agent - who can change only the tests - was sent to",
987
+ `fix it. It ${changed}, and the run fails the same way. What it was told:`,
988
+ "",
989
+ escalation.reason.trim(),
990
+ "",
991
+ "Decide again, now whether the failure belongs to the code rather than the tests. It is the code's when the run",
992
+ "fails on something the code is meant to produce - a build output, an entry point, a module the testing script needs",
993
+ "- or when the tests ran and what the software does is not what the specification says. It stays the tests' when a",
994
+ "test is still wrong and the code is right: say exactly what in the tests, so the next fix is not the last one again.",
995
+ "",
996
+ "Gather the evidence yourself: open the files the failure points at - the code's build configuration and what it",
997
+ "emits, the tests - and read the testing script. Give it in `evidence`: a verdict of code or tests without evidence",
998
+ "you found is not taken. The coding agent is handed your reason and evidence: say what the code has to provide, and",
999
+ "where it now falls short (e.g. `the script runs dist/cli.js; the build emits dist/src/cli.js`).",
1000
+ "</escalation>",
1001
+ ];
1002
+ }
840
1003
  /**
841
1004
  * The coding agent a conformance run sends the code to, when the judge says the code is wrong: it
842
1005
  * works in the code folder, may read the tests but never changes them, and keeps the unit
@@ -858,7 +1021,10 @@ export async function createCodeFixer(options) {
858
1021
  model: options.model,
859
1022
  label: `${options.label} coding agent`, role: "coding",
860
1023
  system: `${fence.note}\n\n${workspaceBlock({ dir: options.buildDir, hasMyDiff: options.toolNames.includes("my_diff") })}`,
861
- task: codeFixerTask(options),
1024
+ // What the conformance testing script needs of the build, the code provides: the fixer reads it, not only a judge's quote of it.
1025
+ task: options.testingScripts !== undefined
1026
+ ? withTestingScripts(codeFixerTask(options), options.testingScripts.scripts, ["conformance", "unit"], options.testingScripts.root)
1027
+ : codeFixerTask(options),
862
1028
  compaction: { instructions: CODER_COMPACTION },
863
1029
  ...(options.checkEnvironment !== undefined ? { checkEnvironment: options.checkEnvironment } : {}),
864
1030
  });
@@ -911,12 +1077,53 @@ export function codeFixPrompt(options) {
911
1077
  `<why the code>\n${options.reason.trim()}\n</why the code>`,
912
1078
  "",
913
1079
  `<failing run command="${options.run.command}">`,
1080
+ runTiming(options.run),
1081
+ "",
914
1082
  options.run.output.trimEnd(),
915
1083
  "</failing run>",
916
1084
  "",
917
1085
  "Fix the code, run the conformance tests and the unit tests, and finish.",
918
1086
  ].join("\n");
919
1087
  }
1088
+ /** How much of the conformance tests an escalation hands the coding agent, whole: the rest it reads itself. */
1089
+ const ESCALATED_TESTS_CHARS = 24_000;
1090
+ /**
1091
+ * An escalated failure for the code fixer: the tests were fixed for it and it did not go away, and the judge found it
1092
+ * the code's. It is handed the conformance testing script's error, the tests as they stand and the judge's evidence.
1093
+ */
1094
+ export function escalatedFixPrompt(options) {
1095
+ let room = ESCALATED_TESTS_CHARS;
1096
+ const shown = options.tests.flatMap((test) => {
1097
+ if (room <= 0)
1098
+ return [`<conformance test path="${test.path}" shown="no - read it yourself" />`];
1099
+ const content = test.content.length > room ? `${test.content.slice(0, room)}\n... (cut short - read the rest yourself)` : test.content;
1100
+ room -= content.length;
1101
+ return [`<conformance test path="${test.path}">`, content.trimEnd(), "</conformance test>"];
1102
+ });
1103
+ return [
1104
+ `The conformance tests failed (exit ${options.run.exitCode}). The conformance agent was sent to fix the tests for it, and`,
1105
+ "the run failed the same way. A judge then looked again, gathered the evidence below itself, and found that the",
1106
+ "failure belongs to the code:",
1107
+ "",
1108
+ `<why the code>\n${options.reason.trim()}\n</why the code>`,
1109
+ "",
1110
+ "<evidence>",
1111
+ ...options.evidence.map((item) => `- ${item.source.trim()}: ${item.shows.trim()}`),
1112
+ "</evidence>",
1113
+ "",
1114
+ `<failing run command="${options.run.command}">`,
1115
+ runTiming(options.run),
1116
+ "",
1117
+ options.run.output.trimEnd(),
1118
+ "</failing run>",
1119
+ "",
1120
+ "The conformance tests as they stand - not yours to change:",
1121
+ ...shown,
1122
+ "",
1123
+ "Fix the code, run the conformance tests and the unit tests, and finish. This is the last attempt at this failure:",
1124
+ "if the run still fails the same way after your fix, the work stops.",
1125
+ ].join("\n");
1126
+ }
920
1127
  /** The run's environment judged what is wrong: the conformance agent checks the tests do not cause it, and changes nothing when they do not. */
921
1128
  export function environmentCheckPrompt(options) {
922
1129
  return [
@@ -944,6 +1151,8 @@ export function testsFixPrompt(options) {
944
1151
  `<why the tests>\n${options.reason.trim()}\n</why the tests>`,
945
1152
  "",
946
1153
  "<failing run>",
1154
+ runTiming(options.run),
1155
+ "",
947
1156
  options.run.output.trimEnd(),
948
1157
  "</failing run>",
949
1158
  "",