@xcompiler/cli 0.2.3 → 0.2.4
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/README.CN.md +137 -202
- package/README.md +138 -205
- package/config.example.yaml +64 -32
- package/debug-wiki/README.md +12 -0
- package/debug-wiki/wiki/agent/calibration-fixtures.md +33 -0
- package/debug-wiki/wiki/agent/calibration-network-api.md +36 -0
- package/debug-wiki/wiki/agent/calibration-python-imports.md +33 -0
- package/debug-wiki/wiki/system/debug-issue-flow.md +29 -0
- package/debug-wiki/wiki/system/no-poisoning.md +29 -0
- package/dist/acp/index.d.ts +17 -2
- package/dist/acp/index.js +2403 -432
- package/dist/acp/index.js.map +1 -1
- package/dist/cli/xcompiler.js +2507 -523
- package/dist/cli/xcompiler.js.map +1 -1
- package/dist/cli/xcompiler_build.js +730 -248
- package/dist/cli/xcompiler_build.js.map +1 -1
- package/dist/cli/xcompiler_run.js +2185 -359
- package/dist/cli/xcompiler_run.js.map +1 -1
- package/dist/plugins/index.d.ts +16 -3
- package/dist/plugins/index.js +27 -17
- package/dist/plugins/index.js.map +1 -1
- package/dist/runtime/runtime.d.ts +38 -13
- package/dist/runtime/runtime.js +2495 -515
- package/dist/runtime/runtime.js.map +1 -1
- package/docs/XCompiler_design.md +542 -0
- package/docs/acp.md +4 -4
- package/docs/assets/iterative-v-model-pipeline.svg +169 -0
- package/docs/assets/system-architecture.svg +134 -0
- package/docs/assets/xcompiler-icon.png +0 -0
- package/docs/deploy.md +381 -0
- package/docs/openrouter.md +10 -1
- package/package.json +5 -1
package/dist/acp/index.js
CHANGED
|
@@ -55,19 +55,19 @@ var init_types = __esm({
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
// src/acp/server.ts
|
|
58
|
-
import
|
|
58
|
+
import path31 from "path";
|
|
59
59
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
60
60
|
|
|
61
61
|
// src/version.ts
|
|
62
|
-
var XCOMPILER_VERSION = "0.2.
|
|
62
|
+
var XCOMPILER_VERSION = "0.2.4";
|
|
63
63
|
var XCOMPILER_PLUGIN_API_VERSION = 1;
|
|
64
64
|
|
|
65
65
|
// src/runtime/commands.ts
|
|
66
|
-
import
|
|
67
|
-
import { promises as
|
|
66
|
+
import path30 from "path";
|
|
67
|
+
import { promises as fs28 } from "fs";
|
|
68
68
|
|
|
69
69
|
// src/core/project_file.ts
|
|
70
|
-
import { promises as
|
|
70
|
+
import { promises as fs3 } from "fs";
|
|
71
71
|
import path4 from "path";
|
|
72
72
|
import { z as z3 } from "zod";
|
|
73
73
|
|
|
@@ -253,7 +253,7 @@ var PlanSchema = z.object({
|
|
|
253
253
|
}));
|
|
254
254
|
|
|
255
255
|
// src/core/storage.ts
|
|
256
|
-
import { promises as
|
|
256
|
+
import { promises as fs2 } from "fs";
|
|
257
257
|
import path3 from "path";
|
|
258
258
|
|
|
259
259
|
// src/core/docs.ts
|
|
@@ -341,6 +341,9 @@ function testPlanDocForIteration(testPhase, iterationId = "P1") {
|
|
|
341
341
|
return basename ? `docs/iterations/${iterationId}/tests/${basename}` : canonical;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
// src/core/language.ts
|
|
345
|
+
import { promises as fs } from "fs";
|
|
346
|
+
|
|
344
347
|
// src/core/entry_gate.ts
|
|
345
348
|
import path from "path";
|
|
346
349
|
|
|
@@ -442,6 +445,7 @@ var PYTHON_EXECUTOR_SYSTEM = `You are XCompiler's Step Executor. You may only in
|
|
|
442
445
|
Every round you must return strict JSON:
|
|
443
446
|
{
|
|
444
447
|
"thoughts": "<one sentence describing this round's intent>",
|
|
448
|
+
"issueResolutionPlan": "<required only in DEBUG issue mode: concise root cause, repair target, and validation plan>",
|
|
445
449
|
"actions": [ { "tool": "<tool name>", "args": { ... } }, ... ],
|
|
446
450
|
"done": true | false
|
|
447
451
|
}
|
|
@@ -526,6 +530,7 @@ var TYPESCRIPT_EXECUTOR_SYSTEM = `You are XCompiler's Step Executor. You may onl
|
|
|
526
530
|
Every round you must return strict JSON:
|
|
527
531
|
{
|
|
528
532
|
"thoughts": "<one sentence describing this round's intent>",
|
|
533
|
+
"issueResolutionPlan": "<required only in DEBUG issue mode: concise root cause, repair target, and validation plan>",
|
|
529
534
|
"actions": [ { "tool": "<tool name>", "args": { ... } }, ... ],
|
|
530
535
|
"done": true | false
|
|
531
536
|
}
|
|
@@ -549,7 +554,7 @@ Rules:
|
|
|
549
554
|
7. package.json is the dependency manifest. Use add_dependency for npm packages; never write requirements.txt.
|
|
550
555
|
8. run_program runs the project entry with \`npx tsx\`, run_tests runs Vitest via \`npm test\`, and the final delivery gate also verifies the direct Node entry command.`;
|
|
551
556
|
var PLANNER_CLARIFY_SYSTEM = `You are the requirements analyst for XCompiler's V-model. Do not restate the topic; expose unresolved decisions that would change functional design, acceptance, or architecture boundaries.
|
|
552
|
-
Return strict JSON only. Each question must be directly answerable by a product owner
|
|
557
|
+
Return strict JSON only. Each question must be directly answerable by a product owner and cover one decision. Avoid vague catch-all or implementation-stack questions, except for the single development-language question when the user prompt explicitly requires it.`;
|
|
553
558
|
function buildPlannerSystem(profile) {
|
|
554
559
|
return (profile.id === "typescript" ? TYPESCRIPT_PLANNER_SYSTEM : PYTHON_PLANNER_SYSTEM) + profile.plannerPromptOverride;
|
|
555
560
|
}
|
|
@@ -641,13 +646,13 @@ var messages = {
|
|
|
641
646
|
preflightOllamaUnreachable: (baseUrl, message) => `preflight: ollama ${baseUrl} unreachable: ${message}`,
|
|
642
647
|
preflightAutoAdded: (providers, roles) => `preflight: auto-added ${providers} provider(s) for roles [${roles}]`,
|
|
643
648
|
scoreFileHeader: "# XCompiler LLM provider score snapshot (maintained automatically by ScoreStore; do not edit)",
|
|
644
|
-
scoreFileSemantics: "# Scores: default 1.0; automatic range 0.1-1.0; providers tagged cluster default to 0.2-0.5 unless llm.cluster_score_min/max widens it; failure -0.5; success +0.1
|
|
649
|
+
scoreFileSemantics: "# Scores: dynamic snapshot; default 1.0; automatic range 0.1-1.0; providers tagged cluster default to 0.2-0.5 unless llm.cluster_score_min/max widens it; failure -0.5; success +0.1. Put user overrides in llm_scores_user.yaml; 0 disables a provider."
|
|
645
650
|
},
|
|
646
651
|
system: {
|
|
647
652
|
configEnvMissing: (names) => `[xcompiler] unset config environment variables were replaced with empty strings: ${names}`,
|
|
648
653
|
unhandledError: (message) => `Unhandled error: ${message}`,
|
|
649
654
|
unsupportedPypiOnlyNetwork: "network=pypi-only is rejected because Docker cannot enforce a PyPI-only allowlist by itself. Use network=off for isolation or network=download-only for explicitly unrestricted outbound downloads.",
|
|
650
|
-
dockerInsideContainerUnsupported: "XCompiler is running inside a container, so sandbox
|
|
655
|
+
dockerInsideContainerUnsupported: "XCompiler is running inside a container, so sandbox mode docker is unsupported because Docker-outside-of-Docker can mis-map bind mounts and docker.sock permissions. Use agent.sandboxes.<language>.mode=subprocess, run XCompiler on the host, or set XC_IN_CONTAINER=0 only in a controlled environment.",
|
|
651
656
|
firejailUnsupported: "sandbox=firejail is not implemented; use subprocess or docker.",
|
|
652
657
|
smokeHeader: (baseUrl) => `Smoke test against ${baseUrl} (streaming)`,
|
|
653
658
|
smokeOk: (model, totalMs, firstTokenMs, chunks, preview) => `[OK total=${totalMs}ms first-token=${firstTokenMs}ms chunks=${chunks}] ${model} -> ${preview}`,
|
|
@@ -664,8 +669,8 @@ var messages = {
|
|
|
664
669
|
loaded: (plugin, version) => `Plugin ${plugin}@${version} loaded.`,
|
|
665
670
|
extensionConflict: (plugin, kind, name) => `Plugin ${plugin} cannot replace existing ${kind} "${name}".`,
|
|
666
671
|
hookFailed: (plugin, stage, message) => `Plugin ${plugin} failed during ${stage}: ${message}`,
|
|
667
|
-
manifestReadFailed: (
|
|
668
|
-
moduleLoadFailed: (plugin,
|
|
672
|
+
manifestReadFailed: (path32, message) => `Cannot read plugin manifest ${path32}: ${message}`,
|
|
673
|
+
moduleLoadFailed: (plugin, path32, message) => `Cannot load plugin ${plugin} from ${path32}: ${message}`,
|
|
669
674
|
exportInvalid: (plugin, exportName) => `Plugin ${plugin} export ${exportName} is not a valid XCompiler plugin`,
|
|
670
675
|
manifestMismatch: (plugin) => `Plugin ${plugin} runtime manifest does not match its preflight manifest`
|
|
671
676
|
},
|
|
@@ -753,6 +758,7 @@ var messages = {
|
|
|
753
758
|
optIntent: "plan intent: greenfield | feature | refactor | self",
|
|
754
759
|
optBaselinePlan: "existing baseline phasePlan.json / plan.json path (default <workspace>/phasePlan.json)",
|
|
755
760
|
optProjectFile: "XXX.xc project file path (default <workspace>/<name>.xc)",
|
|
761
|
+
optDebugWikiPath: "debug wiki root directory path (default <XCompiler path>/.xcompiler/debug-wiki)",
|
|
756
762
|
argPlan: "phasePlan.json or legacy plan.json path (default = <workspace>/phasePlan.json)",
|
|
757
763
|
argProjectFile: "XXX.xc project file",
|
|
758
764
|
argStepId: "Step ID, e.g. S001",
|
|
@@ -899,7 +905,7 @@ var messages = {
|
|
|
899
905
|
stepHeader: (id, phase, title, status, retries, maxRetries) => `${id} ${phase} ${title} ${status} retries=${retries}/${maxRetries}`,
|
|
900
906
|
stepRoleTools: (role, tools) => `role=${role} tools=[${tools}]`,
|
|
901
907
|
stepDependsOn: (ids) => `dependsOn: ${ids}`,
|
|
902
|
-
outputStatus: (
|
|
908
|
+
outputStatus: (exists2, p) => `${exists2 ? "\u2713" : "\u2717"} ${p}`,
|
|
903
909
|
auditEntry: (ts, kind, message) => `${ts} ${kind} ${message}`
|
|
904
910
|
},
|
|
905
911
|
execute: {
|
|
@@ -967,7 +973,7 @@ var messages = {
|
|
|
967
973
|
deliveryGateReason: (command, exitCode, timedOut) => `FUNCTIONAL_TEST gate: \`${command}\` exit=${exitCode}${timedOut ? " (timeout)" : ""}`,
|
|
968
974
|
missingPythonEntrypoint: "missing Python entrypoint: expected src/main.py, src/<package>/__main__.py, or an explicit CLI file such as src/cli.py",
|
|
969
975
|
missingTypeScriptEntrypoint: "missing TypeScript entrypoint: expected package.json start/bin or one of src/main.ts, src/index.ts, src/main.tsx",
|
|
970
|
-
invalidPythonEntrypointSource: (
|
|
976
|
+
invalidPythonEntrypointSource: (path32) => `invalid Python entrypoint source in ${path32}: expected a real CLI entry structure such as def main(...), argparse.ArgumentParser, or if __name__ == "__main__"; placeholder/import-only files are not runnable applications.`,
|
|
971
977
|
entrypointHelpOutputMissing: (command) => `entrypoint probe \`${command}\` exited 0 but produced no meaningful help/usage text; implement --help instead of relying on an empty script exit.`,
|
|
972
978
|
reasonLine: (reason) => `reason: ${reason}`,
|
|
973
979
|
roundsLine: (rounds) => `rounds: ${rounds}`,
|
|
@@ -1036,12 +1042,13 @@ Question mix (functionality first):
|
|
|
1036
1042
|
- Label options sequentially from A through the last generated option, for example A-B, A-C, A-D, or A-E. A should be the recommended/default setting when one is apparent. Options must be concrete business/product settings, not vague placeholders.
|
|
1037
1043
|
- Do not include \u201COther\u201D, \u201CCustom\u201D, or \u201CLet the user decide\u201D as an option. The CLI already allows the user to reply with one of the shown labels or enter a custom free-form answer.
|
|
1038
1044
|
${opts.projectShapeAmbiguous ? "- Required for this topic: ask the API library vs runnable application vs mixed-deliverable boundary explicitly.\n" : ""}
|
|
1045
|
+
${opts.languageAmbiguous ? "- Required for this topic: include exactly one boundary question confirming the target development language. Options must be A. Python (default/recommended) and B. TypeScript / Node.js. The user may still answer with custom free-form text.\n" : ""}
|
|
1039
1046
|
|
|
1040
|
-
[Hard constraint] The implementation stack is already fixed by
|
|
1047
|
+
${opts.languageAmbiguous ? `[Stack decision] XCompiler could not infer the target language from the topic or baseline. Ask only the development-language question described above; do not ask package manager, test framework, or OS questions. If the user does not choose, Python is the default.` : `[Hard constraint] The implementation stack is already fixed by the user's topic or existing project baseline. Do not reopen language/runtime/package-manager decisions.
|
|
1041
1048
|
**Do NOT** ask questions of these forms:
|
|
1042
1049
|
- "Which programming language / framework / runtime should this use?"
|
|
1043
1050
|
- "Which test framework / build tool / package manager?"
|
|
1044
|
-
- "Which OS is the target platform?"
|
|
1051
|
+
- "Which OS is the target platform?"`}
|
|
1045
1052
|
${opts.intent && opts.intent !== "greenfield" ? `This is an incremental ${opts.intent} request against an existing project${opts.hasBaseline ? " with a separate baseline summary that will be provided during decomposition" : ""}. Ask ONLY delta questions; do not ask to rebuild the project from scratch.` : ""}The majority of questions must concern functional behaviour; performance, boundaries, and extensibility should eliminate ambiguities that affect this delivery.`,
|
|
1046
1053
|
plannerDecompose: (raw, qa, addenda, opts = {}) => `Original requirement:
|
|
1047
1054
|
"""
|
|
@@ -1143,7 +1150,7 @@ Return strict JSON StepPlan for the current phase only.`,
|
|
|
1143
1150
|
executorDebugBlock: (reason, suggestions) => `
|
|
1144
1151
|
|
|
1145
1152
|
You are now in DEBUG retry mode. Previous failure reason: ${reason}
|
|
1146
|
-
DEBUG may edit upstream source files and tests within the current allowedWrites. If the failure reveals a real implementation, contract, or downstream integration mismatch, fix that real defect; do not pass by weakening assertions, skipping tests, deleting failing cases, or merely accommodating an incorrect test. If this rollback is in a design/requirements step and the concrete code change belongs to a later V-model step outside the current allowedWrites, update the current contract, test plan, or diagnostic artifact and finish this step so the later CODE step can implement it; do not attempt denied writes. If the failure is a missing third-party dependency or wrong library choice, use add_dependency with the real package name or change the source back to the real library selected by HIGH_LEVEL_DESIGN; never add try/except ImportError fake modules, fake classes/functions, empty implementations, or fallback mocks in production src/ code to bypass the error. Begin with read_file / code_search to localise the issue, then make the smallest possible fix via apply_patch / replace_in_file / add_dependency, and finally run_tests to verify. A DEBUG retry cannot be marked complete from read-only inspection alone: it must produce a successful repair action or a successful verification command in this retry. If the previous failure reason mentions repeated read-only/probe actions, use the existing failure log as sufficient context and make the next action a patch/write/dependency change or a verification command. When a test executes and fails an assertion about returned behaviour, do not repeatedly rewrite fixtures or samples. Only edit fixtures when the evidence is missing-file, malformed-fixture, or parse-error in the fixture itself; otherwise patch the implementation, interface contract, dependency choice, or test expectation that is actually wrong. If the failure log shows a network/API failure, do not stop at probing endpoints: use at most two consecutive http_fetch probes, reject 2xx responses with empty or unusable bodies, then patch the real integration and verify with run_program plus run_tests. Do not set done=true while the entrypoint still reports a network/API failure.` + (suggestions ? `
|
|
1153
|
+
When this retry is handling an issue, every JSON response must include issueResolutionPlan before or while fixing it. The plan must be concise and actionable: root cause hypothesis, files/contracts to change, validation command or gate, and what would disprove the plan. DEBUG may edit upstream source files and tests within the current allowedWrites. If the failure reveals a real implementation, contract, or downstream integration mismatch, fix that real defect; do not pass by weakening assertions, skipping tests, deleting failing cases, or merely accommodating an incorrect test. If this rollback is in a design/requirements step and the concrete code change belongs to a later V-model step outside the current allowedWrites, update the current contract, test plan, or diagnostic artifact and finish this step so the later CODE step can implement it; do not attempt denied writes. If the failure is a missing third-party dependency or wrong library choice, use add_dependency with the real package name or change the source back to the real library selected by HIGH_LEVEL_DESIGN; never add try/except ImportError fake modules, fake classes/functions, empty implementations, or fallback mocks in production src/ code to bypass the error. Begin with read_file / code_search to localise the issue, then make the smallest possible fix via apply_patch / replace_in_file / add_dependency, and finally run_tests to verify. A DEBUG retry cannot be marked complete from read-only inspection alone: it must produce a successful repair action or a successful verification command in this retry. If the previous failure reason mentions repeated read-only/probe actions, use the existing failure log as sufficient context and make the next action a patch/write/dependency change or a verification command. When a test executes and fails an assertion about returned behaviour, do not repeatedly rewrite fixtures or samples. Only edit fixtures when the evidence is missing-file, malformed-fixture, or parse-error in the fixture itself; otherwise patch the implementation, interface contract, dependency choice, or test expectation that is actually wrong. If the failure log shows a network/API failure, do not stop at probing endpoints: use at most two consecutive http_fetch probes, reject 2xx responses with empty or unusable bodies, then patch the real integration and verify with run_program plus run_tests. Do not set done=true while the entrypoint still reports a network/API failure.` + (suggestions ? `
|
|
1147
1154
|
|
|
1148
1155
|
${suggestions}` : ""),
|
|
1149
1156
|
executorGlobalBlock: (globalPrompt) => `
|
|
@@ -1160,7 +1167,8 @@ ${sp}`,
|
|
|
1160
1167
|
executorFeedbackVerifyMissing: (paths) => `outputs still missing: ${paths}. Please continue.`,
|
|
1161
1168
|
executorFeedbackReadOnlyLoopWarning: (rounds, targets) => `Loop guard warning: the last ${rounds} round(s) used only read/probe tools` + (targets ? ` (${targets})` : "") + ". Next response must include a successful repair action (apply_patch / replace_in_file / write_file / add_dependency) or a concrete verification action (run_tests / run_program). Do not continue with only read_file, list_dir, code_search, or http_fetch.",
|
|
1162
1169
|
executorFeedbackReadOnlyRecoveryRequired: "Read-only recovery mode is active because the previous attempt already failed from probing. The next response must use existing failure evidence to patch/write/change dependency or run verification; one more read-only-only response will fail this retry.",
|
|
1163
|
-
executorFeedbackRepairEvidenceMissing: "Invalid DEBUG completion: this retry has not produced repair evidence yet. Before done=true, perform at least one successful repair action or successful verification run; otherwise stop only with a concrete blocker in thoughts."
|
|
1170
|
+
executorFeedbackRepairEvidenceMissing: "Invalid DEBUG completion: this retry has not produced repair evidence yet. Before done=true, perform at least one successful repair action or successful verification run; otherwise stop only with a concrete blocker in thoughts.",
|
|
1171
|
+
executorFeedbackIssueResolutionPlanMissing: "Invalid DEBUG issue completion: issueResolutionPlan is required before the issue can be resolved. Return JSON with a concise handling plan plus the needed repair or verification actions."
|
|
1164
1172
|
},
|
|
1165
1173
|
skills: {
|
|
1166
1174
|
patcher: "Use apply_patch / replace_in_file for small in-place edits to existing files; never overwrite a whole file.",
|
|
@@ -1181,7 +1189,7 @@ ${sp}`,
|
|
|
1181
1189
|
summaryOk: "all checks passed.",
|
|
1182
1190
|
summaryWarn: (n) => `passed with ${n} warning(s).`,
|
|
1183
1191
|
summaryFail: (n) => `${n} failure(s) detected.`,
|
|
1184
|
-
configLoadOk: (
|
|
1192
|
+
configLoadOk: (path32) => `config loaded: ${path32}`,
|
|
1185
1193
|
configLoadFail: (msg) => `failed to load config: ${msg}`,
|
|
1186
1194
|
configLocale: (locale) => `locale=${locale}`,
|
|
1187
1195
|
llmNoProviders: "no LLM providers defined in config.llm.providers",
|
|
@@ -1199,7 +1207,7 @@ ${sp}`,
|
|
|
1199
1207
|
roleOk: (role, provider) => `role "${role}" \u2192 ${provider}`,
|
|
1200
1208
|
sandboxKind: (kind) => `sandbox=${kind}`,
|
|
1201
1209
|
sandboxNetworkPolicy: (policy, ports) => `network=${policy}` + (ports.length ? ` (expose_ports=[${ports.join(", ")}])` : ""),
|
|
1202
|
-
sandboxFullNoPorts: "network=full but no expose_ports configured \u2014 host-side cannot reach container services. Add `agent.
|
|
1210
|
+
sandboxFullNoPorts: "network=full but no expose_ports configured \u2014 host-side cannot reach container services. Add `agent.sandboxes.<language>.<local|docker>.limits.expose_ports: [<port>]` in config.yaml.",
|
|
1203
1211
|
sandboxNodeMissing: "node not found on PATH (required by TypeScript subprocess sandbox)",
|
|
1204
1212
|
sandboxNodeOk: (version) => `node OK (${version})`,
|
|
1205
1213
|
sandboxNpmMissing: "npm not found on PATH (required by TypeScript subprocess sandbox)",
|
|
@@ -1318,6 +1326,7 @@ var PYTHON_EXECUTOR_SYSTEM2 = `\u4F60\u662F XCompiler \u7684 Step Executor\u3002
|
|
|
1318
1326
|
\u6BCF\u4E00\u8F6E\u4F60\u5FC5\u987B\u8FD4\u56DE\u4E25\u683C JSON\uFF1A
|
|
1319
1327
|
{
|
|
1320
1328
|
"thoughts": "<\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u672C\u8F6E\u610F\u56FE>",
|
|
1329
|
+
"issueResolutionPlan": "<\u4EC5 DEBUG issue \u6A21\u5F0F\u5FC5\u586B\uFF1A\u7B80\u660E\u8BF4\u660E\u6839\u56E0\u5047\u8BBE\u3001\u4FEE\u590D\u76EE\u6807\u548C\u9A8C\u8BC1\u65B9\u6848>",
|
|
1321
1330
|
"actions": [ { "tool": "<\u5DE5\u5177\u540D>", "args": { ... } }, ... ],
|
|
1322
1331
|
"done": true | false
|
|
1323
1332
|
}
|
|
@@ -1401,6 +1410,7 @@ var TYPESCRIPT_EXECUTOR_SYSTEM2 = `\u4F60\u662F XCompiler \u7684 Step Executor\u
|
|
|
1401
1410
|
\u6BCF\u4E00\u8F6E\u4F60\u5FC5\u987B\u8FD4\u56DE\u4E25\u683C JSON\uFF1A
|
|
1402
1411
|
{
|
|
1403
1412
|
"thoughts": "<\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u672C\u8F6E\u610F\u56FE>",
|
|
1413
|
+
"issueResolutionPlan": "<\u4EC5 DEBUG issue \u6A21\u5F0F\u5FC5\u586B\uFF1A\u7B80\u660E\u8BF4\u660E\u6839\u56E0\u5047\u8BBE\u3001\u4FEE\u590D\u76EE\u6807\u548C\u9A8C\u8BC1\u65B9\u6848>",
|
|
1404
1414
|
"actions": [ { "tool": "<\u5DE5\u5177\u540D>", "args": { ... } }, ... ],
|
|
1405
1415
|
"done": true | false
|
|
1406
1416
|
}
|
|
@@ -1514,13 +1524,13 @@ var messages2 = {
|
|
|
1514
1524
|
preflightOllamaUnreachable: (baseUrl, message) => `\u9884\u68C0\uFF1AOllama ${baseUrl} \u4E0D\u53EF\u8FBE\uFF1A${message}`,
|
|
1515
1525
|
preflightAutoAdded: (providers, roles) => `\u9884\u68C0\uFF1A\u81EA\u52A8\u589E\u52A0 ${providers} \u4E2A provider\uFF0C\u8986\u76D6\u89D2\u8272 [${roles}]`,
|
|
1516
1526
|
scoreFileHeader: "# XCompiler LLM provider \u8BC4\u5206\u5FEB\u7167\uFF08\u7531 ScoreStore \u81EA\u52A8\u7EF4\u62A4\uFF0C\u8BF7\u52FF\u624B\u5DE5\u7F16\u8F91\uFF09",
|
|
1517
|
-
scoreFileSemantics: "# \u8BC4\u5206\u8BED\u4E49\uFF1A\u9ED8\u8BA4 1.0\uFF1B\u81EA\u52A8\u8BC4\u5206\u8303\u56F4 0.1\uFF5E1.0\uFF1Btags: [cluster] \u7684 provider \u9ED8\u8BA4 0.2\uFF5E0.5\uFF0C\u9664\u975E llm.cluster_score_min/max \u6269\u5BBD\uFF1B\u5931\u8D25 -0.5\uFF1B\u6210\u529F +0.1\
|
|
1527
|
+
scoreFileSemantics: "# \u8BC4\u5206\u8BED\u4E49\uFF1A\u8FD9\u662F\u52A8\u6001\u8BC4\u5206\u5FEB\u7167\uFF1B\u9ED8\u8BA4 1.0\uFF1B\u81EA\u52A8\u8BC4\u5206\u8303\u56F4 0.1\uFF5E1.0\uFF1Btags: [cluster] \u7684 provider \u9ED8\u8BA4 0.2\uFF5E0.5\uFF0C\u9664\u975E llm.cluster_score_min/max \u6269\u5BBD\uFF1B\u5931\u8D25 -0.5\uFF1B\u6210\u529F +0.1\u3002\u7528\u6237\u8986\u76D6\u8BF7\u5199 llm_scores_user.yaml\uFF0C0 \u8868\u793A\u7981\u7528\u3002"
|
|
1518
1528
|
},
|
|
1519
1529
|
system: {
|
|
1520
1530
|
configEnvMissing: (names) => `[xcompiler] \u914D\u7F6E\u4E2D\u7684\u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E\uFF0C\u5DF2\u66FF\u6362\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF1A${names}`,
|
|
1521
1531
|
unhandledError: (message) => `\u672A\u5904\u7406\u9519\u8BEF\uFF1A${message}`,
|
|
1522
1532
|
unsupportedPypiOnlyNetwork: "\u62D2\u7EDD network=pypi-only\uFF1ADocker \u672C\u8EAB\u65E0\u6CD5\u53EF\u9760\u6267\u884C\u201C\u4EC5 PyPI\u201D\u57DF\u540D\u767D\u540D\u5355\u3002\u9700\u8981\u9694\u79BB\u8BF7\u4F7F\u7528 network=off\uFF1B\u660E\u786E\u5141\u8BB8\u4EFB\u610F\u51FA\u7AD9\u4E0B\u8F7D\u65F6\u4F7F\u7528 network=download-only\u3002",
|
|
1523
|
-
dockerInsideContainerUnsupported: "\u68C0\u6D4B\u5230 XCompiler \u8FD0\u884C\u5728\u5BB9\u5668\u5185\uFF0Csandbox=docker \u53EF\u80FD\u5BFC\u81F4 bind-mount \u8DEF\u5F84\u53CA docker.sock \u6743\u9650\u9519\u4F4D\uFF0C\u56E0\u6B64\u4E0D\u53D7\u652F\u6301\u3002\u8BF7\u4F7F\u7528 agent.
|
|
1533
|
+
dockerInsideContainerUnsupported: "\u68C0\u6D4B\u5230 XCompiler \u8FD0\u884C\u5728\u5BB9\u5668\u5185\uFF0Csandbox mode=docker \u53EF\u80FD\u5BFC\u81F4 bind-mount \u8DEF\u5F84\u53CA docker.sock \u6743\u9650\u9519\u4F4D\uFF0C\u56E0\u6B64\u4E0D\u53D7\u652F\u6301\u3002\u8BF7\u4F7F\u7528 agent.sandboxes.<language>.mode=subprocess\u3001\u6539\u5728\u5BBF\u4E3B\u673A\u8FD0\u884C\uFF0C\u6216\u4EC5\u5728\u53D7\u63A7\u73AF\u5883\u8BBE\u7F6E XC_IN_CONTAINER=0\u3002",
|
|
1524
1534
|
firejailUnsupported: "\u5C1A\u672A\u5B9E\u73B0 sandbox=firejail\uFF0C\u8BF7\u4F7F\u7528 subprocess \u6216 docker\u3002",
|
|
1525
1535
|
smokeHeader: (baseUrl) => `\u6B63\u5728\u5BF9 ${baseUrl} \u6267\u884C\u6D41\u5F0F\u5192\u70DF\u6D4B\u8BD5`,
|
|
1526
1536
|
smokeOk: (model, totalMs, firstTokenMs, chunks, preview) => `[\u6210\u529F \u603B\u8017\u65F6=${totalMs}ms \u9996Token=${firstTokenMs}ms \u5206\u5757=${chunks}] ${model} -> ${preview}`,
|
|
@@ -1537,8 +1547,8 @@ var messages2 = {
|
|
|
1537
1547
|
loaded: (plugin, version) => `\u63D2\u4EF6 ${plugin}@${version} \u5DF2\u52A0\u8F7D\u3002`,
|
|
1538
1548
|
extensionConflict: (plugin, kind, name) => `\u63D2\u4EF6 ${plugin} \u4E0D\u80FD\u8986\u76D6\u5DF2\u6709 ${kind} \u201C${name}\u201D\u3002`,
|
|
1539
1549
|
hookFailed: (plugin, stage, message) => `\u63D2\u4EF6 ${plugin} \u5728 ${stage} \u9636\u6BB5\u6267\u884C\u5931\u8D25\uFF1A${message}`,
|
|
1540
|
-
manifestReadFailed: (
|
|
1541
|
-
moduleLoadFailed: (plugin,
|
|
1550
|
+
manifestReadFailed: (path32, message) => `\u65E0\u6CD5\u8BFB\u53D6\u63D2\u4EF6\u6E05\u5355 ${path32}\uFF1A${message}`,
|
|
1551
|
+
moduleLoadFailed: (plugin, path32, message) => `\u65E0\u6CD5\u4ECE ${path32} \u52A0\u8F7D\u63D2\u4EF6 ${plugin}\uFF1A${message}`,
|
|
1542
1552
|
exportInvalid: (plugin, exportName) => `\u63D2\u4EF6 ${plugin} \u7684\u5BFC\u51FA ${exportName} \u4E0D\u662F\u6709\u6548 XCompiler \u63D2\u4EF6`,
|
|
1543
1553
|
manifestMismatch: (plugin) => `\u63D2\u4EF6 ${plugin} \u7684\u8FD0\u884C\u65F6\u6E05\u5355\u4E0E\u9884\u68C0\u6E05\u5355\u4E0D\u4E00\u81F4`
|
|
1544
1554
|
},
|
|
@@ -1626,6 +1636,7 @@ var messages2 = {
|
|
|
1626
1636
|
optIntent: "\u8BA1\u5212\u610F\u56FE\uFF1Agreenfield | feature | refactor | self",
|
|
1627
1637
|
optBaselinePlan: "\u5DF2\u6709\u57FA\u7EBF phasePlan.json / plan.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 <workspace>/phasePlan.json\uFF09",
|
|
1628
1638
|
optProjectFile: "XXX.xc \u5DE5\u7A0B\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 <workspace>/<name>.xc\uFF09",
|
|
1639
|
+
optDebugWikiPath: "debug wiki \u6839\u76EE\u5F55\u8DEF\u5F84\uFF08\u9ED8\u8BA4 <XCompiler path>/.xcompiler/debug-wiki\uFF09",
|
|
1629
1640
|
argPlan: "phasePlan.json \u6216\u5386\u53F2 plan.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 = <workspace>/phasePlan.json\uFF09",
|
|
1630
1641
|
argProjectFile: "XXX.xc \u5DE5\u7A0B\u6587\u4EF6",
|
|
1631
1642
|
argStepId: "Step ID\uFF0C\u5982 S001",
|
|
@@ -1772,7 +1783,7 @@ var messages2 = {
|
|
|
1772
1783
|
stepHeader: (id, phase, title, status, retries, maxRetries) => `${id} ${phase} ${title} ${status} \u91CD\u8BD5=${retries}/${maxRetries}`,
|
|
1773
1784
|
stepRoleTools: (role, tools) => `\u89D2\u8272=${role} \u5DE5\u5177=[${tools}]`,
|
|
1774
1785
|
stepDependsOn: (ids) => `\u4F9D\u8D56\uFF1A${ids}`,
|
|
1775
|
-
outputStatus: (
|
|
1786
|
+
outputStatus: (exists2, p) => `${exists2 ? "\u2713" : "\u2717"} ${p}`,
|
|
1776
1787
|
auditEntry: (ts, kind, message) => `${ts} ${kind} ${message}`
|
|
1777
1788
|
},
|
|
1778
1789
|
execute: {
|
|
@@ -1840,7 +1851,7 @@ var messages2 = {
|
|
|
1840
1851
|
deliveryGateReason: (command, exitCode, timedOut) => `FUNCTIONAL_TEST \u95E8\u7981\uFF1A\`${command}\` \u9000\u51FA\u7801=${exitCode}${timedOut ? "\uFF08\u8D85\u65F6\uFF09" : ""}`,
|
|
1841
1852
|
missingPythonEntrypoint: "\u7F3A\u5C11 Python \u5165\u53E3\uFF1A\u9700\u8981 src/main.py\u3001src/<package>/__main__.py\uFF0C\u6216 src/cli.py \u7B49\u663E\u5F0F CLI \u6587\u4EF6",
|
|
1842
1853
|
missingTypeScriptEntrypoint: "\u7F3A\u5C11 TypeScript \u5165\u53E3\uFF1A\u9700\u8981 package.json start/bin \u6216 src/main.ts\u3001src/index.ts\u3001src/main.tsx \u4E4B\u4E00",
|
|
1843
|
-
invalidPythonEntrypointSource: (
|
|
1854
|
+
invalidPythonEntrypointSource: (path32) => `Python \u5165\u53E3\u6E90\u7801\u65E0\u6548\uFF1A${path32} \u5FC5\u987B\u662F\u771F\u5B9E CLI \u5165\u53E3\uFF0C\u81F3\u5C11\u5305\u542B def main(...)\u3001argparse.ArgumentParser \u6216 if __name__ == "__main__" \u8FD9\u7C7B\u5165\u53E3\u7ED3\u6784\uFF1B\u4EC5 import/comment \u7684\u5360\u4F4D\u6587\u4EF6\u4E0D\u80FD\u7B97\u53EF\u8FD0\u884C\u5E94\u7528\u3002`,
|
|
1844
1855
|
entrypointHelpOutputMissing: (command) => `\u5165\u53E3\u63A2\u6D4B \`${command}\` \u867D\u7136\u9000\u51FA\u7801\u4E3A 0\uFF0C\u4F46\u6CA1\u6709\u8F93\u51FA\u6709\u610F\u4E49\u7684 help/usage \u6587\u672C\uFF1B\u5FC5\u987B\u5B9E\u73B0 --help\uFF0C\u4E0D\u80FD\u9760\u7A7A\u811A\u672C\u81EA\u7136\u9000\u51FA\u8FC7\u5173\u3002`,
|
|
1845
1856
|
reasonLine: (reason) => `\u539F\u56E0\uFF1A${reason}`,
|
|
1846
1857
|
roundsLine: (rounds) => `\u8F6E\u6B21\uFF1A${rounds}`,
|
|
@@ -1885,7 +1896,7 @@ var messages2 = {
|
|
|
1885
1896
|
- \u6BCF\u4E2A CODE/\u6D4B\u8BD5\u4EA7\u7269\u5FC5\u987B\u4E25\u683C\u9650\u5B9A\u5728\u672C\u6B21\u589E\u91CF\u8303\u56F4\uFF0C\u7981\u6B62\u6574\u4F53\u91CD\u5EFA\u6216\u66FF\u6362\u4ED3\u5E93\u3002
|
|
1886
1897
|
- \u5C06\u7A33\u5B9A\u5BBF\u4E3B\u89C6\u4E3A N \u4EE3\u3001\u9694\u79BB worktree \u4E2D\u7684\u5019\u9009\u7248\u672C\u89C6\u4E3A N+1 \u4EE3\uFF1B\u7981\u6B62\u8BBE\u8BA1\u8FDB\u7A0B\u5185\u70ED\u66FF\u6362\u3002`,
|
|
1887
1898
|
plannerClarifySystem: `\u4F60\u662F XCompiler V \u6A21\u578B\u7684\u9700\u6C42\u5206\u6790\u5E08\u3002\u4F60\u7684\u804C\u8D23\u4E0D\u662F\u590D\u8FF0 topic\uFF0C\u800C\u662F\u53D1\u73B0\u4F1A\u6539\u53D8\u529F\u80FD\u8BBE\u8BA1\u3001\u9A8C\u6536\u7ED3\u679C\u6216\u67B6\u6784\u8FB9\u754C\u7684\u672A\u51B3\u4E8B\u9879\u3002
|
|
1888
|
-
\u53EA\u8FD4\u56DE\u4E25\u683C JSON\u3002\u95EE\u9898\u5FC5\u987B\u53EF\u7531\u4E1A\u52A1\u65B9\u76F4\u63A5\u4F5C\u7B54\u3001\u4E00\u6B21\u53EA\u786E\u8BA4\u4E00\u4E2A\u51B3\u7B56\uFF0C\u907F\u514D\u7A7A\u6CDB\u7684\u201C\u8FD8\u6709\u4EC0\u4E48\u8981\u6C42\u201D\u6216\u6280\u672F\u6808\u9009\u578B\u95EE\u9898\u3002`,
|
|
1899
|
+
\u53EA\u8FD4\u56DE\u4E25\u683C JSON\u3002\u95EE\u9898\u5FC5\u987B\u53EF\u7531\u4E1A\u52A1\u65B9\u76F4\u63A5\u4F5C\u7B54\u3001\u4E00\u6B21\u53EA\u786E\u8BA4\u4E00\u4E2A\u51B3\u7B56\uFF0C\u907F\u514D\u7A7A\u6CDB\u7684\u201C\u8FD8\u6709\u4EC0\u4E48\u8981\u6C42\u201D\u6216\u6280\u672F\u6808\u9009\u578B\u95EE\u9898\uFF1B\u53EA\u6709\u7528\u6237\u63D0\u793A\u660E\u786E\u8981\u6C42\u65F6\uFF0C\u624D\u5141\u8BB8\u63D0\u51FA\u552F\u4E00\u4E00\u6761\u5F00\u53D1\u8BED\u8A00\u786E\u8BA4\u95EE\u9898\u3002`,
|
|
1889
1900
|
plannerClarify: (raw, opts = {}) => `\u7528\u6237\u7684\u539F\u59CB\u9700\u6C42\u5982\u4E0B\uFF1A
|
|
1890
1901
|
|
|
1891
1902
|
"""
|
|
@@ -1910,12 +1921,13 @@ ${raw}
|
|
|
1910
1921
|
- \u9009\u9879\u4ECE A \u5F00\u59CB\u8FDE\u7EED\u6807\u53F7\uFF0C\u5230\u5B9E\u9645\u6700\u540E\u4E00\u4E2A\u9009\u9879\u7ED3\u675F\uFF0C\u4F8B\u5982 A-B\u3001A-C\u3001A-D \u6216 A-E\uFF1B\u5982\u679C\u80FD\u5224\u65AD\u63A8\u8350/\u9ED8\u8BA4\u65B9\u6848\uFF0CA \u5E94\u662F\u6700\u9AD8\u4F18\u5148\u7EA7\u65B9\u6848\u3002\u9009\u9879\u5FC5\u987B\u662F\u5177\u4F53\u4E1A\u52A1/\u4EA7\u54C1\u8BBE\u5B9A\uFF0C\u4E0D\u8981\u5199\u6210\u7A7A\u6CDB\u5360\u4F4D\u3002
|
|
1911
1922
|
- \u4E0D\u8981\u628A\u201C\u5176\u4ED6 / \u81EA\u5B9A\u4E49 / \u7528\u6237\u51B3\u5B9A\u201D\u4F5C\u4E3A\u9009\u9879\uFF1BCLI \u5DF2\u652F\u6301\u7528\u6237\u8F93\u5165\u5DF2\u5C55\u793A\u7684\u9009\u9879\u5B57\u6BCD\u6216\u76F4\u63A5\u8F93\u5165\u81EA\u5B9A\u4E49\u56DE\u7B54\u5185\u5BB9\u3002
|
|
1912
1923
|
${opts.projectShapeAmbiguous ? "- \u672C topic \u5FC5\u95EE\uFF1A\u660E\u786E\u786E\u8BA4 API library / \u53EF\u8FD0\u884C\u5E94\u7528 / mixed \u4EA4\u4ED8\u8FB9\u754C\u3002\n" : ""}
|
|
1924
|
+
${opts.languageAmbiguous ? "- \u672C topic \u5FC5\u95EE\uFF1A\u5FC5\u987B\u5305\u542B\u4E14\u53EA\u5305\u542B 1 \u4E2A boundary \u95EE\u9898\u786E\u8BA4\u76EE\u6807\u5F00\u53D1\u8BED\u8A00\u3002\u9009\u9879\u5FC5\u987B\u662F A. Python\uFF08\u9ED8\u8BA4/\u63A8\u8350\uFF09 \u548C B. TypeScript / Node.js\uFF1B\u7528\u6237\u4ECD\u53EF\u8F93\u5165\u81EA\u5B9A\u4E49\u56DE\u7B54\u3002\n" : ""}
|
|
1913
1925
|
|
|
1914
|
-
\u3010\u786C\u7EA6\u675F\u3011\u5B9E\u73B0\u6280\u672F\u6808\u5DF2\u7ECF\u7531
|
|
1926
|
+
${opts.languageAmbiguous ? `\u3010\u6280\u672F\u6808\u786E\u8BA4\u3011XCompiler \u65E0\u6CD5\u4ECE topic \u6216\u57FA\u7EBF\u4E2D\u5224\u65AD\u76EE\u6807\u5F00\u53D1\u8BED\u8A00\u3002\u53EA\u8BE2\u95EE\u4E0A\u9762\u8981\u6C42\u7684\u5F00\u53D1\u8BED\u8A00\u95EE\u9898\uFF1B\u4E0D\u8981\u8BE2\u95EE\u5305\u7BA1\u7406\u5668\u3001\u6D4B\u8BD5\u6846\u67B6\u6216\u64CD\u4F5C\u7CFB\u7EDF\u3002\u82E5\u7528\u6237\u4E0D\u9009\u62E9\uFF0C\u9ED8\u8BA4 Python\u3002` : `\u3010\u786C\u7EA6\u675F\u3011\u5B9E\u73B0\u6280\u672F\u6808\u5DF2\u7ECF\u7531\u7528\u6237 topic / \u73B0\u6709\u5DE5\u7A0B\u57FA\u7EBF\u56FA\u5B9A\uFF0C\u4E0D\u8981\u91CD\u65B0\u8BE2\u95EE\u8BED\u8A00\u3001\u8FD0\u884C\u65F6\u3001\u5305\u7BA1\u7406\u5668\u8FD9\u7C7B\u95EE\u9898\u3002
|
|
1915
1927
|
**\u4E25\u7981**\u63D0\u51FA\u4EE5\u4E0B\u7C7B\u578B\u7684\u95EE\u9898\uFF1A
|
|
1916
1928
|
- "\u5E0C\u671B\u7528\u4EC0\u4E48\u7F16\u7A0B\u8BED\u8A00 / \u6846\u67B6 / \u8FD0\u884C\u65F6\u5B9E\u73B0\uFF1F"
|
|
1917
1929
|
- "\u9700\u8981\u54EA\u79CD\u6D4B\u8BD5\u6846\u67B6 / \u6784\u5EFA\u5DE5\u5177 / \u5305\u7BA1\u7406\u5668\uFF1F"
|
|
1918
|
-
- "\u76EE\u6807\u5E73\u53F0\u662F\u54EA\u79CD\u64CD\u4F5C\u7CFB\u7EDF\uFF1F"
|
|
1930
|
+
- "\u76EE\u6807\u5E73\u53F0\u662F\u54EA\u79CD\u64CD\u4F5C\u7CFB\u7EDF\uFF1F"`}
|
|
1919
1931
|
${opts.intent && opts.intent !== "greenfield" ? `\u8FD9\u662F\u4E00\u6761\u9488\u5BF9\u73B0\u6709\u5DE5\u7A0B\u7684\u589E\u91CF ${opts.intent} \u8BF7\u6C42${opts.hasBaseline ? "\uFF1B\u5206\u89E3\u9636\u6BB5\u8FD8\u4F1A\u63D0\u4F9B\u4E00\u4EFD\u57FA\u7EBF\u6458\u8981" : ""}\u3002\u8BF7\u53EA\u95EE\u201C\u53D8\u66F4\u589E\u91CF\u201D\u76F8\u5173\u95EE\u9898\uFF0C\u4E0D\u8981\u628A\u9879\u76EE\u5F53\u6210\u4ECE\u96F6\u5F00\u59CB\u91CD\u505A\u3002` : ""}\u95EE\u9898\u4E3B\u4F53\u5FC5\u987B\u805A\u7126\u529F\u80FD\u884C\u4E3A\uFF1B\u6027\u80FD\u3001\u8FB9\u754C\u548C\u6269\u5C55\u6027\u7528\u4E8E\u6D88\u9664\u4F1A\u5F71\u54CD\u672C\u671F\u8BBE\u8BA1\u7684\u5173\u952E\u6B67\u4E49\u3002`,
|
|
1920
1932
|
plannerDecompose: (raw, qa, addenda, opts = {}) => `\u539F\u59CB\u9700\u6C42\uFF1A
|
|
1921
1933
|
"""
|
|
@@ -2017,7 +2029,7 @@ ${opts.phasePlan}
|
|
|
2017
2029
|
executorDebugBlock: (reason, suggestions) => `
|
|
2018
2030
|
|
|
2019
2031
|
\u6B63\u5904\u4E8E DEBUG \u91CD\u8BD5\u6A21\u5F0F\u3002\u4E0A\u4E00\u8F6E\u5931\u8D25\u539F\u56E0: ${reason}
|
|
2020
|
-
DEBUG \u53EF\u4EE5\u4FEE\u6539\u5F53\u524D allowedWrites \u5185\u7684\u4E0A\u6E38\u6E90\u7801\u4E0E\u6D4B\u8BD5\u6587\u4EF6\uFF1B\u82E5\u5931\u8D25\u66B4\u9732\u7684\u662F\u5B9E\u73B0\u3001\u5951\u7EA6\u6216\u4E0B\u6E38\u8C03\u7528\u4E0D\u4E00\u81F4\uFF0C\u5FC5\u987B\u4FEE\u771F\u5B9E\u7F3A\u9677\uFF0C\u7981\u6B62\u901A\u8FC7\u524A\u5F31\u65AD\u8A00\u3001\u8DF3\u8FC7\u6D4B\u8BD5\u3001\u5220\u9664\u5931\u8D25\u7528\u4F8B\u6216\u53EA\u8FCE\u5408\u9519\u8BEF\u6D4B\u8BD5\u6765\u8FC7\u5173\u3002\u5982\u679C\u672C\u6B21\u56DE\u9000\u5904\u4E8E\u9700\u6C42/\u8BBE\u8BA1\u9636\u6BB5\uFF0C\u800C\u5177\u4F53\u6E90\u7801\u4FEE\u6539\u5C5E\u4E8E\u540E\u7EED V \u6A21\u578B Step \u4E14\u76EE\u6807\u6587\u4EF6\u4E0D\u5728\u5F53\u524D allowedWrites \u5185\uFF0C\u5E94\u66F4\u65B0\u5F53\u524D\u5951\u7EA6\u3001\u6D4B\u8BD5\u8BA1\u5212\u6216\u8BCA\u65AD\u4EA7\u7269\u5E76\u5B8C\u6210\u5F53\u524D Step\uFF0C\u8BA9\u540E\u7EED CODE Step \u5B9E\u73B0\uFF1B\u4E0D\u8981\u5C1D\u8BD5\u88AB\u62D2\u7EDD\u7684\u8D8A\u6743\u5199\u5165\u3002\u82E5\u5931\u8D25\u662F\u7B2C\u4E09\u65B9\u4F9D\u8D56\u7F3A\u5931\u6216\u5E93\u9009\u578B\u9519\u8BEF\uFF0C\u5FC5\u987B\u7528 add_dependency \u5199\u5165\u771F\u5B9E\u5305\u540D\uFF0C\u6216\u628A\u6E90\u7801\u6539\u56DE HIGH_LEVEL_DESIGN \u9009\u5B9A\u7684\u771F\u5B9E\u5E93\uFF1B\u4E25\u7981\u5728 src/ \u751F\u4EA7\u4EE3\u7801\u91CC try/except ImportError \u540E\u4F2A\u9020 module\u3001fake class/function\u3001\u7A7A\u5B9E\u73B0\u6216 fallback mock \u6765\u7ED5\u8FC7\u9519\u8BEF\u3002\u8BF7\u5305\u542B read_file/code_search \u5148\u5B9A\u4F4D\u95EE\u9898\uFF0C\u518D\u4EE5 apply_patch / replace_in_file / add_dependency \u4F5C\u6700\u5C0F\u4FEE\u6539\uFF0C\u6700\u540E run_tests \u9A8C\u8BC1\u3002DEBUG \u91CD\u8BD5\u4E0D\u80FD\u53EA\u9760\u53EA\u8BFB\u68C0\u67E5\u5C31\u6807\u8BB0\u5B8C\u6210\uFF1A\u672C\u6B21\u91CD\u8BD5\u5FC5\u987B\u4EA7\u751F\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\uFF0C\u6216\u4E00\u6B21\u6210\u529F\u7684\u9A8C\u8BC1\u547D\u4EE4\u3002\u5982\u679C\u4E0A\u4E00\u8F6E\u5931\u8D25\u539F\u56E0\u5305\u542B repeated read-only/probe actions\uFF0C\u8BF7\u628A\u5DF2\u6709 failure log \u89C6\u4E3A\u8DB3\u591F\u4E0A\u4E0B\u6587\uFF0C\u4E0B\u4E00\u6B65\u76F4\u63A5 patch/write/\u6539\u4F9D\u8D56\u6216\u6267\u884C\u9A8C\u8BC1\u547D\u4EE4\u3002\u5F53\u6D4B\u8BD5\u5DF2\u6B63\u5E38\u6267\u884C\u4F46\u5931\u8D25\u70B9\u662F\u8FD4\u56DE\u884C\u4E3A\u65AD\u8A00\u65F6\uFF0C\u7981\u6B62\u53CD\u590D\u91CD\u5199 fixture \u6216\u6837\u4F8B\u3002\u53EA\u6709\u8BC1\u636E\u660E\u786E\u662F\u6587\u4EF6\u7F3A\u5931\u3001fixture \u683C\u5F0F\u9519\u8BEF\u6216 fixture \u672C\u8EAB\u89E3\u6790\u5931\u8D25\u65F6\u624D\u4FEE\u6539 fixture\uFF1B\u5426\u5219\u5E94\u4FEE\u5B9E\u73B0\u3001\u63A5\u53E3\u5951\u7EA6\u3001\u4F9D\u8D56\u9009\u578B\u6216\u786E\u5B9E\u9519\u8BEF\u7684\u65AD\u8A00\u3002\u5982\u679C\u5931\u8D25\u65E5\u5FD7\u663E\u793A\u7F51\u7EDC/API \u8C03\u7528\u5931\u8D25\uFF0C\u4E0D\u5141\u8BB8\u53EA\u505C\u7559\u5728\u63A2\u6D4B\u63A5\u53E3\uFF1A\u6700\u591A\u8FDE\u7EED\u6267\u884C 2 \u6B21 http_fetch \u63A2\u6D4B\uFF1BHTTP 2xx \u4F46 body \u4E3A\u7A7A\u6216\u683C\u5F0F\u4E0D\u53EF\u7528\u4E0D\u7B97\u53EF\u7528\u63A5\u53E3\uFF1B\u968F\u540E\u5FC5\u987B patch \u771F\u5B9E\u96C6\u6210\u4EE3\u7801\uFF0C\u5E76\u7528 run_program \u548C run_tests \u9A8C\u8BC1\u3002\u5165\u53E3\u4ECD\u8F93\u51FA\u7F51\u7EDC/API \u5931\u8D25\u65F6\u4E0D\u5F97 done=true\u3002` + (suggestions ? `
|
|
2032
|
+
\u5F53\u672C\u6B21 DEBUG \u6B63\u5728\u5904\u7406 issue \u65F6\uFF0C\u6BCF\u4E00\u8F6E JSON \u90FD\u5FC5\u987B\u5728\u4FEE\u590D\u524D\u6216\u4FEE\u590D\u8FC7\u7A0B\u4E2D\u989D\u5916\u5305\u542B issueResolutionPlan\u3002\u8BE5\u65B9\u6848\u5FC5\u987B\u7B80\u6D01\u4E14\u53EF\u6267\u884C\uFF1A\u6839\u56E0\u5047\u8BBE\u3001\u8981\u4FEE\u6539\u7684\u6587\u4EF6/\u5951\u7EA6\u3001\u9A8C\u8BC1\u547D\u4EE4\u6216\u95E8\u7981\uFF0C\u4EE5\u53CA\u4EC0\u4E48\u8BC1\u636E\u4F1A\u63A8\u7FFB\u8BE5\u65B9\u6848\u3002DEBUG \u53EF\u4EE5\u4FEE\u6539\u5F53\u524D allowedWrites \u5185\u7684\u4E0A\u6E38\u6E90\u7801\u4E0E\u6D4B\u8BD5\u6587\u4EF6\uFF1B\u82E5\u5931\u8D25\u66B4\u9732\u7684\u662F\u5B9E\u73B0\u3001\u5951\u7EA6\u6216\u4E0B\u6E38\u8C03\u7528\u4E0D\u4E00\u81F4\uFF0C\u5FC5\u987B\u4FEE\u771F\u5B9E\u7F3A\u9677\uFF0C\u7981\u6B62\u901A\u8FC7\u524A\u5F31\u65AD\u8A00\u3001\u8DF3\u8FC7\u6D4B\u8BD5\u3001\u5220\u9664\u5931\u8D25\u7528\u4F8B\u6216\u53EA\u8FCE\u5408\u9519\u8BEF\u6D4B\u8BD5\u6765\u8FC7\u5173\u3002\u5982\u679C\u672C\u6B21\u56DE\u9000\u5904\u4E8E\u9700\u6C42/\u8BBE\u8BA1\u9636\u6BB5\uFF0C\u800C\u5177\u4F53\u6E90\u7801\u4FEE\u6539\u5C5E\u4E8E\u540E\u7EED V \u6A21\u578B Step \u4E14\u76EE\u6807\u6587\u4EF6\u4E0D\u5728\u5F53\u524D allowedWrites \u5185\uFF0C\u5E94\u66F4\u65B0\u5F53\u524D\u5951\u7EA6\u3001\u6D4B\u8BD5\u8BA1\u5212\u6216\u8BCA\u65AD\u4EA7\u7269\u5E76\u5B8C\u6210\u5F53\u524D Step\uFF0C\u8BA9\u540E\u7EED CODE Step \u5B9E\u73B0\uFF1B\u4E0D\u8981\u5C1D\u8BD5\u88AB\u62D2\u7EDD\u7684\u8D8A\u6743\u5199\u5165\u3002\u82E5\u5931\u8D25\u662F\u7B2C\u4E09\u65B9\u4F9D\u8D56\u7F3A\u5931\u6216\u5E93\u9009\u578B\u9519\u8BEF\uFF0C\u5FC5\u987B\u7528 add_dependency \u5199\u5165\u771F\u5B9E\u5305\u540D\uFF0C\u6216\u628A\u6E90\u7801\u6539\u56DE HIGH_LEVEL_DESIGN \u9009\u5B9A\u7684\u771F\u5B9E\u5E93\uFF1B\u4E25\u7981\u5728 src/ \u751F\u4EA7\u4EE3\u7801\u91CC try/except ImportError \u540E\u4F2A\u9020 module\u3001fake class/function\u3001\u7A7A\u5B9E\u73B0\u6216 fallback mock \u6765\u7ED5\u8FC7\u9519\u8BEF\u3002\u8BF7\u5305\u542B read_file/code_search \u5148\u5B9A\u4F4D\u95EE\u9898\uFF0C\u518D\u4EE5 apply_patch / replace_in_file / add_dependency \u4F5C\u6700\u5C0F\u4FEE\u6539\uFF0C\u6700\u540E run_tests \u9A8C\u8BC1\u3002DEBUG \u91CD\u8BD5\u4E0D\u80FD\u53EA\u9760\u53EA\u8BFB\u68C0\u67E5\u5C31\u6807\u8BB0\u5B8C\u6210\uFF1A\u672C\u6B21\u91CD\u8BD5\u5FC5\u987B\u4EA7\u751F\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\uFF0C\u6216\u4E00\u6B21\u6210\u529F\u7684\u9A8C\u8BC1\u547D\u4EE4\u3002\u5982\u679C\u4E0A\u4E00\u8F6E\u5931\u8D25\u539F\u56E0\u5305\u542B repeated read-only/probe actions\uFF0C\u8BF7\u628A\u5DF2\u6709 failure log \u89C6\u4E3A\u8DB3\u591F\u4E0A\u4E0B\u6587\uFF0C\u4E0B\u4E00\u6B65\u76F4\u63A5 patch/write/\u6539\u4F9D\u8D56\u6216\u6267\u884C\u9A8C\u8BC1\u547D\u4EE4\u3002\u5F53\u6D4B\u8BD5\u5DF2\u6B63\u5E38\u6267\u884C\u4F46\u5931\u8D25\u70B9\u662F\u8FD4\u56DE\u884C\u4E3A\u65AD\u8A00\u65F6\uFF0C\u7981\u6B62\u53CD\u590D\u91CD\u5199 fixture \u6216\u6837\u4F8B\u3002\u53EA\u6709\u8BC1\u636E\u660E\u786E\u662F\u6587\u4EF6\u7F3A\u5931\u3001fixture \u683C\u5F0F\u9519\u8BEF\u6216 fixture \u672C\u8EAB\u89E3\u6790\u5931\u8D25\u65F6\u624D\u4FEE\u6539 fixture\uFF1B\u5426\u5219\u5E94\u4FEE\u5B9E\u73B0\u3001\u63A5\u53E3\u5951\u7EA6\u3001\u4F9D\u8D56\u9009\u578B\u6216\u786E\u5B9E\u9519\u8BEF\u7684\u65AD\u8A00\u3002\u5982\u679C\u5931\u8D25\u65E5\u5FD7\u663E\u793A\u7F51\u7EDC/API \u8C03\u7528\u5931\u8D25\uFF0C\u4E0D\u5141\u8BB8\u53EA\u505C\u7559\u5728\u63A2\u6D4B\u63A5\u53E3\uFF1A\u6700\u591A\u8FDE\u7EED\u6267\u884C 2 \u6B21 http_fetch \u63A2\u6D4B\uFF1BHTTP 2xx \u4F46 body \u4E3A\u7A7A\u6216\u683C\u5F0F\u4E0D\u53EF\u7528\u4E0D\u7B97\u53EF\u7528\u63A5\u53E3\uFF1B\u968F\u540E\u5FC5\u987B patch \u771F\u5B9E\u96C6\u6210\u4EE3\u7801\uFF0C\u5E76\u7528 run_program \u548C run_tests \u9A8C\u8BC1\u3002\u5165\u53E3\u4ECD\u8F93\u51FA\u7F51\u7EDC/API \u5931\u8D25\u65F6\u4E0D\u5F97 done=true\u3002` + (suggestions ? `
|
|
2021
2033
|
|
|
2022
2034
|
${suggestions}` : ""),
|
|
2023
2035
|
executorGlobalBlock: (globalPrompt) => `
|
|
@@ -2034,7 +2046,8 @@ ${sp}`,
|
|
|
2034
2046
|
executorFeedbackVerifyMissing: (paths) => `outputs \u4ECD\u7F3A\u5931\uFF1A${paths}\u3002\u8BF7\u7EE7\u7EED\u3002`,
|
|
2035
2047
|
executorFeedbackReadOnlyLoopWarning: (rounds, targets) => `\u5FAA\u73AF\u95E8\u7981\u8B66\u544A\uFF1A\u6700\u8FD1 ${rounds} \u8F6E\u53EA\u8C03\u7528\u4E86\u8BFB\u53D6/\u63A2\u6D4B\u5DE5\u5177` + (targets ? `\uFF08${targets}\uFF09` : "") + "\u3002\u4E0B\u4E00\u8F6E\u5FC5\u987B\u5305\u542B\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\uFF08apply_patch / replace_in_file / write_file / add_dependency\uFF09\u6216\u660E\u786E\u7684\u9A8C\u8BC1\u52A8\u4F5C\uFF08run_tests / run_program\uFF09\u3002\u4E0D\u8981\u7EE7\u7EED\u53EA\u8C03\u7528 read_file\u3001list_dir\u3001code_search \u6216 http_fetch\u3002",
|
|
2036
2048
|
executorFeedbackReadOnlyRecoveryRequired: "\u53EA\u8BFB\u6062\u590D\u6A21\u5F0F\u5DF2\u542F\u7528\uFF1A\u4E0A\u4E00\u8F6E\u5DF2\u7ECF\u56E0\u4E3A\u6301\u7EED\u63A2\u6D4B\u5931\u8D25\u3002\u672C\u8F6E\u5FC5\u987B\u57FA\u4E8E\u5DF2\u6709 failure log \u76F4\u63A5 patch/write/\u6539\u4F9D\u8D56\u6216\u6267\u884C\u9A8C\u8BC1\uFF1B\u5982\u679C\u4E0B\u4E00\u8F6E\u4ECD\u7136\u53EA\u6709\u53EA\u8BFB/\u63A2\u6D4B\u52A8\u4F5C\uFF0C\u672C\u6B21\u91CD\u8BD5\u4F1A\u5931\u8D25\u5E76\u56DE\u9000\u3002",
|
|
2037
|
-
executorFeedbackRepairEvidenceMissing: "DEBUG \u5B8C\u6210\u65E0\u6548\uFF1A\u672C\u6B21\u91CD\u8BD5\u8FD8\u6CA1\u6709\u4EA7\u751F\u4FEE\u590D\u8BC1\u636E\u3002\u8BBE\u7F6E done=true \u524D\uFF0C\u5FC5\u987B\u81F3\u5C11\u5B8C\u6210\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\u6216\u6210\u529F\u7684\u9A8C\u8BC1\u8FD0\u884C\uFF1B\u5426\u5219\u53EA\u80FD\u5728 thoughts \u4E2D\u7ED9\u51FA\u5177\u4F53 blocker \u540E\u505C\u6B62\u3002"
|
|
2049
|
+
executorFeedbackRepairEvidenceMissing: "DEBUG \u5B8C\u6210\u65E0\u6548\uFF1A\u672C\u6B21\u91CD\u8BD5\u8FD8\u6CA1\u6709\u4EA7\u751F\u4FEE\u590D\u8BC1\u636E\u3002\u8BBE\u7F6E done=true \u524D\uFF0C\u5FC5\u987B\u81F3\u5C11\u5B8C\u6210\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\u6216\u6210\u529F\u7684\u9A8C\u8BC1\u8FD0\u884C\uFF1B\u5426\u5219\u53EA\u80FD\u5728 thoughts \u4E2D\u7ED9\u51FA\u5177\u4F53 blocker \u540E\u505C\u6B62\u3002",
|
|
2050
|
+
executorFeedbackIssueResolutionPlanMissing: "DEBUG issue \u5B8C\u6210\u65E0\u6548\uFF1AissueResolutionPlan \u662F issue resolved \u7684\u5FC5\u8981\u5B57\u6BB5\u3002\u8BF7\u8FD4\u56DE\u5305\u542B\u7B80\u660E\u5904\u7406\u65B9\u6848\u7684 JSON\uFF0C\u5E76\u540C\u65F6\u7ED9\u51FA\u5FC5\u8981\u4FEE\u590D\u6216\u9A8C\u8BC1\u52A8\u4F5C\u3002"
|
|
2038
2051
|
},
|
|
2039
2052
|
skills: {
|
|
2040
2053
|
patcher: "\u901A\u8FC7 apply_patch / replace_in_file \u5BF9\u5DF2\u6709\u6587\u4EF6\u505A\u5C0F\u6539\u52A8\uFF0C\u7981\u6B62\u6574\u6587\u4EF6\u8986\u76D6\u3002",
|
|
@@ -2055,7 +2068,7 @@ ${sp}`,
|
|
|
2055
2068
|
summaryOk: "\u5168\u90E8\u68C0\u67E5\u901A\u8FC7\u3002",
|
|
2056
2069
|
summaryWarn: (n) => `\u901A\u8FC7\uFF0C\u4F46\u6709 ${n} \u6761 warning\u3002`,
|
|
2057
2070
|
summaryFail: (n) => `\u68C0\u6D4B\u5230 ${n} \u9879\u5931\u8D25\u3002`,
|
|
2058
|
-
configLoadOk: (
|
|
2071
|
+
configLoadOk: (path32) => `\u914D\u7F6E\u5DF2\u52A0\u8F7D\uFF1A${path32}`,
|
|
2059
2072
|
configLoadFail: (msg) => `\u914D\u7F6E\u52A0\u8F7D\u5931\u8D25\uFF1A${msg}`,
|
|
2060
2073
|
configLocale: (locale) => `locale=${locale}`,
|
|
2061
2074
|
llmNoProviders: "config.llm.providers \u4E3A\u7A7A\uFF0C\u672A\u58F0\u660E\u4EFB\u4F55 provider",
|
|
@@ -2073,7 +2086,7 @@ ${sp}`,
|
|
|
2073
2086
|
roleOk: (role, provider) => `\u89D2\u8272 "${role}" \u2192 ${provider}`,
|
|
2074
2087
|
sandboxKind: (kind) => `sandbox=${kind}`,
|
|
2075
2088
|
sandboxNetworkPolicy: (policy, ports) => `network=${policy}` + (ports.length ? `\uFF08expose_ports=[${ports.join(", ")}]\uFF09` : ""),
|
|
2076
|
-
sandboxFullNoPorts: "network=full \u4F46\u672A\u914D\u7F6E expose_ports\u2014\u5BBF\u4E3B\u4FA7\u65E0\u6CD5\u8BBF\u95EE\u5BB9\u5668\u5185\u670D\u52A1\u3002\u8BF7\u5728 config.yaml \u4E2D\u8BBE\u7F6E `agent.
|
|
2089
|
+
sandboxFullNoPorts: "network=full \u4F46\u672A\u914D\u7F6E expose_ports\u2014\u5BBF\u4E3B\u4FA7\u65E0\u6CD5\u8BBF\u95EE\u5BB9\u5668\u5185\u670D\u52A1\u3002\u8BF7\u5728 config.yaml \u4E2D\u8BBE\u7F6E `agent.sandboxes.<language>.<local|docker>.limits.expose_ports: [<port>]`\u3002",
|
|
2077
2090
|
sandboxNodeMissing: "PATH \u4E0A\u627E\u4E0D\u5230 node\uFF08TypeScript subprocess \u6C99\u76D2\u5FC5\u9700\uFF09",
|
|
2078
2091
|
sandboxNodeOk: (version) => `node OK\uFF08${version}\uFF09`,
|
|
2079
2092
|
sandboxNpmMissing: "PATH \u4E0A\u627E\u4E0D\u5230 npm\uFF08TypeScript subprocess \u6C99\u76D2\u5FC5\u9700\uFF09",
|
|
@@ -2135,8 +2148,10 @@ function detectNetworkApiFailure(text) {
|
|
|
2135
2148
|
function isTestAssertionDiagnosticLine(line) {
|
|
2136
2149
|
const text = line.trim();
|
|
2137
2150
|
if (!text) return false;
|
|
2151
|
+
if (/^\d+\|\s/u.test(text)) return true;
|
|
2138
2152
|
if (/^(?:[→>-]\s*)?expected\b/iu.test(text)) return true;
|
|
2139
2153
|
if (/\bAssertionError\b/iu.test(text) && /\bexpected\b/iu.test(text)) return true;
|
|
2154
|
+
if (/\bexpect\s*\(/u.test(text) && /\.(?:to|not)\w*\s*\(/u.test(text)) return true;
|
|
2140
2155
|
return /\bexpected\b[\s\S]{0,240}\b(?:got|received|to\s+(?:be|equal|throw|contain|have|match))\b/iu.test(text);
|
|
2141
2156
|
}
|
|
2142
2157
|
function isTestRunnerStatusLine(line) {
|
|
@@ -2183,8 +2198,8 @@ async function autoFixSrcImports(ws, audit) {
|
|
|
2183
2198
|
const candidates = [];
|
|
2184
2199
|
if (await ws.exists("src/main.py")) candidates.push("src/main.py");
|
|
2185
2200
|
try {
|
|
2186
|
-
const
|
|
2187
|
-
const entries = await
|
|
2201
|
+
const fs29 = await import("fs/promises");
|
|
2202
|
+
const entries = await fs29.readdir(ws.abs("src"), { withFileTypes: true });
|
|
2188
2203
|
for (const e of entries) {
|
|
2189
2204
|
if (!e.isDirectory()) continue;
|
|
2190
2205
|
const rel = `src/${e.name}/__main__.py`;
|
|
@@ -2335,8 +2350,8 @@ async function detectPythonEntrypoint(ws) {
|
|
|
2335
2350
|
if (await ws.exists("src/main.py")) return fileCandidate("src/main.py");
|
|
2336
2351
|
if (await ws.exists("src/__main__.py")) return fileCandidate("src/__main__.py");
|
|
2337
2352
|
try {
|
|
2338
|
-
const
|
|
2339
|
-
const entries = await
|
|
2353
|
+
const fs29 = await import("fs/promises");
|
|
2354
|
+
const entries = await fs29.readdir(ws.abs("src"), { withFileTypes: true });
|
|
2340
2355
|
for (const e of entries) {
|
|
2341
2356
|
if (!e.isDirectory()) continue;
|
|
2342
2357
|
const rel = `src/${e.name}/__main__.py`;
|
|
@@ -2395,7 +2410,7 @@ var typescriptProfile = {
|
|
|
2395
2410
|
manifestFile: "package.json",
|
|
2396
2411
|
codeExtensions: [".ts", ".tsx"],
|
|
2397
2412
|
seedManifestFromDeps: false,
|
|
2398
|
-
defaultDockerImage: "node:
|
|
2413
|
+
defaultDockerImage: "node:24-slim",
|
|
2399
2414
|
renderManifest(deps) {
|
|
2400
2415
|
const pkg = {
|
|
2401
2416
|
name: "app",
|
|
@@ -2424,6 +2439,9 @@ var typescriptProfile = {
|
|
|
2424
2439
|
},
|
|
2425
2440
|
plannerPromptOverride: "",
|
|
2426
2441
|
executorPromptOverride: "",
|
|
2442
|
+
async autoFixImports(ws, audit) {
|
|
2443
|
+
return autoFixTypeScriptTypeOnlyImports(ws, audit);
|
|
2444
|
+
},
|
|
2427
2445
|
async probeEntry(ws, sandbox) {
|
|
2428
2446
|
return probeTsEntrypoint(ws, sandbox);
|
|
2429
2447
|
}
|
|
@@ -2573,6 +2591,99 @@ async function readJsonFile(ws, rel) {
|
|
|
2573
2591
|
return null;
|
|
2574
2592
|
}
|
|
2575
2593
|
}
|
|
2594
|
+
var TYPE_ONLY_IMPORTS_BY_PACKAGE = {
|
|
2595
|
+
axios: /* @__PURE__ */ new Set([
|
|
2596
|
+
"AxiosAdapter",
|
|
2597
|
+
"AxiosBasicCredentials",
|
|
2598
|
+
"AxiosHeaderValue",
|
|
2599
|
+
"AxiosInstance",
|
|
2600
|
+
"AxiosInterceptorManager",
|
|
2601
|
+
"AxiosPromise",
|
|
2602
|
+
"AxiosProxyConfig",
|
|
2603
|
+
"AxiosRequestConfig",
|
|
2604
|
+
"AxiosRequestHeaders",
|
|
2605
|
+
"AxiosResponse",
|
|
2606
|
+
"AxiosResponseHeaders",
|
|
2607
|
+
"CreateAxiosDefaults",
|
|
2608
|
+
"InternalAxiosRequestConfig",
|
|
2609
|
+
"RawAxiosRequestHeaders"
|
|
2610
|
+
])
|
|
2611
|
+
};
|
|
2612
|
+
async function autoFixTypeScriptTypeOnlyImports(ws, audit) {
|
|
2613
|
+
const files = await listTypeScriptSourceFiles(ws, "src");
|
|
2614
|
+
const fixed = [];
|
|
2615
|
+
for (const rel of files) {
|
|
2616
|
+
const original = await ws.readFile(rel);
|
|
2617
|
+
const next = rewriteKnownTypeOnlyImports(original);
|
|
2618
|
+
if (next === original) continue;
|
|
2619
|
+
await ws.writeFile(rel, next);
|
|
2620
|
+
fixed.push(rel);
|
|
2621
|
+
await audit.event("note", `fixed type-only imports in ${rel}`, {
|
|
2622
|
+
messageId: "audit.typescript_imports_autofix",
|
|
2623
|
+
path: rel
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
return fixed;
|
|
2627
|
+
}
|
|
2628
|
+
async function listTypeScriptSourceFiles(ws, dir) {
|
|
2629
|
+
const abs = ws.abs(dir);
|
|
2630
|
+
let entries;
|
|
2631
|
+
try {
|
|
2632
|
+
entries = await fs.readdir(abs, { withFileTypes: true });
|
|
2633
|
+
} catch {
|
|
2634
|
+
return [];
|
|
2635
|
+
}
|
|
2636
|
+
const files = [];
|
|
2637
|
+
for (const entry of entries) {
|
|
2638
|
+
const rel = `${dir}/${entry.name}`.replace(/\\/g, "/");
|
|
2639
|
+
if (entry.isDirectory()) {
|
|
2640
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
2641
|
+
files.push(...await listTypeScriptSourceFiles(ws, rel));
|
|
2642
|
+
} else if (entry.isFile() && /\.tsx?$/.test(entry.name)) {
|
|
2643
|
+
files.push(rel);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
return files.sort();
|
|
2647
|
+
}
|
|
2648
|
+
function rewriteKnownTypeOnlyImports(source) {
|
|
2649
|
+
const importRe = /^import\s+([^'"\n]+?)\s+from\s+(['"])([^'"]+)\2\s*;?$/gm;
|
|
2650
|
+
return source.replace(importRe, (full, clauseRaw, quote, specifier) => {
|
|
2651
|
+
const knownTypes = TYPE_ONLY_IMPORTS_BY_PACKAGE[specifier];
|
|
2652
|
+
if (!knownTypes) return full;
|
|
2653
|
+
if (clauseRaw.trim().startsWith("type ")) return full;
|
|
2654
|
+
const parsed = splitImportClause(clauseRaw);
|
|
2655
|
+
if (!parsed?.named.length) return full;
|
|
2656
|
+
const valueNamed = [];
|
|
2657
|
+
const typeNamed = [];
|
|
2658
|
+
for (const item of parsed.named) {
|
|
2659
|
+
const importedName = item.replace(/^type\s+/u, "").split(/\s+as\s+/iu)[0]?.trim() ?? "";
|
|
2660
|
+
if (knownTypes.has(importedName)) typeNamed.push(item.replace(/^type\s+/u, "").trim());
|
|
2661
|
+
else valueNamed.push(item);
|
|
2662
|
+
}
|
|
2663
|
+
if (typeNamed.length === 0) return full;
|
|
2664
|
+
const lines = [];
|
|
2665
|
+
if (parsed.defaultImport && valueNamed.length > 0) {
|
|
2666
|
+
lines.push(`import ${parsed.defaultImport}, { ${valueNamed.join(", ")} } from ${quote}${specifier}${quote};`);
|
|
2667
|
+
} else if (parsed.defaultImport) {
|
|
2668
|
+
lines.push(`import ${parsed.defaultImport} from ${quote}${specifier}${quote};`);
|
|
2669
|
+
} else if (valueNamed.length > 0) {
|
|
2670
|
+
lines.push(`import { ${valueNamed.join(", ")} } from ${quote}${specifier}${quote};`);
|
|
2671
|
+
}
|
|
2672
|
+
lines.push(`import type { ${typeNamed.join(", ")} } from ${quote}${specifier}${quote};`);
|
|
2673
|
+
return lines.join("\n");
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
function splitImportClause(clauseRaw) {
|
|
2677
|
+
const clause = clauseRaw.trim();
|
|
2678
|
+
const namedMatch = clause.match(/\{([\s\S]*)\}$/u);
|
|
2679
|
+
if (!namedMatch) return void 0;
|
|
2680
|
+
const beforeNamed = clause.slice(0, namedMatch.index).replace(/,\s*$/u, "").trim();
|
|
2681
|
+
const named = (namedMatch[1] ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
2682
|
+
return {
|
|
2683
|
+
defaultImport: beforeNamed || void 0,
|
|
2684
|
+
named
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2576
2687
|
|
|
2577
2688
|
// src/core/architecture.ts
|
|
2578
2689
|
var COMPLEXITY_SURFACES = [
|
|
@@ -2764,7 +2875,7 @@ function validateArchitectureContract(modules, steps, language, demand) {
|
|
|
2764
2875
|
}
|
|
2765
2876
|
allTestPaths.add(testPath);
|
|
2766
2877
|
}
|
|
2767
|
-
const owners = codeSteps.filter((step) => module.sourcePaths.every((
|
|
2878
|
+
const owners = codeSteps.filter((step) => module.sourcePaths.every((path32) => pathCoveredByOutputs(path32, step.outputs))).sort((a, b) => (stepIndex.get(a.id) ?? 0) - (stepIndex.get(b.id) ?? 0));
|
|
2768
2879
|
if (owners.length === 0) {
|
|
2769
2880
|
issues.push({
|
|
2770
2881
|
message: `${module.id} must map all sourcePaths to a CODE macro step; found 0.`
|
|
@@ -2794,7 +2905,7 @@ function validateArchitectureContract(modules, steps, language, demand) {
|
|
|
2794
2905
|
ownedModules.push(module);
|
|
2795
2906
|
modulesByCodeOwner.set(codeOwner.id, ownedModules);
|
|
2796
2907
|
const matchingTests = testSteps.filter(
|
|
2797
|
-
(step) => module.testPaths.some((
|
|
2908
|
+
(step) => module.testPaths.some((path32) => pathCoveredByOutputs(path32, step.outputs))
|
|
2798
2909
|
);
|
|
2799
2910
|
if (matchingTests.length === 0) {
|
|
2800
2911
|
issues.push({ message: `${module.id} testPaths are not produced by any MODULE_TEST step.` });
|
|
@@ -2851,8 +2962,8 @@ function flattenSubTasks(step) {
|
|
|
2851
2962
|
}
|
|
2852
2963
|
return out;
|
|
2853
2964
|
}
|
|
2854
|
-
function pathCoveredByOutputs(
|
|
2855
|
-
const normalizedPath = normalizePath(
|
|
2965
|
+
function pathCoveredByOutputs(path32, outputs) {
|
|
2966
|
+
const normalizedPath = normalizePath(path32);
|
|
2856
2967
|
return outputs.some((output) => {
|
|
2857
2968
|
const normalizedOutput = normalizePath(output);
|
|
2858
2969
|
return normalizedPath === normalizedOutput || normalizedPath.startsWith(`${normalizedOutput}/`);
|
|
@@ -2862,14 +2973,14 @@ function missingArchitectureDocumentTokens(content, modules) {
|
|
|
2862
2973
|
const required = modules.flatMap((module) => [module.id, ...module.sourcePaths, ...module.testPaths]);
|
|
2863
2974
|
return [...new Set(required)].filter((token) => !content.includes(token));
|
|
2864
2975
|
}
|
|
2865
|
-
function isSourcePath(
|
|
2866
|
-
return
|
|
2976
|
+
function isSourcePath(path32, extensions) {
|
|
2977
|
+
return path32.startsWith("src/") && extensions.some((extension) => path32.endsWith(extension));
|
|
2867
2978
|
}
|
|
2868
|
-
function isTestPath(
|
|
2869
|
-
return
|
|
2979
|
+
function isTestPath(path32, extensions) {
|
|
2980
|
+
return path32.startsWith("tests/") && extensions.some((extension) => path32.endsWith(extension));
|
|
2870
2981
|
}
|
|
2871
|
-
function normalizePath(
|
|
2872
|
-
return
|
|
2982
|
+
function normalizePath(path32) {
|
|
2983
|
+
return path32.replace(/\\/g, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
|
|
2873
2984
|
}
|
|
2874
2985
|
function transitivelyDependsOn(step, targetId, byId) {
|
|
2875
2986
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3451,21 +3562,21 @@ function parseLoadedPlan(json) {
|
|
|
3451
3562
|
}
|
|
3452
3563
|
async function savePlan(planPath, plan) {
|
|
3453
3564
|
PlanSchema.parse(plan);
|
|
3454
|
-
await
|
|
3455
|
-
await
|
|
3565
|
+
await fs2.mkdir(path3.dirname(planPath), { recursive: true });
|
|
3566
|
+
await fs2.writeFile(planPath, JSON.stringify(plan, null, 2) + "\n", "utf8");
|
|
3456
3567
|
}
|
|
3457
3568
|
async function loadPhasePlan(phasePlanPath) {
|
|
3458
|
-
const raw = await
|
|
3569
|
+
const raw = await fs2.readFile(phasePlanPath, "utf8");
|
|
3459
3570
|
return PhasePlanSchema.parse(JSON.parse(raw));
|
|
3460
3571
|
}
|
|
3461
3572
|
async function savePhasePlan(phasePlanPath, phasePlan) {
|
|
3462
3573
|
PhasePlanSchema.parse(phasePlan);
|
|
3463
|
-
await
|
|
3464
|
-
await
|
|
3574
|
+
await fs2.mkdir(path3.dirname(phasePlanPath), { recursive: true });
|
|
3575
|
+
await fs2.writeFile(phasePlanPath, JSON.stringify(phasePlan, null, 2) + "\n", "utf8");
|
|
3465
3576
|
}
|
|
3466
3577
|
async function loadPlanTarget(inputPath) {
|
|
3467
3578
|
const requestedPath = path3.resolve(inputPath);
|
|
3468
|
-
const raw = await
|
|
3579
|
+
const raw = await fs2.readFile(requestedPath, "utf8");
|
|
3469
3580
|
const json = JSON.parse(raw);
|
|
3470
3581
|
const phasePlanResult = PhasePlanSchema.safeParse(json);
|
|
3471
3582
|
if (phasePlanResult.success) {
|
|
@@ -3475,7 +3586,7 @@ async function loadPlanTarget(inputPath) {
|
|
|
3475
3586
|
throw new Error(`phasePlan ${requestedPath} has no planPath for current phase ${phasePlan.currentPhaseId}`);
|
|
3476
3587
|
}
|
|
3477
3588
|
const planPath = path3.resolve(path3.dirname(requestedPath), phase.planPath);
|
|
3478
|
-
const loaded2 = parseLoadedPlan(JSON.parse(await
|
|
3589
|
+
const loaded2 = parseLoadedPlan(JSON.parse(await fs2.readFile(planPath, "utf8")));
|
|
3479
3590
|
return {
|
|
3480
3591
|
plan: loaded2.plan,
|
|
3481
3592
|
planPath,
|
|
@@ -3580,7 +3691,7 @@ async function findProjectFile(workspace) {
|
|
|
3580
3691
|
const ws = path4.resolve(workspace);
|
|
3581
3692
|
let entries;
|
|
3582
3693
|
try {
|
|
3583
|
-
entries = await
|
|
3694
|
+
entries = await fs3.readdir(ws, { withFileTypes: true });
|
|
3584
3695
|
} catch {
|
|
3585
3696
|
return void 0;
|
|
3586
3697
|
}
|
|
@@ -3630,8 +3741,8 @@ async function updateProjectFile(opts) {
|
|
|
3630
3741
|
history: nextHistory
|
|
3631
3742
|
};
|
|
3632
3743
|
XCompilerProjectFileSchema.parse(data);
|
|
3633
|
-
await
|
|
3634
|
-
await
|
|
3744
|
+
await fs3.mkdir(path4.dirname(filePath), { recursive: true });
|
|
3745
|
+
await fs3.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
3635
3746
|
return filePath;
|
|
3636
3747
|
}
|
|
3637
3748
|
function buildProjectProgress(plan) {
|
|
@@ -3674,7 +3785,7 @@ function buildProjectProgress(plan) {
|
|
|
3674
3785
|
}
|
|
3675
3786
|
async function readExistingProjectFile(filePath) {
|
|
3676
3787
|
try {
|
|
3677
|
-
const raw = await
|
|
3788
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
3678
3789
|
return XCompilerProjectFileSchema.parse(JSON.parse(raw));
|
|
3679
3790
|
} catch {
|
|
3680
3791
|
return void 0;
|
|
@@ -3704,10 +3815,10 @@ function assertProjectFileExtension(filePath) {
|
|
|
3704
3815
|
|
|
3705
3816
|
// src/runtime/build.ts
|
|
3706
3817
|
import path13 from "path";
|
|
3707
|
-
import { promises as
|
|
3818
|
+
import { promises as fs12 } from "fs";
|
|
3708
3819
|
|
|
3709
3820
|
// src/config/config.ts
|
|
3710
|
-
import { promises as
|
|
3821
|
+
import { promises as fs4 } from "fs";
|
|
3711
3822
|
import path5 from "path";
|
|
3712
3823
|
import YAML from "yaml";
|
|
3713
3824
|
import { z as z4 } from "zod";
|
|
@@ -3773,6 +3884,73 @@ var ProviderSchema = z4.object({
|
|
|
3773
3884
|
think: z4.boolean().optional()
|
|
3774
3885
|
});
|
|
3775
3886
|
var LocaleSchema = z4.enum(["en", "zh"]);
|
|
3887
|
+
var TargetLanguageSchema = z4.enum(["python", "typescript"]);
|
|
3888
|
+
var SandboxModeSchema = z4.enum(["subprocess", "docker", "firejail"]);
|
|
3889
|
+
var SandboxLimitsSchema = z4.object({
|
|
3890
|
+
cpu: z4.number().positive().default(1),
|
|
3891
|
+
memory_mb: z4.number().int().positive().default(1024),
|
|
3892
|
+
wall_seconds: z4.number().int().positive().default(60),
|
|
3893
|
+
/**
|
|
3894
|
+
* Sandbox network policy.
|
|
3895
|
+
* - `off` — no network at all (`docker --network none`).
|
|
3896
|
+
* - `download-only` — outbound traffic allowed, no inbound port publishing.
|
|
3897
|
+
* - `pypi-only` — legacy value; rejected at sandbox creation rather than silently
|
|
3898
|
+
* allowing unrestricted outbound traffic.
|
|
3899
|
+
* - `full` — outbound + every port in `expose_ports` is published
|
|
3900
|
+
* to `127.0.0.1` so host-side tests can reach the app.
|
|
3901
|
+
*/
|
|
3902
|
+
network: z4.enum(["off", "pypi-only", "download-only", "full"]).default("download-only"),
|
|
3903
|
+
/** Container ports to publish to 127.0.0.1 when `network=full`. */
|
|
3904
|
+
expose_ports: z4.array(z4.number().int().min(1).max(65535)).default([])
|
|
3905
|
+
}).default({
|
|
3906
|
+
cpu: 1,
|
|
3907
|
+
memory_mb: 1024,
|
|
3908
|
+
wall_seconds: 60,
|
|
3909
|
+
network: "download-only",
|
|
3910
|
+
expose_ports: []
|
|
3911
|
+
});
|
|
3912
|
+
var LocalSandboxSchema = z4.object({
|
|
3913
|
+
sandbox_dir: z4.string().min(1).optional(),
|
|
3914
|
+
python_bin: z4.string().min(1).optional(),
|
|
3915
|
+
inherit_env: z4.boolean().optional(),
|
|
3916
|
+
limits: SandboxLimitsSchema
|
|
3917
|
+
}).default(() => ({ limits: defaultSandboxLimits() }));
|
|
3918
|
+
var DockerSandboxSchema = z4.object({
|
|
3919
|
+
image: z4.string().default("python:3.11-slim"),
|
|
3920
|
+
workdir: z4.string().default("/workspace"),
|
|
3921
|
+
pull: z4.boolean().default(false),
|
|
3922
|
+
docker_bin: z4.string().default("docker"),
|
|
3923
|
+
extra_run_args: z4.array(z4.string()).default([]),
|
|
3924
|
+
sandbox_dir: z4.string().min(1).optional(),
|
|
3925
|
+
limits: SandboxLimitsSchema
|
|
3926
|
+
}).default({
|
|
3927
|
+
image: "python:3.11-slim",
|
|
3928
|
+
workdir: "/workspace",
|
|
3929
|
+
pull: false,
|
|
3930
|
+
docker_bin: "docker",
|
|
3931
|
+
extra_run_args: [],
|
|
3932
|
+
limits: defaultSandboxLimits()
|
|
3933
|
+
});
|
|
3934
|
+
var LanguageSandboxSchema = z4.object({
|
|
3935
|
+
mode: SandboxModeSchema.default("subprocess"),
|
|
3936
|
+
local: LocalSandboxSchema,
|
|
3937
|
+
docker: DockerSandboxSchema
|
|
3938
|
+
}).default(() => ({
|
|
3939
|
+
mode: "subprocess",
|
|
3940
|
+
local: { limits: defaultSandboxLimits() },
|
|
3941
|
+
docker: {
|
|
3942
|
+
image: "python:3.11-slim",
|
|
3943
|
+
workdir: "/workspace",
|
|
3944
|
+
pull: false,
|
|
3945
|
+
docker_bin: "docker",
|
|
3946
|
+
extra_run_args: [],
|
|
3947
|
+
limits: defaultSandboxLimits()
|
|
3948
|
+
}
|
|
3949
|
+
}));
|
|
3950
|
+
var SandboxesSchema = z4.object({
|
|
3951
|
+
python: LanguageSandboxSchema.optional(),
|
|
3952
|
+
typescript: LanguageSandboxSchema.optional()
|
|
3953
|
+
}).default({});
|
|
3776
3954
|
var LlmSchema = z4.object({
|
|
3777
3955
|
default: z4.string(),
|
|
3778
3956
|
providers: z4.record(z4.string(), ProviderSchema),
|
|
@@ -3780,7 +3958,7 @@ var LlmSchema = z4.object({
|
|
|
3780
3958
|
* 角色 → provider 数组的映射。
|
|
3781
3959
|
* 兼容旧格式:单字符串 `Coder: ollama_code` 自动归一化为 `[ollama_code]`。
|
|
3782
3960
|
* 数组形式 `Coder: [ollama_code, openai]` 表示该角色的候选 LLM 池;
|
|
3783
|
-
* 实际选择顺序由
|
|
3961
|
+
* 实际选择顺序由 ScoreStore 有效评分降序决定;有效评分为用户覆盖优先,否则使用动态评分。
|
|
3784
3962
|
*/
|
|
3785
3963
|
roles: z4.record(z4.string(), z4.union([z4.string(), z4.array(z4.string())])).default({}).transform((obj) => {
|
|
3786
3964
|
const out = {};
|
|
@@ -3794,9 +3972,8 @@ var LlmSchema = z4.object({
|
|
|
3794
3972
|
/** 可选:按角色指定 fallback 链(覆盖全局) */
|
|
3795
3973
|
role_fallbacks: z4.record(z4.string(), z4.array(z4.string())).default({}),
|
|
3796
3974
|
/**
|
|
3797
|
-
* Provider
|
|
3798
|
-
*
|
|
3799
|
-
* 用户手工设置评分 = 0 表示禁用;运行时自动评分只在 0.1~1.0 范围内调整。
|
|
3975
|
+
* Provider 兼容初值。运行时动态评分由 ScoreStore 写入 llm_scores.yaml;
|
|
3976
|
+
* 用户手动覆盖应写入 llm_scores_user.yaml。旧配置里显式 0 仍表示用户禁用。
|
|
3800
3977
|
*/
|
|
3801
3978
|
scores: z4.record(z4.string(), z4.number().min(0)).default({}),
|
|
3802
3979
|
/**
|
|
@@ -3816,65 +3993,107 @@ var LlmSchema = z4.object({
|
|
|
3816
3993
|
});
|
|
3817
3994
|
}
|
|
3818
3995
|
});
|
|
3996
|
+
var AgentSchema = z4.object({
|
|
3997
|
+
/** @deprecated Target project language is inferred from topic/baseline. Kept for legacy configs only. */
|
|
3998
|
+
language: TargetLanguageSchema.optional(),
|
|
3999
|
+
max_steps: z4.number().int().positive().default(50),
|
|
4000
|
+
max_debug_retries: z4.number().int().positive().default(3),
|
|
4001
|
+
/** Debugger 滑动窗口的硬上限(默认 = max(max_debug_retries*4, 10))。 */
|
|
4002
|
+
max_debug_retries_cap: z4.number().int().positive().optional(),
|
|
4003
|
+
max_rounds_per_step: z4.number().int().positive().default(6),
|
|
4004
|
+
max_debug_rounds_per_step: z4.number().int().positive().optional(),
|
|
4005
|
+
max_edit_lines_per_step: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
4006
|
+
max_write_chunk_bytes: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
4007
|
+
/** @deprecated Use agent.sandboxes.<language>.mode. */
|
|
4008
|
+
sandbox: SandboxModeSchema.optional(),
|
|
4009
|
+
/** @deprecated Use agent.sandboxes.<language>.<local|docker>.limits. */
|
|
4010
|
+
sandbox_limits: SandboxLimitsSchema.optional(),
|
|
4011
|
+
/** @deprecated Use agent.sandboxes.<language>.docker. */
|
|
4012
|
+
sandbox_docker: DockerSandboxSchema.optional(),
|
|
4013
|
+
sandboxes: SandboxesSchema
|
|
4014
|
+
}).transform((agent) => {
|
|
4015
|
+
const legacyLanguage = agent.language ?? "python";
|
|
4016
|
+
const legacyMode = agent.sandbox ?? "subprocess";
|
|
4017
|
+
const legacyLimits = agent.sandbox_limits ?? defaultSandboxLimits();
|
|
4018
|
+
const defaults = {
|
|
4019
|
+
python: defaultLanguageSandbox("python", legacyMode, legacyLimits),
|
|
4020
|
+
typescript: defaultLanguageSandbox("typescript", legacyMode, legacyLimits)
|
|
4021
|
+
};
|
|
4022
|
+
const sandboxes = {
|
|
4023
|
+
python: mergeLanguageSandbox(
|
|
4024
|
+
defaults.python,
|
|
4025
|
+
agent.sandboxes.python,
|
|
4026
|
+
legacyLanguage === "python" ? agent.sandbox_docker : void 0
|
|
4027
|
+
),
|
|
4028
|
+
typescript: mergeLanguageSandbox(
|
|
4029
|
+
defaults.typescript,
|
|
4030
|
+
agent.sandboxes.typescript,
|
|
4031
|
+
legacyLanguage === "typescript" ? agent.sandbox_docker : void 0
|
|
4032
|
+
)
|
|
4033
|
+
};
|
|
4034
|
+
return {
|
|
4035
|
+
...agent,
|
|
4036
|
+
language: legacyLanguage,
|
|
4037
|
+
sandbox: sandboxes.python.mode,
|
|
4038
|
+
sandbox_limits: sandboxes.python.local.limits,
|
|
4039
|
+
sandbox_docker: sandboxes.python.docker,
|
|
4040
|
+
sandboxes
|
|
4041
|
+
};
|
|
4042
|
+
});
|
|
3819
4043
|
var ConfigSchema = z4.object({
|
|
3820
4044
|
/** CLI / prompt locale. Accepts 'en' (default) or 'zh'. */
|
|
3821
4045
|
locale: LocaleSchema.optional(),
|
|
3822
4046
|
/** @deprecated use `locale` instead. Kept as a backwards-compatible alias. */
|
|
3823
4047
|
ui_language: LocaleSchema.optional(),
|
|
3824
4048
|
llm: LlmSchema,
|
|
3825
|
-
agent:
|
|
3826
|
-
language: z4.enum(["python", "typescript"]).default("python"),
|
|
3827
|
-
max_steps: z4.number().int().positive().default(50),
|
|
3828
|
-
max_debug_retries: z4.number().int().positive().default(3),
|
|
3829
|
-
/** Debugger 滑动窗口的硬上限(默认 = max(max_debug_retries*4, 10))。 */
|
|
3830
|
-
max_debug_retries_cap: z4.number().int().positive().optional(),
|
|
3831
|
-
max_rounds_per_step: z4.number().int().positive().default(6),
|
|
3832
|
-
max_debug_rounds_per_step: z4.number().int().positive().optional(),
|
|
3833
|
-
max_edit_lines_per_step: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
3834
|
-
max_write_chunk_bytes: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
3835
|
-
sandbox: z4.enum(["subprocess", "docker", "firejail"]).default("subprocess"),
|
|
3836
|
-
sandbox_limits: z4.object({
|
|
3837
|
-
cpu: z4.number().positive().default(1),
|
|
3838
|
-
memory_mb: z4.number().int().positive().default(1024),
|
|
3839
|
-
wall_seconds: z4.number().int().positive().default(60),
|
|
3840
|
-
/**
|
|
3841
|
-
* Sandbox network policy.
|
|
3842
|
-
* - `off` — no network at all (`docker --network none`).
|
|
3843
|
-
* - `download-only` — outbound traffic allowed, no inbound port publishing
|
|
3844
|
-
* (default; lets python pip-install / fetch web pages).
|
|
3845
|
-
* - `pypi-only` — legacy value; rejected at sandbox creation rather than silently
|
|
3846
|
-
* allowing unrestricted outbound traffic.
|
|
3847
|
-
* - `full` — outbound + every port in `expose_ports` is published
|
|
3848
|
-
* to `127.0.0.1` so host-side tests can reach the app.
|
|
3849
|
-
*/
|
|
3850
|
-
network: z4.enum(["off", "pypi-only", "download-only", "full"]).default("download-only"),
|
|
3851
|
-
/** Container ports to publish to 127.0.0.1 when `network=full`. */
|
|
3852
|
-
expose_ports: z4.array(z4.number().int().min(1).max(65535)).default([])
|
|
3853
|
-
}).default({
|
|
3854
|
-
cpu: 1,
|
|
3855
|
-
memory_mb: 1024,
|
|
3856
|
-
wall_seconds: 60,
|
|
3857
|
-
network: "download-only",
|
|
3858
|
-
expose_ports: []
|
|
3859
|
-
}),
|
|
3860
|
-
sandbox_docker: z4.object({
|
|
3861
|
-
image: z4.string().default("python:3.11-slim"),
|
|
3862
|
-
workdir: z4.string().default("/workspace"),
|
|
3863
|
-
pull: z4.boolean().default(false),
|
|
3864
|
-
docker_bin: z4.string().default("docker"),
|
|
3865
|
-
extra_run_args: z4.array(z4.string()).default([])
|
|
3866
|
-
}).default({
|
|
3867
|
-
image: "python:3.11-slim",
|
|
3868
|
-
workdir: "/workspace",
|
|
3869
|
-
pull: false,
|
|
3870
|
-
docker_bin: "docker",
|
|
3871
|
-
extra_run_args: []
|
|
3872
|
-
})
|
|
3873
|
-
})
|
|
4049
|
+
agent: AgentSchema
|
|
3874
4050
|
}).transform(({ locale, ui_language, ...rest }) => ({
|
|
3875
4051
|
locale: locale ?? ui_language ?? "en",
|
|
3876
4052
|
...rest
|
|
3877
4053
|
}));
|
|
4054
|
+
function defaultSandboxLimits() {
|
|
4055
|
+
return {
|
|
4056
|
+
cpu: 1,
|
|
4057
|
+
memory_mb: 1024,
|
|
4058
|
+
wall_seconds: 60,
|
|
4059
|
+
network: "download-only",
|
|
4060
|
+
expose_ports: []
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
function defaultLanguageSandbox(language, mode, limits) {
|
|
4064
|
+
return {
|
|
4065
|
+
mode,
|
|
4066
|
+
local: {
|
|
4067
|
+
sandbox_dir: `.sandbox/${language}`,
|
|
4068
|
+
limits: { ...limits, expose_ports: [...limits.expose_ports ?? []] }
|
|
4069
|
+
},
|
|
4070
|
+
docker: {
|
|
4071
|
+
image: language === "typescript" ? "node:24-slim" : "python:3.11-slim",
|
|
4072
|
+
workdir: "/workspace",
|
|
4073
|
+
pull: false,
|
|
4074
|
+
docker_bin: "docker",
|
|
4075
|
+
extra_run_args: [],
|
|
4076
|
+
sandbox_dir: `.sandbox/${language}`,
|
|
4077
|
+
limits: { ...limits, expose_ports: [...limits.expose_ports ?? []] }
|
|
4078
|
+
}
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
function mergeLanguageSandbox(defaults, override, legacyDocker) {
|
|
4082
|
+
const dockerOverride = legacyDocker ?? override?.docker;
|
|
4083
|
+
return {
|
|
4084
|
+
mode: override?.mode ?? defaults.mode,
|
|
4085
|
+
local: {
|
|
4086
|
+
...defaults.local,
|
|
4087
|
+
...override?.local ?? {},
|
|
4088
|
+
limits: override?.local?.limits ?? defaults.local.limits
|
|
4089
|
+
},
|
|
4090
|
+
docker: {
|
|
4091
|
+
...defaults.docker,
|
|
4092
|
+
...dockerOverride ?? {},
|
|
4093
|
+
limits: dockerOverride?.limits ?? defaults.docker.limits
|
|
4094
|
+
}
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
3878
4097
|
function getXCompilerPath() {
|
|
3879
4098
|
const env = xcEnv("PATH");
|
|
3880
4099
|
if (env && env.trim()) return path5.resolve(env.trim());
|
|
@@ -3896,7 +4115,7 @@ async function loadConfigWithPath(explicitPath) {
|
|
|
3896
4115
|
for (const abs of candidates) {
|
|
3897
4116
|
tried.push(abs);
|
|
3898
4117
|
try {
|
|
3899
|
-
const raw = await
|
|
4118
|
+
const raw = await fs4.readFile(abs, "utf8");
|
|
3900
4119
|
const expanded = expandEnv(raw);
|
|
3901
4120
|
const data = YAML.parse(expanded);
|
|
3902
4121
|
return { config: ConfigSchema.parse(data), path: abs };
|
|
@@ -3938,6 +4157,11 @@ var LOOP_REPEATS = 12;
|
|
|
3938
4157
|
var LOOP_MIN_LEN = 1500;
|
|
3939
4158
|
var LOOP_MIN_WINDOW = 96;
|
|
3940
4159
|
var LOOP_MAX_PERIOD = 256;
|
|
4160
|
+
var TEXT_LOOP_MIN_LEN = 6e3;
|
|
4161
|
+
var TEXT_LOOP_TAIL = 14e3;
|
|
4162
|
+
var TEXT_LOOP_SAMPLE_LEN = 48;
|
|
4163
|
+
var TEXT_LOOP_STRIDE = 8;
|
|
4164
|
+
var TEXT_LOOP_REPEATS = 5;
|
|
3941
4165
|
function detectCyclicTokenLoop(aggregate) {
|
|
3942
4166
|
if (aggregate.length < LOOP_MIN_LEN) return false;
|
|
3943
4167
|
const maxPeriod = Math.min(LOOP_MAX_PERIOD, Math.floor(aggregate.length / LOOP_REPEATS));
|
|
@@ -3959,6 +4183,35 @@ function detectCyclicTokenLoop(aggregate) {
|
|
|
3959
4183
|
}
|
|
3960
4184
|
return false;
|
|
3961
4185
|
}
|
|
4186
|
+
function detectRepeatedTextLoop(aggregate) {
|
|
4187
|
+
if (aggregate.length < TEXT_LOOP_MIN_LEN) return false;
|
|
4188
|
+
const tail = aggregate.slice(-TEXT_LOOP_TAIL).replace(/\s+/gu, " ").trim();
|
|
4189
|
+
if (tail.length < TEXT_LOOP_MIN_LEN) return false;
|
|
4190
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4191
|
+
for (let i = 0; i + TEXT_LOOP_SAMPLE_LEN <= tail.length; i += TEXT_LOOP_STRIDE) {
|
|
4192
|
+
const sample = tail.slice(i, i + TEXT_LOOP_SAMPLE_LEN);
|
|
4193
|
+
if (!isHighSignalLoopSample(sample)) continue;
|
|
4194
|
+
const prior = seen.get(sample);
|
|
4195
|
+
if (!prior) {
|
|
4196
|
+
seen.set(sample, { count: 1, first: i, last: i });
|
|
4197
|
+
continue;
|
|
4198
|
+
}
|
|
4199
|
+
const next = { count: prior.count + 1, first: prior.first, last: i };
|
|
4200
|
+
seen.set(sample, next);
|
|
4201
|
+
if (next.count >= TEXT_LOOP_REPEATS && next.last - next.first >= TEXT_LOOP_SAMPLE_LEN * 3) {
|
|
4202
|
+
return true;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
return false;
|
|
4206
|
+
}
|
|
4207
|
+
function isHighSignalLoopSample(sample) {
|
|
4208
|
+
const chars = [...sample];
|
|
4209
|
+
const signalChars = chars.filter((ch) => /[\p{L}\p{N}]/u.test(ch));
|
|
4210
|
+
if (signalChars.length < 20) return false;
|
|
4211
|
+
if (signalChars.length / Math.max(1, chars.length) < 0.4) return false;
|
|
4212
|
+
const unique = new Set(signalChars.map((ch) => ch.toLowerCase())).size;
|
|
4213
|
+
return unique >= 10;
|
|
4214
|
+
}
|
|
3962
4215
|
var RepeatTokenDetector = class {
|
|
3963
4216
|
constructor(threshold = 40) {
|
|
3964
4217
|
this.threshold = threshold;
|
|
@@ -4137,6 +4390,12 @@ function streamPostNdjson(url, body, watchdog, onLine, shouldStopWhen) {
|
|
|
4137
4390
|
);
|
|
4138
4391
|
return;
|
|
4139
4392
|
}
|
|
4393
|
+
if (detectRepeatedTextLoop(aggregate)) {
|
|
4394
|
+
fail(
|
|
4395
|
+
new Error("detected repeated text loop in stream (semantic tail repetition); aborting")
|
|
4396
|
+
);
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4140
4399
|
}
|
|
4141
4400
|
if (obj.error) {
|
|
4142
4401
|
fail(new Error(`Ollama error: ${obj.error}`));
|
|
@@ -4165,6 +4424,10 @@ function streamPostNdjson(url, body, watchdog, onLine, shouldStopWhen) {
|
|
|
4165
4424
|
fail(new Error("detected token loop in stream; aborting"));
|
|
4166
4425
|
return;
|
|
4167
4426
|
}
|
|
4427
|
+
if (detectRepeatedTextLoop(aggregate)) {
|
|
4428
|
+
fail(new Error("detected repeated text loop in stream; aborting"));
|
|
4429
|
+
return;
|
|
4430
|
+
}
|
|
4168
4431
|
}
|
|
4169
4432
|
});
|
|
4170
4433
|
res.on("end", () => {
|
|
@@ -4435,6 +4698,9 @@ var OpenAIClient = class {
|
|
|
4435
4698
|
if (detectCyclicTokenLoop(aggregate)) {
|
|
4436
4699
|
throw new Error("detected cyclic token loop in OpenAI stream (periodic tail); aborting");
|
|
4437
4700
|
}
|
|
4701
|
+
if (detectRepeatedTextLoop(aggregate)) {
|
|
4702
|
+
throw new Error("detected repeated text loop in OpenAI stream (semantic tail repetition); aborting");
|
|
4703
|
+
}
|
|
4438
4704
|
if (maxOutputChars > 0 && expectsJsonObject && aggregate.length > maxOutputChars && hasInvalidJsonPrefix(aggregate)) {
|
|
4439
4705
|
throw new Error(`OpenAI stream exceeded ${maxOutputChars} chars without a valid JSON prefix; aborting`);
|
|
4440
4706
|
}
|
|
@@ -4971,22 +5237,28 @@ async function reportRoleModelAdvice(router, audit, reporter = (message) => {
|
|
|
4971
5237
|
}
|
|
4972
5238
|
|
|
4973
5239
|
// src/llm/scores.ts
|
|
4974
|
-
import { promises as
|
|
5240
|
+
import { promises as fs5 } from "fs";
|
|
4975
5241
|
import path6 from "path";
|
|
4976
5242
|
import YAML2 from "yaml";
|
|
4977
5243
|
var CLUSTER_PROVIDER_TAG = "cluster";
|
|
5244
|
+
var LLM_DYNAMIC_SCORES_FILE = "llm_scores.yaml";
|
|
5245
|
+
var LLM_USER_SCORES_FILE = "llm_scores_user.yaml";
|
|
4978
5246
|
var ScoreStore = class _ScoreStore {
|
|
4979
5247
|
constructor(configPath, initial = {}, audit, opts = {}) {
|
|
4980
5248
|
this.audit = audit;
|
|
4981
|
-
|
|
5249
|
+
const configDir = path6.dirname(configPath);
|
|
5250
|
+
this.sidecarPath = path6.join(configDir, LLM_DYNAMIC_SCORES_FILE);
|
|
5251
|
+
this.userScoresPath = path6.join(configDir, LLM_USER_SCORES_FILE);
|
|
4982
5252
|
this.clusterProviders = new Set(opts.clusterProviderNames ?? []);
|
|
4983
5253
|
const bounds = normalizeClusterBounds(opts.clusterScoreMin, opts.clusterScoreMax);
|
|
4984
5254
|
this.clusterMin = bounds.min;
|
|
4985
5255
|
this.clusterMax = bounds.max;
|
|
4986
5256
|
for (const [k, v] of Object.entries(initial)) {
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
5257
|
+
if (v === _ScoreStore.DISABLED) {
|
|
5258
|
+
this.userScores.set(k, _ScoreStore.DISABLED);
|
|
5259
|
+
} else {
|
|
5260
|
+
this.dynamicScores.set(k, this.clampDynamic(k, v));
|
|
5261
|
+
}
|
|
4990
5262
|
}
|
|
4991
5263
|
}
|
|
4992
5264
|
audit;
|
|
@@ -4998,24 +5270,28 @@ var ScoreStore = class _ScoreStore {
|
|
|
4998
5270
|
static CLUSTER_MAX = 0.5;
|
|
4999
5271
|
static DECAY = 0.5;
|
|
5000
5272
|
static BOOST = 0.1;
|
|
5001
|
-
|
|
5002
|
-
|
|
5273
|
+
dynamicScores = /* @__PURE__ */ new Map();
|
|
5274
|
+
userScores = /* @__PURE__ */ new Map();
|
|
5003
5275
|
clusterProviders;
|
|
5004
5276
|
clusterMin;
|
|
5005
5277
|
clusterMax;
|
|
5006
5278
|
dirty = false;
|
|
5007
5279
|
writeQueue = Promise.resolve();
|
|
5008
5280
|
sidecarPath;
|
|
5009
|
-
|
|
5281
|
+
userScoresPath;
|
|
5282
|
+
/** 异步加载动态 sidecar 与用户覆盖文件;失败/不存在不抛错,使用 ctor 提供的初值。 */
|
|
5010
5283
|
async load() {
|
|
5284
|
+
await this.loadDynamicScores();
|
|
5285
|
+
await this.loadUserScores();
|
|
5286
|
+
}
|
|
5287
|
+
async loadDynamicScores() {
|
|
5011
5288
|
try {
|
|
5012
|
-
const raw = await
|
|
5289
|
+
const raw = await fs5.readFile(this.sidecarPath, "utf8");
|
|
5013
5290
|
const parsed = YAML2.parse(raw);
|
|
5014
5291
|
if (parsed && typeof parsed === "object") {
|
|
5015
5292
|
for (const [k, v] of Object.entries(parsed)) {
|
|
5016
5293
|
if (typeof v === "number" && Number.isFinite(v)) {
|
|
5017
|
-
|
|
5018
|
-
this.scores.set(k, this.clampDynamic(k, v));
|
|
5294
|
+
this.dynamicScores.set(k, this.clampPersistedDynamic(k, v));
|
|
5019
5295
|
}
|
|
5020
5296
|
}
|
|
5021
5297
|
}
|
|
@@ -5028,44 +5304,73 @@ var ScoreStore = class _ScoreStore {
|
|
|
5028
5304
|
}
|
|
5029
5305
|
}
|
|
5030
5306
|
}
|
|
5307
|
+
async loadUserScores() {
|
|
5308
|
+
try {
|
|
5309
|
+
const raw = await fs5.readFile(this.userScoresPath, "utf8");
|
|
5310
|
+
const parsed = YAML2.parse(raw);
|
|
5311
|
+
if (parsed && typeof parsed === "object") {
|
|
5312
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
5313
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
5314
|
+
this.userScores.set(k, this.clampUserScore(v));
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5318
|
+
} catch (err) {
|
|
5319
|
+
if (err.code !== "ENOENT") {
|
|
5320
|
+
await this.audit?.event("llm.error", t().llm.scoreReadFailed(this.userScoresPath, err.message), {
|
|
5321
|
+
messageId: "llm.user_score_read_failed",
|
|
5322
|
+
sidecarPath: this.userScoresPath
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5031
5327
|
get(name) {
|
|
5032
|
-
|
|
5328
|
+
if (this.userScores.has(name)) return this.userScores.get(name);
|
|
5329
|
+
return this.dynamicScores.has(name) ? this.dynamicScores.get(name) : this.defaultFor(name);
|
|
5033
5330
|
}
|
|
5034
|
-
/**
|
|
5331
|
+
/** 主动设置动态评分;用户覆盖只从配置或 llm_scores_user.yaml 读取。 */
|
|
5035
5332
|
set(name, value, reason) {
|
|
5036
|
-
if (this.userDisabled.has(name) && value !== _ScoreStore.DISABLED) return;
|
|
5037
5333
|
const v = value === _ScoreStore.DISABLED ? _ScoreStore.DISABLED : this.clampDynamic(name, value);
|
|
5038
|
-
const prev = this.
|
|
5334
|
+
const prev = this.dynamicScores.get(name);
|
|
5039
5335
|
if (prev === v) return;
|
|
5040
|
-
this.
|
|
5336
|
+
this.dynamicScores.set(name, v);
|
|
5041
5337
|
this.dirty = true;
|
|
5042
|
-
void this.audit?.event("llm.score", t().llm.scoreChanged(name, v.toFixed(2), (prev ??
|
|
5338
|
+
void this.audit?.event("llm.score", t().llm.scoreChanged(name, v.toFixed(2), (prev ?? this.defaultFor(name)).toFixed(2)), {
|
|
5043
5339
|
messageId: "llm.score_changed",
|
|
5044
5340
|
provider: name,
|
|
5045
5341
|
score: v,
|
|
5046
|
-
previous: prev ??
|
|
5342
|
+
previous: prev ?? this.defaultFor(name),
|
|
5047
5343
|
reason: reason ?? "set"
|
|
5048
5344
|
});
|
|
5049
5345
|
this.scheduleSave();
|
|
5050
5346
|
}
|
|
5051
5347
|
decay(name, reason) {
|
|
5052
|
-
if (this.
|
|
5053
|
-
|
|
5348
|
+
if (this.isUserDisabled(name)) return;
|
|
5349
|
+
const cur = this.dynamicScore(name);
|
|
5350
|
+
if (cur === _ScoreStore.DISABLED) return;
|
|
5351
|
+
this.set(name, Math.max(this.minFor(name), cur - _ScoreStore.DECAY), reason);
|
|
5054
5352
|
}
|
|
5055
5353
|
boost(name, reason) {
|
|
5056
|
-
|
|
5354
|
+
if (this.isUserDisabled(name)) return;
|
|
5355
|
+
const cur = this.dynamicScore(name);
|
|
5057
5356
|
if (cur === _ScoreStore.DISABLED) return;
|
|
5058
5357
|
if (cur >= this.maxFor(name)) return;
|
|
5059
5358
|
this.set(name, Math.min(this.maxFor(name), cur + _ScoreStore.BOOST), reason);
|
|
5060
5359
|
}
|
|
5061
|
-
/** True only for providers explicitly disabled by user config
|
|
5360
|
+
/** True only for providers explicitly disabled by user config or llm_scores_user.yaml. */
|
|
5062
5361
|
isUserDisabled(name) {
|
|
5063
|
-
return this.
|
|
5362
|
+
return this.userScores.get(name) === _ScoreStore.DISABLED;
|
|
5064
5363
|
}
|
|
5065
|
-
/**
|
|
5364
|
+
/** 动态评分快照(用于持久化、测试与 audit 输出;不包含用户覆盖)。 */
|
|
5066
5365
|
snapshot() {
|
|
5067
5366
|
const out = {};
|
|
5068
|
-
for (const [k, v] of this.
|
|
5367
|
+
for (const [k, v] of this.dynamicScores) out[k] = v;
|
|
5368
|
+
return out;
|
|
5369
|
+
}
|
|
5370
|
+
/** 用户覆盖快照(用于测试/诊断;运行时不会改写用户文件)。 */
|
|
5371
|
+
userSnapshot() {
|
|
5372
|
+
const out = {};
|
|
5373
|
+
for (const [k, v] of this.userScores) out[k] = v;
|
|
5069
5374
|
return out;
|
|
5070
5375
|
}
|
|
5071
5376
|
/** 等待待写入完成(CLI 退出前调用,确保评分已落盘)。 */
|
|
@@ -5088,8 +5393,8 @@ var ScoreStore = class _ScoreStore {
|
|
|
5088
5393
|
${t().llm.scoreFileSemantics}
|
|
5089
5394
|
` + YAML2.stringify(data);
|
|
5090
5395
|
const tmp = `${this.sidecarPath}.tmp`;
|
|
5091
|
-
await
|
|
5092
|
-
await
|
|
5396
|
+
await fs5.writeFile(tmp, yaml, "utf8");
|
|
5397
|
+
await fs5.rename(tmp, this.sidecarPath);
|
|
5093
5398
|
}
|
|
5094
5399
|
defaultFor(name) {
|
|
5095
5400
|
return this.isClusterProvider(name) ? this.clusterMax : _ScoreStore.DEFAULT;
|
|
@@ -5103,10 +5408,8 @@ ${t().llm.scoreFileSemantics}
|
|
|
5103
5408
|
isClusterProvider(name) {
|
|
5104
5409
|
return this.clusterProviders.has(name);
|
|
5105
5410
|
}
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
if (v === _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
|
|
5109
|
-
return this.clampDynamic(name, v);
|
|
5411
|
+
dynamicScore(name) {
|
|
5412
|
+
return this.dynamicScores.has(name) ? this.dynamicScores.get(name) : this.defaultFor(name);
|
|
5110
5413
|
}
|
|
5111
5414
|
clampDynamic(name, v) {
|
|
5112
5415
|
if (!Number.isFinite(v)) return this.defaultFor(name);
|
|
@@ -5115,6 +5418,18 @@ ${t().llm.scoreFileSemantics}
|
|
|
5115
5418
|
if (v > this.maxFor(name)) return this.maxFor(name);
|
|
5116
5419
|
return v;
|
|
5117
5420
|
}
|
|
5421
|
+
clampPersistedDynamic(name, v) {
|
|
5422
|
+
if (!Number.isFinite(v)) return this.defaultFor(name);
|
|
5423
|
+
if (v === _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
|
|
5424
|
+
return this.clampDynamic(name, v);
|
|
5425
|
+
}
|
|
5426
|
+
clampUserScore(v) {
|
|
5427
|
+
if (!Number.isFinite(v)) return _ScoreStore.DEFAULT;
|
|
5428
|
+
if (v <= _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
|
|
5429
|
+
if (v < _ScoreStore.MIN) return _ScoreStore.MIN;
|
|
5430
|
+
if (v > _ScoreStore.MAX) return _ScoreStore.MAX;
|
|
5431
|
+
return v;
|
|
5432
|
+
}
|
|
5118
5433
|
};
|
|
5119
5434
|
function normalizeClusterBounds(rawMin, rawMax) {
|
|
5120
5435
|
const min = clampBound(rawMin ?? ScoreStore.CLUSTER_MIN);
|
|
@@ -5294,7 +5609,7 @@ async function defaultFetchTags(baseUrl, timeoutMs) {
|
|
|
5294
5609
|
}
|
|
5295
5610
|
|
|
5296
5611
|
// src/workspace/workspace.ts
|
|
5297
|
-
import { promises as
|
|
5612
|
+
import { promises as fs6 } from "fs";
|
|
5298
5613
|
import path7 from "path";
|
|
5299
5614
|
var Workspace = class {
|
|
5300
5615
|
constructor(root) {
|
|
@@ -5305,31 +5620,31 @@ var Workspace = class {
|
|
|
5305
5620
|
return path7.resolve(this.root, ...p);
|
|
5306
5621
|
}
|
|
5307
5622
|
async ensure(dir) {
|
|
5308
|
-
await
|
|
5623
|
+
await fs6.mkdir(this.abs(dir), { recursive: true });
|
|
5309
5624
|
}
|
|
5310
5625
|
async writeFile(rel, content) {
|
|
5311
5626
|
const full = this.abs(rel);
|
|
5312
|
-
await
|
|
5313
|
-
await
|
|
5627
|
+
await fs6.mkdir(path7.dirname(full), { recursive: true });
|
|
5628
|
+
await fs6.writeFile(full, content, "utf8");
|
|
5314
5629
|
}
|
|
5315
5630
|
async readFile(rel) {
|
|
5316
|
-
return
|
|
5631
|
+
return fs6.readFile(this.abs(rel), "utf8");
|
|
5317
5632
|
}
|
|
5318
5633
|
async exists(rel) {
|
|
5319
5634
|
try {
|
|
5320
|
-
await
|
|
5635
|
+
await fs6.stat(this.abs(rel));
|
|
5321
5636
|
return true;
|
|
5322
5637
|
} catch {
|
|
5323
5638
|
return false;
|
|
5324
5639
|
}
|
|
5325
5640
|
}
|
|
5326
5641
|
async remove(rel) {
|
|
5327
|
-
await
|
|
5642
|
+
await fs6.rm(this.abs(rel), { recursive: true, force: true });
|
|
5328
5643
|
}
|
|
5329
5644
|
};
|
|
5330
5645
|
|
|
5331
5646
|
// src/workspace/doc_archive.ts
|
|
5332
|
-
import { promises as
|
|
5647
|
+
import { promises as fs7 } from "fs";
|
|
5333
5648
|
import path8 from "path";
|
|
5334
5649
|
async function archiveIfExists(ws, rel, audit) {
|
|
5335
5650
|
const norm = rel.replaceAll("\\", "/");
|
|
@@ -5342,7 +5657,7 @@ async function archiveIfExists(ws, rel, audit) {
|
|
|
5342
5657
|
const target = `docs/history/${base}-${ts}${ext}`;
|
|
5343
5658
|
try {
|
|
5344
5659
|
await ws.ensure("docs/history");
|
|
5345
|
-
await
|
|
5660
|
+
await fs7.rename(ws.abs(norm), ws.abs(target));
|
|
5346
5661
|
await audit?.event("plan.persist", t().audit.documentArchived(norm, target), {
|
|
5347
5662
|
messageId: "audit.document_archived",
|
|
5348
5663
|
from: norm,
|
|
@@ -5544,6 +5859,18 @@ var DOC_PATH_ALIASES = {
|
|
|
5544
5859
|
"docs/integration-test.md": DOC_NAMES.integrationTest,
|
|
5545
5860
|
"docs/module-test.md": DOC_NAMES.moduleTest,
|
|
5546
5861
|
"docs/functional-test.md": DOC_NAMES.functionalTest,
|
|
5862
|
+
"docs/unit-test-plan.md": DOC_NAMES.unitTestPlan,
|
|
5863
|
+
"docs/unit_test_plan.md": DOC_NAMES.unitTestPlan,
|
|
5864
|
+
"docs/tests/unit_test_plan.md": DOC_NAMES.unitTestPlan,
|
|
5865
|
+
"docs/integration-test-plan.md": DOC_NAMES.integrationTestPlan,
|
|
5866
|
+
"docs/integration_test_plan.md": DOC_NAMES.integrationTestPlan,
|
|
5867
|
+
"docs/tests/integration_test_plan.md": DOC_NAMES.integrationTestPlan,
|
|
5868
|
+
"docs/module-test-plan.md": DOC_NAMES.moduleTestPlan,
|
|
5869
|
+
"docs/module_test_plan.md": DOC_NAMES.moduleTestPlan,
|
|
5870
|
+
"docs/tests/module_test_plan.md": DOC_NAMES.moduleTestPlan,
|
|
5871
|
+
"docs/functional-test-plan.md": DOC_NAMES.functionalTestPlan,
|
|
5872
|
+
"docs/functional_test_plan.md": DOC_NAMES.functionalTestPlan,
|
|
5873
|
+
"docs/tests/functional_test_plan.md": DOC_NAMES.functionalTestPlan,
|
|
5547
5874
|
"docs/quick-start.md": DOC_NAMES.quickstart,
|
|
5548
5875
|
"docs/quick_start.md": DOC_NAMES.quickstart,
|
|
5549
5876
|
"docs/quickstart.md": DOC_NAMES.quickstart,
|
|
@@ -5551,13 +5878,25 @@ var DOC_PATH_ALIASES = {
|
|
|
5551
5878
|
"docs/api_guide.md": DOC_NAMES.apiGuide,
|
|
5552
5879
|
"docs/api-guide.md": DOC_NAMES.apiGuide
|
|
5553
5880
|
};
|
|
5881
|
+
function canonicalTestPlanAlias(path32) {
|
|
5882
|
+
const normalized = path32.replace(/\\/g, "/");
|
|
5883
|
+
const match = normalized.match(
|
|
5884
|
+
/^docs\/(?:tests\/)?(?:\d{1,2}[-_])?(functional|integration|module|unit)[-_]?test[-_]?plan\.md$/iu
|
|
5885
|
+
);
|
|
5886
|
+
const kind = match?.[1]?.toLowerCase();
|
|
5887
|
+
if (kind === "functional") return DOC_NAMES.functionalTestPlan;
|
|
5888
|
+
if (kind === "integration") return DOC_NAMES.integrationTestPlan;
|
|
5889
|
+
if (kind === "module") return DOC_NAMES.moduleTestPlan;
|
|
5890
|
+
if (kind === "unit") return DOC_NAMES.unitTestPlan;
|
|
5891
|
+
return void 0;
|
|
5892
|
+
}
|
|
5554
5893
|
function calibrateDocPaths(steps, projectType = "application") {
|
|
5555
|
-
const remap = (p) => DOC_PATH_ALIASES[p] ?? p;
|
|
5894
|
+
const remap = (p) => DOC_PATH_ALIASES[p] ?? canonicalTestPlanAlias(p) ?? p;
|
|
5556
5895
|
const dropTopic = (p) => p !== DOC_NAMES.topic;
|
|
5557
5896
|
return steps.map((s) => {
|
|
5558
5897
|
const iterationId = s.iterationId ?? "P1";
|
|
5559
|
-
const inputs = (s.inputs ?? []).map((p) => iterationScopedInput(remap(p), s.phase, iterationId));
|
|
5560
|
-
let outputs = (s.outputs ?? []).map((p) => iterationScopedDoc(remap(p), s.phase, iterationId)).filter(dropTopic);
|
|
5898
|
+
const inputs = dedup2((s.inputs ?? []).map((p) => iterationScopedInput(remap(p), s.phase, iterationId)));
|
|
5899
|
+
let outputs = dedup2((s.outputs ?? []).map((p) => iterationScopedDoc(remap(p), s.phase, iterationId)).filter(dropTopic));
|
|
5561
5900
|
outputs = outputs.filter((out) => {
|
|
5562
5901
|
const ownerPhase = testPlanOwnerPhase(out, iterationId);
|
|
5563
5902
|
return !ownerPhase || ownerPhase === s.phase;
|
|
@@ -5601,6 +5940,31 @@ function calibrateVModelDependencies(steps) {
|
|
|
5601
5940
|
}
|
|
5602
5941
|
return out;
|
|
5603
5942
|
}
|
|
5943
|
+
function calibrateArchitectureModuleDependencies(modules, dependencies) {
|
|
5944
|
+
const architectureModules = (modules ?? []).map((module) => ({
|
|
5945
|
+
...module,
|
|
5946
|
+
dependencies: [...module.dependencies ?? []]
|
|
5947
|
+
}));
|
|
5948
|
+
const moduleIds = new Set(architectureModules.map((module) => module.id));
|
|
5949
|
+
const projectDependencies = [...dependencies ?? []];
|
|
5950
|
+
for (const module of architectureModules) {
|
|
5951
|
+
const internalDependencies = [];
|
|
5952
|
+
for (const rawDependency of module.dependencies) {
|
|
5953
|
+
const dependency = String(rawDependency ?? "").trim();
|
|
5954
|
+
if (!dependency) continue;
|
|
5955
|
+
if (moduleIds.has(dependency) || /^M\d{3,}$/u.test(dependency)) {
|
|
5956
|
+
internalDependencies.push(dependency);
|
|
5957
|
+
} else {
|
|
5958
|
+
projectDependencies.push(dependency);
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
module.dependencies = dedup2(internalDependencies);
|
|
5962
|
+
}
|
|
5963
|
+
return {
|
|
5964
|
+
architectureModules,
|
|
5965
|
+
dependencies: dedup2(projectDependencies.map((dependency) => dependency.trim()).filter(Boolean))
|
|
5966
|
+
};
|
|
5967
|
+
}
|
|
5604
5968
|
function calibrateLanguageStepOwnership(steps, args) {
|
|
5605
5969
|
const profile = getLanguageProfile(args.language);
|
|
5606
5970
|
const out = steps.map((step) => ({
|
|
@@ -5662,35 +6026,35 @@ function preferredTestOwnerPhase(output, modules) {
|
|
|
5662
6026
|
function findIterationStep(steps, iterationId, phase) {
|
|
5663
6027
|
return steps.find((step) => (step.iterationId ?? "P1") === iterationId && step.phase === phase);
|
|
5664
6028
|
}
|
|
5665
|
-
function testPlanOwnerPhase(
|
|
6029
|
+
function testPlanOwnerPhase(path32, iterationId) {
|
|
5666
6030
|
for (const [sourcePhase, testPhase] of Object.entries(V_MODEL_SOURCE_TO_TEST_PHASE)) {
|
|
5667
|
-
if (
|
|
6031
|
+
if (path32 === testPlanDocForIteration(testPhase, iterationId)) {
|
|
5668
6032
|
return sourcePhase;
|
|
5669
6033
|
}
|
|
5670
6034
|
}
|
|
5671
6035
|
return void 0;
|
|
5672
6036
|
}
|
|
5673
|
-
function iterationScopedDoc(
|
|
5674
|
-
if (iterationId === "P1") return
|
|
5675
|
-
if (
|
|
6037
|
+
function iterationScopedDoc(path32, phase, iterationId) {
|
|
6038
|
+
if (iterationId === "P1") return path32;
|
|
6039
|
+
if (path32 === DOC_NAMES.readme && phase === "FUNCTIONAL_TEST") {
|
|
5676
6040
|
return `docs/iterations/${iterationId}/README.md`;
|
|
5677
6041
|
}
|
|
5678
|
-
if (
|
|
6042
|
+
if (path32 === DOC_NAMES.quickstart && phase === "FUNCTIONAL_TEST") {
|
|
5679
6043
|
return `docs/iterations/${iterationId}/quickstart.md`;
|
|
5680
6044
|
}
|
|
5681
|
-
if (
|
|
6045
|
+
if (path32 === DOC_NAMES.apiGuide && phase === "FUNCTIONAL_TEST") {
|
|
5682
6046
|
return `docs/iterations/${iterationId}/api-guide.md`;
|
|
5683
6047
|
}
|
|
5684
6048
|
for (const [docPhase, canonical] of Object.entries(PHASE_DOC)) {
|
|
5685
|
-
if (
|
|
5686
|
-
return phaseDocForIteration(docPhase, iterationId) ??
|
|
6049
|
+
if (path32 === canonical) {
|
|
6050
|
+
return phaseDocForIteration(docPhase, iterationId) ?? path32;
|
|
5687
6051
|
}
|
|
5688
6052
|
}
|
|
5689
|
-
return
|
|
6053
|
+
return path32;
|
|
5690
6054
|
}
|
|
5691
|
-
function iterationScopedInput(
|
|
5692
|
-
if (iterationId === "P1" || phase === "REQUIREMENT_ANALYSIS") return
|
|
5693
|
-
return iterationScopedDoc(
|
|
6055
|
+
function iterationScopedInput(path32, phase, iterationId) {
|
|
6056
|
+
if (iterationId === "P1" || phase === "REQUIREMENT_ANALYSIS") return path32;
|
|
6057
|
+
return iterationScopedDoc(path32, phase, iterationId);
|
|
5694
6058
|
}
|
|
5695
6059
|
function calibrateStepIds(steps) {
|
|
5696
6060
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -6021,8 +6385,8 @@ function dedupModules(modules) {
|
|
|
6021
6385
|
}
|
|
6022
6386
|
return out;
|
|
6023
6387
|
}
|
|
6024
|
-
function isTestImplementationPath(
|
|
6025
|
-
return /^tests\/.+\.(?:py|ts|tsx)$/i.test(
|
|
6388
|
+
function isTestImplementationPath(path32) {
|
|
6389
|
+
return /^tests\/.+\.(?:py|ts|tsx)$/i.test(path32);
|
|
6026
6390
|
}
|
|
6027
6391
|
function isNonModuleTestPhase(phase) {
|
|
6028
6392
|
return phase === "UNIT_TEST" || phase === "INTEGRATION_TEST" || phase === "FUNCTIONAL_TEST";
|
|
@@ -6045,7 +6409,7 @@ function testPathPrefixForPhase(phase) {
|
|
|
6045
6409
|
return "unit";
|
|
6046
6410
|
}
|
|
6047
6411
|
function inferUnitTestExtension(modules) {
|
|
6048
|
-
return modules.some((module) => module.testPaths.some((
|
|
6412
|
+
return modules.some((module) => module.testPaths.some((path32) => /\.tsx?$/i.test(path32))) ? ".test.ts" : ".py";
|
|
6049
6413
|
}
|
|
6050
6414
|
function collectTransitiveDependencyIds(step, stepById) {
|
|
6051
6415
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6545,7 +6909,8 @@ var Planner = class {
|
|
|
6545
6909
|
intent: opts.intent ?? "greenfield",
|
|
6546
6910
|
hasBaseline: !!opts.hasBaseline,
|
|
6547
6911
|
complex: demand.nonTrivial,
|
|
6548
|
-
projectShapeAmbiguous
|
|
6912
|
+
projectShapeAmbiguous,
|
|
6913
|
+
languageAmbiguous: !!opts.languageAmbiguous
|
|
6549
6914
|
});
|
|
6550
6915
|
const rep = makeStreamReporter("Planner.clarify", this.llm.name);
|
|
6551
6916
|
let provider;
|
|
@@ -6569,7 +6934,8 @@ var Planner = class {
|
|
|
6569
6934
|
// 在 provider fallback 层校验问题集质量,避免“只有两三个泛泛问题”直接进入 Gate 1。
|
|
6570
6935
|
validate: (t2) => validateClarifyJson(t2, demand.nonTrivial, {
|
|
6571
6936
|
projectShapeAmbiguous,
|
|
6572
|
-
externalApiRequired
|
|
6937
|
+
externalApiRequired,
|
|
6938
|
+
languageAmbiguous: !!opts.languageAmbiguous
|
|
6573
6939
|
})
|
|
6574
6940
|
}
|
|
6575
6941
|
);
|
|
@@ -6640,14 +7006,14 @@ ${t().prompts.plannerSelfMode}` : "")
|
|
|
6640
7006
|
},
|
|
6641
7007
|
{ role: "user", content: prompt + feedback }
|
|
6642
7008
|
],
|
|
6643
|
-
validate: (t2) => parsePhaseStepPlanJson(t2, parseContext, phasePlan, currentPhase
|
|
7009
|
+
validate: (t2) => parsePhaseStepPlanJson(t2, parseContext, phasePlan, currentPhase)
|
|
6644
7010
|
});
|
|
6645
7011
|
await this.audit?.plannerThought("decompose", text, {
|
|
6646
7012
|
qaCount: input.clarifications.length,
|
|
6647
7013
|
provider,
|
|
6648
7014
|
phaseId: currentPhase.id
|
|
6649
7015
|
});
|
|
6650
|
-
return parsePhaseStepPlanJson(text, parseContext, phasePlan, currentPhase
|
|
7016
|
+
return parsePhaseStepPlanJson(text, parseContext, phasePlan, currentPhase);
|
|
6651
7017
|
}
|
|
6652
7018
|
async chatWithStructuredValidationRetry(input) {
|
|
6653
7019
|
const maxAttempts = plannerStructuredRepairAttemptLimit(input.context);
|
|
@@ -6726,7 +7092,7 @@ function formatPlannerValidationFeedback(err) {
|
|
|
6726
7092
|
function isPlannerStructuredValidationError(err) {
|
|
6727
7093
|
const message = errorMessage2(err);
|
|
6728
7094
|
if (isPlannerTransportFailure(message)) return false;
|
|
6729
|
-
return /^Planner (?:architecture|phase|JSON|draft|complexityAssessment|implementationPhases|iteration)/u.test(message);
|
|
7095
|
+
return /^Planner (?:architecture|phase|PhasePlan|JSON|draft|complexityAssessment|implementationPhases|iteration)/u.test(message);
|
|
6730
7096
|
}
|
|
6731
7097
|
function isPlannerTransportFailure(message) {
|
|
6732
7098
|
const text = message.toLowerCase();
|
|
@@ -6767,6 +7133,12 @@ function buildPlan(draft, opts = {}) {
|
|
|
6767
7133
|
draft.requirementDigest
|
|
6768
7134
|
);
|
|
6769
7135
|
const phaseId = implementationPhases.find((phase) => phase.status === "current")?.id ?? draft.steps.find((step) => step.iterationId)?.iterationId ?? "P1";
|
|
7136
|
+
const architectureDependencyCalibration = calibrateArchitectureModuleDependencies(
|
|
7137
|
+
draft.architectureModules ?? [],
|
|
7138
|
+
draft.dependencies
|
|
7139
|
+
);
|
|
7140
|
+
const architectureModules = architectureDependencyCalibration.architectureModules;
|
|
7141
|
+
const draftDependencies = architectureDependencyCalibration.dependencies;
|
|
6770
7142
|
const iterated = normalizeStepIterations(draft.steps, implementationPhases);
|
|
6771
7143
|
const shaped = calibrateLanguageStepOwnership(
|
|
6772
7144
|
calibrateVModelDependencies(
|
|
@@ -6775,14 +7147,14 @@ function buildPlan(draft, opts = {}) {
|
|
|
6775
7147
|
{
|
|
6776
7148
|
language,
|
|
6777
7149
|
intent: opts.intent ?? "greenfield",
|
|
6778
|
-
architectureModules
|
|
7150
|
+
architectureModules
|
|
6779
7151
|
}
|
|
6780
7152
|
);
|
|
6781
|
-
const mapped = calibrateArchitectureStepMappings(shaped,
|
|
6782
|
-
const contracted = injectArchitectureContractPrompts(mapped,
|
|
7153
|
+
const mapped = calibrateArchitectureStepMappings(shaped, architectureModules);
|
|
7154
|
+
const contracted = injectArchitectureContractPrompts(mapped, architectureModules);
|
|
6783
7155
|
const languageContracted = injectLanguageContractPrompts(contracted, language);
|
|
6784
7156
|
const steps = calibratePlanCoverage(languageContracted, language);
|
|
6785
|
-
const dependencies = language === "python" ? calibratePythonRequirements(
|
|
7157
|
+
const dependencies = language === "python" ? calibratePythonRequirements(draftDependencies) : [...new Set((draftDependencies ?? []).map((d) => d.trim()).filter(Boolean))];
|
|
6786
7158
|
return {
|
|
6787
7159
|
version: "1",
|
|
6788
7160
|
language,
|
|
@@ -6792,7 +7164,7 @@ function buildPlan(draft, opts = {}) {
|
|
|
6792
7164
|
requirementDigest: draft.requirementDigest,
|
|
6793
7165
|
complexityAssessment,
|
|
6794
7166
|
implementationPhases,
|
|
6795
|
-
architectureModules
|
|
7167
|
+
architectureModules,
|
|
6796
7168
|
globalPrompt: draft.globalPrompt,
|
|
6797
7169
|
baselineSummary: opts.baselineSummary ?? "",
|
|
6798
7170
|
dependencies,
|
|
@@ -7002,6 +7374,11 @@ function validateClarifyJson(text, complex, opts = {}) {
|
|
|
7002
7374
|
"clarify missing required external API credential question: ask whether the user has an API/key/token; if not, default to open no-key APIs"
|
|
7003
7375
|
);
|
|
7004
7376
|
}
|
|
7377
|
+
if (opts.languageAmbiguous && !parsed.some(isDevelopmentLanguageClarification)) {
|
|
7378
|
+
throw new Error(
|
|
7379
|
+
"clarify missing required development language question: ask whether the project should use Python or TypeScript/Node.js, with Python as the default option"
|
|
7380
|
+
);
|
|
7381
|
+
}
|
|
7005
7382
|
return parsed;
|
|
7006
7383
|
}
|
|
7007
7384
|
function parsePhasePlanJson(text, context) {
|
|
@@ -7069,7 +7446,7 @@ function parsePhasePlanJson(text, context) {
|
|
|
7069
7446
|
implementationPhases
|
|
7070
7447
|
};
|
|
7071
7448
|
}
|
|
7072
|
-
function parsePhaseStepPlanJson(text, context, phasePlan,
|
|
7449
|
+
function parsePhaseStepPlanJson(text, context, phasePlan, currentPhase) {
|
|
7073
7450
|
const data = safeJson(text);
|
|
7074
7451
|
if (!data || typeof data !== "object") {
|
|
7075
7452
|
throw new Error("Planner did not return a JSON object for phase Step planning.");
|
|
@@ -7077,7 +7454,7 @@ function parsePhaseStepPlanJson(text, context, phasePlan, currentIterationId) {
|
|
|
7077
7454
|
const obj = data;
|
|
7078
7455
|
const rawDeps = Array.isArray(obj.dependencies) ? obj.dependencies : Array.isArray(obj.pythonRequirements) ? obj.pythonRequirements : [];
|
|
7079
7456
|
const draft = {
|
|
7080
|
-
requirementDigest: typeof obj.requirementDigest === "string" && obj.requirementDigest.trim() ? obj.requirementDigest : phasePlan.requirementDigest,
|
|
7457
|
+
requirementDigest: typeof obj.requirementDigest === "string" && obj.requirementDigest.trim() ? obj.requirementDigest : currentPhase.objective || phasePlan.requirementDigest,
|
|
7081
7458
|
globalPrompt: typeof obj.globalPrompt === "string" && obj.globalPrompt.trim() ? obj.globalPrompt : phasePlan.globalPrompt,
|
|
7082
7459
|
projectType: phasePlan.projectType,
|
|
7083
7460
|
complexityAssessment: phasePlan.complexityAssessment,
|
|
@@ -7086,7 +7463,11 @@ function parsePhaseStepPlanJson(text, context, phasePlan, currentIterationId) {
|
|
|
7086
7463
|
architectureModules: obj.architectureModules,
|
|
7087
7464
|
steps: obj.steps
|
|
7088
7465
|
};
|
|
7089
|
-
const parsed = parseDraftPlanJson(JSON.stringify(draft),
|
|
7466
|
+
const parsed = parseDraftPlanJson(JSON.stringify(draft), {
|
|
7467
|
+
...context,
|
|
7468
|
+
phaseDemandText: phaseDemandText(currentPhase)
|
|
7469
|
+
});
|
|
7470
|
+
const currentIterationId = currentPhase.id;
|
|
7090
7471
|
const wrongIteration = parsed.steps.find((step) => (step.iterationId ?? "P1") !== currentIterationId);
|
|
7091
7472
|
if (wrongIteration) {
|
|
7092
7473
|
throw new Error(
|
|
@@ -7121,7 +7502,7 @@ function parseDraftPlanJson(text, context) {
|
|
|
7121
7502
|
context?.baselineSummary ?? ""
|
|
7122
7503
|
].join("\n"));
|
|
7123
7504
|
const rawDeps = Array.isArray(obj.dependencies) ? obj.dependencies : Array.isArray(obj.pythonRequirements) ? obj.pythonRequirements : [];
|
|
7124
|
-
|
|
7505
|
+
let dependencies = rawDeps.filter((s) => typeof s === "string");
|
|
7125
7506
|
if (context) {
|
|
7126
7507
|
const validPhaseNames = new Set(PHASES);
|
|
7127
7508
|
const nonCanonical = steps.map((rawStep, index) => {
|
|
@@ -7154,7 +7535,12 @@ function parseDraftPlanJson(text, context) {
|
|
|
7154
7535
|
if (!architectureResult.success) {
|
|
7155
7536
|
throw new Error(`Planner architectureModules invalid: ${architectureResult.error.issues.map((i) => i.message).join("; ")}`);
|
|
7156
7537
|
}
|
|
7157
|
-
const
|
|
7538
|
+
const architectureDependencyCalibration = calibrateArchitectureModuleDependencies(
|
|
7539
|
+
architectureResult.data,
|
|
7540
|
+
dependencies
|
|
7541
|
+
);
|
|
7542
|
+
const architectureModules = architectureDependencyCalibration.architectureModules;
|
|
7543
|
+
dependencies = architectureDependencyCalibration.dependencies;
|
|
7158
7544
|
const parsedComplexityAssessment = parseComplexityAssessment(obj.complexityAssessment);
|
|
7159
7545
|
if (context && !parsedComplexityAssessment) {
|
|
7160
7546
|
throw new Error("Planner JSON missing valid complexityAssessment; complexity must be assessed during plan decomposition.");
|
|
@@ -7188,14 +7574,7 @@ function parseDraftPlanJson(text, context) {
|
|
|
7188
7574
|
}
|
|
7189
7575
|
if (context) {
|
|
7190
7576
|
const demand = analyzeArchitectureDemand(
|
|
7191
|
-
|
|
7192
|
-
requirementDigest: digest,
|
|
7193
|
-
rawRequirement: context.rawRequirement,
|
|
7194
|
-
userAddenda: context.userAddenda,
|
|
7195
|
-
globalPrompt,
|
|
7196
|
-
baselineSummary: context.baselineSummary,
|
|
7197
|
-
intent: context.intent
|
|
7198
|
-
},
|
|
7577
|
+
architectureDemandInputForDraft(context, digest, globalPrompt),
|
|
7199
7578
|
context.language
|
|
7200
7579
|
);
|
|
7201
7580
|
const forcedPhaseSplit = hasForcedPhaseSplit([
|
|
@@ -7245,6 +7624,34 @@ function parseDraftPlanJson(text, context) {
|
|
|
7245
7624
|
steps: stepsWithIterations
|
|
7246
7625
|
};
|
|
7247
7626
|
}
|
|
7627
|
+
function architectureDemandInputForDraft(context, digest, globalPrompt) {
|
|
7628
|
+
if (!context.phaseDemandText) {
|
|
7629
|
+
return {
|
|
7630
|
+
requirementDigest: digest,
|
|
7631
|
+
rawRequirement: context.rawRequirement,
|
|
7632
|
+
userAddenda: context.userAddenda,
|
|
7633
|
+
globalPrompt,
|
|
7634
|
+
baselineSummary: context.baselineSummary,
|
|
7635
|
+
intent: context.intent
|
|
7636
|
+
};
|
|
7637
|
+
}
|
|
7638
|
+
return {
|
|
7639
|
+
requirementDigest: context.phaseDemandText,
|
|
7640
|
+
baselineSummary: context.baselineSummary,
|
|
7641
|
+
intent: context.intent
|
|
7642
|
+
};
|
|
7643
|
+
}
|
|
7644
|
+
function phaseDemandText(currentPhase) {
|
|
7645
|
+
const gate = currentPhase.verificationGate;
|
|
7646
|
+
return [
|
|
7647
|
+
currentPhase.title,
|
|
7648
|
+
currentPhase.objective,
|
|
7649
|
+
currentPhase.scope.join("\n"),
|
|
7650
|
+
currentPhase.deliverables.join("\n"),
|
|
7651
|
+
gate?.summary ?? "",
|
|
7652
|
+
gate?.checks.join("\n") ?? ""
|
|
7653
|
+
].map((part) => part.trim()).filter(Boolean).join("\n");
|
|
7654
|
+
}
|
|
7248
7655
|
function parseComplexityAssessment(value) {
|
|
7249
7656
|
const result = ComplexityAssessmentSchema.safeParse(value);
|
|
7250
7657
|
return result.success ? result.data : void 0;
|
|
@@ -7331,6 +7738,15 @@ ${question.options.map((option) => option.answer).join("\n")}`.toLowerCase();
|
|
|
7331
7738
|
const externalApiContext = /\b(api|url|endpoint|provider|external|third[- ]party|fetch|request)\b/u.test(text) || /外部|第三方|接口|数据源|天气|节假日|联网/u.test(text);
|
|
7332
7739
|
return externalApiContext && asksCredential && mentionsNoKeyFallback;
|
|
7333
7740
|
}
|
|
7741
|
+
function isDevelopmentLanguageClarification(question) {
|
|
7742
|
+
const text = `${question.question}
|
|
7743
|
+
${question.why}
|
|
7744
|
+
${question.options.map((option) => option.answer).join("\n")}`.toLowerCase();
|
|
7745
|
+
const mentionsPython = /\bpython\b/u.test(text) || /python\s*脚本|python\s*项目/u.test(text);
|
|
7746
|
+
const mentionsTypeScript = /\btypescript\b|\btype\s*script\b|\bnode(?:\.js)?\b/u.test(text) || /type\s*script|ts\s*(程序|工程|项目|脚本|语言|实现)|typescript\s*项目/u.test(text);
|
|
7747
|
+
const asksLanguage = /\b(language|runtime|implementation stack|programming language)\b/u.test(text) || /开发语言|编程语言|实现语言|运行时|技术栈/u.test(text);
|
|
7748
|
+
return asksLanguage && mentionsPython && mentionsTypeScript;
|
|
7749
|
+
}
|
|
7334
7750
|
function projectShapeSignals(text) {
|
|
7335
7751
|
const lower = text.toLowerCase();
|
|
7336
7752
|
const libraryLike = /\b(api[- ]?library|library|sdk|package|npm package|pypi package|client library|api client|reusable module|public api)\b/u.test(lower) || /api\s*(库|客户端)|公共\s*api|可复用接口|公共库|库项目|软件包|客户端库|开发包/u.test(text);
|
|
@@ -7458,11 +7874,11 @@ ${text.slice(0, 500)}`);
|
|
|
7458
7874
|
}
|
|
7459
7875
|
|
|
7460
7876
|
// src/core/incremental.ts
|
|
7461
|
-
import { promises as
|
|
7877
|
+
import { promises as fs9 } from "fs";
|
|
7462
7878
|
import path10 from "path";
|
|
7463
7879
|
|
|
7464
7880
|
// src/core/project_memory.ts
|
|
7465
|
-
import { promises as
|
|
7881
|
+
import { promises as fs8 } from "fs";
|
|
7466
7882
|
import path9 from "path";
|
|
7467
7883
|
var PROJECT_MEMORY_PATH = ".xcompiler/project_memory.json";
|
|
7468
7884
|
async function refreshProjectMemory(ws, opts = {}) {
|
|
@@ -7620,7 +8036,7 @@ async function defaultPlanMetadataPath(root) {
|
|
|
7620
8036
|
}
|
|
7621
8037
|
async function fileExists(filePath) {
|
|
7622
8038
|
try {
|
|
7623
|
-
await
|
|
8039
|
+
await fs8.stat(filePath);
|
|
7624
8040
|
return true;
|
|
7625
8041
|
} catch {
|
|
7626
8042
|
return false;
|
|
@@ -7648,7 +8064,7 @@ async function readWorkspaceText(ws, rel, maxChars) {
|
|
|
7648
8064
|
}
|
|
7649
8065
|
async function readAbsoluteText(fullPath, maxChars) {
|
|
7650
8066
|
try {
|
|
7651
|
-
const raw = await
|
|
8067
|
+
const raw = await fs8.readFile(fullPath, "utf8");
|
|
7652
8068
|
return truncate(raw, maxChars);
|
|
7653
8069
|
} catch {
|
|
7654
8070
|
return "";
|
|
@@ -7670,7 +8086,7 @@ async function walk(abs, rel, out, depth) {
|
|
|
7670
8086
|
if (depth > 6 || out.length >= 160) return;
|
|
7671
8087
|
let entries;
|
|
7672
8088
|
try {
|
|
7673
|
-
entries = await
|
|
8089
|
+
entries = await fs8.readdir(abs, { withFileTypes: true });
|
|
7674
8090
|
} catch {
|
|
7675
8091
|
return;
|
|
7676
8092
|
}
|
|
@@ -7897,20 +8313,20 @@ function extractImportedStems(text) {
|
|
|
7897
8313
|
function normalizeImportStem(value) {
|
|
7898
8314
|
return value.replace(/^\.\//u, "").replace(/^\.\.\//u, "").replace(/\.(js|ts|tsx|py)$/u, "").replace(/\/index$/u, "").trim();
|
|
7899
8315
|
}
|
|
7900
|
-
function extractContractsFromDoc(
|
|
8316
|
+
function extractContractsFromDoc(path32, text) {
|
|
7901
8317
|
const contracts = [];
|
|
7902
8318
|
for (const rawLine of text.split("\n")) {
|
|
7903
8319
|
const line = rawLine.replace(/^[-*#\d.\s]+/u, "").trim();
|
|
7904
8320
|
if (line.length < 18) continue;
|
|
7905
8321
|
const lower = line.toLowerCase();
|
|
7906
8322
|
if (INVARIANT_RE.test(lower)) {
|
|
7907
|
-
contracts.push({ kind: "invariant", subject:
|
|
8323
|
+
contracts.push({ kind: "invariant", subject: path32, path: path32, detail: trimContractLine(line) });
|
|
7908
8324
|
} else if (LIMITATION_RE.test(lower)) {
|
|
7909
|
-
contracts.push({ kind: "limitation", subject:
|
|
8325
|
+
contracts.push({ kind: "limitation", subject: path32, path: path32, detail: trimContractLine(line) });
|
|
7910
8326
|
} else if (EXTENSION_RE.test(lower)) {
|
|
7911
|
-
contracts.push({ kind: "extension-point", subject:
|
|
8327
|
+
contracts.push({ kind: "extension-point", subject: path32, path: path32, detail: trimContractLine(line) });
|
|
7912
8328
|
} else if (INTEGRATION_RE.test(lower)) {
|
|
7913
|
-
contracts.push({ kind: "integration", subject:
|
|
8329
|
+
contracts.push({ kind: "integration", subject: path32, path: path32, detail: trimContractLine(line) });
|
|
7914
8330
|
}
|
|
7915
8331
|
}
|
|
7916
8332
|
return contracts;
|
|
@@ -8009,7 +8425,7 @@ ${sec.slice(0, remain)}
|
|
|
8009
8425
|
}
|
|
8010
8426
|
async function loadPlanSummary(_ws, planPath, label) {
|
|
8011
8427
|
try {
|
|
8012
|
-
const raw = await
|
|
8428
|
+
const raw = await fs9.readFile(planPath, "utf8");
|
|
8013
8429
|
const json = JSON.parse(raw);
|
|
8014
8430
|
const phasePlanParsed = PhasePlanSchema.safeParse(json);
|
|
8015
8431
|
if (phasePlanParsed.success) {
|
|
@@ -8077,7 +8493,7 @@ async function defaultBaselinePlanPath(root) {
|
|
|
8077
8493
|
}
|
|
8078
8494
|
async function fileExists2(filePath) {
|
|
8079
8495
|
try {
|
|
8080
|
-
await
|
|
8496
|
+
await fs9.stat(filePath);
|
|
8081
8497
|
return true;
|
|
8082
8498
|
} catch {
|
|
8083
8499
|
return false;
|
|
@@ -8141,7 +8557,7 @@ async function walk2(abs, rel, out, depth) {
|
|
|
8141
8557
|
if (depth > 6 || out.length >= 120) return;
|
|
8142
8558
|
let entries;
|
|
8143
8559
|
try {
|
|
8144
|
-
entries = await
|
|
8560
|
+
entries = await fs9.readdir(abs, { withFileTypes: true });
|
|
8145
8561
|
} catch {
|
|
8146
8562
|
return;
|
|
8147
8563
|
}
|
|
@@ -8319,7 +8735,7 @@ function renderSubTasks(tasks, depth) {
|
|
|
8319
8735
|
}
|
|
8320
8736
|
|
|
8321
8737
|
// src/audit/audit.ts
|
|
8322
|
-
import { promises as
|
|
8738
|
+
import { promises as fs10, appendFileSync, mkdirSync, existsSync, writeFileSync } from "fs";
|
|
8323
8739
|
import { createHash } from "crypto";
|
|
8324
8740
|
import path11 from "path";
|
|
8325
8741
|
var AuditLogger = class {
|
|
@@ -8476,8 +8892,8 @@ var AuditLogger = class {
|
|
|
8476
8892
|
});
|
|
8477
8893
|
}
|
|
8478
8894
|
/**
|
|
8479
|
-
* 记录一轮 Executor
|
|
8480
|
-
* 写入 jsonl + markdown
|
|
8895
|
+
* 记录一轮 Executor 执行摘要:简短 intent、issue 处理方案、计划调用的 actions、是否完成。
|
|
8896
|
+
* 写入 jsonl + markdown 折叠块,交付时可追溯每轮决策和动作。
|
|
8481
8897
|
*/
|
|
8482
8898
|
async executorTurn(stepId, role, round, payload) {
|
|
8483
8899
|
const storedPayload = protectAuditContent(payload, this.contentMode);
|
|
@@ -8549,8 +8965,8 @@ ${t().audit.processLogPreamble}
|
|
|
8549
8965
|
}
|
|
8550
8966
|
async appendMd(text) {
|
|
8551
8967
|
this.mdQueue = this.mdQueue.then(
|
|
8552
|
-
() =>
|
|
8553
|
-
() =>
|
|
8968
|
+
() => fs10.appendFile(this.mdAbs, text, "utf8"),
|
|
8969
|
+
() => fs10.appendFile(this.mdAbs, text, "utf8")
|
|
8554
8970
|
).catch((err) => {
|
|
8555
8971
|
console.warn(t().audit.markdownAppendFailed(err.message));
|
|
8556
8972
|
});
|
|
@@ -8632,7 +9048,7 @@ function redactText(value) {
|
|
|
8632
9048
|
}
|
|
8633
9049
|
|
|
8634
9050
|
// src/core/lock.ts
|
|
8635
|
-
import { promises as
|
|
9051
|
+
import { promises as fs11, unlinkSync } from "fs";
|
|
8636
9052
|
import path12 from "path";
|
|
8637
9053
|
import os from "os";
|
|
8638
9054
|
var LockError = class extends Error {
|
|
@@ -8659,7 +9075,7 @@ function isAlive(pid) {
|
|
|
8659
9075
|
}
|
|
8660
9076
|
async function readLock(file) {
|
|
8661
9077
|
try {
|
|
8662
|
-
const raw = await
|
|
9078
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
8663
9079
|
return JSON.parse(raw);
|
|
8664
9080
|
} catch {
|
|
8665
9081
|
return null;
|
|
@@ -8667,7 +9083,7 @@ async function readLock(file) {
|
|
|
8667
9083
|
}
|
|
8668
9084
|
async function acquireLock(workspace, command, options = {}) {
|
|
8669
9085
|
const file = lockPath(workspace);
|
|
8670
|
-
await
|
|
9086
|
+
await fs11.mkdir(path12.dirname(file), { recursive: true });
|
|
8671
9087
|
const info = {
|
|
8672
9088
|
pid: process.pid,
|
|
8673
9089
|
host: os.hostname(),
|
|
@@ -8677,7 +9093,7 @@ async function acquireLock(workspace, command, options = {}) {
|
|
|
8677
9093
|
const payload = JSON.stringify(info, null, 2);
|
|
8678
9094
|
const tryCreate = async () => {
|
|
8679
9095
|
try {
|
|
8680
|
-
const fh = await
|
|
9096
|
+
const fh = await fs11.open(file, "wx");
|
|
8681
9097
|
await fh.writeFile(payload, "utf8");
|
|
8682
9098
|
await fh.close();
|
|
8683
9099
|
return true;
|
|
@@ -8690,14 +9106,14 @@ async function acquireLock(workspace, command, options = {}) {
|
|
|
8690
9106
|
return makeReleaser(file);
|
|
8691
9107
|
}
|
|
8692
9108
|
if (options.force) {
|
|
8693
|
-
await
|
|
9109
|
+
await fs11.writeFile(file, payload, "utf8");
|
|
8694
9110
|
return makeReleaser(file);
|
|
8695
9111
|
}
|
|
8696
9112
|
const existing = await readLock(file);
|
|
8697
9113
|
if (existing) {
|
|
8698
9114
|
const sameHost = existing.host === info.host;
|
|
8699
9115
|
if (sameHost && !isAlive(existing.pid)) {
|
|
8700
|
-
await
|
|
9116
|
+
await fs11.writeFile(file, payload, "utf8");
|
|
8701
9117
|
return makeReleaser(file);
|
|
8702
9118
|
}
|
|
8703
9119
|
throw new LockError(
|
|
@@ -8706,7 +9122,7 @@ async function acquireLock(workspace, command, options = {}) {
|
|
|
8706
9122
|
existing
|
|
8707
9123
|
);
|
|
8708
9124
|
}
|
|
8709
|
-
await
|
|
9125
|
+
await fs11.writeFile(file, payload, "utf8");
|
|
8710
9126
|
return makeReleaser(file);
|
|
8711
9127
|
}
|
|
8712
9128
|
function makeReleaser(file) {
|
|
@@ -8715,7 +9131,7 @@ function makeReleaser(file) {
|
|
|
8715
9131
|
if (released) return;
|
|
8716
9132
|
released = true;
|
|
8717
9133
|
try {
|
|
8718
|
-
await
|
|
9134
|
+
await fs11.unlink(file);
|
|
8719
9135
|
} catch {
|
|
8720
9136
|
}
|
|
8721
9137
|
};
|
|
@@ -9079,8 +9495,51 @@ function resolveClarificationAnswer(q, rawAnswer) {
|
|
|
9079
9495
|
}
|
|
9080
9496
|
return answer;
|
|
9081
9497
|
}
|
|
9082
|
-
function resolveCompileLanguage(
|
|
9083
|
-
|
|
9498
|
+
function resolveCompileLanguage(inputOrConfigured, intent, baseline) {
|
|
9499
|
+
if (typeof inputOrConfigured === "string") {
|
|
9500
|
+
return isIncrementalIntent(intent ?? "greenfield") ? baseline?.language ?? inputOrConfigured : inputOrConfigured;
|
|
9501
|
+
}
|
|
9502
|
+
const input = inputOrConfigured;
|
|
9503
|
+
if (isIncrementalIntent(input.intent ?? "greenfield") && input.baseline?.language) {
|
|
9504
|
+
return { language: input.baseline.language, source: "baseline", ambiguous: false };
|
|
9505
|
+
}
|
|
9506
|
+
const topicInferred = inferCompileLanguageFromText(input.rawRequirement);
|
|
9507
|
+
if (topicInferred) {
|
|
9508
|
+
return {
|
|
9509
|
+
language: topicInferred,
|
|
9510
|
+
source: "topic",
|
|
9511
|
+
ambiguous: false
|
|
9512
|
+
};
|
|
9513
|
+
}
|
|
9514
|
+
const clarificationText = formatLanguageClarificationText(input.clarifications ?? []);
|
|
9515
|
+
const clarifiedInferred = inferCompileLanguageFromText([
|
|
9516
|
+
clarificationText,
|
|
9517
|
+
input.userAddenda ?? ""
|
|
9518
|
+
].join("\n"));
|
|
9519
|
+
if (clarifiedInferred) {
|
|
9520
|
+
return { language: clarifiedInferred, source: "clarification", ambiguous: false };
|
|
9521
|
+
}
|
|
9522
|
+
return { language: "python", source: "default", ambiguous: true };
|
|
9523
|
+
}
|
|
9524
|
+
function inferCompileLanguageFromText(text) {
|
|
9525
|
+
const normalized = text.toLowerCase().replace(/[,。;:、()【】]/gu, " ").replace(/\s+/gu, " ");
|
|
9526
|
+
const pythonStrong = /\bpython\b/u.test(normalized) || /python\s*脚本/u.test(normalized) || /\.py\b/u.test(normalized) || /\bpytest\b/u.test(normalized) || /\bpip\b/u.test(normalized);
|
|
9527
|
+
const typescriptStrong = /\btypescript\b/u.test(normalized) || /\btype\s*script\b/u.test(normalized) || /(^|[^a-z0-9])ts\s*(程序|工程|项目|脚本|语言|实现)/u.test(normalized) || /\.tsx?\b/u.test(normalized) || /\btsx\b/u.test(normalized);
|
|
9528
|
+
if (pythonStrong && !typescriptStrong) return "python";
|
|
9529
|
+
if (typescriptStrong && !pythonStrong) return "typescript";
|
|
9530
|
+
const pythonWeak = /\bopenpyxl\b/u.test(normalized) || /\bpandas\b/u.test(normalized) || /\bfastapi\b/u.test(normalized) || /\bflask\b/u.test(normalized);
|
|
9531
|
+
const typescriptWeak = /\bnode(?:\.js)?\b/u.test(normalized) || /\bnpm\b/u.test(normalized) || /\bvitest\b/u.test(normalized) || /\bpackage\.json\b/u.test(normalized) || /\bjavascript\b/u.test(normalized) || /\bjs\s*(程序|工程|项目|脚本|语言|实现)\b/u.test(normalized);
|
|
9532
|
+
if ((pythonStrong || pythonWeak) && !(typescriptStrong || typescriptWeak)) return "python";
|
|
9533
|
+
if ((typescriptStrong || typescriptWeak) && !(pythonStrong || pythonWeak)) return "typescript";
|
|
9534
|
+
return void 0;
|
|
9535
|
+
}
|
|
9536
|
+
function formatLanguageClarificationText(input) {
|
|
9537
|
+
return input.map((item) => [
|
|
9538
|
+
item.question,
|
|
9539
|
+
item.answer,
|
|
9540
|
+
item.why ?? "",
|
|
9541
|
+
...(item.options ?? []).map((option) => option.answer)
|
|
9542
|
+
].join("\n")).join("\n\n");
|
|
9084
9543
|
}
|
|
9085
9544
|
async function runCompile(opts) {
|
|
9086
9545
|
const io = opts.io ?? silentRuntimeIO;
|
|
@@ -9157,15 +9616,7 @@ async function runCompile(opts) {
|
|
|
9157
9616
|
if (baseline.summary) {
|
|
9158
9617
|
await runtimeLog(io, "success", M.compile.baselineLoaded(intent, baseline.sources.join(", ")));
|
|
9159
9618
|
}
|
|
9160
|
-
const
|
|
9161
|
-
if (isIncrementalIntent(intent) && baseline.language && baseline.language !== cfg.agent.language) {
|
|
9162
|
-
await runtimeLog(
|
|
9163
|
-
io,
|
|
9164
|
-
"warning",
|
|
9165
|
-
M.compile.baselineLanguageOverride(baseline.language, baseline.languageSource ?? "baseline", cfg.agent.language)
|
|
9166
|
-
);
|
|
9167
|
-
}
|
|
9168
|
-
const planner = new Planner(router.for("Planner"), audit, language);
|
|
9619
|
+
const plannerClient = router.for("Planner");
|
|
9169
9620
|
const trace = (msg) => {
|
|
9170
9621
|
if (xcEnv("TRACE") === "1") {
|
|
9171
9622
|
void runtimeLog(io, "dim", t().audit.traceLine("xcompiler-trace", msg));
|
|
@@ -9174,7 +9625,7 @@ async function runCompile(opts) {
|
|
|
9174
9625
|
let rawRequirement;
|
|
9175
9626
|
if (topicMode) {
|
|
9176
9627
|
trace("topic.read");
|
|
9177
|
-
rawRequirement = await
|
|
9628
|
+
rawRequirement = await fs12.readFile(path13.resolve(opts.topicFile), "utf8");
|
|
9178
9629
|
if (!rawRequirement.trim()) {
|
|
9179
9630
|
await runtimeLog(io, "error", M.compile.topicEmptyExit);
|
|
9180
9631
|
await audit.end({ status: "aborted", reason: "empty topic file" });
|
|
@@ -9195,19 +9646,26 @@ async function runCompile(opts) {
|
|
|
9195
9646
|
await audit.userInput(M.compile.auditOriginalRequirement, rawRequirement);
|
|
9196
9647
|
trace("audit.userInput.intake.done");
|
|
9197
9648
|
}
|
|
9649
|
+
const initialLanguage = resolveCompileLanguage({
|
|
9650
|
+
rawRequirement,
|
|
9651
|
+
intent,
|
|
9652
|
+
baseline
|
|
9653
|
+
});
|
|
9198
9654
|
trace("clarify.section.enter");
|
|
9199
9655
|
const clarifications = [];
|
|
9200
9656
|
let clarificationQuestions = [];
|
|
9201
9657
|
trace(`clarify.section.flag yes=${opts.yes} topicMode=${topicMode}`);
|
|
9202
9658
|
if (!opts.yes && !topicMode) {
|
|
9659
|
+
const clarifyPlanner = new Planner(plannerClient, audit, initialLanguage.language);
|
|
9203
9660
|
trace("ora.clarify.start");
|
|
9204
9661
|
const spin = io.progress(M.compile.spinClarify, { animate: false });
|
|
9205
9662
|
trace("ora.clarify.started");
|
|
9206
9663
|
try {
|
|
9207
9664
|
trace("planner.clarify.call");
|
|
9208
|
-
clarificationQuestions = await
|
|
9665
|
+
clarificationQuestions = await clarifyPlanner.clarify(rawRequirement, {
|
|
9209
9666
|
intent,
|
|
9210
|
-
hasBaseline: !!baseline.summary
|
|
9667
|
+
hasBaseline: !!baseline.summary,
|
|
9668
|
+
languageAmbiguous: initialLanguage.ambiguous
|
|
9211
9669
|
});
|
|
9212
9670
|
trace(`planner.clarify.return n=${clarificationQuestions.length}`);
|
|
9213
9671
|
spin.succeed(M.compile.clarifySucceed(clarificationQuestions.length));
|
|
@@ -9296,6 +9754,20 @@ ${M.compile.topicPreviewHeader}`);
|
|
|
9296
9754
|
}
|
|
9297
9755
|
trace("ws.readFile.finalTopic");
|
|
9298
9756
|
const finalTopicMd = await ws.readFile(draftTopic);
|
|
9757
|
+
const languageResolution = resolveCompileLanguage({
|
|
9758
|
+
rawRequirement: finalTopicMd,
|
|
9759
|
+
clarifications,
|
|
9760
|
+
userAddenda,
|
|
9761
|
+
intent,
|
|
9762
|
+
baseline
|
|
9763
|
+
});
|
|
9764
|
+
const language = languageResolution.language;
|
|
9765
|
+
await audit.event("note", `target language resolved: ${language}`, {
|
|
9766
|
+
messageId: "compile.language_resolved",
|
|
9767
|
+
language,
|
|
9768
|
+
source: languageResolution.source,
|
|
9769
|
+
ambiguous: languageResolution.ambiguous
|
|
9770
|
+
});
|
|
9299
9771
|
await archiveIfExists(ws, DOC_NAMES.topic, audit);
|
|
9300
9772
|
await ws.writeFile(DOC_NAMES.topic, finalTopicMd);
|
|
9301
9773
|
await audit.event("topic.persist", M.compile.auditTopicPersisted(ws.abs(DOC_NAMES.topic)), {
|
|
@@ -9309,6 +9781,7 @@ ${M.compile.topicPreviewHeader}`);
|
|
|
9309
9781
|
trace("ora.spin2.started");
|
|
9310
9782
|
let draft;
|
|
9311
9783
|
try {
|
|
9784
|
+
const planner = new Planner(plannerClient, audit, language);
|
|
9312
9785
|
const plannerInput = {
|
|
9313
9786
|
rawRequirement: finalTopicMd,
|
|
9314
9787
|
clarifications,
|
|
@@ -9446,7 +9919,7 @@ async function tryLoadPhasePlan(phasePlanPath) {
|
|
|
9446
9919
|
}
|
|
9447
9920
|
async function intake(inputFile, io) {
|
|
9448
9921
|
if (inputFile) {
|
|
9449
|
-
return
|
|
9922
|
+
return fs12.readFile(path13.resolve(inputFile), "utf8");
|
|
9450
9923
|
}
|
|
9451
9924
|
return requireRuntimeInteraction(io, "requirement intake").readMultiline({
|
|
9452
9925
|
message: t().compile.requirementInputHint
|
|
@@ -9490,10 +9963,10 @@ function renderTopicDraft(raw, qa, addenda = "") {
|
|
|
9490
9963
|
}
|
|
9491
9964
|
|
|
9492
9965
|
// src/runtime/run.ts
|
|
9493
|
-
import
|
|
9966
|
+
import path28 from "path";
|
|
9494
9967
|
|
|
9495
9968
|
// src/workspace/git.ts
|
|
9496
|
-
import { promises as
|
|
9969
|
+
import { promises as fs13 } from "fs";
|
|
9497
9970
|
import path14 from "path";
|
|
9498
9971
|
import { simpleGit } from "simple-git";
|
|
9499
9972
|
var RUNTIME_EXCLUDE_PATTERNS = [
|
|
@@ -9545,14 +10018,14 @@ var GitService = class {
|
|
|
9545
10018
|
const excludePath = this.ws.abs(".git/info/exclude");
|
|
9546
10019
|
let current2 = "";
|
|
9547
10020
|
try {
|
|
9548
|
-
current2 = await
|
|
10021
|
+
current2 = await fs13.readFile(excludePath, "utf8");
|
|
9549
10022
|
} catch {
|
|
9550
10023
|
return;
|
|
9551
10024
|
}
|
|
9552
10025
|
const missing = RUNTIME_EXCLUDE_PATTERNS.filter((pattern) => !current2.split(/\r?\n/u).includes(pattern));
|
|
9553
10026
|
if (missing.length === 0) return;
|
|
9554
10027
|
const prefix = current2.endsWith("\n") ? "\n" : "\n\n";
|
|
9555
|
-
await
|
|
10028
|
+
await fs13.appendFile(
|
|
9556
10029
|
excludePath,
|
|
9557
10030
|
`${prefix}# XCompiler runtime artifacts
|
|
9558
10031
|
${missing.join("\n")}
|
|
@@ -9597,7 +10070,7 @@ import { existsSync as existsSync2, readFileSync } from "fs";
|
|
|
9597
10070
|
|
|
9598
10071
|
// src/sandbox/subprocess.ts
|
|
9599
10072
|
import { spawn } from "child_process";
|
|
9600
|
-
import { promises as
|
|
10073
|
+
import { promises as fs14 } from "fs";
|
|
9601
10074
|
import path15 from "path";
|
|
9602
10075
|
import crypto from "crypto";
|
|
9603
10076
|
|
|
@@ -9651,6 +10124,9 @@ function formatCommand(cmd, argv) {
|
|
|
9651
10124
|
}
|
|
9652
10125
|
|
|
9653
10126
|
// src/sandbox/subprocess.ts
|
|
10127
|
+
var INSTALL_IDLE_TIMEOUT_FLOOR_MS = 15 * 6e4;
|
|
10128
|
+
var INSTALL_IDLE_TIMEOUT_MULTIPLIER = 10;
|
|
10129
|
+
var INSTALL_PROGRESS_CHECK_INTERVAL_MS = 15e3;
|
|
9654
10130
|
var SubprocessSandbox = class {
|
|
9655
10131
|
constructor(opts) {
|
|
9656
10132
|
this.opts = opts;
|
|
@@ -9704,12 +10180,13 @@ var SubprocessSandbox = class {
|
|
|
9704
10180
|
* Python → requirements.txt (venv + pip);TypeScript → package.json (npm install)。
|
|
9705
10181
|
*/
|
|
9706
10182
|
async build(manifestFile) {
|
|
9707
|
-
await
|
|
10183
|
+
await fs14.mkdir(this.sandboxAbs, { recursive: true });
|
|
9708
10184
|
if (this.opts.inheritEnv === false) {
|
|
9709
10185
|
await Promise.all([
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
10186
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "home"), { recursive: true }),
|
|
10187
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "tmp"), { recursive: true }),
|
|
10188
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "npm-cache"), { recursive: true }),
|
|
10189
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "pip-cache"), { recursive: true })
|
|
9713
10190
|
]);
|
|
9714
10191
|
}
|
|
9715
10192
|
if (this.language === "typescript") {
|
|
@@ -9719,10 +10196,10 @@ var SubprocessSandbox = class {
|
|
|
9719
10196
|
}
|
|
9720
10197
|
async buildPython(requirementsTxt) {
|
|
9721
10198
|
const reqAbs = this.opts.ws.abs(requirementsTxt);
|
|
9722
|
-
const reqContent = await
|
|
10199
|
+
const reqContent = await fs14.readFile(reqAbs, "utf8").catch(() => "");
|
|
9723
10200
|
const sig = crypto.createHash("sha256").update(reqContent).digest("hex");
|
|
9724
|
-
const cached = await
|
|
9725
|
-
const venvExists = await
|
|
10201
|
+
const cached = await fs14.readFile(this.cacheFile, "utf8").catch(() => "");
|
|
10202
|
+
const venvExists = await fs14.stat(this.pythonInVenv).then(() => true).catch(() => false);
|
|
9726
10203
|
if (venvExists && cached === sig) {
|
|
9727
10204
|
return { rebuilt: false, reason: "cache hit" };
|
|
9728
10205
|
}
|
|
@@ -9739,7 +10216,7 @@ var SubprocessSandbox = class {
|
|
|
9739
10216
|
}
|
|
9740
10217
|
}
|
|
9741
10218
|
const pyVenv = this.pythonInVenv;
|
|
9742
|
-
const pyVenvExists = await
|
|
10219
|
+
const pyVenvExists = await fs14.stat(pyVenv).then(() => true).catch(() => false);
|
|
9743
10220
|
if (!pyVenvExists) {
|
|
9744
10221
|
throw new Error(`venv python missing after creation: ${pyVenv} (install system package python3-venv / python3-virtualenv)`);
|
|
9745
10222
|
}
|
|
@@ -9756,17 +10233,26 @@ Install on the host: apt-get install python3-venv python3-pip or yum install
|
|
|
9756
10233
|
}
|
|
9757
10234
|
}
|
|
9758
10235
|
if (reqContent.trim().length > 0) {
|
|
10236
|
+
const progressWatch = createInstallProgressWatch(
|
|
10237
|
+
[this.venvAbs, path15.join(this.sandboxAbs, "pip-cache")],
|
|
10238
|
+
this.opts.limits,
|
|
10239
|
+
"pip install"
|
|
10240
|
+
);
|
|
10241
|
+
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.command("subprocess", `pip install -r ${reqAbs}`), {
|
|
10242
|
+
messageId: "sandbox.command",
|
|
10243
|
+
cwd: this.opts.ws.root,
|
|
10244
|
+
progressIdleTimeoutMs: progressWatch.idleTimeoutMs,
|
|
10245
|
+
progressPaths: progressWatch.paths
|
|
10246
|
+
});
|
|
9759
10247
|
const r = await execRaw(pyVenv, ["-m", "pip", "install", "-r", reqAbs, "--quiet", "--disable-pip-version-check"], {
|
|
9760
|
-
|
|
10248
|
+
env: this.baseEnvironment(),
|
|
10249
|
+
progressWatch
|
|
9761
10250
|
});
|
|
9762
10251
|
if (r.exitCode !== 0) {
|
|
9763
|
-
throw new Error(
|
|
9764
|
-
`pip install failed (venv=${this.venvAbs}, requirements=${reqAbs}):
|
|
9765
|
-
${r.stderr || r.stdout}`
|
|
9766
|
-
);
|
|
10252
|
+
throw new Error(formatExecFailure(`pip install failed (venv=${this.venvAbs}, requirements=${reqAbs})`, r));
|
|
9767
10253
|
}
|
|
9768
10254
|
}
|
|
9769
|
-
await
|
|
10255
|
+
await fs14.writeFile(this.cacheFile, sig, "utf8");
|
|
9770
10256
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.subprocessBuilt(!!reqContent), {
|
|
9771
10257
|
messageId: "sandbox.subprocess_built"
|
|
9772
10258
|
});
|
|
@@ -9774,29 +10260,39 @@ ${r.stderr || r.stdout}`
|
|
|
9774
10260
|
}
|
|
9775
10261
|
async buildNode(manifestFile) {
|
|
9776
10262
|
const pkgAbs = this.opts.ws.abs(manifestFile);
|
|
9777
|
-
const pkgContent = await
|
|
10263
|
+
const pkgContent = await fs14.readFile(pkgAbs, "utf8").catch(() => "");
|
|
9778
10264
|
if (!pkgContent) {
|
|
9779
10265
|
return { rebuilt: false, reason: "no package.json yet" };
|
|
9780
10266
|
}
|
|
9781
10267
|
const lockAbs = this.opts.ws.abs("package-lock.json");
|
|
9782
|
-
const lockContent = await
|
|
10268
|
+
const lockContent = await fs14.readFile(lockAbs, "utf8").catch(() => "");
|
|
9783
10269
|
const sig = crypto.createHash("sha256").update(pkgContent + "\n" + lockContent).digest("hex");
|
|
9784
|
-
const cached = await
|
|
9785
|
-
const modulesExist = await
|
|
10270
|
+
const cached = await fs14.readFile(this.cacheFile, "utf8").catch(() => "");
|
|
10271
|
+
const modulesExist = await fs14.stat(this.opts.ws.abs("node_modules")).then(() => true).catch(() => false);
|
|
9786
10272
|
if (modulesExist && cached === sig) {
|
|
9787
10273
|
return { rebuilt: false, reason: "cache hit" };
|
|
9788
10274
|
}
|
|
9789
10275
|
const installArgs = lockContent.trim() ? ["ci", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
10276
|
+
const progressWatch = createInstallProgressWatch(
|
|
10277
|
+
[this.opts.ws.abs("node_modules"), path15.join(this.sandboxAbs, "npm-cache")],
|
|
10278
|
+
this.opts.limits,
|
|
10279
|
+
"npm install"
|
|
10280
|
+
);
|
|
10281
|
+
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.command("subprocess", `npm ${installArgs.join(" ")}`), {
|
|
10282
|
+
messageId: "sandbox.command",
|
|
10283
|
+
cwd: this.opts.ws.root,
|
|
10284
|
+
progressIdleTimeoutMs: progressWatch.idleTimeoutMs,
|
|
10285
|
+
progressPaths: progressWatch.paths
|
|
10286
|
+
});
|
|
9790
10287
|
const r = await execRaw("npm", installArgs, {
|
|
9791
10288
|
cwd: this.opts.ws.root,
|
|
9792
10289
|
env: this.baseEnvironment(),
|
|
9793
|
-
|
|
10290
|
+
progressWatch
|
|
9794
10291
|
});
|
|
9795
10292
|
if (r.exitCode !== 0) {
|
|
9796
|
-
throw new Error(`npm dependency install failed (cwd=${this.opts.ws.root})
|
|
9797
|
-
${r.stderr || r.stdout}`);
|
|
10293
|
+
throw new Error(formatExecFailure(`npm dependency install failed (cwd=${this.opts.ws.root})`, r));
|
|
9798
10294
|
}
|
|
9799
|
-
await
|
|
10295
|
+
await fs14.writeFile(this.cacheFile, sig, "utf8");
|
|
9800
10296
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.subprocessNodeBuilt, {
|
|
9801
10297
|
messageId: "sandbox.subprocess_node_built"
|
|
9802
10298
|
});
|
|
@@ -9820,9 +10316,11 @@ ${r.stderr || r.stdout}`);
|
|
|
9820
10316
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.command("subprocess", `${cmd} ${argv.join(" ")}`), {
|
|
9821
10317
|
messageId: "sandbox.command",
|
|
9822
10318
|
cwd,
|
|
9823
|
-
timeoutMs
|
|
10319
|
+
timeoutMs,
|
|
10320
|
+
progressIdleTimeoutMs: extra?.progressWatch?.idleTimeoutMs,
|
|
10321
|
+
progressPaths: extra?.progressWatch?.paths
|
|
9824
10322
|
});
|
|
9825
|
-
const r = await execRaw(cmd, argv, { cwd, env, timeoutMs });
|
|
10323
|
+
const r = await execRaw(cmd, argv, { cwd, env, timeoutMs, progressWatch: extra?.progressWatch });
|
|
9826
10324
|
return r;
|
|
9827
10325
|
}
|
|
9828
10326
|
baseEnvironment() {
|
|
@@ -9856,16 +10354,55 @@ ${r.stderr || r.stdout}`);
|
|
|
9856
10354
|
}
|
|
9857
10355
|
/** 安装额外依赖(不会写入依赖清单,需要由调用方自行回写)。 */
|
|
9858
10356
|
async installDeps(packages) {
|
|
10357
|
+
const progressWatch = createInstallProgressWatch(
|
|
10358
|
+
[
|
|
10359
|
+
this.language === "typescript" ? this.opts.ws.abs("node_modules") : this.venvAbs,
|
|
10360
|
+
path15.join(this.sandboxAbs, this.language === "typescript" ? "npm-cache" : "pip-cache")
|
|
10361
|
+
],
|
|
10362
|
+
this.opts.limits,
|
|
10363
|
+
this.language === "typescript" ? "npm install" : "pip install"
|
|
10364
|
+
);
|
|
9859
10365
|
if (this.language === "typescript") {
|
|
9860
|
-
return this.exec("npm", ["install", "--no-audit", "--no-fund", ...packages]
|
|
10366
|
+
return this.exec("npm", ["install", "--no-audit", "--no-fund", ...packages], {
|
|
10367
|
+
timeoutMs: 0,
|
|
10368
|
+
progressWatch
|
|
10369
|
+
});
|
|
9861
10370
|
}
|
|
9862
|
-
return this.exec(this.pythonInVenv, ["-m", "pip", "install", ...packages, "--quiet", "--disable-pip-version-check"]
|
|
10371
|
+
return this.exec(this.pythonInVenv, ["-m", "pip", "install", ...packages, "--quiet", "--disable-pip-version-check"], {
|
|
10372
|
+
timeoutMs: 0,
|
|
10373
|
+
progressWatch
|
|
10374
|
+
});
|
|
9863
10375
|
}
|
|
9864
10376
|
};
|
|
9865
10377
|
function sanitizeVenvName(name) {
|
|
9866
10378
|
const cleaned = name.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9867
10379
|
return cleaned.length > 0 ? cleaned : "venv";
|
|
9868
10380
|
}
|
|
10381
|
+
function createInstallProgressWatch(paths, limits, label = "dependency install") {
|
|
10382
|
+
const idleTimeoutMs = Math.max(
|
|
10383
|
+
limits.wall_seconds * 1e3 * INSTALL_IDLE_TIMEOUT_MULTIPLIER,
|
|
10384
|
+
INSTALL_IDLE_TIMEOUT_FLOOR_MS
|
|
10385
|
+
);
|
|
10386
|
+
return {
|
|
10387
|
+
paths: dedupPaths(paths),
|
|
10388
|
+
idleTimeoutMs,
|
|
10389
|
+
checkIntervalMs: INSTALL_PROGRESS_CHECK_INTERVAL_MS,
|
|
10390
|
+
label
|
|
10391
|
+
};
|
|
10392
|
+
}
|
|
10393
|
+
function formatExecFailure(label, r) {
|
|
10394
|
+
const parts = [
|
|
10395
|
+
`${label} (exit=${r.exitCode}, timedOut=${r.timedOut ? "true" : "false"}, durationMs=${r.durationMs}${r.timeoutReason ? `, reason=${r.timeoutReason}` : ""}):`
|
|
10396
|
+
];
|
|
10397
|
+
const stdout = clipExecOutput(r.stdout);
|
|
10398
|
+
const stderr = clipExecOutput(r.stderr);
|
|
10399
|
+
if (stdout) parts.push(`stdout:
|
|
10400
|
+
${stdout}`);
|
|
10401
|
+
if (stderr) parts.push(`stderr:
|
|
10402
|
+
${stderr}`);
|
|
10403
|
+
if (!stdout && !stderr) parts.push("(no stdout/stderr captured)");
|
|
10404
|
+
return parts.join("\n");
|
|
10405
|
+
}
|
|
9869
10406
|
async function execRaw(cmd, argv, opts = {}) {
|
|
9870
10407
|
const start = Date.now();
|
|
9871
10408
|
return new Promise((resolve) => {
|
|
@@ -9877,10 +10414,46 @@ async function execRaw(cmd, argv, opts = {}) {
|
|
|
9877
10414
|
let stdout = "";
|
|
9878
10415
|
let stderr = "";
|
|
9879
10416
|
let timedOut = false;
|
|
10417
|
+
let timeoutReason;
|
|
10418
|
+
let settled = false;
|
|
10419
|
+
let progressTimer = null;
|
|
10420
|
+
let progressStopped = false;
|
|
9880
10421
|
const t2 = opts.timeoutMs ? setTimeout(() => {
|
|
9881
10422
|
timedOut = true;
|
|
10423
|
+
timeoutReason = `wall-clock timeout after ${opts.timeoutMs}ms`;
|
|
9882
10424
|
child.kill("SIGKILL");
|
|
9883
10425
|
}, opts.timeoutMs) : null;
|
|
10426
|
+
const finish = (result) => {
|
|
10427
|
+
if (settled) return;
|
|
10428
|
+
settled = true;
|
|
10429
|
+
progressStopped = true;
|
|
10430
|
+
if (t2) clearTimeout(t2);
|
|
10431
|
+
if (progressTimer) clearTimeout(progressTimer);
|
|
10432
|
+
resolve(result);
|
|
10433
|
+
};
|
|
10434
|
+
if (opts.progressWatch?.paths.length) {
|
|
10435
|
+
const watch = opts.progressWatch;
|
|
10436
|
+
let lastProgressAt = start;
|
|
10437
|
+
let lastObservedSize = null;
|
|
10438
|
+
const checkProgress = async () => {
|
|
10439
|
+
if (progressStopped) return;
|
|
10440
|
+
const observedAt = Date.now();
|
|
10441
|
+
const currentSize = await directoryTreeSize(watch.paths);
|
|
10442
|
+
if (progressStopped) return;
|
|
10443
|
+
if (lastObservedSize !== null && currentSize > lastObservedSize) {
|
|
10444
|
+
lastProgressAt = observedAt;
|
|
10445
|
+
}
|
|
10446
|
+
lastObservedSize = currentSize;
|
|
10447
|
+
if (observedAt - lastProgressAt >= watch.idleTimeoutMs) {
|
|
10448
|
+
timedOut = true;
|
|
10449
|
+
timeoutReason = `${watch.label ?? "process"} progress idle for ${watch.idleTimeoutMs}ms; watched paths did not grow: ${watch.paths.join(", ")}`;
|
|
10450
|
+
child.kill("SIGKILL");
|
|
10451
|
+
return;
|
|
10452
|
+
}
|
|
10453
|
+
progressTimer = setTimeout(checkProgress, watch.checkIntervalMs ?? INSTALL_PROGRESS_CHECK_INTERVAL_MS);
|
|
10454
|
+
};
|
|
10455
|
+
progressTimer = setTimeout(checkProgress, watch.checkIntervalMs ?? INSTALL_PROGRESS_CHECK_INTERVAL_MS);
|
|
10456
|
+
}
|
|
9884
10457
|
child.stdout.on("data", (b) => {
|
|
9885
10458
|
stdout += b.toString();
|
|
9886
10459
|
});
|
|
@@ -9888,31 +10461,69 @@ async function execRaw(cmd, argv, opts = {}) {
|
|
|
9888
10461
|
stderr += b.toString();
|
|
9889
10462
|
});
|
|
9890
10463
|
child.on("error", (err) => {
|
|
9891
|
-
|
|
9892
|
-
resolve({
|
|
10464
|
+
finish({
|
|
9893
10465
|
exitCode: -1,
|
|
9894
10466
|
stdout,
|
|
9895
10467
|
stderr: stderr + `
|
|
9896
10468
|
[spawn error] ${err.message}`,
|
|
9897
10469
|
timedOut,
|
|
10470
|
+
...timeoutReason ? { timeoutReason } : {},
|
|
9898
10471
|
durationMs: Date.now() - start
|
|
9899
10472
|
});
|
|
9900
10473
|
});
|
|
9901
10474
|
child.on("close", (code) => {
|
|
9902
|
-
|
|
9903
|
-
resolve({
|
|
10475
|
+
finish({
|
|
9904
10476
|
exitCode: code ?? -1,
|
|
9905
10477
|
stdout,
|
|
9906
10478
|
stderr,
|
|
9907
10479
|
timedOut,
|
|
10480
|
+
...timeoutReason ? { timeoutReason } : {},
|
|
9908
10481
|
durationMs: Date.now() - start
|
|
9909
10482
|
});
|
|
9910
10483
|
});
|
|
9911
10484
|
});
|
|
9912
10485
|
}
|
|
10486
|
+
async function directoryTreeSize(paths) {
|
|
10487
|
+
let total = 0;
|
|
10488
|
+
for (const p of dedupPaths(paths)) {
|
|
10489
|
+
total += await pathTreeSize(p);
|
|
10490
|
+
}
|
|
10491
|
+
return total;
|
|
10492
|
+
}
|
|
10493
|
+
async function pathTreeSize(abs) {
|
|
10494
|
+
let stat;
|
|
10495
|
+
try {
|
|
10496
|
+
stat = await fs14.lstat(abs);
|
|
10497
|
+
} catch {
|
|
10498
|
+
return 0;
|
|
10499
|
+
}
|
|
10500
|
+
if (!stat.isDirectory()) {
|
|
10501
|
+
return stat.isFile() ? stat.size : 0;
|
|
10502
|
+
}
|
|
10503
|
+
let total = stat.size;
|
|
10504
|
+
let entries;
|
|
10505
|
+
try {
|
|
10506
|
+
entries = await fs14.readdir(abs, { withFileTypes: true });
|
|
10507
|
+
} catch {
|
|
10508
|
+
return total;
|
|
10509
|
+
}
|
|
10510
|
+
for (const entry of entries) {
|
|
10511
|
+
total += await pathTreeSize(path15.join(abs, entry.name));
|
|
10512
|
+
}
|
|
10513
|
+
return total;
|
|
10514
|
+
}
|
|
10515
|
+
function dedupPaths(paths) {
|
|
10516
|
+
return Array.from(new Set(paths.map((p) => path15.resolve(p)).filter(Boolean)));
|
|
10517
|
+
}
|
|
10518
|
+
function clipExecOutput(text, max = 6e3) {
|
|
10519
|
+
const normalized = String(text ?? "").trim();
|
|
10520
|
+
if (normalized.length <= max) return normalized;
|
|
10521
|
+
return `${normalized.slice(0, max)}
|
|
10522
|
+
...[truncated ${normalized.length - max} chars]`;
|
|
10523
|
+
}
|
|
9913
10524
|
|
|
9914
10525
|
// src/sandbox/docker.ts
|
|
9915
|
-
import { promises as
|
|
10526
|
+
import { promises as fs15 } from "fs";
|
|
9916
10527
|
import path16 from "path";
|
|
9917
10528
|
import crypto2 from "crypto";
|
|
9918
10529
|
var DockerSandbox = class {
|
|
@@ -9922,7 +10533,7 @@ var DockerSandbox = class {
|
|
|
9922
10533
|
throw new Error(t().system.unsupportedPypiOnlyNetwork);
|
|
9923
10534
|
}
|
|
9924
10535
|
this.language = opts.language ?? "python";
|
|
9925
|
-
this.image = opts.image ?? (this.language === "typescript" ? "node:
|
|
10536
|
+
this.image = opts.image ?? (this.language === "typescript" ? "node:24-slim" : "python:3.11-slim");
|
|
9926
10537
|
this.workdir = opts.workdir ?? "/workspace";
|
|
9927
10538
|
this.sandboxRel = (opts.sandboxDir ?? ".sandbox").replaceAll("\\", "/");
|
|
9928
10539
|
this.cacheRel = `${this.sandboxRel}/${this.language === "typescript" ? "package.sha256" : "requirements.sha256"}`;
|
|
@@ -9971,7 +10582,7 @@ var DockerSandbox = class {
|
|
|
9971
10582
|
async build(manifestFile) {
|
|
9972
10583
|
await this.assertDocker();
|
|
9973
10584
|
const sandboxAbs = this.opts.ws.abs(this.sandboxRel);
|
|
9974
|
-
await
|
|
10585
|
+
await fs15.mkdir(sandboxAbs, { recursive: true });
|
|
9975
10586
|
if (this.language === "typescript") {
|
|
9976
10587
|
return this.buildNode(manifestFile ?? "package.json");
|
|
9977
10588
|
}
|
|
@@ -9980,11 +10591,11 @@ var DockerSandbox = class {
|
|
|
9980
10591
|
async buildPython(requirementsTxt) {
|
|
9981
10592
|
const sandboxAbs = this.opts.ws.abs(this.sandboxRel);
|
|
9982
10593
|
const reqAbs = this.opts.ws.abs(requirementsTxt);
|
|
9983
|
-
const reqContent = await
|
|
10594
|
+
const reqContent = await fs15.readFile(reqAbs, "utf8").catch(() => "");
|
|
9984
10595
|
const sig = crypto2.createHash("sha256").update(this.image + "\n" + reqContent).digest("hex");
|
|
9985
10596
|
const cacheAbs = this.opts.ws.abs(this.cacheRel);
|
|
9986
|
-
const cached = await
|
|
9987
|
-
const venvExists = await
|
|
10597
|
+
const cached = await fs15.readFile(cacheAbs, "utf8").catch(() => "");
|
|
10598
|
+
const venvExists = await fs15.stat(path16.join(sandboxAbs, this.venvName, "bin", "python")).then(() => true).catch(() => false);
|
|
9988
10599
|
if (venvExists && cached === sig) {
|
|
9989
10600
|
return { rebuilt: false, reason: "cache hit" };
|
|
9990
10601
|
}
|
|
@@ -9996,6 +10607,11 @@ var DockerSandbox = class {
|
|
|
9996
10607
|
}
|
|
9997
10608
|
const reqInContainer = `${this.workdir}/${requirementsTxt}`;
|
|
9998
10609
|
const installCmd = reqContent.trim().length > 0 ? `python -m venv ${this.venvInContainer} && ${this.pipInContainer} install --quiet -r ${reqInContainer}` : `python -m venv ${this.venvInContainer}`;
|
|
10610
|
+
const progressWatch = createInstallProgressWatch(
|
|
10611
|
+
[path16.join(sandboxAbs, this.venvName)],
|
|
10612
|
+
this.opts.limits,
|
|
10613
|
+
"docker pip install"
|
|
10614
|
+
);
|
|
9999
10615
|
const r = await execRaw(
|
|
10000
10616
|
this.dockerBin,
|
|
10001
10617
|
[
|
|
@@ -10011,15 +10627,12 @@ var DockerSandbox = class {
|
|
|
10011
10627
|
"-lc",
|
|
10012
10628
|
installCmd
|
|
10013
10629
|
],
|
|
10014
|
-
{
|
|
10630
|
+
{ progressWatch }
|
|
10015
10631
|
);
|
|
10016
10632
|
if (r.exitCode !== 0) {
|
|
10017
|
-
throw new Error(
|
|
10018
|
-
`docker sandbox build failed (exit=${r.exitCode}):
|
|
10019
|
-
${r.stderr || r.stdout}`
|
|
10020
|
-
);
|
|
10633
|
+
throw new Error(formatExecFailure(`docker sandbox build failed (image=${this.image}, cwd=${this.opts.ws.root})`, r));
|
|
10021
10634
|
}
|
|
10022
|
-
await
|
|
10635
|
+
await fs15.writeFile(cacheAbs, sig, "utf8");
|
|
10023
10636
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.dockerBuilt(!!reqContent), {
|
|
10024
10637
|
messageId: "sandbox.docker_built",
|
|
10025
10638
|
image: this.image
|
|
@@ -10028,15 +10641,15 @@ ${r.stderr || r.stdout}`
|
|
|
10028
10641
|
}
|
|
10029
10642
|
async buildNode(manifestFile) {
|
|
10030
10643
|
const pkgAbs = this.opts.ws.abs(manifestFile);
|
|
10031
|
-
const pkgContent = await
|
|
10644
|
+
const pkgContent = await fs15.readFile(pkgAbs, "utf8").catch(() => "");
|
|
10032
10645
|
if (!pkgContent) {
|
|
10033
10646
|
return { rebuilt: false, reason: "no package.json yet" };
|
|
10034
10647
|
}
|
|
10035
|
-
const lockContent = await
|
|
10648
|
+
const lockContent = await fs15.readFile(this.opts.ws.abs("package-lock.json"), "utf8").catch(() => "");
|
|
10036
10649
|
const sig = crypto2.createHash("sha256").update(this.image + "\n" + pkgContent + "\n" + lockContent).digest("hex");
|
|
10037
10650
|
const cacheAbs = this.opts.ws.abs(this.cacheRel);
|
|
10038
|
-
const cached = await
|
|
10039
|
-
const modulesExist = await
|
|
10651
|
+
const cached = await fs15.readFile(cacheAbs, "utf8").catch(() => "");
|
|
10652
|
+
const modulesExist = await fs15.stat(this.opts.ws.abs("node_modules")).then(() => true).catch(() => false);
|
|
10040
10653
|
if (modulesExist && cached === sig) {
|
|
10041
10654
|
return { rebuilt: false, reason: "cache hit" };
|
|
10042
10655
|
}
|
|
@@ -10047,6 +10660,11 @@ ${r.stderr || r.stdout}`
|
|
|
10047
10660
|
}
|
|
10048
10661
|
}
|
|
10049
10662
|
const installCommand = lockContent.trim() ? "npm ci --ignore-scripts --no-audit --no-fund" : "npm install --ignore-scripts --no-audit --no-fund";
|
|
10663
|
+
const progressWatch = createInstallProgressWatch(
|
|
10664
|
+
[this.opts.ws.abs("node_modules")],
|
|
10665
|
+
this.opts.limits,
|
|
10666
|
+
"docker npm install"
|
|
10667
|
+
);
|
|
10050
10668
|
const r = await execRaw(
|
|
10051
10669
|
this.dockerBin,
|
|
10052
10670
|
[
|
|
@@ -10062,13 +10680,12 @@ ${r.stderr || r.stdout}`
|
|
|
10062
10680
|
"-lc",
|
|
10063
10681
|
installCommand
|
|
10064
10682
|
],
|
|
10065
|
-
{
|
|
10683
|
+
{ progressWatch }
|
|
10066
10684
|
);
|
|
10067
10685
|
if (r.exitCode !== 0) {
|
|
10068
|
-
throw new Error(`docker sandbox build failed (
|
|
10069
|
-
${r.stderr || r.stdout}`);
|
|
10686
|
+
throw new Error(formatExecFailure(`docker sandbox build failed (image=${this.image}, cwd=${this.opts.ws.root})`, r));
|
|
10070
10687
|
}
|
|
10071
|
-
await
|
|
10688
|
+
await fs15.writeFile(cacheAbs, sig, "utf8");
|
|
10072
10689
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.dockerNodeBuilt, {
|
|
10073
10690
|
messageId: "sandbox.docker_node_built",
|
|
10074
10691
|
image: this.image
|
|
@@ -10121,7 +10738,7 @@ ${r.stderr || r.stdout}`);
|
|
|
10121
10738
|
image: this.image,
|
|
10122
10739
|
network: this.opts.limits.network
|
|
10123
10740
|
});
|
|
10124
|
-
return execRaw(this.dockerBin, dockerArgs, { timeoutMs });
|
|
10741
|
+
return execRaw(this.dockerBin, dockerArgs, { timeoutMs, progressWatch: extra?.progressWatch });
|
|
10125
10742
|
}
|
|
10126
10743
|
async runProgram(args, extra) {
|
|
10127
10744
|
if (this.language === "typescript") {
|
|
@@ -10139,10 +10756,23 @@ ${r.stderr || r.stdout}`);
|
|
|
10139
10756
|
return this.exec(this.pythonInContainer, ["-m", "pytest", ...args], extra);
|
|
10140
10757
|
}
|
|
10141
10758
|
async installDeps(packages) {
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10759
|
+
const progressWatch = createInstallProgressWatch(
|
|
10760
|
+
[
|
|
10761
|
+
this.language === "typescript" ? this.opts.ws.abs("node_modules") : this.opts.ws.abs(`${this.sandboxRel}/${this.venvName}`)
|
|
10762
|
+
],
|
|
10763
|
+
this.opts.limits,
|
|
10764
|
+
this.language === "typescript" ? "docker npm install" : "docker pip install"
|
|
10765
|
+
);
|
|
10766
|
+
if (this.language === "typescript") {
|
|
10767
|
+
return this.exec("npm", ["install", "--no-audit", "--no-fund", ...packages], {
|
|
10768
|
+
timeoutMs: 0,
|
|
10769
|
+
progressWatch
|
|
10770
|
+
});
|
|
10771
|
+
}
|
|
10772
|
+
return this.exec(this.pipInContainer, ["install", "--quiet", ...packages], {
|
|
10773
|
+
timeoutMs: 0,
|
|
10774
|
+
progressWatch
|
|
10775
|
+
});
|
|
10146
10776
|
}
|
|
10147
10777
|
/** 将宿主机绝对路径映射回容器路径;相对路径或 undefined 原样返回(相对则拼到 workdir)。 */
|
|
10148
10778
|
toContainerPath(p) {
|
|
@@ -10173,12 +10803,11 @@ function isRunningInContainer() {
|
|
|
10173
10803
|
}
|
|
10174
10804
|
return false;
|
|
10175
10805
|
}
|
|
10176
|
-
function createSandbox(cfg, ws, audit, language =
|
|
10177
|
-
const
|
|
10178
|
-
const
|
|
10179
|
-
const
|
|
10180
|
-
|
|
10181
|
-
if (cfg.agent.sandbox_limits.network === "pypi-only") {
|
|
10806
|
+
function createSandbox(cfg, ws, audit, language = "python") {
|
|
10807
|
+
const languageSandbox = cfg.agent.sandboxes?.[language] ?? legacyLanguageSandbox(cfg, language);
|
|
10808
|
+
const kind = languageSandbox.mode;
|
|
10809
|
+
const activeLimits = kind === "docker" ? languageSandbox.docker.limits : languageSandbox.local.limits;
|
|
10810
|
+
if (activeLimits.network === "pypi-only") {
|
|
10182
10811
|
throw new Error(t().system.unsupportedPypiOnlyNetwork);
|
|
10183
10812
|
}
|
|
10184
10813
|
if (kind === "docker") {
|
|
@@ -10187,24 +10816,60 @@ function createSandbox(cfg, ws, audit, language = cfg.agent.language) {
|
|
|
10187
10816
|
}
|
|
10188
10817
|
return new DockerSandbox({
|
|
10189
10818
|
ws,
|
|
10190
|
-
limits:
|
|
10819
|
+
limits: languageSandbox.docker.limits,
|
|
10191
10820
|
audit,
|
|
10192
10821
|
language,
|
|
10193
|
-
image,
|
|
10194
|
-
workdir:
|
|
10195
|
-
pull:
|
|
10196
|
-
dockerBin:
|
|
10197
|
-
extraRunArgs:
|
|
10822
|
+
image: languageSandbox.docker.image ?? getLanguageProfile(language).defaultDockerImage,
|
|
10823
|
+
workdir: languageSandbox.docker.workdir,
|
|
10824
|
+
pull: languageSandbox.docker.pull,
|
|
10825
|
+
dockerBin: languageSandbox.docker.docker_bin,
|
|
10826
|
+
extraRunArgs: languageSandbox.docker.extra_run_args,
|
|
10827
|
+
sandboxDir: languageSandbox.docker.sandbox_dir
|
|
10198
10828
|
});
|
|
10199
10829
|
}
|
|
10200
10830
|
if (kind === "firejail") {
|
|
10201
10831
|
throw new Error(t().system.firejailUnsupported);
|
|
10202
10832
|
}
|
|
10203
|
-
return new SubprocessSandbox({
|
|
10833
|
+
return new SubprocessSandbox({
|
|
10834
|
+
ws,
|
|
10835
|
+
limits: languageSandbox.local.limits,
|
|
10836
|
+
audit,
|
|
10837
|
+
language,
|
|
10838
|
+
sandboxDir: languageSandbox.local.sandbox_dir,
|
|
10839
|
+
pythonBin: languageSandbox.local.python_bin,
|
|
10840
|
+
inheritEnv: languageSandbox.local.inherit_env
|
|
10841
|
+
});
|
|
10842
|
+
}
|
|
10843
|
+
function legacyLanguageSandbox(cfg, language) {
|
|
10844
|
+
const legacyAgent = cfg.agent;
|
|
10845
|
+
const limits = legacyAgent.sandbox_limits ?? {
|
|
10846
|
+
cpu: 1,
|
|
10847
|
+
memory_mb: 1024,
|
|
10848
|
+
wall_seconds: 60,
|
|
10849
|
+
network: "download-only",
|
|
10850
|
+
expose_ports: []
|
|
10851
|
+
};
|
|
10852
|
+
const useLegacyDocker = legacyAgent.language === language ? legacyAgent.sandbox_docker : void 0;
|
|
10853
|
+
return {
|
|
10854
|
+
mode: legacyAgent.sandbox ?? "subprocess",
|
|
10855
|
+
local: {
|
|
10856
|
+
sandbox_dir: `.sandbox/${language}`,
|
|
10857
|
+
limits
|
|
10858
|
+
},
|
|
10859
|
+
docker: {
|
|
10860
|
+
image: useLegacyDocker?.image ?? getLanguageProfile(language).defaultDockerImage,
|
|
10861
|
+
workdir: useLegacyDocker?.workdir ?? "/workspace",
|
|
10862
|
+
pull: useLegacyDocker?.pull ?? false,
|
|
10863
|
+
docker_bin: useLegacyDocker?.docker_bin ?? "docker",
|
|
10864
|
+
extra_run_args: useLegacyDocker?.extra_run_args ?? [],
|
|
10865
|
+
sandbox_dir: useLegacyDocker?.sandbox_dir ?? `.sandbox/${language}`,
|
|
10866
|
+
limits: useLegacyDocker?.limits ?? limits
|
|
10867
|
+
}
|
|
10868
|
+
};
|
|
10204
10869
|
}
|
|
10205
10870
|
|
|
10206
10871
|
// src/core/engine.ts
|
|
10207
|
-
import
|
|
10872
|
+
import path27 from "path";
|
|
10208
10873
|
import chalk2 from "chalk";
|
|
10209
10874
|
|
|
10210
10875
|
// src/util/spinner.ts
|
|
@@ -10290,10 +10955,10 @@ init_types();
|
|
|
10290
10955
|
// src/tools/fs.ts
|
|
10291
10956
|
init_types();
|
|
10292
10957
|
import path18 from "path";
|
|
10293
|
-
import { promises as
|
|
10958
|
+
import { promises as fs17 } from "fs";
|
|
10294
10959
|
|
|
10295
10960
|
// src/tools/path_guard.ts
|
|
10296
|
-
import { promises as
|
|
10961
|
+
import { promises as fs16 } from "fs";
|
|
10297
10962
|
import path17 from "path";
|
|
10298
10963
|
async function resolveWorkspacePath(ws, rawPath, operation, opts = {}) {
|
|
10299
10964
|
const raw = rawPath && rawPath.trim() ? rawPath : ".";
|
|
@@ -10302,19 +10967,19 @@ async function resolveWorkspacePath(ws, rawPath, operation, opts = {}) {
|
|
|
10302
10967
|
if (!isInside(root, abs)) return deny(operation, raw);
|
|
10303
10968
|
if (opts.mustExist) {
|
|
10304
10969
|
try {
|
|
10305
|
-
const real = await
|
|
10970
|
+
const real = await fs16.realpath(abs);
|
|
10306
10971
|
if (!isInside(root, real)) return deny(operation, raw);
|
|
10307
10972
|
} catch (err) {
|
|
10308
10973
|
return { ok: false, error: `${operation} failed: ${err.message}` };
|
|
10309
10974
|
}
|
|
10310
10975
|
} else if (opts.forWrite) {
|
|
10311
|
-
const existingTarget = await
|
|
10976
|
+
const existingTarget = await fs16.realpath(abs).catch(() => void 0);
|
|
10312
10977
|
if (existingTarget && !isInside(root, existingTarget)) return deny(operation, raw);
|
|
10313
10978
|
const parent = await nearestExistingParent(path17.dirname(abs));
|
|
10314
|
-
const realParent = await
|
|
10979
|
+
const realParent = await fs16.realpath(parent).catch(() => parent);
|
|
10315
10980
|
if (!isInside(root, realParent)) return deny(operation, raw);
|
|
10316
10981
|
} else {
|
|
10317
|
-
const existing = await
|
|
10982
|
+
const existing = await fs16.realpath(abs).catch(() => void 0);
|
|
10318
10983
|
if (existing && !isInside(root, existing)) return deny(operation, raw);
|
|
10319
10984
|
}
|
|
10320
10985
|
return { ok: true, abs };
|
|
@@ -10324,13 +10989,13 @@ function isInside(root, candidate) {
|
|
|
10324
10989
|
return rel === "" || !!rel && !rel.startsWith("..") && !path17.isAbsolute(rel);
|
|
10325
10990
|
}
|
|
10326
10991
|
async function realpathOrResolve(p) {
|
|
10327
|
-
return
|
|
10992
|
+
return fs16.realpath(p).catch(() => path17.resolve(p));
|
|
10328
10993
|
}
|
|
10329
10994
|
async function nearestExistingParent(start) {
|
|
10330
10995
|
let current2 = path17.resolve(start);
|
|
10331
10996
|
while (true) {
|
|
10332
10997
|
try {
|
|
10333
|
-
const stat = await
|
|
10998
|
+
const stat = await fs16.stat(current2);
|
|
10334
10999
|
if (stat.isDirectory()) return current2;
|
|
10335
11000
|
} catch {
|
|
10336
11001
|
}
|
|
@@ -10356,9 +11021,9 @@ var readFileTool = {
|
|
|
10356
11021
|
const resolved = await resolveWorkspacePath(ctx.ws, args.path, "read_file", { mustExist: true });
|
|
10357
11022
|
if (!resolved.ok) return { ok: false, error: resolved.error };
|
|
10358
11023
|
const abs = resolved.abs;
|
|
10359
|
-
const stat = await
|
|
11024
|
+
const stat = await fs17.stat(abs);
|
|
10360
11025
|
if (!stat.isFile()) return { ok: false, error: "not a file" };
|
|
10361
|
-
const buf = await
|
|
11026
|
+
const buf = await fs17.readFile(abs);
|
|
10362
11027
|
const limit = args.maxBytes ?? 2e5;
|
|
10363
11028
|
const content = buf.byteLength > limit ? buf.subarray(0, limit).toString("utf8") + `
|
|
10364
11029
|
... [truncated ${buf.byteLength - limit} bytes]` : buf.toString("utf8");
|
|
@@ -10419,8 +11084,8 @@ var writeFileTool = {
|
|
|
10419
11084
|
}
|
|
10420
11085
|
try {
|
|
10421
11086
|
const abs = resolved.abs;
|
|
10422
|
-
await
|
|
10423
|
-
await
|
|
11087
|
+
await fs17.mkdir(path18.dirname(abs), { recursive: true });
|
|
11088
|
+
await fs17.writeFile(abs, args.content, "utf8");
|
|
10424
11089
|
return {
|
|
10425
11090
|
ok: true,
|
|
10426
11091
|
data: { bytes: size },
|
|
@@ -10456,11 +11121,11 @@ var appendFileTool = {
|
|
|
10456
11121
|
}
|
|
10457
11122
|
try {
|
|
10458
11123
|
const abs = resolved.abs;
|
|
10459
|
-
await
|
|
10460
|
-
await
|
|
11124
|
+
await fs17.mkdir(path18.dirname(abs), { recursive: true });
|
|
11125
|
+
await fs17.appendFile(abs, args.content, "utf8");
|
|
10461
11126
|
let total = size;
|
|
10462
11127
|
try {
|
|
10463
|
-
total = (await
|
|
11128
|
+
total = (await fs17.stat(abs)).size;
|
|
10464
11129
|
} catch {
|
|
10465
11130
|
}
|
|
10466
11131
|
return {
|
|
@@ -10493,7 +11158,7 @@ var listDirTool = {
|
|
|
10493
11158
|
const resolved = await resolveWorkspacePath(ctx.ws, args.path ?? ".", "list_dir", { mustExist: true });
|
|
10494
11159
|
if (!resolved.ok) return { ok: false, error: resolved.error };
|
|
10495
11160
|
const abs = resolved.abs;
|
|
10496
|
-
const entries = await
|
|
11161
|
+
const entries = await fs17.readdir(abs, { withFileTypes: true });
|
|
10497
11162
|
return {
|
|
10498
11163
|
ok: true,
|
|
10499
11164
|
data: { entries: entries.map((e) => e.isDirectory() ? e.name + "/" : e.name) },
|
|
@@ -10508,7 +11173,7 @@ var listDirTool = {
|
|
|
10508
11173
|
// src/tools/patch.ts
|
|
10509
11174
|
init_types();
|
|
10510
11175
|
import path19 from "path";
|
|
10511
|
-
import { promises as
|
|
11176
|
+
import { promises as fs18 } from "fs";
|
|
10512
11177
|
var applyPatchTool = {
|
|
10513
11178
|
name: "apply_patch",
|
|
10514
11179
|
description: "\u5E94\u7528 unified diff \u8865\u4E01\uFF1B\u76EE\u6807\u6587\u4EF6\u5FC5\u987B\u5728\u5F53\u524D Step writable allowlist \u5185\u3002",
|
|
@@ -10526,14 +11191,14 @@ var applyPatchTool = {
|
|
|
10526
11191
|
const abs = resolved.abs;
|
|
10527
11192
|
let original = "";
|
|
10528
11193
|
try {
|
|
10529
|
-
original = await
|
|
11194
|
+
original = await fs18.readFile(abs, "utf8");
|
|
10530
11195
|
} catch {
|
|
10531
11196
|
if (!fd.isNewFile) return { ok: false, error: `target file missing: ${fd.target}` };
|
|
10532
11197
|
}
|
|
10533
11198
|
const next = applyHunks(original, fd.hunks);
|
|
10534
11199
|
if (next.error) return { ok: false, error: `${fd.target}: ${next.error}` };
|
|
10535
|
-
await
|
|
10536
|
-
await
|
|
11200
|
+
await fs18.mkdir(path19.dirname(abs), { recursive: true });
|
|
11201
|
+
await fs18.writeFile(abs, next.content, "utf8");
|
|
10537
11202
|
changed.push(fd.target);
|
|
10538
11203
|
}
|
|
10539
11204
|
return { ok: true, data: { changedFiles: changed }, summary: `patched ${changed.join(", ")}` };
|
|
@@ -10749,7 +11414,7 @@ var pipInstallTool = {
|
|
|
10749
11414
|
// src/tools/edit.ts
|
|
10750
11415
|
init_types();
|
|
10751
11416
|
import path20 from "path";
|
|
10752
|
-
import { promises as
|
|
11417
|
+
import { promises as fs19 } from "fs";
|
|
10753
11418
|
var replaceInFileTool = {
|
|
10754
11419
|
name: "replace_in_file",
|
|
10755
11420
|
description: "\u628A\u5F53\u524D Step writable allowlist \u5185\u76EE\u6807\u6587\u4EF6\u7684 find \u5B57\u7B26\u4E32\u7CBE\u786E\u66FF\u6362\u4E3A replace\uFF08\u9ED8\u8BA4\u8981\u6C42\u51FA\u73B0 1 \u6B21\uFF09\u3002",
|
|
@@ -10770,7 +11435,7 @@ var replaceInFileTool = {
|
|
|
10770
11435
|
const abs = resolved.abs;
|
|
10771
11436
|
let original;
|
|
10772
11437
|
try {
|
|
10773
|
-
original = await
|
|
11438
|
+
original = await fs19.readFile(abs, "utf8");
|
|
10774
11439
|
} catch {
|
|
10775
11440
|
return { ok: false, error: `file not found: ${args.path}` };
|
|
10776
11441
|
}
|
|
@@ -10802,8 +11467,8 @@ var replaceInFileTool = {
|
|
|
10802
11467
|
return { ok: false, error: hint.join("\n") };
|
|
10803
11468
|
}
|
|
10804
11469
|
const next = parts.join(args.replace);
|
|
10805
|
-
await
|
|
10806
|
-
await
|
|
11470
|
+
await fs19.mkdir(path20.dirname(abs), { recursive: true });
|
|
11471
|
+
await fs19.writeFile(abs, next, "utf8");
|
|
10807
11472
|
return {
|
|
10808
11473
|
ok: true,
|
|
10809
11474
|
data: { occurrences },
|
|
@@ -10820,7 +11485,7 @@ var codeSearchTool = {
|
|
|
10820
11485
|
const resolved = await resolveWorkspacePath(ctx.ws, args.root ?? ".", "code_search", { mustExist: true });
|
|
10821
11486
|
if (!resolved.ok) return { ok: false, error: resolved.error };
|
|
10822
11487
|
const root = resolved.abs;
|
|
10823
|
-
const workspaceRoot = await
|
|
11488
|
+
const workspaceRoot = await fs19.realpath(ctx.ws.root).catch(() => ctx.ws.root);
|
|
10824
11489
|
const max = args.maxResults ?? 50;
|
|
10825
11490
|
const exts = args.ext && args.ext.length > 0 ? new Set(args.ext.map((e) => e.startsWith(".") ? e : "." + e)) : null;
|
|
10826
11491
|
const matches = [];
|
|
@@ -10829,9 +11494,9 @@ var codeSearchTool = {
|
|
|
10829
11494
|
if (/(?:^|\/)(node_modules|\.git|\.sandbox|\.xcompiler|dist|__pycache__)\//.test(abs)) return;
|
|
10830
11495
|
let content;
|
|
10831
11496
|
try {
|
|
10832
|
-
const stat = await
|
|
11497
|
+
const stat = await fs19.stat(abs);
|
|
10833
11498
|
if (stat.size > 512e3) return;
|
|
10834
|
-
content = await
|
|
11499
|
+
content = await fs19.readFile(abs, "utf8");
|
|
10835
11500
|
} catch {
|
|
10836
11501
|
return;
|
|
10837
11502
|
}
|
|
@@ -10854,7 +11519,7 @@ var codeSearchTool = {
|
|
|
10854
11519
|
async function walk3(dir, onFile) {
|
|
10855
11520
|
let entries;
|
|
10856
11521
|
try {
|
|
10857
|
-
entries = await
|
|
11522
|
+
entries = await fs19.readdir(dir, { withFileTypes: true });
|
|
10858
11523
|
} catch {
|
|
10859
11524
|
return;
|
|
10860
11525
|
}
|
|
@@ -10908,7 +11573,7 @@ var analyzeErrorTool = {
|
|
|
10908
11573
|
};
|
|
10909
11574
|
|
|
10910
11575
|
// src/tools/deps.ts
|
|
10911
|
-
import { promises as
|
|
11576
|
+
import { promises as fs20 } from "fs";
|
|
10912
11577
|
var addDependencyTool = {
|
|
10913
11578
|
name: "add_dependency",
|
|
10914
11579
|
description: "\u5411\u4F9D\u8D56\u6E05\u5355\u8FFD\u52A0\u4F9D\u8D56\uFF08python: requirements.txt\uFF1Btypescript: package.json\uFF09\u5E76\u91CD\u5EFA\u6C99\u76D2\u3002",
|
|
@@ -10928,7 +11593,7 @@ var addDependencyTool = {
|
|
|
10928
11593
|
const added = [];
|
|
10929
11594
|
let final;
|
|
10930
11595
|
if (ctx.language === "typescript") {
|
|
10931
|
-
const pkg = await
|
|
11596
|
+
const pkg = await fs20.readFile(abs, "utf8").then((text) => JSON.parse(text)).catch(() => ({}));
|
|
10932
11597
|
const existingDeps = pkg.dependencies && typeof pkg.dependencies === "object" && !Array.isArray(pkg.dependencies) ? { ...pkg.dependencies } : {};
|
|
10933
11598
|
const before = new Set(Object.keys(existingDeps));
|
|
10934
11599
|
for (const name of normalized) {
|
|
@@ -10937,11 +11602,11 @@ var addDependencyTool = {
|
|
|
10937
11602
|
}
|
|
10938
11603
|
final = Object.keys(existingDeps).sort();
|
|
10939
11604
|
pkg.dependencies = Object.fromEntries(final.map((name) => [name, existingDeps[name] ?? "*"]));
|
|
10940
|
-
await
|
|
11605
|
+
await fs20.writeFile(abs, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
10941
11606
|
} else {
|
|
10942
11607
|
let existing = "";
|
|
10943
11608
|
try {
|
|
10944
|
-
existing = await
|
|
11609
|
+
existing = await fs20.readFile(abs, "utf8");
|
|
10945
11610
|
} catch {
|
|
10946
11611
|
}
|
|
10947
11612
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -10955,7 +11620,7 @@ var addDependencyTool = {
|
|
|
10955
11620
|
set.add(p);
|
|
10956
11621
|
}
|
|
10957
11622
|
final = [...set].sort();
|
|
10958
|
-
await
|
|
11623
|
+
await fs20.writeFile(abs, final.join("\n") + "\n", "utf8");
|
|
10959
11624
|
}
|
|
10960
11625
|
try {
|
|
10961
11626
|
await ctx.sandbox.build(manifestPath);
|
|
@@ -10974,7 +11639,7 @@ var addDependencyTool = {
|
|
|
10974
11639
|
};
|
|
10975
11640
|
|
|
10976
11641
|
// src/tools/net.ts
|
|
10977
|
-
import { promises as
|
|
11642
|
+
import { promises as fs21 } from "fs";
|
|
10978
11643
|
import path21 from "path";
|
|
10979
11644
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
10980
11645
|
var DEFAULT_MAX_BYTES = 256 * 1024;
|
|
@@ -11036,8 +11701,8 @@ var httpFetchTool = {
|
|
|
11036
11701
|
if (timer) clearTimeout(timer);
|
|
11037
11702
|
const trimmed = buf.length > maxBytes ? buf.subarray(0, maxBytes) : buf;
|
|
11038
11703
|
const abs = saveAsAbs;
|
|
11039
|
-
await
|
|
11040
|
-
await
|
|
11704
|
+
await fs21.mkdir(path21.dirname(abs), { recursive: true });
|
|
11705
|
+
await fs21.writeFile(abs, trimmed);
|
|
11041
11706
|
await ctx.audit?.event("tool.call", t().audit.httpFetchSaved(method, args.url, args.saveAs, trimmed.length), {
|
|
11042
11707
|
messageId: "audit.http_fetch_saved",
|
|
11043
11708
|
stepId: ctx.stepId,
|
|
@@ -11095,7 +11760,7 @@ var httpFetchTool = {
|
|
|
11095
11760
|
init_types();
|
|
11096
11761
|
|
|
11097
11762
|
// src/tools/guard.ts
|
|
11098
|
-
import { promises as
|
|
11763
|
+
import { promises as fs22 } from "fs";
|
|
11099
11764
|
import path22 from "path";
|
|
11100
11765
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "append_file", "apply_patch", "replace_in_file", "add_dependency"]);
|
|
11101
11766
|
var DEFAULT_EDIT_LINES_PER_STEP = 400;
|
|
@@ -11178,8 +11843,8 @@ var EditGuard = class {
|
|
|
11178
11843
|
async record(partial) {
|
|
11179
11844
|
const rec = { ts: (/* @__PURE__ */ new Date()).toISOString(), stepId: this.opts.stepId, ...partial };
|
|
11180
11845
|
try {
|
|
11181
|
-
await
|
|
11182
|
-
await
|
|
11846
|
+
await fs22.mkdir(path22.dirname(this.logAbs), { recursive: true });
|
|
11847
|
+
await fs22.appendFile(this.logAbs, JSON.stringify(rec) + "\n", "utf8");
|
|
11183
11848
|
} catch {
|
|
11184
11849
|
}
|
|
11185
11850
|
}
|
|
@@ -11235,8 +11900,9 @@ function buildDefaultRegistry() {
|
|
|
11235
11900
|
|
|
11236
11901
|
// src/agents/executor.ts
|
|
11237
11902
|
import path23 from "path";
|
|
11238
|
-
import { promises as
|
|
11903
|
+
import { promises as fs23 } from "fs";
|
|
11239
11904
|
import { jsonrepair } from "jsonrepair";
|
|
11905
|
+
var MISSING_OUTPUT_STALL_ROUND_LIMIT = 3;
|
|
11240
11906
|
var StepExecutor = class {
|
|
11241
11907
|
constructor(opts) {
|
|
11242
11908
|
this.opts = opts;
|
|
@@ -11260,8 +11926,12 @@ var StepExecutor = class {
|
|
|
11260
11926
|
];
|
|
11261
11927
|
const calls = [];
|
|
11262
11928
|
let finalThought;
|
|
11263
|
-
|
|
11929
|
+
let issueResolutionPlan;
|
|
11930
|
+
const initialVerify = await verifyOutputs(inp);
|
|
11931
|
+
const initialMissing = initialVerify.missing.length;
|
|
11264
11932
|
const hardRoundLimit = Math.max(maxRounds, maxRounds + Math.min(12, Math.max(4, initialMissing * 2)));
|
|
11933
|
+
let lastMissingCount = initialMissing;
|
|
11934
|
+
let missingOutputStallRounds = 0;
|
|
11265
11935
|
let parseFailures = 0;
|
|
11266
11936
|
let repeatedTurns = 0;
|
|
11267
11937
|
let lastActionsKey = null;
|
|
@@ -11310,11 +11980,12 @@ var StepExecutor = class {
|
|
|
11310
11980
|
} catch (err) {
|
|
11311
11981
|
rep.done("failed");
|
|
11312
11982
|
const errMsg = err.message;
|
|
11983
|
+
actualRounds = round;
|
|
11313
11984
|
const dumpRel = `.xcompiler/llm-stream/${inp.step.id}-${role}-r${round}.txt`;
|
|
11314
11985
|
try {
|
|
11315
11986
|
const dumpAbs = inp.ctx.ws.abs(dumpRel);
|
|
11316
|
-
await
|
|
11317
|
-
await
|
|
11987
|
+
await fs23.mkdir(path23.dirname(dumpAbs), { recursive: true });
|
|
11988
|
+
await fs23.writeFile(
|
|
11318
11989
|
dumpAbs,
|
|
11319
11990
|
`${t().audit.partialFailureHeader(errMsg)}
|
|
11320
11991
|
${t().audit.streamLength(rawAggregate.length)}
|
|
@@ -11343,11 +12014,33 @@ ${rawAggregate}`,
|
|
|
11343
12014
|
partialBytes: rawAggregate.length
|
|
11344
12015
|
}
|
|
11345
12016
|
);
|
|
12017
|
+
if (isLowQualityLLMResponseError(errMsg)) {
|
|
12018
|
+
repeatedTurns++;
|
|
12019
|
+
const verify2 = await verifyOutputs(inp);
|
|
12020
|
+
const metrics2 = computeMetrics({
|
|
12021
|
+
rounds: actualRounds,
|
|
12022
|
+
parseFailures,
|
|
12023
|
+
repeatedTurns,
|
|
12024
|
+
calls,
|
|
12025
|
+
initialMissing,
|
|
12026
|
+
currentMissing: verify2.missing.length
|
|
12027
|
+
});
|
|
12028
|
+
return {
|
|
12029
|
+
success: false,
|
|
12030
|
+
rounds: round,
|
|
12031
|
+
toolCalls: calls,
|
|
12032
|
+
finalThought,
|
|
12033
|
+
issueResolutionPlan,
|
|
12034
|
+
error: errMsg,
|
|
12035
|
+
metrics: metrics2
|
|
12036
|
+
};
|
|
12037
|
+
}
|
|
11346
12038
|
throw err;
|
|
11347
12039
|
}
|
|
11348
12040
|
rep.done();
|
|
11349
12041
|
const turn = parseTurn(text);
|
|
11350
12042
|
finalThought = turn.thoughts;
|
|
12043
|
+
issueResolutionPlan = extractIssueResolutionPlan(turn) ?? issueResolutionPlan;
|
|
11351
12044
|
const normalizedActions = normalizeActions(turn.actions);
|
|
11352
12045
|
const actions = normalizedActions.actions;
|
|
11353
12046
|
if (normalizedActions.invalid.length > 0) {
|
|
@@ -11397,6 +12090,7 @@ ${rawAggregate}`,
|
|
|
11397
12090
|
}
|
|
11398
12091
|
await inp.ctx.audit?.executorTurn(inp.step.id, role, round, {
|
|
11399
12092
|
thoughts: turn.thoughts,
|
|
12093
|
+
issueResolutionPlan,
|
|
11400
12094
|
actions,
|
|
11401
12095
|
done: turn.done === true,
|
|
11402
12096
|
raw: text,
|
|
@@ -11505,12 +12199,25 @@ ${rawAggregate}`,
|
|
|
11505
12199
|
turnResults.push({ ...r, tool: a.tool });
|
|
11506
12200
|
}
|
|
11507
12201
|
const verify = await verifyOutputs(inp);
|
|
12202
|
+
const mutationSucceededThisRound = turnResults.some((r) => r.ok && isRepairEvidenceTool(r.tool));
|
|
12203
|
+
if (verify.missing.length < lastMissingCount) {
|
|
12204
|
+
lastMissingCount = verify.missing.length;
|
|
12205
|
+
missingOutputStallRounds = 0;
|
|
12206
|
+
} else if (!verify.ok && initialMissing > 0 && mutationSucceededThisRound && !readOnlyRound && unresolvedToolFailures.size === 0) {
|
|
12207
|
+
missingOutputStallRounds++;
|
|
12208
|
+
}
|
|
11508
12209
|
if (turnResults.some((r) => r.tool === "run_tests" && !r.ok) && !advisoryFailureTools.has("run_tests")) {
|
|
11509
12210
|
failedTestRunRounds++;
|
|
11510
12211
|
}
|
|
11511
12212
|
const repairGateOk = !repairRequired || repairEvidence || canAcceptOutputCompletionRecovery(inp, initialMissing);
|
|
12213
|
+
const issueResolutionPlanRequired = role === "Debugger" && !!inp.debugContext?.issueId;
|
|
12214
|
+
const issueResolutionPlanOk = !issueResolutionPlanRequired || !!issueResolutionPlan?.trim();
|
|
11512
12215
|
const verifiedCompletion = !turn.done && hasSuccessfulCompletionVerification(calls);
|
|
11513
|
-
|
|
12216
|
+
const outputCompletionRecovery = verify.ok && canAcceptOutputCompletionRecovery(inp, initialMissing);
|
|
12217
|
+
const supersededContractFailures = repairEvidence && hasSuccessfulCompletionVerification(calls) && hasOnlySupersededToolContractFailures(unresolvedToolFailures);
|
|
12218
|
+
const unresolvedFailuresOk = unresolvedToolFailures.size === 0 || outputCompletionRecovery && hasOnlyUntargetedToolContractFailures(unresolvedToolFailures) || supersededContractFailures;
|
|
12219
|
+
const completionSignal = turn.done || verifiedCompletion || outputCompletionRecovery;
|
|
12220
|
+
if (completionSignal && verify.ok && unresolvedFailuresOk && repairGateOk && issueResolutionPlanOk) {
|
|
11514
12221
|
const metrics2 = computeMetrics({
|
|
11515
12222
|
rounds: actualRounds,
|
|
11516
12223
|
parseFailures,
|
|
@@ -11519,7 +12226,7 @@ ${rawAggregate}`,
|
|
|
11519
12226
|
initialMissing,
|
|
11520
12227
|
currentMissing: verify.missing.length
|
|
11521
12228
|
});
|
|
11522
|
-
return { success: true, rounds: round, toolCalls: calls, finalThought, metrics: metrics2 };
|
|
12229
|
+
return { success: true, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, metrics: metrics2 };
|
|
11523
12230
|
}
|
|
11524
12231
|
if (this.opts.maxFailedTestRuns && failedTestRunRounds >= this.opts.maxFailedTestRuns) {
|
|
11525
12232
|
const metrics2 = computeMetrics({
|
|
@@ -11538,7 +12245,27 @@ ${rawAggregate}`,
|
|
|
11538
12245
|
failedTestRunRounds,
|
|
11539
12246
|
maxFailedTestRuns: this.opts.maxFailedTestRuns
|
|
11540
12247
|
});
|
|
11541
|
-
return { success: false, rounds: round, toolCalls: calls, finalThought, error, metrics: metrics2 };
|
|
12248
|
+
return { success: false, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, error, metrics: metrics2 };
|
|
12249
|
+
}
|
|
12250
|
+
if (missingOutputStallRounds >= MISSING_OUTPUT_STALL_ROUND_LIMIT) {
|
|
12251
|
+
repeatedTurns++;
|
|
12252
|
+
const metrics2 = computeMetrics({
|
|
12253
|
+
rounds: actualRounds,
|
|
12254
|
+
parseFailures,
|
|
12255
|
+
repeatedTurns,
|
|
12256
|
+
calls,
|
|
12257
|
+
initialMissing,
|
|
12258
|
+
currentMissing: verify.missing.length
|
|
12259
|
+
});
|
|
12260
|
+
const error = `write/progress actions did not reduce missing outputs for ${missingOutputStallRounds} rounds; missing outputs: ${verify.missing.join(", ")}. Next attempt must create those exact outputs before rewriting already-existing files.`;
|
|
12261
|
+
await inp.ctx.audit?.event("note", error, {
|
|
12262
|
+
messageId: "audit.executor_missing_output_stall",
|
|
12263
|
+
stepId: inp.step.id,
|
|
12264
|
+
round,
|
|
12265
|
+
missingOutputStallRounds,
|
|
12266
|
+
missingOutputs: verify.missing
|
|
12267
|
+
});
|
|
12268
|
+
return { success: false, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, error, metrics: metrics2 };
|
|
11542
12269
|
}
|
|
11543
12270
|
const readOnlyRecoveryViolation = readOnlyRecoveryMode && readOnlyRecoveryRounds >= 2;
|
|
11544
12271
|
const readOnlyRoundLimit = directRepairMode ? 2 : 3;
|
|
@@ -11561,7 +12288,7 @@ ${rawAggregate}`,
|
|
|
11561
12288
|
consecutiveReadOnlyRounds,
|
|
11562
12289
|
actions
|
|
11563
12290
|
});
|
|
11564
|
-
return { success: false, rounds: round, toolCalls: calls, finalThought, error, metrics: metrics2 };
|
|
12291
|
+
return { success: false, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, error, metrics: metrics2 };
|
|
11565
12292
|
}
|
|
11566
12293
|
if (round >= roundLimit && roundLimit < hardRoundLimit && shouldExtendProductiveRun({
|
|
11567
12294
|
parseFailures,
|
|
@@ -11595,8 +12322,13 @@ ${rawAggregate}`,
|
|
|
11595
12322
|
rounds: consecutiveReadOnlyRounds,
|
|
11596
12323
|
targets: actions.flatMap((action) => actionTargetPaths(action.tool, action.args)).join(", ")
|
|
11597
12324
|
} : void 0,
|
|
12325
|
+
missingOutputStallWarning: missingOutputStallRounds >= MISSING_OUTPUT_STALL_ROUND_LIMIT - 1 && !verify.ok ? {
|
|
12326
|
+
rounds: missingOutputStallRounds,
|
|
12327
|
+
missing: verify.missing.join(", ")
|
|
12328
|
+
} : void 0,
|
|
11598
12329
|
readOnlyRecoveryWarning: (readOnlyRecoveryMode || directRepairMode) && readOnlyRound,
|
|
11599
|
-
repairEvidenceMissing: repairRequired && turn.done === true && verify.ok && unresolvedToolFailures.size === 0 && !repairEvidence
|
|
12330
|
+
repairEvidenceMissing: repairRequired && turn.done === true && verify.ok && unresolvedToolFailures.size === 0 && !repairEvidence,
|
|
12331
|
+
issueResolutionPlanMissing: issueResolutionPlanRequired && turn.done === true && verify.ok && unresolvedToolFailures.size === 0 && !issueResolutionPlanOk
|
|
11600
12332
|
})
|
|
11601
12333
|
});
|
|
11602
12334
|
}
|
|
@@ -11614,7 +12346,8 @@ ${rawAggregate}`,
|
|
|
11614
12346
|
rounds: actualRounds || roundLimit,
|
|
11615
12347
|
toolCalls: calls,
|
|
11616
12348
|
finalThought,
|
|
11617
|
-
|
|
12349
|
+
issueResolutionPlan,
|
|
12350
|
+
error: role === "Debugger" && !!inp.debugContext?.issueId && !issueResolutionPlan?.trim() ? "DEBUG issue completion missing issueResolutionPlan; provide a concrete handling plan before marking the issue resolved." : repairRequired && finalVerify.ok && unresolvedToolFailures.size === 0 && !repairEvidence && !canAcceptOutputCompletionRecovery(inp, initialMissing) ? "DEBUG retry ended without repair evidence; run a successful patch/write/dependency change or verification command before done=true." : finalVerify.ok && unresolvedToolFailures.size > 0 ? `unresolved tool failures remain: ${[...unresolvedToolFailures.values()].join("; ")}` : "max rounds exceeded without satisfying outputs",
|
|
11618
12351
|
metrics
|
|
11619
12352
|
};
|
|
11620
12353
|
}
|
|
@@ -11627,6 +12360,9 @@ function isReadOnlyOrProbeAction(action) {
|
|
|
11627
12360
|
function isReadOnlyLoopFailure(reason) {
|
|
11628
12361
|
return /repeated read-only\/probe actions without progress/i.test(reason) || /read-only recovery mode repeated probe actions/i.test(reason);
|
|
11629
12362
|
}
|
|
12363
|
+
function isLowQualityLLMResponseError(message) {
|
|
12364
|
+
return /low-quality (?:debugger )?response/i.test(message) || /read-only\/probe actions in read-only recovery mode/i.test(message);
|
|
12365
|
+
}
|
|
11630
12366
|
function hasActionableDebuggerFailure(debugContext) {
|
|
11631
12367
|
if (!debugContext) return false;
|
|
11632
12368
|
const text = [
|
|
@@ -11642,6 +12378,18 @@ function canAcceptOutputCompletionRecovery(inp, initialMissing) {
|
|
|
11642
12378
|
if (initialMissing !== 0) return false;
|
|
11643
12379
|
return isOutputCompletionFailure(inp.debugContext.reason, inp.debugContext.failureLog);
|
|
11644
12380
|
}
|
|
12381
|
+
function hasOnlyUntargetedToolContractFailures(unresolved) {
|
|
12382
|
+
if (unresolved.size === 0) return true;
|
|
12383
|
+
return [...unresolved.entries()].every(
|
|
12384
|
+
([key, detail]) => key.startsWith("tool:") && /invalid (?:write_file|append_file|replace_in_file|read_file) args: path must be a non-empty string/i.test(detail)
|
|
12385
|
+
);
|
|
12386
|
+
}
|
|
12387
|
+
function hasOnlySupersededToolContractFailures(unresolved) {
|
|
12388
|
+
if (unresolved.size === 0) return true;
|
|
12389
|
+
return [...unresolved.values()].every(
|
|
12390
|
+
(detail) => /invalid (?:write_file|append_file|replace_in_file|apply_patch|read_file) args/i.test(detail) || /(?:read_file|write_file|append_file|replace_in_file|apply_patch) denied: path "\." is outside the project directory/i.test(detail) || /(?:read_file|write_file|append_file|replace_in_file) failed:.*path must be a non-empty string/i.test(detail) || /path must be a non-empty string/i.test(detail)
|
|
12391
|
+
);
|
|
12392
|
+
}
|
|
11645
12393
|
function isOutputCompletionFailure(reason = "", failureLog = "") {
|
|
11646
12394
|
const text = `${reason}
|
|
11647
12395
|
${failureLog}`;
|
|
@@ -11716,6 +12464,7 @@ function compactTurnForHistory(turn) {
|
|
|
11716
12464
|
const normalized = normalizeActions(turn.actions);
|
|
11717
12465
|
return JSON.stringify({
|
|
11718
12466
|
thoughts: truncate2(turn.thoughts ?? "", 500),
|
|
12467
|
+
issueResolutionPlan: truncate2(extractIssueResolutionPlan(turn) ?? "", 900),
|
|
11719
12468
|
actions: normalized.actions.map((action) => ({
|
|
11720
12469
|
tool: action.tool,
|
|
11721
12470
|
args: compactActionArgs(action.tool, action.args)
|
|
@@ -12054,8 +12803,8 @@ async function verifyOutputs(inp) {
|
|
|
12054
12803
|
const missing = [];
|
|
12055
12804
|
for (const out of inp.step.outputs) {
|
|
12056
12805
|
if (out.endsWith("/")) continue;
|
|
12057
|
-
const
|
|
12058
|
-
if (!
|
|
12806
|
+
const exists2 = await inp.ctx.ws.exists(out);
|
|
12807
|
+
if (!exists2) missing.push(out);
|
|
12059
12808
|
}
|
|
12060
12809
|
return { ok: missing.length === 0, missing };
|
|
12061
12810
|
}
|
|
@@ -12071,13 +12820,21 @@ function renderUserPrompt(inp, toolDocs) {
|
|
|
12071
12820
|
${truncate2(s.content, s.path === ".xcompiler/architecture-contract.json" ? architectureLimit : snippetLimit)}
|
|
12072
12821
|
\`\`\``
|
|
12073
12822
|
).join("\n\n");
|
|
12074
|
-
const dbg = inp.debugContext ?
|
|
12823
|
+
const dbg = inp.debugContext ? [
|
|
12824
|
+
inp.debugContext.issueId ? `## issue
|
|
12825
|
+
id: ${inp.debugContext.issueId}
|
|
12826
|
+
` : "",
|
|
12827
|
+
inp.debugContext.debugBrief ? `${inp.debugContext.debugBrief}
|
|
12828
|
+
` : "",
|
|
12829
|
+
`## compact failure evidence
|
|
12075
12830
|
\`\`\`
|
|
12076
12831
|
${truncate2(inp.debugContext.failureLog, failureLogLimit)}
|
|
12077
12832
|
\`\`\`
|
|
12078
|
-
|
|
12833
|
+
`,
|
|
12834
|
+
inp.debugContext.suggestions ? `
|
|
12079
12835
|
${inp.debugContext.suggestions}
|
|
12080
|
-
` : ""
|
|
12836
|
+
` : ""
|
|
12837
|
+
].join("\n") : "";
|
|
12081
12838
|
return [
|
|
12082
12839
|
`# Step ${inp.step.id} \u2014 ${inp.step.title}`,
|
|
12083
12840
|
`phase: ${inp.step.phase}`,
|
|
@@ -12165,6 +12922,11 @@ function renderFeedback(results, verify, turn) {
|
|
|
12165
12922
|
)
|
|
12166
12923
|
);
|
|
12167
12924
|
}
|
|
12925
|
+
if (turn.missingOutputStallWarning) {
|
|
12926
|
+
lines.push(
|
|
12927
|
+
`Output progress warning: required outputs have not decreased for ${turn.missingOutputStallWarning.rounds} write/progress rounds. Create these exact missing outputs next: ${turn.missingOutputStallWarning.missing}. Do not keep rewriting files that already satisfy declared outputs.`
|
|
12928
|
+
);
|
|
12929
|
+
}
|
|
12168
12930
|
if (turn.readOnlyRecoveryWarning) {
|
|
12169
12931
|
lines.push(M.executorFeedbackReadOnlyRecoveryRequired);
|
|
12170
12932
|
if (!verify.ok && verify.missing.length > 0) {
|
|
@@ -12173,6 +12935,9 @@ function renderFeedback(results, verify, turn) {
|
|
|
12173
12935
|
);
|
|
12174
12936
|
}
|
|
12175
12937
|
}
|
|
12938
|
+
if (turn.issueResolutionPlanMissing) {
|
|
12939
|
+
lines.push(M.executorFeedbackIssueResolutionPlanMissing);
|
|
12940
|
+
}
|
|
12176
12941
|
if (turn.unresolvedFailures && turn.unresolvedFailures.length > 0) {
|
|
12177
12942
|
lines.push(
|
|
12178
12943
|
`Unresolved tool failures remain: ${turn.unresolvedFailures.map((failure2) => truncate2(failure2, 1200)).join("; ")}`
|
|
@@ -12383,6 +13148,21 @@ function isTurnObject(value) {
|
|
|
12383
13148
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
12384
13149
|
return value;
|
|
12385
13150
|
}
|
|
13151
|
+
function extractIssueResolutionPlan(turn) {
|
|
13152
|
+
const candidates = [
|
|
13153
|
+
turn.issueResolutionPlan,
|
|
13154
|
+
turn.issue_resolution_plan,
|
|
13155
|
+
turn.resolutionPlan,
|
|
13156
|
+
turn.handlingPlan,
|
|
13157
|
+
turn.fixPlan
|
|
13158
|
+
];
|
|
13159
|
+
for (const value of candidates) {
|
|
13160
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
13161
|
+
return truncate2(value.trim(), 2400);
|
|
13162
|
+
}
|
|
13163
|
+
}
|
|
13164
|
+
return void 0;
|
|
13165
|
+
}
|
|
12386
13166
|
function normalizeJsonLikeStrings(text) {
|
|
12387
13167
|
let out = "";
|
|
12388
13168
|
let inStr = false;
|
|
@@ -12520,8 +13300,301 @@ function buildDefaultSkills() {
|
|
|
12520
13300
|
}
|
|
12521
13301
|
|
|
12522
13302
|
// src/core/debug_cache.ts
|
|
12523
|
-
import { promises as
|
|
13303
|
+
import { promises as fs24 } from "fs";
|
|
12524
13304
|
import path24 from "path";
|
|
13305
|
+
|
|
13306
|
+
// src/core/debug_brief.ts
|
|
13307
|
+
var MAX_EVIDENCE = 8;
|
|
13308
|
+
var MAX_EVIDENCE_LINE = 260;
|
|
13309
|
+
var MAX_TOOL_FAILURES = 6;
|
|
13310
|
+
var MAX_FAILED_TESTS = 8;
|
|
13311
|
+
var MAX_FILES = 10;
|
|
13312
|
+
function buildDebugBrief(input) {
|
|
13313
|
+
const reason = oneLine(input.reason ?? "");
|
|
13314
|
+
const raw = `${reason}
|
|
13315
|
+
${input.failureLog ?? ""}`.trim();
|
|
13316
|
+
const sections = splitFailureSections(raw);
|
|
13317
|
+
const rootSignals = extractSignals(sections.root || raw);
|
|
13318
|
+
const latestSignals = sections.latest ? extractSignals(sections.latest) : void 0;
|
|
13319
|
+
const chosen = choosePrimarySignals(rootSignals, latestSignals);
|
|
13320
|
+
const category = chosen.category;
|
|
13321
|
+
const primaryError = chosen.primaryError || reason || "Unknown failure";
|
|
13322
|
+
const failedTests = dedup4([...rootSignals.failedTests ?? [], ...latestSignals?.failedTests ?? []]).slice(0, MAX_FAILED_TESTS);
|
|
13323
|
+
const files = dedup4([...rootSignals.files ?? [], ...latestSignals?.files ?? []]).slice(0, MAX_FILES);
|
|
13324
|
+
const toolFailures = dedup4([...rootSignals.toolFailures ?? [], ...latestSignals?.toolFailures ?? []]).slice(0, MAX_TOOL_FAILURES);
|
|
13325
|
+
const statusCodes = dedup4([...rootSignals.statusCodes ?? [], ...latestSignals?.statusCodes ?? []]).slice(0, 6);
|
|
13326
|
+
const evidence = selectEvidenceLines(raw, category, primaryError, failedTests, files, toolFailures);
|
|
13327
|
+
return {
|
|
13328
|
+
version: 1,
|
|
13329
|
+
category,
|
|
13330
|
+
summary: buildSummary({ category, reason, primaryError, failedTests, files, phase: input.phase, targetPhase: input.targetPhase }),
|
|
13331
|
+
primaryError,
|
|
13332
|
+
debugDemand: buildDebugDemand(category, input.targetPhase ?? input.phase, statusCodes),
|
|
13333
|
+
failedTests,
|
|
13334
|
+
files,
|
|
13335
|
+
toolFailures,
|
|
13336
|
+
statusCodes,
|
|
13337
|
+
evidence: evidence.lines,
|
|
13338
|
+
omittedEvidenceLines: evidence.omitted
|
|
13339
|
+
};
|
|
13340
|
+
}
|
|
13341
|
+
function renderDebugBriefForPrompt(brief) {
|
|
13342
|
+
const lines = [
|
|
13343
|
+
"## debug brief",
|
|
13344
|
+
`- category: ${brief.category}`,
|
|
13345
|
+
`- summary: ${brief.summary}`,
|
|
13346
|
+
`- primaryError: ${brief.primaryError}`,
|
|
13347
|
+
`- debugDemand: ${brief.debugDemand}`
|
|
13348
|
+
];
|
|
13349
|
+
if (brief.failedTests.length > 0) lines.push(`- failedTests: ${brief.failedTests.join(", ")}`);
|
|
13350
|
+
if (brief.files.length > 0) lines.push(`- likelyFiles: ${brief.files.join(", ")}`);
|
|
13351
|
+
if (brief.toolFailures.length > 0) lines.push(`- toolFailures: ${brief.toolFailures.join(" | ")}`);
|
|
13352
|
+
if (brief.statusCodes.length > 0) lines.push(`- httpStatus: ${brief.statusCodes.join(", ")}`);
|
|
13353
|
+
if (brief.evidence.length > 0) {
|
|
13354
|
+
lines.push("- keyEvidence:");
|
|
13355
|
+
for (const line of brief.evidence) lines.push(` - ${line}`);
|
|
13356
|
+
}
|
|
13357
|
+
if (brief.omittedEvidenceLines > 0) {
|
|
13358
|
+
lines.push(`- omittedEvidenceLines: ${brief.omittedEvidenceLines}`);
|
|
13359
|
+
}
|
|
13360
|
+
return lines.join("\n");
|
|
13361
|
+
}
|
|
13362
|
+
function compactFailureEvidence(input) {
|
|
13363
|
+
const maxChars = input.maxChars ?? 2400;
|
|
13364
|
+
const maxLines = input.maxLines ?? 50;
|
|
13365
|
+
const reason = shouldSuppressReasonInEvidence(input.reason, input.failureLog) ? "" : input.reason ?? "";
|
|
13366
|
+
const raw = `${reason}
|
|
13367
|
+
${input.failureLog ?? ""}`.trim();
|
|
13368
|
+
if (!raw) return "";
|
|
13369
|
+
const brief = buildDebugBrief({ ...input, reason });
|
|
13370
|
+
const important = selectEvidenceLines(
|
|
13371
|
+
raw,
|
|
13372
|
+
brief.category,
|
|
13373
|
+
brief.primaryError,
|
|
13374
|
+
brief.failedTests,
|
|
13375
|
+
brief.files,
|
|
13376
|
+
brief.toolFailures,
|
|
13377
|
+
Math.min(MAX_EVIDENCE + 4, 12)
|
|
13378
|
+
).lines;
|
|
13379
|
+
const tail = raw.split(/\r?\n/u).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-Math.max(10, Math.floor(maxLines / 2))).map((line) => truncateLine(line, 320));
|
|
13380
|
+
const lines = dedup4([...important, ...tail]).slice(-maxLines);
|
|
13381
|
+
const joined = lines.join("\n");
|
|
13382
|
+
if (joined.length <= maxChars) return joined;
|
|
13383
|
+
const head = joined.slice(0, Math.floor(maxChars * 0.45));
|
|
13384
|
+
const tailText2 = joined.slice(-Math.floor(maxChars * 0.45));
|
|
13385
|
+
return `${head}
|
|
13386
|
+
... [debug evidence truncated ${joined.length - head.length - tailText2.length} chars]
|
|
13387
|
+
${tailText2}`;
|
|
13388
|
+
}
|
|
13389
|
+
function splitFailureSections(text) {
|
|
13390
|
+
const marker = /\n##\s+latest Debugger attempt failure\b/u.exec(text);
|
|
13391
|
+
if (!marker) return { root: text };
|
|
13392
|
+
return {
|
|
13393
|
+
root: text.slice(0, marker.index).trim(),
|
|
13394
|
+
latest: text.slice(marker.index + 1).trim()
|
|
13395
|
+
};
|
|
13396
|
+
}
|
|
13397
|
+
function choosePrimarySignals(root, latest) {
|
|
13398
|
+
if (!latest) return root;
|
|
13399
|
+
if (isProcessNoise(latest.category) && !isProcessNoise(root.category)) return root;
|
|
13400
|
+
if (latest.category !== "unknown") return latest;
|
|
13401
|
+
return root.category !== "unknown" ? root : latest;
|
|
13402
|
+
}
|
|
13403
|
+
function extractSignals(text) {
|
|
13404
|
+
const lines = normalizedLines(text);
|
|
13405
|
+
const failedTests = extractFailedTests(text);
|
|
13406
|
+
const files = extractFiles(text);
|
|
13407
|
+
const toolFailures = extractToolFailures(lines);
|
|
13408
|
+
const statusCodes = extractStatusCodes(text);
|
|
13409
|
+
const category = classify(text, lines, failedTests, toolFailures);
|
|
13410
|
+
return {
|
|
13411
|
+
category,
|
|
13412
|
+
primaryError: findPrimaryError(text, lines, category, failedTests, toolFailures),
|
|
13413
|
+
failedTests,
|
|
13414
|
+
files,
|
|
13415
|
+
toolFailures,
|
|
13416
|
+
statusCodes
|
|
13417
|
+
};
|
|
13418
|
+
}
|
|
13419
|
+
function classify(text, lines, failedTests, toolFailures) {
|
|
13420
|
+
const lower = text.toLowerCase();
|
|
13421
|
+
if (/openai|ollama|openrouter|llm provider|provider_call_failed|all llm providers failed|prefill_memory_exceeded|context window|token limit|prompt too long/u.test(lower)) {
|
|
13422
|
+
return "llm_provider";
|
|
13423
|
+
}
|
|
13424
|
+
if (/repeated read-only\/probe actions|read-only recovery mode|low-quality debugger response/u.test(lower)) {
|
|
13425
|
+
return "tool_loop";
|
|
13426
|
+
}
|
|
13427
|
+
if (/permission denied/u.test(lower)) return "permission_denied";
|
|
13428
|
+
if (/network api failure detected|http_fetch|https?:\/\/|http\s+(?:401|403|404|408|409|410|422|429|5\d\d)|timed out|timeout/u.test(lower)) {
|
|
13429
|
+
if (!/openai|ollama|openrouter|llm provider/u.test(lower)) return "network_api_failure";
|
|
13430
|
+
}
|
|
13431
|
+
if (/modulenotfounderror|importerror/u.test(lower)) return "import_error";
|
|
13432
|
+
if (/could not find a version|no matching distribution|pip install|add_dependency/u.test(lower)) return "dependency_error";
|
|
13433
|
+
if (/syntaxerror|indentationerror|taberror/u.test(lower)) return "syntax_error";
|
|
13434
|
+
if (/outputs? (?:still )?missing|missing required outputs?|outputs? \S*缺失|仍缺失/u.test(lower)) return "missing_output";
|
|
13435
|
+
if (failedTests.length > 0 || /pytest exit=\s*[1-9]|tests? exit=\s*[1-9]|test gate|测试门禁|vitest|assertionerror|failed tests?|test failures?|(?:unit|integration|module|functional|gate) regression failed/u.test(lower)) {
|
|
13436
|
+
return "test_failure";
|
|
13437
|
+
}
|
|
13438
|
+
if (toolFailures.length > 0) return "exception";
|
|
13439
|
+
if (lines.some((line) => /error|exception|traceback|failed/i.test(line))) return "exception";
|
|
13440
|
+
return "unknown";
|
|
13441
|
+
}
|
|
13442
|
+
function findPrimaryError(text, lines, category, failedTests, toolFailures) {
|
|
13443
|
+
if (failedTests.length > 0 && category === "test_failure") return `failed test: ${failedTests[0]}`;
|
|
13444
|
+
if (toolFailures.length > 0 && (category === "tool_loop" || category === "exception")) return toolFailures[0];
|
|
13445
|
+
const patterns = [
|
|
13446
|
+
/(?:SyntaxError|IndentationError|TabError|ModuleNotFoundError|ImportError|AssertionError|TypeError|ValueError|FileNotFoundError|AttributeError|RuntimeError):[^\n]+/u,
|
|
13447
|
+
/\bFAILED\s+[^\n]+/u,
|
|
13448
|
+
/\b(?:pytest|vitest)[^\n]*(?:exit|failed|FAIL)[^\n]*/iu,
|
|
13449
|
+
/\bHTTP\s+(?:401|403|404|408|409|410|422|429|5\d\d)[^\n]*/iu,
|
|
13450
|
+
/Network API failure detected[^\n]*/iu,
|
|
13451
|
+
/outputs?[^\n]*(?:missing|缺失|仍缺失)[^\n]*/iu,
|
|
13452
|
+
/repeated read-only\/probe actions[^\n]*/iu
|
|
13453
|
+
];
|
|
13454
|
+
for (const pattern of patterns) {
|
|
13455
|
+
const match = text.match(pattern)?.[0];
|
|
13456
|
+
if (match) return oneLine(match);
|
|
13457
|
+
}
|
|
13458
|
+
const lastMeaningful = [...lines].reverse().find((line) => /error|exception|failed|exit=\s*[1-9]|缺失/i.test(line));
|
|
13459
|
+
return oneLine(lastMeaningful ?? lines.at(-1) ?? "Unknown failure");
|
|
13460
|
+
}
|
|
13461
|
+
function buildSummary(args) {
|
|
13462
|
+
const scope = args.targetPhase || args.phase ? ` in ${args.targetPhase ?? args.phase}` : "";
|
|
13463
|
+
if (args.failedTests.length > 0) return `${args.category}${scope}: ${args.failedTests[0]} failed`;
|
|
13464
|
+
if (args.files.length > 0) return `${args.category}${scope}: ${args.primaryError} (${args.files[0]})`;
|
|
13465
|
+
return `${args.category}${scope}: ${args.primaryError || args.reason || "failure"}`;
|
|
13466
|
+
}
|
|
13467
|
+
function buildDebugDemand(category, phase, statusCodes = []) {
|
|
13468
|
+
const phaseHint = phase ? ` for ${phase}` : "";
|
|
13469
|
+
switch (category) {
|
|
13470
|
+
case "test_failure":
|
|
13471
|
+
return `Fix the root implementation/contract defect${phaseHint}, then run the smallest relevant test command before done=true. Do not rewrite fixtures unless evidence says the fixture is missing or malformed.`;
|
|
13472
|
+
case "syntax_error":
|
|
13473
|
+
return `Read the referenced file, patch the syntax/indentation at the failing location, then run tests.`;
|
|
13474
|
+
case "import_error":
|
|
13475
|
+
return `Resolve the real import/module path or dependency. Do not add fake fallback modules or swallow ImportError in production code.`;
|
|
13476
|
+
case "dependency_error":
|
|
13477
|
+
return `Replace hallucinated dependency names with real package names and update the manifest via add_dependency.`;
|
|
13478
|
+
case "network_api_failure":
|
|
13479
|
+
return networkDemand(statusCodes);
|
|
13480
|
+
case "missing_output":
|
|
13481
|
+
return `Create or repair the declared output files. Do not mark done=true until verify outputs passes.`;
|
|
13482
|
+
case "tool_loop":
|
|
13483
|
+
return `Stop repeating read-only/probe actions. Use the current evidence to make a patch/write/dependency change or run a concrete verification command.`;
|
|
13484
|
+
case "permission_denied":
|
|
13485
|
+
return `Treat the denied operation as a real blocker unless an allowed alternative exists; do not bypass the permission gate.`;
|
|
13486
|
+
case "llm_provider":
|
|
13487
|
+
return `This is provider/context infrastructure, not a project code bug. Reduce prompt/debug context or fix provider config before retrying.`;
|
|
13488
|
+
case "exception":
|
|
13489
|
+
return `Localize the exception to a file or tool call, make the smallest allowed repair, then verify.`;
|
|
13490
|
+
case "unknown":
|
|
13491
|
+
return `Read the most relevant files and produce a concrete diagnosis before making a minimal allowed repair.`;
|
|
13492
|
+
}
|
|
13493
|
+
}
|
|
13494
|
+
function networkDemand(statusCodes) {
|
|
13495
|
+
if (statusCodes.some((code) => code === "401" || code === "403")) {
|
|
13496
|
+
return "The API is unauthorized/forbidden. If no user key/token is available, switch to a public no-key API and verify the real integration.";
|
|
13497
|
+
}
|
|
13498
|
+
if (statusCodes.some((code) => code === "404" || code === "410")) {
|
|
13499
|
+
return "The API URL/resource is unavailable. Stop retrying the same URL; switch to a maintained endpoint and verify response shape.";
|
|
13500
|
+
}
|
|
13501
|
+
if (statusCodes.includes("429")) {
|
|
13502
|
+
return "The API is rate-limited. Switch to a suitable fallback API or implement explicit retry/cache behaviour and tests.";
|
|
13503
|
+
}
|
|
13504
|
+
if (statusCodes.some((code) => /^5/u.test(code))) {
|
|
13505
|
+
return "The API server failed. Use a stable fallback endpoint or fail closed with a clear user-visible error path.";
|
|
13506
|
+
}
|
|
13507
|
+
return "Locate the failing URL/status/body, patch the real API integration, and verify with run_program plus tests. Do not hide the API failure.";
|
|
13508
|
+
}
|
|
13509
|
+
function isProcessNoise(category) {
|
|
13510
|
+
return ["tool_loop", "llm_provider", "permission_denied", "exception"].includes(category);
|
|
13511
|
+
}
|
|
13512
|
+
function shouldSuppressReasonInEvidence(reason, failureLog) {
|
|
13513
|
+
if (!reason || !failureLog?.trim()) return false;
|
|
13514
|
+
const lowerReason = reason.toLowerCase();
|
|
13515
|
+
const lowerLog = failureLog.toLowerCase();
|
|
13516
|
+
const hasRootSignal = /pytest exit=\s*[1-9]|tests? exit=\s*[1-9]|test gate|测试门禁|failed\s+(?:tests?|src)\/|assertionerror|syntaxerror|modulenotfounderror|importerror|network api failure|http\s+(?:401|403|404|408|409|410|422|429|5\d\d)|outputs?.*(?:missing|缺失|仍缺失)/u.test(lowerLog);
|
|
13517
|
+
if (!hasRootSignal) return false;
|
|
13518
|
+
return /script exhausted|completed phase debug finished without|repeated read-only\/probe actions|read-only recovery mode|low-quality debugger response|openai http (?:400|401|403|408|409|429|5\d\d)|rate limit exceeded|free-models-per-day|stream (?:wall-clock|idle)|request timed out|provider_call_failed|all llm providers failed/u.test(lowerReason);
|
|
13519
|
+
}
|
|
13520
|
+
function extractFailedTests(text) {
|
|
13521
|
+
const patterns = [
|
|
13522
|
+
/\bFAILED\s+([^\s]+(?:\.py|\.ts|\.tsx|\.js|\.jsx)(?:::[^\s]+)?)/gu,
|
|
13523
|
+
/([^\s]+(?:\.py|\.ts|\.tsx|\.js|\.jsx)::[A-Za-z0-9_:[\].-]+)/gu,
|
|
13524
|
+
/[×x]\s+([^\n]+?>\s+[^\n]+)/gu
|
|
13525
|
+
];
|
|
13526
|
+
const out = [];
|
|
13527
|
+
for (const pattern of patterns) {
|
|
13528
|
+
for (const match of text.matchAll(pattern)) {
|
|
13529
|
+
const value = oneLine(match[1] ?? "");
|
|
13530
|
+
if (value) out.push(value);
|
|
13531
|
+
}
|
|
13532
|
+
}
|
|
13533
|
+
return dedup4(out);
|
|
13534
|
+
}
|
|
13535
|
+
function extractFiles(text) {
|
|
13536
|
+
const out = [];
|
|
13537
|
+
const patterns = [
|
|
13538
|
+
/\b((?:src|tests?|docs)\/[A-Za-z0-9_./-]+\.(?:py|ts|tsx|js|jsx|json|md|dbc|csv|xlsx?))/gu,
|
|
13539
|
+
/File\s+["']([^"']+)["']/gu
|
|
13540
|
+
];
|
|
13541
|
+
for (const pattern of patterns) {
|
|
13542
|
+
for (const match of text.matchAll(pattern)) {
|
|
13543
|
+
const file = normalizePath2(match[1] ?? "");
|
|
13544
|
+
if (file && !file.includes("node_modules/")) out.push(file);
|
|
13545
|
+
}
|
|
13546
|
+
}
|
|
13547
|
+
return dedup4(out);
|
|
13548
|
+
}
|
|
13549
|
+
function extractToolFailures(lines) {
|
|
13550
|
+
return lines.filter((line) => /(?:\b(?:FAIL|failed|denied|exit=[1-9]|Error:)|失败)/iu.test(line)).filter((line) => /\b(?:run_tests|run_program|write_file|replace_in_file|apply_patch|append_file|http_fetch|add_dependency|read_file)\b/u.test(line)).map((line) => oneLine(line)).slice(0, MAX_TOOL_FAILURES);
|
|
13551
|
+
}
|
|
13552
|
+
function extractStatusCodes(text) {
|
|
13553
|
+
const out = [];
|
|
13554
|
+
for (const match of text.matchAll(/\b(?:HTTP\s*)?(401|403|404|408|409|410|422|429|5\d\d)\b/giu)) {
|
|
13555
|
+
out.push(match[1]);
|
|
13556
|
+
}
|
|
13557
|
+
return dedup4(out);
|
|
13558
|
+
}
|
|
13559
|
+
function selectEvidenceLines(text, category, primaryError, failedTests, files, toolFailures, maxEvidence = MAX_EVIDENCE) {
|
|
13560
|
+
const lines = normalizedLines(text);
|
|
13561
|
+
const needles = [
|
|
13562
|
+
primaryError,
|
|
13563
|
+
...failedTests,
|
|
13564
|
+
...files,
|
|
13565
|
+
...toolFailures,
|
|
13566
|
+
category === "network_api_failure" ? "http" : "",
|
|
13567
|
+
category === "missing_output" ? "missing" : "",
|
|
13568
|
+
category === "tool_loop" ? "read-only" : ""
|
|
13569
|
+
].map((item) => item.toLowerCase()).filter(Boolean);
|
|
13570
|
+
const selected = [];
|
|
13571
|
+
for (const line of lines) {
|
|
13572
|
+
const lower = line.toLowerCase();
|
|
13573
|
+
if (needles.some((needle) => lower.includes(needle.slice(0, 80))) || /\b(?:FAILED|Traceback|Error|Exception|AssertionError|SyntaxError|ModuleNotFoundError|pytest exit=|HTTP\s*[45]\d\d|outputs?.*missing)\b/iu.test(line) || /失败/u.test(line)) {
|
|
13574
|
+
selected.push(truncateLine(line, MAX_EVIDENCE_LINE));
|
|
13575
|
+
}
|
|
13576
|
+
}
|
|
13577
|
+
const compact = dedup4(selected).slice(0, maxEvidence);
|
|
13578
|
+
return { lines: compact, omitted: Math.max(0, selected.length - compact.length) };
|
|
13579
|
+
}
|
|
13580
|
+
function normalizedLines(text) {
|
|
13581
|
+
return text.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).filter((line) => !/^##\s+历史\s+DEBUG/u.test(line)).filter((line) => !/^##\s+修复建议/u.test(line)).filter((line) => !/^prior suggestions:/iu.test(line));
|
|
13582
|
+
}
|
|
13583
|
+
function oneLine(text) {
|
|
13584
|
+
return truncateLine(text.replace(/\s+/gu, " ").trim(), MAX_EVIDENCE_LINE);
|
|
13585
|
+
}
|
|
13586
|
+
function truncateLine(text, max) {
|
|
13587
|
+
if (text.length <= max) return text;
|
|
13588
|
+
return `${text.slice(0, Math.max(0, max - 24))}... [truncated ${text.length - max + 24} chars]`;
|
|
13589
|
+
}
|
|
13590
|
+
function normalizePath2(file) {
|
|
13591
|
+
return file.replace(/\\/g, "/").replace(/^.*?((?:src|tests?|docs)\/)/u, "$1");
|
|
13592
|
+
}
|
|
13593
|
+
function dedup4(items) {
|
|
13594
|
+
return [...new Set(items.filter((item) => String(item ?? "").length > 0))];
|
|
13595
|
+
}
|
|
13596
|
+
|
|
13597
|
+
// src/core/debug_cache.ts
|
|
12525
13598
|
var EMPTY = { version: 1, steps: {} };
|
|
12526
13599
|
function sanitizeDebugFailureLogForPrompt(log) {
|
|
12527
13600
|
const lines = log.split(/\r?\n/u);
|
|
@@ -12533,19 +13606,23 @@ function sanitizeDebugFailureLogForPrompt(log) {
|
|
|
12533
13606
|
mode = "skip-history";
|
|
12534
13607
|
continue;
|
|
12535
13608
|
}
|
|
13609
|
+
if (/^##\s+(?:debug brief|root issue brief|current retry brief)\b/u.test(line)) {
|
|
13610
|
+
mode = "skip-brief";
|
|
13611
|
+
continue;
|
|
13612
|
+
}
|
|
12536
13613
|
if (/^##\s+修复建议/u.test(line)) {
|
|
12537
13614
|
mode = "skip-suggestions";
|
|
12538
13615
|
continue;
|
|
12539
13616
|
}
|
|
12540
13617
|
if (mode === "skip-history") {
|
|
12541
|
-
if (/^(原因:|Reason:|##\s+debug failure log\b)/u.test(line)) {
|
|
13618
|
+
if (/^(原因:|Reason:|##\s+(?:debug failure log|compact failure evidence)\b)/u.test(line)) {
|
|
12542
13619
|
mode = "keep";
|
|
12543
13620
|
} else {
|
|
12544
13621
|
continue;
|
|
12545
13622
|
}
|
|
12546
13623
|
}
|
|
12547
13624
|
if (mode === "skip-suggestions") {
|
|
12548
|
-
if (/^(原因:|Reason:|轮次:|工具调用:|---\s+|##\s+历史\s+DEBUG\s+尝试|##\s+debug failure log\b)/u.test(line)) {
|
|
13625
|
+
if (/^(原因:|Reason:|轮次:|工具调用:|---\s+|##\s+历史\s+DEBUG\s+尝试|##\s+(?:debug failure log|compact failure evidence)\b)/u.test(line)) {
|
|
12549
13626
|
mode = "keep";
|
|
12550
13627
|
if (/^##\s+历史\s+DEBUG\s+尝试/u.test(line)) {
|
|
12551
13628
|
mode = "skip-history";
|
|
@@ -12555,6 +13632,13 @@ function sanitizeDebugFailureLogForPrompt(log) {
|
|
|
12555
13632
|
continue;
|
|
12556
13633
|
}
|
|
12557
13634
|
}
|
|
13635
|
+
if (mode === "skip-brief") {
|
|
13636
|
+
if (/^(原因:|Reason:|轮次:|工具调用:|---\s+|##\s+修复建议|##\s+历史\s+DEBUG\s+尝试|##\s+(?:debug failure log|compact failure evidence)\b)/u.test(line)) {
|
|
13637
|
+
mode = "keep";
|
|
13638
|
+
} else {
|
|
13639
|
+
continue;
|
|
13640
|
+
}
|
|
13641
|
+
}
|
|
12558
13642
|
if (/^\s*suggestions:\s/u.test(line)) continue;
|
|
12559
13643
|
if (/^(原因:|Reason:)/u.test(line)) {
|
|
12560
13644
|
const normalized = line.trim();
|
|
@@ -12592,7 +13676,7 @@ var DebugCache = class {
|
|
|
12592
13676
|
if (this.loaded) return;
|
|
12593
13677
|
this.loaded = true;
|
|
12594
13678
|
try {
|
|
12595
|
-
const raw = await
|
|
13679
|
+
const raw = await fs24.readFile(this.file, "utf8");
|
|
12596
13680
|
const parsed = JSON.parse(raw);
|
|
12597
13681
|
if (parsed && parsed.version === 1 && parsed.steps && typeof parsed.steps === "object") {
|
|
12598
13682
|
this.data = { version: 1, steps: parsed.steps };
|
|
@@ -12620,11 +13704,24 @@ var DebugCache = class {
|
|
|
12620
13704
|
const cleanedFailureLog = stripNestedLatestDebuggerFailures(
|
|
12621
13705
|
sanitizeDebugFailureLogForPrompt(entry.failureLogTail ?? "")
|
|
12622
13706
|
);
|
|
13707
|
+
const debugBrief = buildDebugBrief({
|
|
13708
|
+
reason: entry.reason,
|
|
13709
|
+
failureLog: cleanedFailureLog
|
|
13710
|
+
});
|
|
13711
|
+
const compactFailureLog = compactFailureEvidence({
|
|
13712
|
+
reason: entry.reason,
|
|
13713
|
+
failureLog: cleanedFailureLog,
|
|
13714
|
+
maxChars: 3600,
|
|
13715
|
+
maxLines: this.maxLogLines
|
|
13716
|
+
});
|
|
12623
13717
|
const e = {
|
|
12624
13718
|
attempt: entry.attempt,
|
|
12625
13719
|
ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
12626
13720
|
reason: (entry.reason ?? "").slice(0, 500),
|
|
12627
|
-
failureLogTail: tail(
|
|
13721
|
+
failureLogTail: tail(compactFailureLog),
|
|
13722
|
+
debugBrief,
|
|
13723
|
+
contextMode: entry.contextMode,
|
|
13724
|
+
testScopeArgs: entry.testScopeArgs,
|
|
12628
13725
|
suggestions: entry.suggestions,
|
|
12629
13726
|
snapshotSha: entry.snapshotSha,
|
|
12630
13727
|
metrics: entry.metrics
|
|
@@ -12689,8 +13786,10 @@ var DebugCache = class {
|
|
|
12689
13786
|
const m = e.metrics ? ` [health=${e.metrics.healthScore.toFixed(2)} parseFail=${e.metrics.parseFailures} repeat=${e.metrics.repeatedTurns}]` : "";
|
|
12690
13787
|
const sugg = e.suggestions && e.suggestions.length > 0 ? `
|
|
12691
13788
|
prior suggestions: omitted (${e.suggestions.length}) to avoid stale guidance; use the current failure log first.` : "";
|
|
13789
|
+
const brief = e.debugBrief ? `
|
|
13790
|
+
brief: ${e.debugBrief.category} \u2014 ${e.debugBrief.summary}` : "";
|
|
12692
13791
|
return `- attempt #${e.attempt} @ ${e.ts}${m}
|
|
12693
|
-
reason: ${e.reason}${sugg}`;
|
|
13792
|
+
reason: ${e.reason}${brief}${sugg}`;
|
|
12694
13793
|
});
|
|
12695
13794
|
const omitted = [
|
|
12696
13795
|
noisyCount > 0 ? `- omitted ${noisyCount} noisy provider/read-only/recovery attempt(s); keep focus on the current actionable failure.` : "",
|
|
@@ -12706,18 +13805,609 @@ var DebugCache = class {
|
|
|
12706
13805
|
].join("\n");
|
|
12707
13806
|
}
|
|
12708
13807
|
async save() {
|
|
12709
|
-
await
|
|
12710
|
-
await
|
|
13808
|
+
await fs24.mkdir(path24.dirname(this.file), { recursive: true });
|
|
13809
|
+
await fs24.writeFile(this.file, JSON.stringify(this.data, null, 2), "utf8");
|
|
12711
13810
|
}
|
|
12712
13811
|
};
|
|
12713
13812
|
function isNoisyDebugReason(reason) {
|
|
12714
13813
|
const text = reason.toLowerCase();
|
|
12715
|
-
return /repeated read-only\/probe actions without progress/u.test(text) || /read-only recovery mode repeated probe actions/u.test(text) || /low-quality debugger response/u.test(text) || /without repair evidence/u.test(text) || /had an unresolved failure from a previous run.*rolling back/u.test(text) || /tool verification failed.*rolling back/u.test(text) || /openai http (?:400|401|403|408|409|429|5\d\d)/u.test(text) || /rate limit exceeded|free-models-per-day|retry_after_seconds|retry-after/u.test(text) || /response_format|json_object|json_schema|invalid_request_body/u.test(text) || /stream (?:wall-clock|idle)|request timed out|fetch failed/u.test(text);
|
|
13814
|
+
return /repeated read-only\/probe actions without progress/u.test(text) || /read-only recovery mode repeated probe actions/u.test(text) || /low-quality debugger response/u.test(text) || /script exhausted/u.test(text) || /without repair evidence/u.test(text) || /without a successful repair mutation or verification tool call/u.test(text) || /had an unresolved failure from a previous run.*rolling back/u.test(text) || /tool verification failed.*rolling back/u.test(text) || /openai http (?:400|401|403|408|409|429|5\d\d)/u.test(text) || /rate limit exceeded|free-models-per-day|retry_after_seconds|retry-after/u.test(text) || /response_format|json_object|json_schema|invalid_request_body/u.test(text) || /stream (?:wall-clock|idle)|request timed out|fetch failed/u.test(text);
|
|
12716
13815
|
}
|
|
12717
13816
|
|
|
12718
|
-
// src/core/
|
|
12719
|
-
import { promises as
|
|
13817
|
+
// src/core/debug_wiki.ts
|
|
13818
|
+
import { promises as fs25 } from "fs";
|
|
12720
13819
|
import path25 from "path";
|
|
13820
|
+
import { fileURLToPath } from "url";
|
|
13821
|
+
import YAML3 from "yaml";
|
|
13822
|
+
var DEFAULT_DEBUG_WIKI_REL_PATH = ".xcompiler/debug-wiki";
|
|
13823
|
+
var BUNDLED_DEBUG_WIKI_REL_PATH = "debug-wiki";
|
|
13824
|
+
var DEBUG_WIKI_VERSION = 1;
|
|
13825
|
+
var LAYERS = ["system", "agent", "external"];
|
|
13826
|
+
var EMPTY_STATS = { uses: 0, successes: 0, failures: 0 };
|
|
13827
|
+
function defaultDebugWikiPath() {
|
|
13828
|
+
const configured = xcEnv("PATH")?.trim();
|
|
13829
|
+
const base = configured ? path25.resolve(configured) : path25.resolve(path25.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
13830
|
+
return path25.join(base, DEFAULT_DEBUG_WIKI_REL_PATH);
|
|
13831
|
+
}
|
|
13832
|
+
function bundledDebugWikiPath() {
|
|
13833
|
+
return path25.join(path25.resolve(path25.dirname(fileURLToPath(import.meta.url)), "..", ".."), BUNDLED_DEBUG_WIKI_REL_PATH);
|
|
13834
|
+
}
|
|
13835
|
+
var DebugWiki = class {
|
|
13836
|
+
loaded = false;
|
|
13837
|
+
entries = [];
|
|
13838
|
+
rootPath;
|
|
13839
|
+
filePath;
|
|
13840
|
+
bundledPath;
|
|
13841
|
+
constructor(rootPath, opts = {}) {
|
|
13842
|
+
this.rootPath = path25.resolve(rootPath);
|
|
13843
|
+
this.filePath = this.rootPath;
|
|
13844
|
+
this.bundledPath = opts.bundledPath ?? bundledDebugWikiPath();
|
|
13845
|
+
}
|
|
13846
|
+
async load() {
|
|
13847
|
+
if (this.loaded) return;
|
|
13848
|
+
this.loaded = true;
|
|
13849
|
+
await this.ensureLayout();
|
|
13850
|
+
await this.ensureRootReadme();
|
|
13851
|
+
await this.ensureOperationLog();
|
|
13852
|
+
await this.copyBundledLayers();
|
|
13853
|
+
this.entries = [];
|
|
13854
|
+
for (const layer of LAYERS) {
|
|
13855
|
+
this.entries.push(...await this.readLayer(layer));
|
|
13856
|
+
}
|
|
13857
|
+
await this.applyFeedbackLog();
|
|
13858
|
+
await this.writeIndex();
|
|
13859
|
+
}
|
|
13860
|
+
async search(brief, opts = {}) {
|
|
13861
|
+
await this.load();
|
|
13862
|
+
const limit = opts.limit ?? 3;
|
|
13863
|
+
return this.rank(brief, opts.language).filter((match) => match.score >= 4).slice(0, limit);
|
|
13864
|
+
}
|
|
13865
|
+
async recordUse(entryIds, input) {
|
|
13866
|
+
await this.load();
|
|
13867
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13868
|
+
const feedback = this.feedbackFrom(entryIds, input, now, "used");
|
|
13869
|
+
if (feedback.length === 0) return;
|
|
13870
|
+
for (const item of feedback) {
|
|
13871
|
+
const entry = this.byId(item.entryId);
|
|
13872
|
+
if (!entry) continue;
|
|
13873
|
+
entry.stats.uses += 1;
|
|
13874
|
+
entry.lastUsedAt = now;
|
|
13875
|
+
entry.updatedAt = now;
|
|
13876
|
+
pushFeedback(entry, item);
|
|
13877
|
+
}
|
|
13878
|
+
await this.appendLayerFeedback(feedback);
|
|
13879
|
+
await this.persistExternalEntries();
|
|
13880
|
+
await this.writeIndex(now);
|
|
13881
|
+
await this.appendOperationLog({
|
|
13882
|
+
at: now,
|
|
13883
|
+
action: "use",
|
|
13884
|
+
entryIds: feedback.map((item) => item.entryId).filter(Boolean),
|
|
13885
|
+
issueId: input.issueId,
|
|
13886
|
+
stepId: input.stepId,
|
|
13887
|
+
phase: input.phase,
|
|
13888
|
+
summary: input.brief.summary
|
|
13889
|
+
});
|
|
13890
|
+
}
|
|
13891
|
+
async recordFailure(entryIds, input) {
|
|
13892
|
+
await this.load();
|
|
13893
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13894
|
+
const feedback = this.feedbackFrom(entryIds, input, now, "failure", input.reason);
|
|
13895
|
+
if (feedback.length === 0) return;
|
|
13896
|
+
for (const item of feedback) {
|
|
13897
|
+
const entry = this.byId(item.entryId);
|
|
13898
|
+
if (!entry) continue;
|
|
13899
|
+
entry.stats.failures += 1;
|
|
13900
|
+
entry.status = "needs_review";
|
|
13901
|
+
entry.updatedAt = now;
|
|
13902
|
+
pushFeedback(entry, item);
|
|
13903
|
+
}
|
|
13904
|
+
await this.appendLayerFeedback(feedback);
|
|
13905
|
+
await this.persistExternalEntries();
|
|
13906
|
+
await this.writeIndex(now);
|
|
13907
|
+
await this.appendOperationLog({
|
|
13908
|
+
at: now,
|
|
13909
|
+
action: "failure",
|
|
13910
|
+
entryIds: feedback.map((item) => item.entryId).filter(Boolean),
|
|
13911
|
+
issueId: input.issueId,
|
|
13912
|
+
stepId: input.stepId,
|
|
13913
|
+
phase: input.phase,
|
|
13914
|
+
summary: input.brief.summary,
|
|
13915
|
+
reason: input.reason
|
|
13916
|
+
});
|
|
13917
|
+
}
|
|
13918
|
+
async recordResolution(input) {
|
|
13919
|
+
await this.load();
|
|
13920
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13921
|
+
const used = this.byIds(input.usedEntryIds ?? []);
|
|
13922
|
+
const externalTargets = used.filter((entry) => entry.layer === "external");
|
|
13923
|
+
const target = externalTargets.length > 0 ? externalTargets : this.rank(input.brief, input.language).filter((match) => match.entry.layer === "external" && match.score >= 8).slice(0, 1).map((m) => m.entry);
|
|
13924
|
+
const updated = [];
|
|
13925
|
+
let createdId;
|
|
13926
|
+
for (const entry of target) {
|
|
13927
|
+
this.applyResolution(entry, input, now, entry.stats.failures > 0 ? "corrected" : "success");
|
|
13928
|
+
updated.push(entry.id);
|
|
13929
|
+
}
|
|
13930
|
+
if (updated.length === 0) {
|
|
13931
|
+
const created = createEntry(input, now, this.nextExternalId(now), "external");
|
|
13932
|
+
created.supersedes = used.length > 0 ? used.map((entry) => entry.id) : void 0;
|
|
13933
|
+
this.entries.push(created);
|
|
13934
|
+
createdId = created.id;
|
|
13935
|
+
}
|
|
13936
|
+
const correctedFeedback = this.feedbackFrom(
|
|
13937
|
+
used.filter((entry) => entry.layer !== "external").map((entry) => entry.id),
|
|
13938
|
+
input,
|
|
13939
|
+
now,
|
|
13940
|
+
"corrected"
|
|
13941
|
+
);
|
|
13942
|
+
for (const item of correctedFeedback) {
|
|
13943
|
+
const entry = this.byId(item.entryId);
|
|
13944
|
+
if (!entry) continue;
|
|
13945
|
+
entry.stats.successes += 1;
|
|
13946
|
+
entry.status = "active";
|
|
13947
|
+
entry.updatedAt = now;
|
|
13948
|
+
pushFeedback(entry, item);
|
|
13949
|
+
}
|
|
13950
|
+
await this.appendLayerFeedback(correctedFeedback);
|
|
13951
|
+
await this.persistExternalEntries();
|
|
13952
|
+
await this.writeIndex(now);
|
|
13953
|
+
await this.appendOperationLog({
|
|
13954
|
+
at: now,
|
|
13955
|
+
action: createdId ? "resolution_created" : "resolution_updated",
|
|
13956
|
+
entryIds: createdId ? [createdId] : updated,
|
|
13957
|
+
issueId: input.issueId,
|
|
13958
|
+
stepId: input.stepId,
|
|
13959
|
+
phase: input.phase,
|
|
13960
|
+
summary: input.brief.summary
|
|
13961
|
+
});
|
|
13962
|
+
return createdId ? { created: createdId, updated: [] } : { updated };
|
|
13963
|
+
}
|
|
13964
|
+
async ensureLayout() {
|
|
13965
|
+
for (const layer of LAYERS) {
|
|
13966
|
+
await fs25.mkdir(this.layerDir(layer), { recursive: true });
|
|
13967
|
+
}
|
|
13968
|
+
}
|
|
13969
|
+
async ensureRootReadme() {
|
|
13970
|
+
const to = path25.join(this.rootPath, "README.md");
|
|
13971
|
+
if (await exists(to)) return;
|
|
13972
|
+
const from = path25.join(this.bundledPath, "README.md");
|
|
13973
|
+
const fallback = defaultDebugWikiReadme();
|
|
13974
|
+
const text = await fs25.readFile(from, "utf8").catch(() => fallback);
|
|
13975
|
+
await fs25.writeFile(to, text.endsWith("\n") ? text : `${text}
|
|
13976
|
+
`, "utf8");
|
|
13977
|
+
}
|
|
13978
|
+
async ensureOperationLog() {
|
|
13979
|
+
const file = this.operationLogPath();
|
|
13980
|
+
if (await exists(file)) return;
|
|
13981
|
+
await fs25.writeFile(file, "# XCompiler Debug Wiki Log\n\nAppend-only operational notes for retrieval, failed reuse, and confirmed repairs.\n", "utf8");
|
|
13982
|
+
}
|
|
13983
|
+
async copyBundledLayers() {
|
|
13984
|
+
if (path25.resolve(this.bundledPath) === this.rootPath) return;
|
|
13985
|
+
for (const layer of ["system", "agent"]) {
|
|
13986
|
+
const from = path25.join(this.bundledPath, "wiki", layer);
|
|
13987
|
+
const to = this.layerDir(layer);
|
|
13988
|
+
if (!await exists(from)) continue;
|
|
13989
|
+
await fs25.cp(from, to, { recursive: true, force: true });
|
|
13990
|
+
}
|
|
13991
|
+
}
|
|
13992
|
+
async readLayer(layer) {
|
|
13993
|
+
const dir = this.layerDir(layer);
|
|
13994
|
+
const files = (await fs25.readdir(dir).catch(() => [])).filter((file) => file.endsWith(".md")).sort();
|
|
13995
|
+
const entries = [];
|
|
13996
|
+
for (const file of files) {
|
|
13997
|
+
const abs = path25.join(dir, file);
|
|
13998
|
+
const page = parseWikiPage(await fs25.readFile(abs, "utf8"));
|
|
13999
|
+
const entry = normalizeEntry({ ...page.data, layer, solution: page.data.solution ?? page.body }, layer);
|
|
14000
|
+
entry.sourcePath = path25.relative(this.rootPath, abs).replace(/\\/g, "/");
|
|
14001
|
+
entries.push(entry);
|
|
14002
|
+
}
|
|
14003
|
+
return entries;
|
|
14004
|
+
}
|
|
14005
|
+
async applyFeedbackLog() {
|
|
14006
|
+
const log = await fs25.readFile(this.feedbackPath(), "utf8").catch(() => "");
|
|
14007
|
+
for (const line of log.split(/\r?\n/u)) {
|
|
14008
|
+
if (!line.trim()) continue;
|
|
14009
|
+
const item = JSON.parse(line);
|
|
14010
|
+
const entry = this.byId(item.entryId);
|
|
14011
|
+
if (!entry) continue;
|
|
14012
|
+
if (item.kind === "used") entry.stats.uses += 1;
|
|
14013
|
+
if (item.kind === "failure") {
|
|
14014
|
+
entry.stats.failures += 1;
|
|
14015
|
+
entry.status = "needs_review";
|
|
14016
|
+
}
|
|
14017
|
+
if (item.kind === "success" || item.kind === "corrected") {
|
|
14018
|
+
entry.stats.successes += 1;
|
|
14019
|
+
if (item.kind === "corrected") entry.status = "active";
|
|
14020
|
+
}
|
|
14021
|
+
entry.updatedAt = item.at;
|
|
14022
|
+
pushFeedback(entry, item);
|
|
14023
|
+
}
|
|
14024
|
+
}
|
|
14025
|
+
applyResolution(entry, input, now, kind) {
|
|
14026
|
+
entry.status = "active";
|
|
14027
|
+
entry.updatedAt = now;
|
|
14028
|
+
entry.summary = input.brief.summary;
|
|
14029
|
+
entry.primaryError = input.brief.primaryError;
|
|
14030
|
+
entry.debugDemand = input.brief.debugDemand;
|
|
14031
|
+
entry.fingerprints = dedup5([...entry.fingerprints, ...fingerprints(input.brief)]);
|
|
14032
|
+
entry.symptoms = dedup5([...input.brief.evidence, ...entry.symptoms]).slice(0, 12);
|
|
14033
|
+
entry.evidence = dedup5([...input.evidence ?? [], ...entry.evidence]).slice(0, 12);
|
|
14034
|
+
if (input.resolutionPlan?.trim()) entry.resolutionPlan = input.resolutionPlan.trim();
|
|
14035
|
+
entry.solution = mergeSolution(entry.solution, input.solution);
|
|
14036
|
+
entry.repairFiles = dedup5([...input.repairFiles ?? [], ...entry.repairFiles ?? []]).slice(0, 12);
|
|
14037
|
+
entry.stats.successes += 1;
|
|
14038
|
+
pushFeedback(entry, {
|
|
14039
|
+
at: now,
|
|
14040
|
+
kind,
|
|
14041
|
+
entryId: entry.id,
|
|
14042
|
+
issueId: input.issueId,
|
|
14043
|
+
stepId: input.stepId,
|
|
14044
|
+
phase: input.phase,
|
|
14045
|
+
summary: input.brief.summary
|
|
14046
|
+
});
|
|
14047
|
+
}
|
|
14048
|
+
async persistExternalEntries() {
|
|
14049
|
+
for (const entry of this.entries.filter((item) => item.layer === "external")) {
|
|
14050
|
+
const abs = path25.join(this.rootPath, entry.sourcePath ?? this.externalEntryPath(entry));
|
|
14051
|
+
entry.sourcePath = path25.relative(this.rootPath, abs).replace(/\\/g, "/");
|
|
14052
|
+
await fs25.mkdir(path25.dirname(abs), { recursive: true });
|
|
14053
|
+
await fs25.writeFile(abs, renderWikiPage(entry), "utf8");
|
|
14054
|
+
}
|
|
14055
|
+
}
|
|
14056
|
+
async appendFeedback(feedback) {
|
|
14057
|
+
if (feedback.length === 0) return;
|
|
14058
|
+
await fs25.mkdir(path25.dirname(this.feedbackPath()), { recursive: true });
|
|
14059
|
+
await fs25.appendFile(this.feedbackPath(), feedback.map((item) => JSON.stringify(item)).join("\n") + "\n", "utf8");
|
|
14060
|
+
}
|
|
14061
|
+
async appendLayerFeedback(feedback) {
|
|
14062
|
+
await this.appendFeedback(feedback.filter((item) => this.byId(item.entryId)?.layer !== "external"));
|
|
14063
|
+
}
|
|
14064
|
+
async appendOperationLog(entry) {
|
|
14065
|
+
await fs25.mkdir(path25.dirname(this.operationLogPath()), { recursive: true });
|
|
14066
|
+
await fs25.appendFile(this.operationLogPath(), renderOperationLogEntry(entry), "utf8");
|
|
14067
|
+
}
|
|
14068
|
+
async writeIndex(now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
14069
|
+
const layerCounts = Object.fromEntries(LAYERS.map((layer) => [
|
|
14070
|
+
layer,
|
|
14071
|
+
{ entries: this.entries.filter((entry) => entry.layer === layer).length, writable: layer === "external" }
|
|
14072
|
+
]));
|
|
14073
|
+
const index = {
|
|
14074
|
+
version: DEBUG_WIKI_VERSION,
|
|
14075
|
+
updatedAt: now,
|
|
14076
|
+
root: this.rootPath,
|
|
14077
|
+
layers: layerCounts,
|
|
14078
|
+
entries: this.entries.map((entry) => ({
|
|
14079
|
+
id: entry.id,
|
|
14080
|
+
layer: entry.layer,
|
|
14081
|
+
status: entry.status,
|
|
14082
|
+
category: entry.category,
|
|
14083
|
+
summary: entry.summary,
|
|
14084
|
+
updatedAt: entry.updatedAt,
|
|
14085
|
+
sourcePath: entry.sourcePath
|
|
14086
|
+
}))
|
|
14087
|
+
};
|
|
14088
|
+
await fs25.writeFile(path25.join(this.rootPath, "index.json"), `${JSON.stringify(index, null, 2)}
|
|
14089
|
+
`, "utf8");
|
|
14090
|
+
await fs25.writeFile(path25.join(this.rootPath, "index.md"), renderReadableIndex(index, this.entries), "utf8");
|
|
14091
|
+
}
|
|
14092
|
+
feedbackFrom(ids, input, now, kind, reason) {
|
|
14093
|
+
return dedup5(ids).filter((id) => this.byId(id)).map((id) => ({
|
|
14094
|
+
at: now,
|
|
14095
|
+
kind,
|
|
14096
|
+
entryId: id,
|
|
14097
|
+
issueId: input.issueId,
|
|
14098
|
+
stepId: input.stepId,
|
|
14099
|
+
phase: input.phase,
|
|
14100
|
+
summary: input.brief.summary,
|
|
14101
|
+
reason
|
|
14102
|
+
}));
|
|
14103
|
+
}
|
|
14104
|
+
rank(brief, language) {
|
|
14105
|
+
const queryTokens = new Set(tokensForBrief(brief));
|
|
14106
|
+
const queryFingerprints = fingerprints(brief);
|
|
14107
|
+
return this.entries.filter((entry) => entry.status !== "superseded").map((entry) => {
|
|
14108
|
+
const reasons = [];
|
|
14109
|
+
let score = entry.layer === "agent" ? 1 : entry.layer === "system" ? 0.5 : 0;
|
|
14110
|
+
if (entry.category === brief.category) {
|
|
14111
|
+
score += 4;
|
|
14112
|
+
reasons.push(`category:${entry.category}`);
|
|
14113
|
+
}
|
|
14114
|
+
if (language && entry.language === language) score += 1;
|
|
14115
|
+
const exact = entry.fingerprints.filter((fp) => queryFingerprints.includes(fp));
|
|
14116
|
+
if (exact.length > 0) {
|
|
14117
|
+
score += exact.length * 3;
|
|
14118
|
+
reasons.push(`fingerprint:${exact.length}`);
|
|
14119
|
+
}
|
|
14120
|
+
const entryTokens = new Set(tokensForEntry(entry));
|
|
14121
|
+
let overlap = 0;
|
|
14122
|
+
for (const token of queryTokens) if (entryTokens.has(token)) overlap++;
|
|
14123
|
+
score += Math.min(6, overlap);
|
|
14124
|
+
if (overlap > 0) reasons.push(`tokens:${overlap}`);
|
|
14125
|
+
const confidence = confidenceFor(entry);
|
|
14126
|
+
return { entry, score: score * confidence, confidence, reasons };
|
|
14127
|
+
}).sort((a, b) => b.score - a.score);
|
|
14128
|
+
}
|
|
14129
|
+
byId(id) {
|
|
14130
|
+
return this.entries.find((entry) => entry.id === id);
|
|
14131
|
+
}
|
|
14132
|
+
byIds(ids) {
|
|
14133
|
+
const wanted = new Set(ids);
|
|
14134
|
+
return this.entries.filter((entry) => wanted.has(entry.id));
|
|
14135
|
+
}
|
|
14136
|
+
layerDir(layer) {
|
|
14137
|
+
return path25.join(this.rootPath, "wiki", layer);
|
|
14138
|
+
}
|
|
14139
|
+
feedbackPath() {
|
|
14140
|
+
return path25.join(this.rootPath, "wiki", "external", "feedback.jsonl");
|
|
14141
|
+
}
|
|
14142
|
+
operationLogPath() {
|
|
14143
|
+
return path25.join(this.rootPath, "log.md");
|
|
14144
|
+
}
|
|
14145
|
+
nextExternalId(now) {
|
|
14146
|
+
const stamp = now.replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
14147
|
+
const count = this.entries.filter((entry) => entry.layer === "external").length + 1;
|
|
14148
|
+
return `external.${stamp}.${String(count).padStart(4, "0")}`;
|
|
14149
|
+
}
|
|
14150
|
+
externalEntryPath(entry) {
|
|
14151
|
+
return path25.join("wiki", "external", `${slugify(entry.id)}.md`);
|
|
14152
|
+
}
|
|
14153
|
+
};
|
|
14154
|
+
function renderDebugWikiMatchesForPrompt(matches) {
|
|
14155
|
+
if (matches.length === 0) return "";
|
|
14156
|
+
const lines = [
|
|
14157
|
+
"## debug wiki matches",
|
|
14158
|
+
"LLM-wiki layered retrieval. Treat entries as hypotheses, verify against current files/tests, and stop using any entry that current evidence disproves."
|
|
14159
|
+
];
|
|
14160
|
+
for (const match of matches) {
|
|
14161
|
+
const entry = match.entry;
|
|
14162
|
+
lines.push(
|
|
14163
|
+
`- ${entry.id} layer=${entry.layer} score=${match.score.toFixed(2)} confidence=${match.confidence.toFixed(2)} status=${entry.status}`,
|
|
14164
|
+
` problem: [${entry.category}] ${entry.summary}`,
|
|
14165
|
+
` symptoms: ${entry.symptoms.slice(0, 4).join(" | ") || entry.primaryError}`,
|
|
14166
|
+
entry.resolutionPlan ? ` priorPlan: ${entry.resolutionPlan}` : "",
|
|
14167
|
+
` confirmedSolution: ${entry.solution}`,
|
|
14168
|
+
` feedback: uses=${entry.stats.uses} successes=${entry.stats.successes} failures=${entry.stats.failures}`
|
|
14169
|
+
);
|
|
14170
|
+
if (entry.repairFiles?.length) lines.push(` repairFiles: ${entry.repairFiles.join(", ")}`);
|
|
14171
|
+
if (entry.supersedes?.length) lines.push(` supersedes: ${entry.supersedes.join(", ")}`);
|
|
14172
|
+
if (match.reasons.length) lines.push(` matchedBy: ${match.reasons.join(", ")}`);
|
|
14173
|
+
}
|
|
14174
|
+
return lines.filter(Boolean).join("\n");
|
|
14175
|
+
}
|
|
14176
|
+
function createEntry(input, now, id, layer) {
|
|
14177
|
+
return normalizeEntry({
|
|
14178
|
+
id,
|
|
14179
|
+
layer,
|
|
14180
|
+
createdAt: now,
|
|
14181
|
+
updatedAt: now,
|
|
14182
|
+
status: "active",
|
|
14183
|
+
category: input.brief.category,
|
|
14184
|
+
summary: input.brief.summary,
|
|
14185
|
+
primaryError: input.brief.primaryError,
|
|
14186
|
+
debugDemand: input.brief.debugDemand,
|
|
14187
|
+
fingerprints: fingerprints(input.brief),
|
|
14188
|
+
symptoms: input.brief.evidence.slice(0, 12),
|
|
14189
|
+
resolutionPlan: input.resolutionPlan?.trim(),
|
|
14190
|
+
solution: input.solution,
|
|
14191
|
+
evidence: (input.evidence ?? input.brief.evidence).slice(0, 12),
|
|
14192
|
+
sourceIssueId: input.issueId,
|
|
14193
|
+
sourceStepId: input.stepId,
|
|
14194
|
+
sourcePhase: input.phase,
|
|
14195
|
+
targetPhase: input.targetPhase,
|
|
14196
|
+
language: input.language,
|
|
14197
|
+
repairFiles: input.repairFiles?.slice(0, 12),
|
|
14198
|
+
stats: { uses: 0, successes: 1, failures: 0 },
|
|
14199
|
+
feedback: [{ at: now, kind: "success", entryId: id, issueId: input.issueId, stepId: input.stepId, phase: input.phase, summary: input.brief.summary }]
|
|
14200
|
+
}, layer);
|
|
14201
|
+
}
|
|
14202
|
+
function normalizeEntry(raw, layer) {
|
|
14203
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14204
|
+
return {
|
|
14205
|
+
id: String(raw.id ?? `${layer}.${slugify(raw.summary ?? raw.primaryError ?? "entry")}`),
|
|
14206
|
+
layer: raw.layer ?? layer,
|
|
14207
|
+
createdAt: raw.createdAt ?? now,
|
|
14208
|
+
updatedAt: raw.updatedAt ?? raw.createdAt ?? now,
|
|
14209
|
+
status: raw.status ?? "active",
|
|
14210
|
+
category: raw.category ?? "unknown",
|
|
14211
|
+
summary: raw.summary ?? raw.primaryError ?? "Debug wiki entry",
|
|
14212
|
+
primaryError: raw.primaryError ?? raw.summary ?? "",
|
|
14213
|
+
debugDemand: raw.debugDemand ?? "",
|
|
14214
|
+
fingerprints: raw.fingerprints ?? [],
|
|
14215
|
+
symptoms: raw.symptoms ?? [],
|
|
14216
|
+
resolutionPlan: raw.resolutionPlan,
|
|
14217
|
+
solution: raw.solution ?? "",
|
|
14218
|
+
evidence: raw.evidence ?? [],
|
|
14219
|
+
sourceIssueId: raw.sourceIssueId,
|
|
14220
|
+
sourceStepId: raw.sourceStepId,
|
|
14221
|
+
sourcePhase: raw.sourcePhase,
|
|
14222
|
+
targetPhase: raw.targetPhase,
|
|
14223
|
+
language: raw.language,
|
|
14224
|
+
repairFiles: raw.repairFiles ?? [],
|
|
14225
|
+
supersedes: raw.supersedes ?? [],
|
|
14226
|
+
stats: { ...EMPTY_STATS, ...raw.stats ?? {} },
|
|
14227
|
+
lastUsedAt: raw.lastUsedAt,
|
|
14228
|
+
feedback: (raw.feedback ?? []).slice(-20),
|
|
14229
|
+
sourcePath: raw.sourcePath
|
|
14230
|
+
};
|
|
14231
|
+
}
|
|
14232
|
+
function renderWikiPage(entry) {
|
|
14233
|
+
const frontmatter = { ...entry, sourcePath: void 0 };
|
|
14234
|
+
return [
|
|
14235
|
+
"---",
|
|
14236
|
+
YAML3.stringify(frontmatter).trim(),
|
|
14237
|
+
"---",
|
|
14238
|
+
"",
|
|
14239
|
+
`# ${entry.summary}`,
|
|
14240
|
+
"",
|
|
14241
|
+
"## Problem",
|
|
14242
|
+
"",
|
|
14243
|
+
`- category: ${entry.category}`,
|
|
14244
|
+
`- status: ${entry.status}`,
|
|
14245
|
+
`- primaryError: ${entry.primaryError || "n/a"}`,
|
|
14246
|
+
`- debugDemand: ${entry.debugDemand || "n/a"}`,
|
|
14247
|
+
"",
|
|
14248
|
+
"## Resolution Plan",
|
|
14249
|
+
"",
|
|
14250
|
+
entry.resolutionPlan?.trim() || "No explicit plan recorded.",
|
|
14251
|
+
"",
|
|
14252
|
+
"## Confirmed Solution",
|
|
14253
|
+
"",
|
|
14254
|
+
entry.solution.trim() || "No confirmed solution recorded.",
|
|
14255
|
+
"",
|
|
14256
|
+
"## Evidence",
|
|
14257
|
+
"",
|
|
14258
|
+
renderMarkdownList(entry.evidence),
|
|
14259
|
+
"",
|
|
14260
|
+
"## Retrieval",
|
|
14261
|
+
"",
|
|
14262
|
+
`- fingerprints: ${entry.fingerprints.join(", ") || "n/a"}`,
|
|
14263
|
+
`- repairFiles: ${(entry.repairFiles ?? []).join(", ") || "n/a"}`,
|
|
14264
|
+
`- stats: uses=${entry.stats.uses} successes=${entry.stats.successes} failures=${entry.stats.failures}`,
|
|
14265
|
+
"",
|
|
14266
|
+
"## Feedback",
|
|
14267
|
+
"",
|
|
14268
|
+
renderMarkdownList(entry.feedback.slice(-8).map((item) => `${item.at} ${item.kind}: ${item.summary}`)),
|
|
14269
|
+
""
|
|
14270
|
+
].join("\n");
|
|
14271
|
+
}
|
|
14272
|
+
function parseWikiPage(text) {
|
|
14273
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/u);
|
|
14274
|
+
if (!match) return { data: {}, body: text.trim() };
|
|
14275
|
+
return { data: YAML3.parse(match[1] ?? "") ?? {}, body: (match[2] ?? "").trim() };
|
|
14276
|
+
}
|
|
14277
|
+
function fingerprints(brief) {
|
|
14278
|
+
return dedup5([
|
|
14279
|
+
`cat:${brief.category}`,
|
|
14280
|
+
brief.primaryError ? `err:${normalize(brief.primaryError)}` : "",
|
|
14281
|
+
...brief.failedTests.map((test) => `test:${normalize(test)}`),
|
|
14282
|
+
...brief.files.map((file) => `file:${normalize(file)}`),
|
|
14283
|
+
...brief.statusCodes.map((code) => `http:${code}`)
|
|
14284
|
+
]);
|
|
14285
|
+
}
|
|
14286
|
+
function tokensForBrief(brief) {
|
|
14287
|
+
return tokenize2([brief.summary, brief.primaryError, brief.debugDemand, ...brief.failedTests, ...brief.files, ...brief.evidence, ...brief.statusCodes].join(" "));
|
|
14288
|
+
}
|
|
14289
|
+
function tokensForEntry(entry) {
|
|
14290
|
+
return tokenize2([entry.id, entry.summary, entry.primaryError, entry.debugDemand, entry.resolutionPlan ?? "", entry.solution, ...entry.symptoms, ...entry.evidence, ...entry.repairFiles ?? []].join(" "));
|
|
14291
|
+
}
|
|
14292
|
+
function tokenize2(text) {
|
|
14293
|
+
return dedup5(text.toLowerCase().split(/[^a-z0-9_./:-]+/u).filter((token) => token.length >= 3));
|
|
14294
|
+
}
|
|
14295
|
+
function normalize(text) {
|
|
14296
|
+
return tokenize2(text).slice(0, 24).join(" ");
|
|
14297
|
+
}
|
|
14298
|
+
function renderReadableIndex(index, entries) {
|
|
14299
|
+
const lines = [
|
|
14300
|
+
"# XCompiler Debug Wiki Index",
|
|
14301
|
+
"",
|
|
14302
|
+
`Updated: ${index.updatedAt}`,
|
|
14303
|
+
"",
|
|
14304
|
+
"This file is regenerated from wiki pages and feedback overlays. Edit knowledge pages under `wiki/`, not this index.",
|
|
14305
|
+
"",
|
|
14306
|
+
"## Layers",
|
|
14307
|
+
"",
|
|
14308
|
+
"| Layer | Entries | Writable | Purpose |",
|
|
14309
|
+
"| --- | ---: | --- | --- |"
|
|
14310
|
+
];
|
|
14311
|
+
for (const layer of LAYERS) {
|
|
14312
|
+
const info = index.layers[layer];
|
|
14313
|
+
lines.push(`| ${layer} | ${info.entries} | ${info.writable ? "yes" : "no"} | ${layerPurpose(layer)} |`);
|
|
14314
|
+
}
|
|
14315
|
+
for (const layer of LAYERS) {
|
|
14316
|
+
const layerEntries = entries.filter((entry) => entry.layer === layer);
|
|
14317
|
+
lines.push("", `## ${layer}`, "");
|
|
14318
|
+
if (layerEntries.length === 0) {
|
|
14319
|
+
lines.push("No entries.");
|
|
14320
|
+
continue;
|
|
14321
|
+
}
|
|
14322
|
+
lines.push("| ID | Status | Category | Summary | Source |", "| --- | --- | --- | --- | --- |");
|
|
14323
|
+
for (const entry of layerEntries) {
|
|
14324
|
+
const source = entry.sourcePath ? `[${entry.sourcePath}](${entry.sourcePath})` : "";
|
|
14325
|
+
lines.push(`| ${escapeTable(entry.id)} | ${entry.status} | ${entry.category} | ${escapeTable(entry.summary)} | ${source} |`);
|
|
14326
|
+
}
|
|
14327
|
+
}
|
|
14328
|
+
return `${lines.join("\n")}
|
|
14329
|
+
`;
|
|
14330
|
+
}
|
|
14331
|
+
function renderOperationLogEntry(entry) {
|
|
14332
|
+
const lines = [
|
|
14333
|
+
"",
|
|
14334
|
+
`- ${entry.at} ${entry.action}: ${entry.entryIds.join(", ") || "none"}`,
|
|
14335
|
+
` - issue: ${entry.issueId ?? "n/a"}; step: ${entry.stepId ?? "n/a"}; phase: ${entry.phase ?? "n/a"}`,
|
|
14336
|
+
` - summary: ${entry.summary}`
|
|
14337
|
+
];
|
|
14338
|
+
if (entry.reason) lines.push(` - reason: ${entry.reason}`);
|
|
14339
|
+
return `${lines.join("\n")}
|
|
14340
|
+
`;
|
|
14341
|
+
}
|
|
14342
|
+
function defaultDebugWikiReadme() {
|
|
14343
|
+
return [
|
|
14344
|
+
"# XCompiler Debug Wiki",
|
|
14345
|
+
"",
|
|
14346
|
+
"This directory is an LLM-wiki style knowledge base for Debugger repair.",
|
|
14347
|
+
"",
|
|
14348
|
+
"- `wiki/system/` contains system-level debug policies and safety rules.",
|
|
14349
|
+
"- `wiki/agent/` contains agent-level calibration knowledge derived from recurring LLM failure patterns.",
|
|
14350
|
+
"- `wiki/external/` stores real project issue resolutions and feedback.",
|
|
14351
|
+
"- `index.md` is a human-readable regenerated catalog.",
|
|
14352
|
+
"- `index.json` is the machine-readable retrieval cache.",
|
|
14353
|
+
"- `log.md` is an append-only operational log.",
|
|
14354
|
+
""
|
|
14355
|
+
].join("\n");
|
|
14356
|
+
}
|
|
14357
|
+
function layerPurpose(layer) {
|
|
14358
|
+
switch (layer) {
|
|
14359
|
+
case "system":
|
|
14360
|
+
return "bundled system debug policies";
|
|
14361
|
+
case "agent":
|
|
14362
|
+
return "bundled agent calibration knowledge";
|
|
14363
|
+
case "external":
|
|
14364
|
+
return "local project issue resolutions";
|
|
14365
|
+
}
|
|
14366
|
+
}
|
|
14367
|
+
function renderMarkdownList(items) {
|
|
14368
|
+
const compact = items.map((item) => item.trim()).filter(Boolean);
|
|
14369
|
+
if (compact.length === 0) return "- n/a";
|
|
14370
|
+
return compact.map((item) => `- ${item}`).join("\n");
|
|
14371
|
+
}
|
|
14372
|
+
function escapeTable(text) {
|
|
14373
|
+
return text.replace(/\|/gu, "\\|").replace(/\r?\n/gu, " ");
|
|
14374
|
+
}
|
|
14375
|
+
function confidenceFor(entry) {
|
|
14376
|
+
const total = entry.stats.uses + entry.stats.successes + entry.stats.failures;
|
|
14377
|
+
const base = (entry.stats.successes + 1) / Math.max(2, total + 2);
|
|
14378
|
+
const statusFactor = entry.status === "needs_review" ? 0.45 : 1;
|
|
14379
|
+
const layerFactor = entry.layer === "system" ? 0.9 : 1;
|
|
14380
|
+
return Math.max(0.1, Math.min(1, base * statusFactor * layerFactor));
|
|
14381
|
+
}
|
|
14382
|
+
function mergeSolution(previous, next) {
|
|
14383
|
+
const trimmed = next.trim();
|
|
14384
|
+
if (!trimmed || previous.includes(trimmed)) return previous;
|
|
14385
|
+
if (!previous.trim()) return trimmed;
|
|
14386
|
+
return `${previous.trim()}
|
|
14387
|
+
Corrected/confirmed resolution: ${trimmed}`;
|
|
14388
|
+
}
|
|
14389
|
+
function pushFeedback(entry, feedback) {
|
|
14390
|
+
entry.feedback.push(feedback);
|
|
14391
|
+
if (entry.feedback.length > 20) entry.feedback.splice(0, entry.feedback.length - 20);
|
|
14392
|
+
}
|
|
14393
|
+
function slugify(text) {
|
|
14394
|
+
return text.toLowerCase().replace(/[^a-z0-9._-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 96) || "entry";
|
|
14395
|
+
}
|
|
14396
|
+
async function exists(filePath) {
|
|
14397
|
+
try {
|
|
14398
|
+
await fs25.stat(filePath);
|
|
14399
|
+
return true;
|
|
14400
|
+
} catch {
|
|
14401
|
+
return false;
|
|
14402
|
+
}
|
|
14403
|
+
}
|
|
14404
|
+
function dedup5(items) {
|
|
14405
|
+
return [...new Set(items.filter((item) => String(item ?? "").length > 0))];
|
|
14406
|
+
}
|
|
14407
|
+
|
|
14408
|
+
// src/core/project_audit.ts
|
|
14409
|
+
import { promises as fs26 } from "fs";
|
|
14410
|
+
import path26 from "path";
|
|
12721
14411
|
function renderProjectAuditFailureLog(result) {
|
|
12722
14412
|
const failed = result.checks.filter((check) => !check.ok);
|
|
12723
14413
|
const interesting = failed.length > 0 ? failed : result.checks;
|
|
@@ -12766,12 +14456,12 @@ async function checkDocumentationBundle(ws, projectType, iterationId) {
|
|
|
12766
14456
|
const docs = iterationId ? deliveryDocsForIteration(projectType, iterationId) : deliveryDocsForProjectType(projectType);
|
|
12767
14457
|
const checks = [];
|
|
12768
14458
|
for (const doc of docs) {
|
|
12769
|
-
const
|
|
14459
|
+
const exists2 = await ws.exists(doc);
|
|
12770
14460
|
checks.push({
|
|
12771
14461
|
name: docCheckName(doc),
|
|
12772
|
-
severity:
|
|
12773
|
-
ok:
|
|
12774
|
-
summary:
|
|
14462
|
+
severity: exists2 ? "info" : "error",
|
|
14463
|
+
ok: exists2,
|
|
14464
|
+
summary: exists2 ? t().execute.auditDocPresent(doc) : t().execute.auditDocMissing(doc)
|
|
12775
14465
|
});
|
|
12776
14466
|
}
|
|
12777
14467
|
return checks;
|
|
@@ -12892,14 +14582,14 @@ async function listFiles(ws, dir) {
|
|
|
12892
14582
|
async function walk4(abs, rel, out) {
|
|
12893
14583
|
let entries;
|
|
12894
14584
|
try {
|
|
12895
|
-
entries = await
|
|
14585
|
+
entries = await fs26.readdir(abs, { withFileTypes: true });
|
|
12896
14586
|
} catch {
|
|
12897
14587
|
return;
|
|
12898
14588
|
}
|
|
12899
14589
|
for (const entry of entries) {
|
|
12900
14590
|
if (entry.name.startsWith(".")) continue;
|
|
12901
14591
|
const childRel = `${rel}/${entry.name}`;
|
|
12902
|
-
const childAbs =
|
|
14592
|
+
const childAbs = path26.join(abs, entry.name);
|
|
12903
14593
|
if (entry.isDirectory()) {
|
|
12904
14594
|
await walk4(childAbs, childRel, out);
|
|
12905
14595
|
} else {
|
|
@@ -12919,6 +14609,7 @@ var PhaseEngine = class {
|
|
|
12919
14609
|
this.skills = opts.skills ?? buildDefaultSkills();
|
|
12920
14610
|
this.plugins = opts.plugins ?? new PluginHost();
|
|
12921
14611
|
this.debugCache = new DebugCache(opts.ws.abs(".xcompiler/debug_cache.json"));
|
|
14612
|
+
this.debugWiki = new DebugWiki(opts.debugWikiPath ?? defaultDebugWikiPath());
|
|
12922
14613
|
}
|
|
12923
14614
|
opts;
|
|
12924
14615
|
registry;
|
|
@@ -12927,6 +14618,8 @@ var PhaseEngine = class {
|
|
|
12927
14618
|
pluginExtensionsApplied = false;
|
|
12928
14619
|
/** 跨 xcompiler run 持久化的 debug 历史(`<workspace>/.xcompiler/debug_cache.json`)。 */
|
|
12929
14620
|
debugCache;
|
|
14621
|
+
/** 跨项目 debug 知识库,记录错误摘要、解决方案和正/负反馈。 */
|
|
14622
|
+
debugWiki;
|
|
12930
14623
|
/** 当前 Plan 的语言 profile(在 run() 起始处按 plan.language 解析)。 */
|
|
12931
14624
|
profile = getLanguageProfile("python");
|
|
12932
14625
|
/** 当前 workspace 的项目记忆,用于给执行阶段注入更稳定的跨轮上下文。 */
|
|
@@ -12946,6 +14639,21 @@ var PhaseEngine = class {
|
|
|
12946
14639
|
spin(text, options) {
|
|
12947
14640
|
return this.terminalOutput ? spinner(text, options).start() : null;
|
|
12948
14641
|
}
|
|
14642
|
+
async withDebugWiki(operation, action, fallback) {
|
|
14643
|
+
try {
|
|
14644
|
+
return await action();
|
|
14645
|
+
} catch (err) {
|
|
14646
|
+
const message = err.message;
|
|
14647
|
+
await this.opts.audit.event("note", `debug wiki ${operation} failed: ${message}`, {
|
|
14648
|
+
messageId: "engine.debug_wiki_failed",
|
|
14649
|
+
operation,
|
|
14650
|
+
path: this.debugWiki.filePath,
|
|
14651
|
+
error: message
|
|
14652
|
+
});
|
|
14653
|
+
if (this.opts.debugWikiStrict) throw err;
|
|
14654
|
+
return fallback;
|
|
14655
|
+
}
|
|
14656
|
+
}
|
|
12949
14657
|
async requestEnginePermission(request2) {
|
|
12950
14658
|
if (!this.opts.requestPermission) return { approved: true };
|
|
12951
14659
|
const decision = await this.opts.requestPermission(request2);
|
|
@@ -12970,6 +14678,7 @@ var PhaseEngine = class {
|
|
|
12970
14678
|
}
|
|
12971
14679
|
async run(plan) {
|
|
12972
14680
|
await this.plugins.initialize();
|
|
14681
|
+
await this.withDebugWiki("load", () => this.debugWiki.load(), void 0);
|
|
12973
14682
|
if (!this.pluginExtensionsApplied) {
|
|
12974
14683
|
this.plugins.applyExtensions({ tools: this.registry, skills: this.skills });
|
|
12975
14684
|
this.pluginExtensionsApplied = true;
|
|
@@ -13177,7 +14886,23 @@ var PhaseEngine = class {
|
|
|
13177
14886
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13178
14887
|
this.issueSeq += 1;
|
|
13179
14888
|
const id = `ISSUE-${now.replace(/[-:.TZ]/g, "").slice(0, 14)}-${String(this.issueSeq).padStart(3, "0")}`;
|
|
13180
|
-
const
|
|
14889
|
+
const rawFailureLog = input.failureLog ?? "";
|
|
14890
|
+
const cleanedFailureLog = cleanFailureLogForDebugContext(rawFailureLog);
|
|
14891
|
+
const debugBrief = buildDebugBrief({
|
|
14892
|
+
reason: input.reason,
|
|
14893
|
+
failureLog: cleanedFailureLog,
|
|
14894
|
+
phase: step?.phase
|
|
14895
|
+
});
|
|
14896
|
+
const failureLog = compactFailureEvidence({
|
|
14897
|
+
reason: input.reason,
|
|
14898
|
+
failureLog: cleanedFailureLog,
|
|
14899
|
+
phase: step?.phase,
|
|
14900
|
+
maxChars: 6e3,
|
|
14901
|
+
maxLines: 90
|
|
14902
|
+
});
|
|
14903
|
+
const rawFailureLogPath = `.xcompiler/issues/${id}/failure.raw.log`;
|
|
14904
|
+
await this.opts.ws.writeFile(rawFailureLogPath, rawFailureLog.endsWith("\n") ? rawFailureLog : `${rawFailureLog}
|
|
14905
|
+
`);
|
|
13181
14906
|
const issue = {
|
|
13182
14907
|
id,
|
|
13183
14908
|
createdAt: now,
|
|
@@ -13195,6 +14920,9 @@ var PhaseEngine = class {
|
|
|
13195
14920
|
title: step?.title,
|
|
13196
14921
|
reason: input.reason,
|
|
13197
14922
|
failureLog,
|
|
14923
|
+
failureLogBytes: Buffer.byteLength(rawFailureLog, "utf8"),
|
|
14924
|
+
rawFailureLogPath,
|
|
14925
|
+
debugBrief,
|
|
13198
14926
|
metrics: input.metrics,
|
|
13199
14927
|
evidence: input.evidence
|
|
13200
14928
|
};
|
|
@@ -13212,6 +14940,12 @@ var PhaseEngine = class {
|
|
|
13212
14940
|
issue.status = "routed";
|
|
13213
14941
|
issue.targetStepId = target.id;
|
|
13214
14942
|
issue.targetPhase = target.phase;
|
|
14943
|
+
issue.debugBrief = buildDebugBrief({
|
|
14944
|
+
reason: issue.reason,
|
|
14945
|
+
failureLog: issue.failureLog,
|
|
14946
|
+
phase: issue.phase,
|
|
14947
|
+
targetPhase: target.phase
|
|
14948
|
+
});
|
|
13215
14949
|
issue.routedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13216
14950
|
issue.updatedAt = issue.routedAt;
|
|
13217
14951
|
await this.persistIssue(issue, "routed", { routingReason: reason });
|
|
@@ -13230,13 +14964,27 @@ var PhaseEngine = class {
|
|
|
13230
14964
|
issue.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13231
14965
|
await this.persistIssue(issue, "unresolved", { reason });
|
|
13232
14966
|
}
|
|
13233
|
-
async markIssueResolved(issueId, step, repair) {
|
|
14967
|
+
async markIssueResolved(issueId, step, repair, issueResolutionPlan) {
|
|
13234
14968
|
const issue = issueId ? this.findIssue(issueId) : void 0;
|
|
13235
14969
|
if (!issue) return;
|
|
13236
14970
|
issue.status = "resolved";
|
|
13237
14971
|
issue.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13238
14972
|
issue.updatedAt = issue.resolvedAt;
|
|
13239
14973
|
if (repair) issue.repair = repair;
|
|
14974
|
+
if (issueResolutionPlan?.trim()) {
|
|
14975
|
+
issue.issueResolutionPlan = issueResolutionPlan.trim();
|
|
14976
|
+
issue.resolutionPlanHistory = [
|
|
14977
|
+
...issue.resolutionPlanHistory ?? [],
|
|
14978
|
+
{
|
|
14979
|
+
at: issue.resolvedAt,
|
|
14980
|
+
stepId: step.id,
|
|
14981
|
+
phase: step.phase,
|
|
14982
|
+
plan: issue.issueResolutionPlan,
|
|
14983
|
+
outcome: "accepted"
|
|
14984
|
+
}
|
|
14985
|
+
].slice(-8);
|
|
14986
|
+
}
|
|
14987
|
+
await this.recordDebugWikiResolution(issue, step, repair);
|
|
13240
14988
|
await this.persistIssue(issue, "resolved");
|
|
13241
14989
|
await this.opts.audit.event("issue.resolve", `${issue.id} resolved by ${step.id} ${step.phase}`, {
|
|
13242
14990
|
messageId: "engine.issue_resolved",
|
|
@@ -13246,6 +14994,82 @@ var PhaseEngine = class {
|
|
|
13246
14994
|
repair
|
|
13247
14995
|
});
|
|
13248
14996
|
}
|
|
14997
|
+
async recordDebugWikiFailure(step, debug, outcome) {
|
|
14998
|
+
const entryIds = dedup6(debug.debugWikiEntryIds ?? []);
|
|
14999
|
+
if (entryIds.length === 0) return;
|
|
15000
|
+
const issue = debug.issueId ? this.findIssue(debug.issueId) : void 0;
|
|
15001
|
+
const brief = buildDebugBrief({
|
|
15002
|
+
reason: outcome.reason ?? debug.reason,
|
|
15003
|
+
failureLog: cleanFailureLogForDebugContext(outcome.failureLog),
|
|
15004
|
+
phase: issue?.phase ?? step.phase,
|
|
15005
|
+
targetPhase: issue?.targetPhase ?? step.phase
|
|
15006
|
+
});
|
|
15007
|
+
await this.withDebugWiki(
|
|
15008
|
+
"record-failure",
|
|
15009
|
+
() => this.debugWiki.recordFailure(entryIds, {
|
|
15010
|
+
brief,
|
|
15011
|
+
issueId: issue?.id,
|
|
15012
|
+
stepId: step.id,
|
|
15013
|
+
phase: step.phase,
|
|
15014
|
+
targetPhase: issue?.targetPhase,
|
|
15015
|
+
language: this.profile.id,
|
|
15016
|
+
solution: "retrieved wiki solution did not resolve this attempt",
|
|
15017
|
+
reason: outcome.reason ?? debug.reason
|
|
15018
|
+
}),
|
|
15019
|
+
void 0
|
|
15020
|
+
);
|
|
15021
|
+
await this.opts.audit.event("note", `debug wiki marked ${entryIds.join(", ")} for review`, {
|
|
15022
|
+
messageId: "engine.debug_wiki_feedback",
|
|
15023
|
+
kind: "failure",
|
|
15024
|
+
entryIds,
|
|
15025
|
+
issueId: issue?.id,
|
|
15026
|
+
stepId: step.id,
|
|
15027
|
+
reason: outcome.reason ?? debug.reason
|
|
15028
|
+
});
|
|
15029
|
+
}
|
|
15030
|
+
async recordDebugWikiResolution(issue, step, repair) {
|
|
15031
|
+
const brief = issue.debugBrief ?? buildDebugBrief({
|
|
15032
|
+
reason: issue.reason,
|
|
15033
|
+
failureLog: issue.failureLog,
|
|
15034
|
+
phase: issue.phase,
|
|
15035
|
+
targetPhase: issue.targetPhase
|
|
15036
|
+
});
|
|
15037
|
+
const repairFiles = repair?.changedFiles?.length ? repair.changedFiles : step.outputs;
|
|
15038
|
+
const evidenceSummary = [
|
|
15039
|
+
`Resolved ${issue.kind} by Debugger in ${step.id}/${step.phase}.`,
|
|
15040
|
+
`Mode: ${repair?.mode ?? "verification"}.`,
|
|
15041
|
+
repairFiles.length > 0 ? `Changed/verified files: ${repairFiles.join(", ")}.` : "",
|
|
15042
|
+
repair?.patchPath ? `Patch: ${repair.patchPath}.` : "",
|
|
15043
|
+
`Demand: ${brief.debugDemand}`
|
|
15044
|
+
].filter(Boolean).join(" ");
|
|
15045
|
+
const solution = issue.issueResolutionPlan ? `${issue.issueResolutionPlan}
|
|
15046
|
+
Resolution evidence: ${evidenceSummary}` : evidenceSummary;
|
|
15047
|
+
const result = await this.withDebugWiki(
|
|
15048
|
+
"record-resolution",
|
|
15049
|
+
() => this.debugWiki.recordResolution({
|
|
15050
|
+
brief,
|
|
15051
|
+
issueId: issue.id,
|
|
15052
|
+
stepId: step.id,
|
|
15053
|
+
phase: step.phase,
|
|
15054
|
+
targetPhase: issue.targetPhase,
|
|
15055
|
+
language: this.profile.id,
|
|
15056
|
+
resolutionPlan: issue.issueResolutionPlan,
|
|
15057
|
+
solution,
|
|
15058
|
+
evidence: brief.evidence,
|
|
15059
|
+
repairFiles,
|
|
15060
|
+
usedEntryIds: issue.debugWikiEntryIds
|
|
15061
|
+
}),
|
|
15062
|
+
{ updated: [] }
|
|
15063
|
+
);
|
|
15064
|
+
if (result.created || result.updated.length > 0) {
|
|
15065
|
+
await this.opts.audit.event("note", `debug wiki updated after ${issue.id}`, {
|
|
15066
|
+
messageId: "engine.debug_wiki_updated",
|
|
15067
|
+
issueId: issue.id,
|
|
15068
|
+
created: result.created,
|
|
15069
|
+
updated: result.updated
|
|
15070
|
+
});
|
|
15071
|
+
}
|
|
15072
|
+
}
|
|
13249
15073
|
findIssue(issueId) {
|
|
13250
15074
|
return this.issues.find((issue) => issue.id === issueId);
|
|
13251
15075
|
}
|
|
@@ -13265,6 +15089,10 @@ var PhaseEngine = class {
|
|
|
13265
15089
|
targetStepId: issue.targetStepId,
|
|
13266
15090
|
targetPhase: issue.targetPhase,
|
|
13267
15091
|
reason: issue.reason,
|
|
15092
|
+
debugSummary: issue.debugBrief?.summary,
|
|
15093
|
+
debugDemand: issue.debugBrief?.debugDemand,
|
|
15094
|
+
debugWikiEntryIds: issue.debugWikiEntryIds,
|
|
15095
|
+
issueResolutionPlan: issue.issueResolutionPlan,
|
|
13268
15096
|
...extra
|
|
13269
15097
|
});
|
|
13270
15098
|
await this.opts.ws.writeFile(eventPath, `${existing}${line}
|
|
@@ -13284,6 +15112,7 @@ var PhaseEngine = class {
|
|
|
13284
15112
|
const diff = await this.opts.git.raw().diff([beforeRef, "--"]).catch((err) => `# git diff failed: ${err.message}
|
|
13285
15113
|
`);
|
|
13286
15114
|
const mode = inferRepairMode(toolCalls);
|
|
15115
|
+
const changedFiles = parsePatchChangedFiles(diff);
|
|
13287
15116
|
await this.opts.ws.writeFile(patchPath, diff || "# No textual diff captured.\n");
|
|
13288
15117
|
await this.opts.ws.writeFile(
|
|
13289
15118
|
summaryPath,
|
|
@@ -13307,7 +15136,8 @@ var PhaseEngine = class {
|
|
|
13307
15136
|
completedBeforeDebug,
|
|
13308
15137
|
mode,
|
|
13309
15138
|
patchPath,
|
|
13310
|
-
summaryPath
|
|
15139
|
+
summaryPath,
|
|
15140
|
+
changedFiles
|
|
13311
15141
|
};
|
|
13312
15142
|
}
|
|
13313
15143
|
async completedPhaseRepairViolation(toolCalls) {
|
|
@@ -13324,13 +15154,13 @@ var PhaseEngine = class {
|
|
|
13324
15154
|
return void 0;
|
|
13325
15155
|
}
|
|
13326
15156
|
async repairChangedFiles() {
|
|
13327
|
-
const planPath = normalizeGitPath(
|
|
15157
|
+
const planPath = normalizeGitPath(path27.relative(this.opts.ws.root, path27.resolve(this.opts.planPath)));
|
|
13328
15158
|
const status = await this.opts.git.raw().status();
|
|
13329
15159
|
return status.files.map((file) => normalizeGitPath(file.path)).filter((file) => file.length > 0).filter((file) => !isRuntimeOnlyChange(file, planPath));
|
|
13330
15160
|
}
|
|
13331
15161
|
testGateArgsForStep(step) {
|
|
13332
15162
|
if (step.phase === "FUNCTIONAL_TEST") return [];
|
|
13333
|
-
return
|
|
15163
|
+
return dedup6(
|
|
13334
15164
|
step.outputs.filter((out) => typeof out === "string" && !out.endsWith("/")).map((out) => normalizeGitPath(out)).filter(isTestFilePath)
|
|
13335
15165
|
);
|
|
13336
15166
|
}
|
|
@@ -13498,7 +15328,7 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13498
15328
|
initialDebug: {
|
|
13499
15329
|
reason,
|
|
13500
15330
|
failureLog: debugFailureLog,
|
|
13501
|
-
contextPaths:
|
|
15331
|
+
contextPaths: dedup6([
|
|
13502
15332
|
...sourceStep.inputs,
|
|
13503
15333
|
...sourceStep.outputs,
|
|
13504
15334
|
...routedTest.inputs,
|
|
@@ -13506,7 +15336,7 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13506
15336
|
...failedTest.inputs,
|
|
13507
15337
|
...failedTest.outputs
|
|
13508
15338
|
]),
|
|
13509
|
-
extraAllowedWrites:
|
|
15339
|
+
extraAllowedWrites: dedup6([...routedTest.outputs]),
|
|
13510
15340
|
contextMode: "test-rollback",
|
|
13511
15341
|
testScopeArgs: this.testGateArgsForStep(routedTest),
|
|
13512
15342
|
issueId: issue?.id,
|
|
@@ -13647,6 +15477,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13647
15477
|
await this.debugCache.load();
|
|
13648
15478
|
const initialDebug = opts.initialDebug;
|
|
13649
15479
|
const inheritedExtraAllowedWrites = initialDebug?.extraAllowedWrites;
|
|
15480
|
+
const inheritedContextMode = initialDebug?.contextMode;
|
|
15481
|
+
const inheritedTestScopeArgs = initialDebug?.testScopeArgs;
|
|
13650
15482
|
const completedBeforeDebug = initialDebug?.completedBeforeDebug ?? step.status === "DONE";
|
|
13651
15483
|
const rootDebugFailureLog = initialDebug ? cleanFailureLogForDebugContext(initialDebug.failureLog) : void 0;
|
|
13652
15484
|
const includeRootDebugFailureLog = (failureLog, reason) => rootDebugFailureLog ? composeDebugRetryFailureLog(rootDebugFailureLog, failureLog, reason) : failureLog;
|
|
@@ -13680,7 +15512,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13680
15512
|
priorAttemptsPrompt: priorPrompt,
|
|
13681
15513
|
contextPaths: initialDebug.contextPaths,
|
|
13682
15514
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
13683
|
-
contextMode:
|
|
15515
|
+
contextMode: inheritedContextMode,
|
|
15516
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
13684
15517
|
issueId: activeIssueId,
|
|
13685
15518
|
completedBeforeDebug
|
|
13686
15519
|
});
|
|
@@ -13688,6 +15521,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13688
15521
|
const attempts = this.debugCache.attempts(step.id);
|
|
13689
15522
|
const last = attempts.slice(-1)[0];
|
|
13690
15523
|
const resume = latestActionableDebugAttempt(attempts) ?? last;
|
|
15524
|
+
const cachedTestScopeArgs = inferCachedTestScopeArgs(resume);
|
|
15525
|
+
const cachedContextMode = resume.contextMode ?? (cachedTestScopeArgs.length > 0 ? "test-rollback" : void 0);
|
|
13691
15526
|
this.log(
|
|
13692
15527
|
chalk2.yellow(
|
|
13693
15528
|
t().engine.debugResumeNotice(step.id, attempts.length)
|
|
@@ -13715,6 +15550,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13715
15550
|
reason: resume.reason,
|
|
13716
15551
|
priorAttemptsPrompt: priorPrompt,
|
|
13717
15552
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
15553
|
+
contextMode: cachedContextMode,
|
|
15554
|
+
testScopeArgs: cachedTestScopeArgs,
|
|
13718
15555
|
issueId: activeIssueId,
|
|
13719
15556
|
completedBeforeDebug
|
|
13720
15557
|
});
|
|
@@ -13743,6 +15580,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13743
15580
|
reason: resume.reason,
|
|
13744
15581
|
priorAttemptsPrompt: priorPrompt,
|
|
13745
15582
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
15583
|
+
contextMode: cachedContextMode,
|
|
15584
|
+
testScopeArgs: cachedTestScopeArgs,
|
|
13746
15585
|
issueId: activeIssueId,
|
|
13747
15586
|
completedBeforeDebug
|
|
13748
15587
|
});
|
|
@@ -13793,6 +15632,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13793
15632
|
suggestions: calibrateDebugSuggestions(cleanFailureLogForDebugContext(initialFailureLogForRecord), initialReason).map(
|
|
13794
15633
|
(s) => `[${s.code}] ${s.hint}`
|
|
13795
15634
|
),
|
|
15635
|
+
contextMode: inheritedContextMode,
|
|
15636
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
13796
15637
|
metrics: initial.metrics ? {
|
|
13797
15638
|
healthScore: initial.metrics.healthScore,
|
|
13798
15639
|
parseFailures: initial.metrics.parseFailures,
|
|
@@ -13864,6 +15705,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13864
15705
|
reason: lastReason,
|
|
13865
15706
|
priorAttemptsPrompt: priorPrompt,
|
|
13866
15707
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
15708
|
+
contextMode: inheritedContextMode,
|
|
15709
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
13867
15710
|
issueId: activeIssueId,
|
|
13868
15711
|
completedBeforeDebug
|
|
13869
15712
|
});
|
|
@@ -13919,6 +15762,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13919
15762
|
suggestions: calibrateDebugSuggestions(cleanFailureLogForDebugContext(recordedFailureLog2), rollbackReason).map(
|
|
13920
15763
|
(s) => `[${s.code}] ${s.hint}`
|
|
13921
15764
|
),
|
|
15765
|
+
contextMode: inheritedContextMode,
|
|
15766
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
13922
15767
|
metrics: r.metrics ? {
|
|
13923
15768
|
healthScore: r.metrics.healthScore,
|
|
13924
15769
|
parseFailures: r.metrics.parseFailures,
|
|
@@ -13938,9 +15783,11 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13938
15783
|
}
|
|
13939
15784
|
const m = r.metrics;
|
|
13940
15785
|
const readOnlyProbeLoop = isReadOnlyProbeLoopFailure(r.reason);
|
|
15786
|
+
const missingOutputStall = isMissingOutputStallFailure(r.reason);
|
|
15787
|
+
const lowQualityDebuggerResponse = isLowQualityDebuggerResponseFailure(r.reason);
|
|
13941
15788
|
const repairEvidenceMissing = isRepairEvidenceMissingFailure(r.reason);
|
|
13942
|
-
const healthy = !readOnlyProbeLoop && !repairEvidenceMissing && !!m && m.parseFailures === 0 && m.repeatedTurns <= 1 && m.healthScore >= 0.6;
|
|
13943
|
-
const bad = readOnlyProbeLoop || repairEvidenceMissing || !!m && (m.healthScore < 0.3 || m.parseFailures + m.repeatedTurns >= Math.max(2, Math.ceil(m.rounds / 2)));
|
|
15789
|
+
const healthy = !readOnlyProbeLoop && !missingOutputStall && !lowQualityDebuggerResponse && !repairEvidenceMissing && !!m && m.parseFailures === 0 && m.repeatedTurns <= 1 && m.healthScore >= 0.6;
|
|
15790
|
+
const bad = readOnlyProbeLoop || missingOutputStall || lowQualityDebuggerResponse || repairEvidenceMissing || !!m && (m.healthScore < 0.3 || m.parseFailures + m.repeatedTurns >= Math.max(2, Math.ceil(m.rounds / 2)));
|
|
13944
15791
|
const tag = m ? `health=${m.healthScore.toFixed(2)} parseFail=${m.parseFailures} repeat=${m.repeatedTurns} progress=${m.progressRatio.toFixed(2)}` : "";
|
|
13945
15792
|
if (healthy) {
|
|
13946
15793
|
const before = budget;
|
|
@@ -13991,6 +15838,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
13991
15838
|
suggestions: calibrateDebugSuggestions(cleanFailureLogForDebugContext(recordedFailureLog), r.reason ?? "").map(
|
|
13992
15839
|
(s) => `[${s.code}] ${s.hint}`
|
|
13993
15840
|
),
|
|
15841
|
+
contextMode: inheritedContextMode,
|
|
15842
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
13994
15843
|
metrics: m ? {
|
|
13995
15844
|
healthScore: m.healthScore,
|
|
13996
15845
|
parseFailures: m.parseFailures,
|
|
@@ -14110,9 +15959,75 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
14110
15959
|
`${iterationPrefix}/api-guide.md`
|
|
14111
15960
|
] : []
|
|
14112
15961
|
];
|
|
14113
|
-
if (failedNames.has("entrypoint")) return
|
|
14114
|
-
if (failedNames.has("tests") || failedNames.has("test-files")) return
|
|
14115
|
-
return
|
|
15962
|
+
if (failedNames.has("entrypoint")) return dedup6([...codeAndTests, ...docs]);
|
|
15963
|
+
if (failedNames.has("tests") || failedNames.has("test-files")) return dedup6([...codeAndTests, ...docs]);
|
|
15964
|
+
return dedup6([...codeAndTests, ...step.inputs, ...docs]);
|
|
15965
|
+
}
|
|
15966
|
+
async buildDebugPromptPayload(step, debug, failureLog) {
|
|
15967
|
+
const issue = debug.issueId ? this.findIssue(debug.issueId) : void 0;
|
|
15968
|
+
const currentBrief = buildDebugBrief({
|
|
15969
|
+
reason: debug.reason,
|
|
15970
|
+
failureLog,
|
|
15971
|
+
phase: issue?.phase ?? step.phase,
|
|
15972
|
+
targetPhase: issue?.targetPhase ?? step.phase
|
|
15973
|
+
});
|
|
15974
|
+
const rootBrief = issue?.debugBrief;
|
|
15975
|
+
const briefBlocks = rootBrief && rootBrief.summary !== currentBrief.summary ? [
|
|
15976
|
+
"## root issue brief",
|
|
15977
|
+
renderDebugBriefForPrompt(rootBrief),
|
|
15978
|
+
"",
|
|
15979
|
+
"## current retry brief",
|
|
15980
|
+
renderDebugBriefForPrompt(currentBrief)
|
|
15981
|
+
] : [renderDebugBriefForPrompt(currentBrief)];
|
|
15982
|
+
const evidence = compactFailureEvidence({
|
|
15983
|
+
reason: debug.reason,
|
|
15984
|
+
failureLog,
|
|
15985
|
+
phase: issue?.phase ?? step.phase,
|
|
15986
|
+
targetPhase: issue?.targetPhase ?? step.phase,
|
|
15987
|
+
maxChars: 2600,
|
|
15988
|
+
maxLines: 50
|
|
15989
|
+
});
|
|
15990
|
+
const suggestions = renderDebugSuggestions(
|
|
15991
|
+
calibrateDebugSuggestions(failureLog, debug.reason)
|
|
15992
|
+
);
|
|
15993
|
+
const wikiMatches = await this.withDebugWiki(
|
|
15994
|
+
"search",
|
|
15995
|
+
() => this.debugWiki.search(currentBrief, { language: this.profile.id, limit: 3 }),
|
|
15996
|
+
[]
|
|
15997
|
+
);
|
|
15998
|
+
const debugWikiEntryIds = wikiMatches.map((match) => match.entry.id);
|
|
15999
|
+
if (debugWikiEntryIds.length > 0) {
|
|
16000
|
+
debug.debugWikiEntryIds = dedup6([...debug.debugWikiEntryIds ?? [], ...debugWikiEntryIds]);
|
|
16001
|
+
if (issue) {
|
|
16002
|
+
issue.debugWikiEntryIds = dedup6([...issue.debugWikiEntryIds ?? [], ...debugWikiEntryIds]);
|
|
16003
|
+
}
|
|
16004
|
+
await this.withDebugWiki(
|
|
16005
|
+
"record-use",
|
|
16006
|
+
() => this.debugWiki.recordUse(debugWikiEntryIds, {
|
|
16007
|
+
brief: currentBrief,
|
|
16008
|
+
issueId: issue?.id,
|
|
16009
|
+
stepId: step.id,
|
|
16010
|
+
phase: step.phase,
|
|
16011
|
+
targetPhase: issue?.targetPhase,
|
|
16012
|
+
language: this.profile.id,
|
|
16013
|
+
solution: "retrieved for Debugger prompt"
|
|
16014
|
+
}),
|
|
16015
|
+
void 0
|
|
16016
|
+
);
|
|
16017
|
+
await this.opts.audit.event("note", `debug wiki matched ${debugWikiEntryIds.join(", ")}`, {
|
|
16018
|
+
messageId: "engine.debug_wiki_matched",
|
|
16019
|
+
entryIds: debugWikiEntryIds,
|
|
16020
|
+
stepId: step.id,
|
|
16021
|
+
phase: step.phase
|
|
16022
|
+
});
|
|
16023
|
+
}
|
|
16024
|
+
const wikiPrompt = renderDebugWikiMatchesForPrompt(wikiMatches);
|
|
16025
|
+
return {
|
|
16026
|
+
debugBrief: [briefBlocks.join("\n"), wikiPrompt].filter(Boolean).join("\n\n"),
|
|
16027
|
+
failureLog: evidence,
|
|
16028
|
+
suggestions,
|
|
16029
|
+
debugWikiEntryIds
|
|
16030
|
+
};
|
|
14116
16031
|
}
|
|
14117
16032
|
/** 一次执行尝试:可选 debug 模式(使用 Debugger 角色 + 注入失败日志)。 */
|
|
14118
16033
|
async runOneAttempt(plan, step, debug) {
|
|
@@ -14126,6 +16041,9 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
14126
16041
|
});
|
|
14127
16042
|
try {
|
|
14128
16043
|
const outcome = await this.runOneAttemptCore(plan, step, debug);
|
|
16044
|
+
if (debug && !outcome.ok) {
|
|
16045
|
+
await this.recordDebugWikiFailure(step, debug, outcome);
|
|
16046
|
+
}
|
|
14129
16047
|
await this.plugins.emit("step.attempt.after", {
|
|
14130
16048
|
plan,
|
|
14131
16049
|
step,
|
|
@@ -14141,6 +16059,9 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
14141
16059
|
failureLog: error instanceof Error ? error.stack ?? error.message : String(error),
|
|
14142
16060
|
reason: error instanceof Error ? error.message : String(error)
|
|
14143
16061
|
};
|
|
16062
|
+
if (debug) {
|
|
16063
|
+
await this.recordDebugWikiFailure(step, debug, outcome);
|
|
16064
|
+
}
|
|
14144
16065
|
await this.plugins.emit("step.attempt.after", {
|
|
14145
16066
|
plan,
|
|
14146
16067
|
step,
|
|
@@ -14164,10 +16085,10 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
14164
16085
|
hints.push(`[debugger] ${dbg.prompt}`);
|
|
14165
16086
|
}
|
|
14166
16087
|
}
|
|
14167
|
-
const allNames =
|
|
16088
|
+
const allNames = dedup6([...resolvedToolNames, ...extraNames]);
|
|
14168
16089
|
const baseTools = this.registry.pick(allNames);
|
|
14169
|
-
const allowedWrites = debug ?
|
|
14170
|
-
const augmentedWrites = this.isVModelTestPhase(step.phase) || step.phase === "DEBUG" || debug ?
|
|
16090
|
+
const allowedWrites = debug ? dedup6([...this.computeDebugAllowedWrites(plan, step), ...debug.extraAllowedWrites ?? []]) : this.computeStepAllowedWrites(plan, step);
|
|
16091
|
+
const augmentedWrites = this.isVModelTestPhase(step.phase) || step.phase === "DEBUG" || debug ? dedup6([...allowedWrites, "tests/fixtures"]) : allowedWrites;
|
|
14171
16092
|
const budgetContext = {
|
|
14172
16093
|
phase: step.phase,
|
|
14173
16094
|
role,
|
|
@@ -14254,6 +16175,7 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
14254
16175
|
{ animate: false }
|
|
14255
16176
|
);
|
|
14256
16177
|
const debugFailureLog = debug ? cleanFailureLogForDebugContext(debug.failureLog) : void 0;
|
|
16178
|
+
const debugPayload = debug ? await this.buildDebugPromptPayload(step, debug, debugFailureLog ?? debug.failureLog) : void 0;
|
|
14257
16179
|
try {
|
|
14258
16180
|
const r = await executor.run({
|
|
14259
16181
|
step,
|
|
@@ -14263,14 +16185,15 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
14263
16185
|
contextSnippets: ctxSnippets,
|
|
14264
16186
|
skillHints: hints,
|
|
14265
16187
|
debugContext: debug ? {
|
|
16188
|
+
issueId: debug.issueId,
|
|
14266
16189
|
reason: debug.reason,
|
|
14267
|
-
failureLog: debugFailureLog ?? debug.failureLog,
|
|
16190
|
+
failureLog: debugPayload?.failureLog ?? debugFailureLog ?? debug.failureLog,
|
|
16191
|
+
debugBrief: debugPayload?.debugBrief,
|
|
14268
16192
|
repairRequired: !debug.completedBeforeDebug,
|
|
14269
|
-
suggestions:
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
${debug.priorAttemptsPrompt}` : "")
|
|
16193
|
+
suggestions: [
|
|
16194
|
+
debugPayload?.suggestions,
|
|
16195
|
+
debug.priorAttemptsPrompt
|
|
16196
|
+
].filter(Boolean).join("\n\n")
|
|
14274
16197
|
} : void 0,
|
|
14275
16198
|
globalPrompt: plan.globalPrompt,
|
|
14276
16199
|
languageProfile: this.profile
|
|
@@ -14523,7 +16446,7 @@ ${pt.stderr}`);
|
|
|
14523
16446
|
r.toolCalls
|
|
14524
16447
|
) : void 0;
|
|
14525
16448
|
if (debug?.issueId) {
|
|
14526
|
-
await this.markIssueResolved(debug.issueId, step, repair);
|
|
16449
|
+
await this.markIssueResolved(debug.issueId, step, repair, r.issueResolutionPlan);
|
|
14527
16450
|
}
|
|
14528
16451
|
await this.opts.git.snapshot(step.id, step.retries, debug ? "debug done" : "done");
|
|
14529
16452
|
spin?.succeed(t().engine.phaseDone(step.id, r.rounds));
|
|
@@ -14532,9 +16455,12 @@ ${pt.stderr}`);
|
|
|
14532
16455
|
rounds: r.rounds,
|
|
14533
16456
|
retry: step.retries
|
|
14534
16457
|
});
|
|
14535
|
-
return { ok: true, failureLog: "" };
|
|
16458
|
+
return { ok: true, failureLog: "", issueResolutionPlan: r.issueResolutionPlan };
|
|
16459
|
+
}
|
|
16460
|
+
let reason = r.error ?? t().engine.outputsMissing(verify.missing.join(", "));
|
|
16461
|
+
if (debug?.completedBeforeDebug && !hasSuccessfulRepairMutation(r.toolCalls) && hasFailedVerificationEvidence(r.toolCalls)) {
|
|
16462
|
+
reason = "completed phase debug finished with failed verification but without a successful repair mutation";
|
|
14536
16463
|
}
|
|
14537
|
-
const reason = r.error ?? t().engine.outputsMissing(verify.missing.join(", "));
|
|
14538
16464
|
const m = r.metrics;
|
|
14539
16465
|
const metricsLine = m ? t().engine.metricsLine(m.healthScore.toFixed(2), m.parseFailures, m.repeatedTurns, m.toolFailRatio.toFixed(2), m.progressRatio.toFixed(2)) : t().engine.metricsUnavailable;
|
|
14540
16466
|
const failureLog = [
|
|
@@ -14568,7 +16494,7 @@ ${pt.stderr}`);
|
|
|
14568
16494
|
} else {
|
|
14569
16495
|
await this.opts.git.revertTo(sha);
|
|
14570
16496
|
}
|
|
14571
|
-
return { ok: false, failureLog, reason, metrics: m, issueKind: "phase" };
|
|
16497
|
+
return { ok: false, failureLog, reason, metrics: m, issueKind: "phase", issueResolutionPlan: r.issueResolutionPlan };
|
|
14572
16498
|
} catch (err) {
|
|
14573
16499
|
const msg = err.message;
|
|
14574
16500
|
const stack = err.stack ?? msg;
|
|
@@ -14582,7 +16508,7 @@ ${pt.stderr}`);
|
|
|
14582
16508
|
});
|
|
14583
16509
|
return { ok: false, failureLog: stack, reason: msg, issueKind: "exception" };
|
|
14584
16510
|
} finally {
|
|
14585
|
-
void
|
|
16511
|
+
void path27;
|
|
14586
16512
|
}
|
|
14587
16513
|
}
|
|
14588
16514
|
async buildContextSnippets(plan, step, debug) {
|
|
@@ -14639,7 +16565,7 @@ ${pt.stderr}`);
|
|
|
14639
16565
|
if (downstream) {
|
|
14640
16566
|
out.set(`.xcompiler/downstream/${step.id}.md`, downstream);
|
|
14641
16567
|
}
|
|
14642
|
-
return [...out.entries()].map(([
|
|
16568
|
+
return [...out.entries()].map(([path32, content]) => ({ path: path32, content }));
|
|
14643
16569
|
}
|
|
14644
16570
|
findOwningTestStepForFailure(plan, currentStep, failureText) {
|
|
14645
16571
|
const failedPaths = extractFailedTestPaths(failureText);
|
|
@@ -14762,7 +16688,7 @@ ${pt.stderr}`);
|
|
|
14762
16688
|
].join("\n").length;
|
|
14763
16689
|
}
|
|
14764
16690
|
};
|
|
14765
|
-
function
|
|
16691
|
+
function dedup6(arr) {
|
|
14766
16692
|
return [...new Set(arr)];
|
|
14767
16693
|
}
|
|
14768
16694
|
function isDesignSourcePhase(phase) {
|
|
@@ -14794,6 +16720,9 @@ var REPAIR_VERIFICATION_TOOLS = /* @__PURE__ */ new Set([
|
|
|
14794
16720
|
function hasSuccessfulVerificationEvidence(toolCalls) {
|
|
14795
16721
|
return toolCalls.some((call) => call.ok && REPAIR_VERIFICATION_TOOLS.has(call.tool));
|
|
14796
16722
|
}
|
|
16723
|
+
function hasFailedVerificationEvidence(toolCalls) {
|
|
16724
|
+
return toolCalls.some((call) => !call.ok && REPAIR_VERIFICATION_TOOLS.has(call.tool));
|
|
16725
|
+
}
|
|
14797
16726
|
function shouldRollbackTestPhaseFromToolFailures(step, toolCalls) {
|
|
14798
16727
|
let unresolvedTestFailure = false;
|
|
14799
16728
|
for (const call of toolCalls) {
|
|
@@ -14913,32 +16842,65 @@ function extractFailedTestPaths(text) {
|
|
|
14913
16842
|
if (file && isTestFilePath(file)) found.push(file);
|
|
14914
16843
|
}
|
|
14915
16844
|
}
|
|
14916
|
-
return
|
|
16845
|
+
return dedup6(found);
|
|
14917
16846
|
}
|
|
14918
16847
|
function isNonDebuggableInfrastructureFailure(reason, failureLog) {
|
|
14919
16848
|
const text = `${reason ?? ""}
|
|
14920
16849
|
${failureLog ?? ""}`.toLowerCase();
|
|
14921
|
-
|
|
16850
|
+
if (/low-quality debugger response/u.test(text)) return false;
|
|
16851
|
+
return /typeerror:\s*fetch failed/u.test(text) || /(?:openai|ollama) http (?:401|403|408|409|429|5\d\d)\b/u.test(text) || /rate limit exceeded|free-models-per-day|retry_after_seconds|retry-after/u.test(text) || /(?:openai|ollama|llm|provider)[^\n]{0,180}(?:rate[- ]?limit|rate limited|rate-limited|retry-after|retry_after_seconds)/u.test(text) || /(?:response_format|json_object|json_schema)[^\n]{0,220}(?:not support|unsupported|invalid_request_body|supported formats)/u.test(text) || /openai stream (?:wall-clock|idle)/u.test(text) || /ollama stream (?:wall-clock|idle)/u.test(text) || /request timed out after \d+ms/u.test(text) || /prefill_memory_exceeded|prefill memory guard|dynamic ceiling/u.test(text) || /context (?:length|window)|token limit|too many tokens|prompt too long|max(?:imum)? context/u.test(text) || /input[^\n]{0,80}tokens[^\n]{0,120}(?:exceed|limit|ceiling)/u.test(text) || /llm provider|provider_call_failed|all llm providers failed/u.test(text);
|
|
14922
16852
|
}
|
|
14923
16853
|
function isReadOnlyProbeLoopFailure(reason) {
|
|
14924
16854
|
return /repeated read-only\/probe actions without progress/u.test(reason ?? "") || /read-only recovery mode repeated probe actions/u.test(reason ?? "");
|
|
14925
16855
|
}
|
|
16856
|
+
function isMissingOutputStallFailure(reason) {
|
|
16857
|
+
return /write\/progress actions did not reduce missing outputs/u.test(reason ?? "");
|
|
16858
|
+
}
|
|
16859
|
+
function isLowQualityDebuggerResponseFailure(reason) {
|
|
16860
|
+
return /low-quality debugger response/u.test(reason ?? "");
|
|
16861
|
+
}
|
|
14926
16862
|
function isRepairEvidenceMissingFailure(reason) {
|
|
14927
|
-
return /without repair evidence/u.test(reason ?? "") || /without a successful repair mutation or verification tool call/u.test(reason ?? "");
|
|
16863
|
+
return /without repair evidence/u.test(reason ?? "") || /without a successful repair mutation/u.test(reason ?? "") || /without a successful repair mutation or verification tool call/u.test(reason ?? "");
|
|
14928
16864
|
}
|
|
14929
16865
|
function shouldRollbackTestPhaseFailure(reason, failureLog) {
|
|
14930
16866
|
const text = `${reason ?? ""}
|
|
14931
16867
|
${failureLog ?? ""}`.toLowerCase();
|
|
14932
16868
|
if (isReadOnlyProbeLoopFailure(reason) || isRepairEvidenceMissingFailure(reason)) return false;
|
|
16869
|
+
if (/tool verification failed.*rolling back to paired/u.test(text)) return true;
|
|
14933
16870
|
if (isCachedTestArtifactDiscoveryFailure(text)) return false;
|
|
14934
16871
|
if (/missing outputs|outputs? 校验|verify outputs|declared outputs|产物/u.test(text)) return false;
|
|
14935
16872
|
if (/invalid action|args must be an object|invalid json|parse failure/u.test(text)) return false;
|
|
14936
|
-
if (/
|
|
14937
|
-
if (/test gate|functional gate|functional entry probe|entry probe|delivery gate/u.test(text)) return true;
|
|
16873
|
+
if (/test gate|functional gate|functional entry probe|entry probe|delivery gate|测试门禁|功能门禁|功能入口探测|入口探测|交付门禁/u.test(text)) return true;
|
|
14938
16874
|
if (/pytest exit=\s*[1-9]\d*|exit code\s*[1-9]\d*|failed tests?|test failures?/u.test(text)) return true;
|
|
14939
16875
|
if (/assertionerror|failed:\s+did not|traceback \(most recent call last\)/u.test(text)) return true;
|
|
14940
16876
|
return false;
|
|
14941
16877
|
}
|
|
16878
|
+
function inferCachedTestScopeArgs(entry) {
|
|
16879
|
+
const explicit = (entry.testScopeArgs ?? []).map(normalizeGitPath).filter(isTestFilePath);
|
|
16880
|
+
if (explicit.length > 0) return dedup6(explicit);
|
|
16881
|
+
const text = [
|
|
16882
|
+
entry.failureLogTail,
|
|
16883
|
+
entry.debugBrief?.toolFailures?.join("\n") ?? "",
|
|
16884
|
+
entry.debugBrief?.evidence?.join("\n") ?? ""
|
|
16885
|
+
].filter(Boolean).join("\n");
|
|
16886
|
+
const fromRunTestsArgs = extractRunTestsArgs(text);
|
|
16887
|
+
if (fromRunTestsArgs.length > 0) return fromRunTestsArgs;
|
|
16888
|
+
const failedPaths = extractFailedTestPaths(text);
|
|
16889
|
+
if (failedPaths.length > 0) return failedPaths;
|
|
16890
|
+
return dedup6((entry.debugBrief?.files ?? []).map(normalizeGitPath).filter(isTestFilePath));
|
|
16891
|
+
}
|
|
16892
|
+
function extractRunTestsArgs(text) {
|
|
16893
|
+
const out = [];
|
|
16894
|
+
for (const match of text.matchAll(/\brun_tests[^\n]*\bargs=([^\n]+)/giu)) {
|
|
16895
|
+
const raw = match[1] ?? "";
|
|
16896
|
+
for (const token of raw.split(/\s+/u)) {
|
|
16897
|
+
const cleaned = token.replace(/^["'`]+|["'`,;]+$/gu, "");
|
|
16898
|
+
const normalized = normalizeGitPath(cleaned);
|
|
16899
|
+
if (isTestFilePath(normalized)) out.push(normalized);
|
|
16900
|
+
}
|
|
16901
|
+
}
|
|
16902
|
+
return dedup6(out);
|
|
16903
|
+
}
|
|
14942
16904
|
function isCachedTestArtifactDiscoveryFailure(text) {
|
|
14943
16905
|
return /no test files? found|no tests? found/u.test(text) || /filter:\s+tests?\//u.test(text) || /(?:enoent|no such file or directory|not a file)[^\n]{0,240}tests?\//u.test(text);
|
|
14944
16906
|
}
|
|
@@ -14994,6 +16956,13 @@ function inferRepairMode(toolCalls) {
|
|
|
14994
16956
|
if (usedRewrite) return "rewrite";
|
|
14995
16957
|
return "verification";
|
|
14996
16958
|
}
|
|
16959
|
+
function parsePatchChangedFiles(diff) {
|
|
16960
|
+
const files = [];
|
|
16961
|
+
for (const match of diff.matchAll(/^diff --git a\/(.+?) b\/(.+)$/gmu)) {
|
|
16962
|
+
files.push(normalizeGitPath(match[2] ?? match[1] ?? ""));
|
|
16963
|
+
}
|
|
16964
|
+
return dedup6(files.filter(Boolean));
|
|
16965
|
+
}
|
|
14997
16966
|
|
|
14998
16967
|
// src/runtime/run.ts
|
|
14999
16968
|
async function runExecute(opts) {
|
|
@@ -15002,7 +16971,7 @@ async function runExecute(opts) {
|
|
|
15002
16971
|
io.interaction?.pauseStdin?.();
|
|
15003
16972
|
} catch {
|
|
15004
16973
|
}
|
|
15005
|
-
const ws = new Workspace(
|
|
16974
|
+
const ws = new Workspace(path28.resolve(opts.workspace));
|
|
15006
16975
|
const { config: cfg, path: cfgPath } = await loadConfigWithPath(opts.configPath);
|
|
15007
16976
|
if (!hasXcEnv("LANG")) setLocale(cfg.locale);
|
|
15008
16977
|
let lock;
|
|
@@ -15172,6 +17141,8 @@ async function runExecute(opts) {
|
|
|
15172
17141
|
maxEditLinesPerStep: cfg.agent.max_edit_lines_per_step,
|
|
15173
17142
|
maxWriteChunkBytes: cfg.agent.max_write_chunk_bytes,
|
|
15174
17143
|
terminalOutput: opts.terminalOutput ?? true,
|
|
17144
|
+
debugWikiPath: opts.debugWikiPath ? path28.resolve(opts.debugWikiPath) : void 0,
|
|
17145
|
+
debugWikiStrict: !!opts.debugWikiPath,
|
|
15175
17146
|
requestPermission: io.requestPermission ? async (request2) => {
|
|
15176
17147
|
await io.emit({ type: "permission", status: "requested", request: request2 });
|
|
15177
17148
|
const decision = await io.requestPermission(request2);
|
|
@@ -15416,8 +17387,8 @@ async function emitProjectAudit(io, result) {
|
|
|
15416
17387
|
}
|
|
15417
17388
|
|
|
15418
17389
|
// src/runtime/workspace.ts
|
|
15419
|
-
import
|
|
15420
|
-
import { promises as
|
|
17390
|
+
import path29 from "path";
|
|
17391
|
+
import { promises as fs27 } from "fs";
|
|
15421
17392
|
function defaultProjectName(now = /* @__PURE__ */ new Date()) {
|
|
15422
17393
|
const pad = (n) => n.toString().padStart(2, "0");
|
|
15423
17394
|
return `xcompiler-${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
@@ -15425,14 +17396,14 @@ function defaultProjectName(now = /* @__PURE__ */ new Date()) {
|
|
|
15425
17396
|
async function resolveCompileWorkspace(opts) {
|
|
15426
17397
|
const explicit = opts.output ?? opts.workspace;
|
|
15427
17398
|
if (explicit) {
|
|
15428
|
-
const ws2 =
|
|
15429
|
-
await
|
|
17399
|
+
const ws2 = path29.resolve(explicit);
|
|
17400
|
+
await fs27.mkdir(ws2, { recursive: true });
|
|
15430
17401
|
return ws2;
|
|
15431
17402
|
}
|
|
15432
|
-
const base = opts.baseDir ?
|
|
17403
|
+
const base = opts.baseDir ? path29.resolve(opts.baseDir) : "/tmp";
|
|
15433
17404
|
const name = opts.name ?? defaultProjectName();
|
|
15434
|
-
const ws =
|
|
15435
|
-
await
|
|
17405
|
+
const ws = path29.join(base, name);
|
|
17406
|
+
await fs27.mkdir(ws, { recursive: true });
|
|
15436
17407
|
return ws;
|
|
15437
17408
|
}
|
|
15438
17409
|
|
|
@@ -15449,8 +17420,8 @@ async function runBuildCommand(opts) {
|
|
|
15449
17420
|
async function runRunCommand(opts) {
|
|
15450
17421
|
const cwd = opts.cwd ?? process.cwd();
|
|
15451
17422
|
const explicit = opts.output ?? opts.workspace;
|
|
15452
|
-
const workspace = explicit ?
|
|
15453
|
-
const planPath = opts.planArg ?
|
|
17423
|
+
const workspace = explicit ? path30.resolve(explicit) : opts.planArg ? path30.dirname(path30.resolve(opts.planArg)) : cwd;
|
|
17424
|
+
const planPath = opts.planArg ? path30.resolve(opts.planArg) : await defaultRunnablePlanPath(workspace);
|
|
15454
17425
|
return runExecute({
|
|
15455
17426
|
...opts,
|
|
15456
17427
|
workspace,
|
|
@@ -15459,15 +17430,15 @@ async function runRunCommand(opts) {
|
|
|
15459
17430
|
});
|
|
15460
17431
|
}
|
|
15461
17432
|
async function defaultRunnablePlanPath(workspace) {
|
|
15462
|
-
const phasePlanPath =
|
|
17433
|
+
const phasePlanPath = path30.join(workspace, DEFAULT_PHASE_PLAN_FILE);
|
|
15463
17434
|
if (await fileExists3(phasePlanPath)) return phasePlanPath;
|
|
15464
|
-
const legacyPlanPath =
|
|
17435
|
+
const legacyPlanPath = path30.join(workspace, DEFAULT_PLAN_FILE);
|
|
15465
17436
|
if (await fileExists3(legacyPlanPath)) return legacyPlanPath;
|
|
15466
17437
|
return phasePlanPath;
|
|
15467
17438
|
}
|
|
15468
17439
|
async function fileExists3(filePath) {
|
|
15469
17440
|
try {
|
|
15470
|
-
await
|
|
17441
|
+
await fs28.stat(filePath);
|
|
15471
17442
|
return true;
|
|
15472
17443
|
} catch {
|
|
15473
17444
|
return false;
|
|
@@ -16039,7 +18010,7 @@ var AcpServer = class {
|
|
|
16039
18010
|
}
|
|
16040
18011
|
createSession(params) {
|
|
16041
18012
|
const p = asOptionalRecord(params);
|
|
16042
|
-
const workspace = typeof p.cwd === "string" ?
|
|
18013
|
+
const workspace = typeof p.cwd === "string" ? path31.resolve(p.cwd) : typeof p.workspace === "string" ? path31.resolve(p.workspace) : process.cwd();
|
|
16043
18014
|
const session = this.sessions.create(workspace);
|
|
16044
18015
|
return { sessionId: session.id };
|
|
16045
18016
|
}
|
|
@@ -16051,7 +18022,7 @@ var AcpServer = class {
|
|
|
16051
18022
|
async prompt(params) {
|
|
16052
18023
|
const p = parsePromptParams(params);
|
|
16053
18024
|
const session = this.sessions.get(p.sessionId);
|
|
16054
|
-
const workspace =
|
|
18025
|
+
const workspace = path31.resolve(session.workspace || process.cwd());
|
|
16055
18026
|
const task = this.sessions.createTask(session, { workspace, userTask: p.task, protocol: "acp" });
|
|
16056
18027
|
try {
|
|
16057
18028
|
await this.runCodeTask(session, task, { ...p, workspace });
|
|
@@ -16071,7 +18042,7 @@ var AcpServer = class {
|
|
|
16071
18042
|
startTask(params, protocol) {
|
|
16072
18043
|
const p = parseTaskParams(params);
|
|
16073
18044
|
const session = this.sessions.get(p.sessionId);
|
|
16074
|
-
const workspace =
|
|
18045
|
+
const workspace = path31.resolve(p.workspace || session.workspace || process.cwd());
|
|
16075
18046
|
const task = this.sessions.createTask(session, { workspace, userTask: p.task, protocol });
|
|
16076
18047
|
this.runCodeTask(session, task, { ...p, workspace }).catch((err) => {
|
|
16077
18048
|
task.status = "failed";
|