@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.
@@ -21,7 +21,7 @@ import { Command } from "commander";
21
21
 
22
22
  // src/runtime/build.ts
23
23
  import path13 from "path";
24
- import { promises as fs11 } from "fs";
24
+ import { promises as fs12 } from "fs";
25
25
 
26
26
  // src/config/config.ts
27
27
  import { promises as fs } from "fs";
@@ -128,6 +128,7 @@ var PYTHON_EXECUTOR_SYSTEM = `You are XCompiler's Step Executor. You may only in
128
128
  Every round you must return strict JSON:
129
129
  {
130
130
  "thoughts": "<one sentence describing this round's intent>",
131
+ "issueResolutionPlan": "<required only in DEBUG issue mode: concise root cause, repair target, and validation plan>",
131
132
  "actions": [ { "tool": "<tool name>", "args": { ... } }, ... ],
132
133
  "done": true | false
133
134
  }
@@ -212,6 +213,7 @@ var TYPESCRIPT_EXECUTOR_SYSTEM = `You are XCompiler's Step Executor. You may onl
212
213
  Every round you must return strict JSON:
213
214
  {
214
215
  "thoughts": "<one sentence describing this round's intent>",
216
+ "issueResolutionPlan": "<required only in DEBUG issue mode: concise root cause, repair target, and validation plan>",
215
217
  "actions": [ { "tool": "<tool name>", "args": { ... } }, ... ],
216
218
  "done": true | false
217
219
  }
@@ -235,7 +237,7 @@ Rules:
235
237
  7. package.json is the dependency manifest. Use add_dependency for npm packages; never write requirements.txt.
236
238
  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.`;
237
239
  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.
238
- Return strict JSON only. Each question must be directly answerable by a product owner, cover one decision, and avoid vague catch-all or implementation-stack questions.`;
240
+ 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.`;
239
241
  function buildPlannerSystem(profile) {
240
242
  return (profile.id === "typescript" ? TYPESCRIPT_PLANNER_SYSTEM : PYTHON_PLANNER_SYSTEM) + profile.plannerPromptOverride;
241
243
  }
@@ -327,13 +329,13 @@ var messages = {
327
329
  preflightOllamaUnreachable: (baseUrl, message) => `preflight: ollama ${baseUrl} unreachable: ${message}`,
328
330
  preflightAutoAdded: (providers, roles) => `preflight: auto-added ${providers} provider(s) for roles [${roles}]`,
329
331
  scoreFileHeader: "# XCompiler LLM provider score snapshot (maintained automatically by ScoreStore; do not edit)",
330
- 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; only user-configured score=0 disables a provider."
332
+ 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."
331
333
  },
332
334
  system: {
333
335
  configEnvMissing: (names) => `[xcompiler] unset config environment variables were replaced with empty strings: ${names}`,
334
336
  unhandledError: (message) => `Unhandled error: ${message}`,
335
337
  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.",
336
- dockerInsideContainerUnsupported: "XCompiler is running inside a container, so sandbox=docker is unsupported because Docker-outside-of-Docker can mis-map bind mounts and docker.sock permissions. Use agent.sandbox=subprocess, run XCompiler on the host, or set XC_IN_CONTAINER=0 only in a controlled environment.",
338
+ 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.",
337
339
  firejailUnsupported: "sandbox=firejail is not implemented; use subprocess or docker.",
338
340
  smokeHeader: (baseUrl) => `Smoke test against ${baseUrl} (streaming)`,
339
341
  smokeOk: (model, totalMs, firstTokenMs, chunks, preview) => `[OK total=${totalMs}ms first-token=${firstTokenMs}ms chunks=${chunks}] ${model} -> ${preview}`,
@@ -350,8 +352,8 @@ var messages = {
350
352
  loaded: (plugin, version) => `Plugin ${plugin}@${version} loaded.`,
351
353
  extensionConflict: (plugin, kind, name) => `Plugin ${plugin} cannot replace existing ${kind} "${name}".`,
352
354
  hookFailed: (plugin, stage, message) => `Plugin ${plugin} failed during ${stage}: ${message}`,
353
- manifestReadFailed: (path30, message) => `Cannot read plugin manifest ${path30}: ${message}`,
354
- moduleLoadFailed: (plugin, path30, message) => `Cannot load plugin ${plugin} from ${path30}: ${message}`,
355
+ manifestReadFailed: (path31, message) => `Cannot read plugin manifest ${path31}: ${message}`,
356
+ moduleLoadFailed: (plugin, path31, message) => `Cannot load plugin ${plugin} from ${path31}: ${message}`,
355
357
  exportInvalid: (plugin, exportName) => `Plugin ${plugin} export ${exportName} is not a valid XCompiler plugin`,
356
358
  manifestMismatch: (plugin) => `Plugin ${plugin} runtime manifest does not match its preflight manifest`
357
359
  },
@@ -439,6 +441,7 @@ var messages = {
439
441
  optIntent: "plan intent: greenfield | feature | refactor | self",
440
442
  optBaselinePlan: "existing baseline phasePlan.json / plan.json path (default <workspace>/phasePlan.json)",
441
443
  optProjectFile: "XXX.xc project file path (default <workspace>/<name>.xc)",
444
+ optDebugWikiPath: "debug wiki root directory path (default <XCompiler path>/.xcompiler/debug-wiki)",
442
445
  argPlan: "phasePlan.json or legacy plan.json path (default = <workspace>/phasePlan.json)",
443
446
  argProjectFile: "XXX.xc project file",
444
447
  argStepId: "Step ID, e.g. S001",
@@ -653,7 +656,7 @@ var messages = {
653
656
  deliveryGateReason: (command, exitCode, timedOut) => `FUNCTIONAL_TEST gate: \`${command}\` exit=${exitCode}${timedOut ? " (timeout)" : ""}`,
654
657
  missingPythonEntrypoint: "missing Python entrypoint: expected src/main.py, src/<package>/__main__.py, or an explicit CLI file such as src/cli.py",
655
658
  missingTypeScriptEntrypoint: "missing TypeScript entrypoint: expected package.json start/bin or one of src/main.ts, src/index.ts, src/main.tsx",
656
- invalidPythonEntrypointSource: (path30) => `invalid Python entrypoint source in ${path30}: expected a real CLI entry structure such as def main(...), argparse.ArgumentParser, or if __name__ == "__main__"; placeholder/import-only files are not runnable applications.`,
659
+ invalidPythonEntrypointSource: (path31) => `invalid Python entrypoint source in ${path31}: expected a real CLI entry structure such as def main(...), argparse.ArgumentParser, or if __name__ == "__main__"; placeholder/import-only files are not runnable applications.`,
657
660
  entrypointHelpOutputMissing: (command) => `entrypoint probe \`${command}\` exited 0 but produced no meaningful help/usage text; implement --help instead of relying on an empty script exit.`,
658
661
  reasonLine: (reason) => `reason: ${reason}`,
659
662
  roundsLine: (rounds) => `rounds: ${rounds}`,
@@ -722,12 +725,13 @@ Question mix (functionality first):
722
725
  - 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.
723
726
  - 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.
724
727
  ${opts.projectShapeAmbiguous ? "- Required for this topic: ask the API library vs runnable application vs mixed-deliverable boundary explicitly.\n" : ""}
728
+ ${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" : ""}
725
729
 
726
- [Hard constraint] The implementation stack is already fixed by XCompiler config / the existing project baseline. Do not reopen language/runtime/package-manager decisions.
730
+ ${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.
727
731
  **Do NOT** ask questions of these forms:
728
732
  - "Which programming language / framework / runtime should this use?"
729
733
  - "Which test framework / build tool / package manager?"
730
- - "Which OS is the target platform?"
734
+ - "Which OS is the target platform?"`}
731
735
  ${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.`,
732
736
  plannerDecompose: (raw, qa, addenda, opts = {}) => `Original requirement:
733
737
  """
@@ -829,7 +833,7 @@ Return strict JSON StepPlan for the current phase only.`,
829
833
  executorDebugBlock: (reason, suggestions) => `
830
834
 
831
835
  You are now in DEBUG retry mode. Previous failure reason: ${reason}
832
- 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 ? `
836
+ 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 ? `
833
837
 
834
838
  ${suggestions}` : ""),
835
839
  executorGlobalBlock: (globalPrompt) => `
@@ -846,7 +850,8 @@ ${sp}`,
846
850
  executorFeedbackVerifyMissing: (paths) => `outputs still missing: ${paths}. Please continue.`,
847
851
  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.",
848
852
  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.",
849
- 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."
853
+ 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.",
854
+ 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."
850
855
  },
851
856
  skills: {
852
857
  patcher: "Use apply_patch / replace_in_file for small in-place edits to existing files; never overwrite a whole file.",
@@ -867,7 +872,7 @@ ${sp}`,
867
872
  summaryOk: "all checks passed.",
868
873
  summaryWarn: (n) => `passed with ${n} warning(s).`,
869
874
  summaryFail: (n) => `${n} failure(s) detected.`,
870
- configLoadOk: (path30) => `config loaded: ${path30}`,
875
+ configLoadOk: (path31) => `config loaded: ${path31}`,
871
876
  configLoadFail: (msg) => `failed to load config: ${msg}`,
872
877
  configLocale: (locale) => `locale=${locale}`,
873
878
  llmNoProviders: "no LLM providers defined in config.llm.providers",
@@ -885,7 +890,7 @@ ${sp}`,
885
890
  roleOk: (role, provider) => `role "${role}" \u2192 ${provider}`,
886
891
  sandboxKind: (kind) => `sandbox=${kind}`,
887
892
  sandboxNetworkPolicy: (policy, ports) => `network=${policy}` + (ports.length ? ` (expose_ports=[${ports.join(", ")}])` : ""),
888
- sandboxFullNoPorts: "network=full but no expose_ports configured \u2014 host-side cannot reach container services. Add `agent.sandbox_limits.expose_ports: [<port>]` in config.yaml.",
893
+ 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.",
889
894
  sandboxNodeMissing: "node not found on PATH (required by TypeScript subprocess sandbox)",
890
895
  sandboxNodeOk: (version) => `node OK (${version})`,
891
896
  sandboxNpmMissing: "npm not found on PATH (required by TypeScript subprocess sandbox)",
@@ -1004,6 +1009,7 @@ var PYTHON_EXECUTOR_SYSTEM2 = `\u4F60\u662F XCompiler \u7684 Step Executor\u3002
1004
1009
  \u6BCF\u4E00\u8F6E\u4F60\u5FC5\u987B\u8FD4\u56DE\u4E25\u683C JSON\uFF1A
1005
1010
  {
1006
1011
  "thoughts": "<\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u672C\u8F6E\u610F\u56FE>",
1012
+ "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>",
1007
1013
  "actions": [ { "tool": "<\u5DE5\u5177\u540D>", "args": { ... } }, ... ],
1008
1014
  "done": true | false
1009
1015
  }
@@ -1087,6 +1093,7 @@ var TYPESCRIPT_EXECUTOR_SYSTEM2 = `\u4F60\u662F XCompiler \u7684 Step Executor\u
1087
1093
  \u6BCF\u4E00\u8F6E\u4F60\u5FC5\u987B\u8FD4\u56DE\u4E25\u683C JSON\uFF1A
1088
1094
  {
1089
1095
  "thoughts": "<\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u672C\u8F6E\u610F\u56FE>",
1096
+ "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>",
1090
1097
  "actions": [ { "tool": "<\u5DE5\u5177\u540D>", "args": { ... } }, ... ],
1091
1098
  "done": true | false
1092
1099
  }
@@ -1200,13 +1207,13 @@ var messages2 = {
1200
1207
  preflightOllamaUnreachable: (baseUrl, message) => `\u9884\u68C0\uFF1AOllama ${baseUrl} \u4E0D\u53EF\u8FBE\uFF1A${message}`,
1201
1208
  preflightAutoAdded: (providers, roles) => `\u9884\u68C0\uFF1A\u81EA\u52A8\u589E\u52A0 ${providers} \u4E2A provider\uFF0C\u8986\u76D6\u89D2\u8272 [${roles}]`,
1202
1209
  scoreFileHeader: "# XCompiler LLM provider \u8BC4\u5206\u5FEB\u7167\uFF08\u7531 ScoreStore \u81EA\u52A8\u7EF4\u62A4\uFF0C\u8BF7\u52FF\u624B\u5DE5\u7F16\u8F91\uFF09",
1203
- 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\uFF1B\u53EA\u6709\u7528\u6237\u914D\u7F6E score=0 \u8868\u793A\u7981\u7528\u3002"
1210
+ 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"
1204
1211
  },
1205
1212
  system: {
1206
1213
  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}`,
1207
1214
  unhandledError: (message) => `\u672A\u5904\u7406\u9519\u8BEF\uFF1A${message}`,
1208
1215
  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",
1209
- 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.sandbox=subprocess\u3001\u6539\u5728\u5BBF\u4E3B\u673A\u8FD0\u884C\uFF0C\u6216\u4EC5\u5728\u53D7\u63A7\u73AF\u5883\u8BBE\u7F6E XC_IN_CONTAINER=0\u3002",
1216
+ 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",
1210
1217
  firejailUnsupported: "\u5C1A\u672A\u5B9E\u73B0 sandbox=firejail\uFF0C\u8BF7\u4F7F\u7528 subprocess \u6216 docker\u3002",
1211
1218
  smokeHeader: (baseUrl) => `\u6B63\u5728\u5BF9 ${baseUrl} \u6267\u884C\u6D41\u5F0F\u5192\u70DF\u6D4B\u8BD5`,
1212
1219
  smokeOk: (model, totalMs, firstTokenMs, chunks, preview) => `[\u6210\u529F \u603B\u8017\u65F6=${totalMs}ms \u9996Token=${firstTokenMs}ms \u5206\u5757=${chunks}] ${model} -> ${preview}`,
@@ -1223,8 +1230,8 @@ var messages2 = {
1223
1230
  loaded: (plugin, version) => `\u63D2\u4EF6 ${plugin}@${version} \u5DF2\u52A0\u8F7D\u3002`,
1224
1231
  extensionConflict: (plugin, kind, name) => `\u63D2\u4EF6 ${plugin} \u4E0D\u80FD\u8986\u76D6\u5DF2\u6709 ${kind} \u201C${name}\u201D\u3002`,
1225
1232
  hookFailed: (plugin, stage, message) => `\u63D2\u4EF6 ${plugin} \u5728 ${stage} \u9636\u6BB5\u6267\u884C\u5931\u8D25\uFF1A${message}`,
1226
- manifestReadFailed: (path30, message) => `\u65E0\u6CD5\u8BFB\u53D6\u63D2\u4EF6\u6E05\u5355 ${path30}\uFF1A${message}`,
1227
- moduleLoadFailed: (plugin, path30, message) => `\u65E0\u6CD5\u4ECE ${path30} \u52A0\u8F7D\u63D2\u4EF6 ${plugin}\uFF1A${message}`,
1233
+ manifestReadFailed: (path31, message) => `\u65E0\u6CD5\u8BFB\u53D6\u63D2\u4EF6\u6E05\u5355 ${path31}\uFF1A${message}`,
1234
+ moduleLoadFailed: (plugin, path31, message) => `\u65E0\u6CD5\u4ECE ${path31} \u52A0\u8F7D\u63D2\u4EF6 ${plugin}\uFF1A${message}`,
1228
1235
  exportInvalid: (plugin, exportName) => `\u63D2\u4EF6 ${plugin} \u7684\u5BFC\u51FA ${exportName} \u4E0D\u662F\u6709\u6548 XCompiler \u63D2\u4EF6`,
1229
1236
  manifestMismatch: (plugin) => `\u63D2\u4EF6 ${plugin} \u7684\u8FD0\u884C\u65F6\u6E05\u5355\u4E0E\u9884\u68C0\u6E05\u5355\u4E0D\u4E00\u81F4`
1230
1237
  },
@@ -1312,6 +1319,7 @@ var messages2 = {
1312
1319
  optIntent: "\u8BA1\u5212\u610F\u56FE\uFF1Agreenfield | feature | refactor | self",
1313
1320
  optBaselinePlan: "\u5DF2\u6709\u57FA\u7EBF phasePlan.json / plan.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 <workspace>/phasePlan.json\uFF09",
1314
1321
  optProjectFile: "XXX.xc \u5DE5\u7A0B\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 <workspace>/<name>.xc\uFF09",
1322
+ optDebugWikiPath: "debug wiki \u6839\u76EE\u5F55\u8DEF\u5F84\uFF08\u9ED8\u8BA4 <XCompiler path>/.xcompiler/debug-wiki\uFF09",
1315
1323
  argPlan: "phasePlan.json \u6216\u5386\u53F2 plan.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 = <workspace>/phasePlan.json\uFF09",
1316
1324
  argProjectFile: "XXX.xc \u5DE5\u7A0B\u6587\u4EF6",
1317
1325
  argStepId: "Step ID\uFF0C\u5982 S001",
@@ -1526,7 +1534,7 @@ var messages2 = {
1526
1534
  deliveryGateReason: (command, exitCode, timedOut) => `FUNCTIONAL_TEST \u95E8\u7981\uFF1A\`${command}\` \u9000\u51FA\u7801=${exitCode}${timedOut ? "\uFF08\u8D85\u65F6\uFF09" : ""}`,
1527
1535
  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",
1528
1536
  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",
1529
- invalidPythonEntrypointSource: (path30) => `Python \u5165\u53E3\u6E90\u7801\u65E0\u6548\uFF1A${path30} \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`,
1537
+ invalidPythonEntrypointSource: (path31) => `Python \u5165\u53E3\u6E90\u7801\u65E0\u6548\uFF1A${path31} \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`,
1530
1538
  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`,
1531
1539
  reasonLine: (reason) => `\u539F\u56E0\uFF1A${reason}`,
1532
1540
  roundsLine: (rounds) => `\u8F6E\u6B21\uFF1A${rounds}`,
@@ -1571,7 +1579,7 @@ var messages2 = {
1571
1579
  - \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
1572
1580
  - \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`,
1573
1581
  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
1574
- \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`,
1582
+ \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`,
1575
1583
  plannerClarify: (raw, opts = {}) => `\u7528\u6237\u7684\u539F\u59CB\u9700\u6C42\u5982\u4E0B\uFF1A
1576
1584
 
1577
1585
  """
@@ -1596,12 +1604,13 @@ ${raw}
1596
1604
  - \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
1597
1605
  - \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
1598
1606
  ${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" : ""}
1607
+ ${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" : ""}
1599
1608
 
1600
- \u3010\u786C\u7EA6\u675F\u3011\u5B9E\u73B0\u6280\u672F\u6808\u5DF2\u7ECF\u7531 XCompiler \u914D\u7F6E / \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
1609
+ ${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
1601
1610
  **\u4E25\u7981**\u63D0\u51FA\u4EE5\u4E0B\u7C7B\u578B\u7684\u95EE\u9898\uFF1A
1602
1611
  - "\u5E0C\u671B\u7528\u4EC0\u4E48\u7F16\u7A0B\u8BED\u8A00 / \u6846\u67B6 / \u8FD0\u884C\u65F6\u5B9E\u73B0\uFF1F"
1603
1612
  - "\u9700\u8981\u54EA\u79CD\u6D4B\u8BD5\u6846\u67B6 / \u6784\u5EFA\u5DE5\u5177 / \u5305\u7BA1\u7406\u5668\uFF1F"
1604
- - "\u76EE\u6807\u5E73\u53F0\u662F\u54EA\u79CD\u64CD\u4F5C\u7CFB\u7EDF\uFF1F"
1613
+ - "\u76EE\u6807\u5E73\u53F0\u662F\u54EA\u79CD\u64CD\u4F5C\u7CFB\u7EDF\uFF1F"`}
1605
1614
  ${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`,
1606
1615
  plannerDecompose: (raw, qa, addenda, opts = {}) => `\u539F\u59CB\u9700\u6C42\uFF1A
1607
1616
  """
@@ -1703,7 +1712,7 @@ ${opts.phasePlan}
1703
1712
  executorDebugBlock: (reason, suggestions) => `
1704
1713
 
1705
1714
  \u6B63\u5904\u4E8E DEBUG \u91CD\u8BD5\u6A21\u5F0F\u3002\u4E0A\u4E00\u8F6E\u5931\u8D25\u539F\u56E0: ${reason}
1706
- 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 ? `
1715
+ \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 ? `
1707
1716
 
1708
1717
  ${suggestions}` : ""),
1709
1718
  executorGlobalBlock: (globalPrompt) => `
@@ -1720,7 +1729,8 @@ ${sp}`,
1720
1729
  executorFeedbackVerifyMissing: (paths) => `outputs \u4ECD\u7F3A\u5931\uFF1A${paths}\u3002\u8BF7\u7EE7\u7EED\u3002`,
1721
1730
  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",
1722
1731
  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",
1723
- 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"
1732
+ 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",
1733
+ 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"
1724
1734
  },
1725
1735
  skills: {
1726
1736
  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",
@@ -1741,7 +1751,7 @@ ${sp}`,
1741
1751
  summaryOk: "\u5168\u90E8\u68C0\u67E5\u901A\u8FC7\u3002",
1742
1752
  summaryWarn: (n) => `\u901A\u8FC7\uFF0C\u4F46\u6709 ${n} \u6761 warning\u3002`,
1743
1753
  summaryFail: (n) => `\u68C0\u6D4B\u5230 ${n} \u9879\u5931\u8D25\u3002`,
1744
- configLoadOk: (path30) => `\u914D\u7F6E\u5DF2\u52A0\u8F7D\uFF1A${path30}`,
1754
+ configLoadOk: (path31) => `\u914D\u7F6E\u5DF2\u52A0\u8F7D\uFF1A${path31}`,
1745
1755
  configLoadFail: (msg) => `\u914D\u7F6E\u52A0\u8F7D\u5931\u8D25\uFF1A${msg}`,
1746
1756
  configLocale: (locale) => `locale=${locale}`,
1747
1757
  llmNoProviders: "config.llm.providers \u4E3A\u7A7A\uFF0C\u672A\u58F0\u660E\u4EFB\u4F55 provider",
@@ -1759,7 +1769,7 @@ ${sp}`,
1759
1769
  roleOk: (role, provider) => `\u89D2\u8272 "${role}" \u2192 ${provider}`,
1760
1770
  sandboxKind: (kind) => `sandbox=${kind}`,
1761
1771
  sandboxNetworkPolicy: (policy, ports) => `network=${policy}` + (ports.length ? `\uFF08expose_ports=[${ports.join(", ")}]\uFF09` : ""),
1762
- 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.sandbox_limits.expose_ports: [<port>]`\u3002",
1772
+ 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",
1763
1773
  sandboxNodeMissing: "PATH \u4E0A\u627E\u4E0D\u5230 node\uFF08TypeScript subprocess \u6C99\u76D2\u5FC5\u9700\uFF09",
1764
1774
  sandboxNodeOk: (version) => `node OK\uFF08${version}\uFF09`,
1765
1775
  sandboxNpmMissing: "PATH \u4E0A\u627E\u4E0D\u5230 npm\uFF08TypeScript subprocess \u6C99\u76D2\u5FC5\u9700\uFF09",
@@ -1858,6 +1868,73 @@ var ProviderSchema = z.object({
1858
1868
  think: z.boolean().optional()
1859
1869
  });
1860
1870
  var LocaleSchema = z.enum(["en", "zh"]);
1871
+ var TargetLanguageSchema = z.enum(["python", "typescript"]);
1872
+ var SandboxModeSchema = z.enum(["subprocess", "docker", "firejail"]);
1873
+ var SandboxLimitsSchema = z.object({
1874
+ cpu: z.number().positive().default(1),
1875
+ memory_mb: z.number().int().positive().default(1024),
1876
+ wall_seconds: z.number().int().positive().default(60),
1877
+ /**
1878
+ * Sandbox network policy.
1879
+ * - `off` — no network at all (`docker --network none`).
1880
+ * - `download-only` — outbound traffic allowed, no inbound port publishing.
1881
+ * - `pypi-only` — legacy value; rejected at sandbox creation rather than silently
1882
+ * allowing unrestricted outbound traffic.
1883
+ * - `full` — outbound + every port in `expose_ports` is published
1884
+ * to `127.0.0.1` so host-side tests can reach the app.
1885
+ */
1886
+ network: z.enum(["off", "pypi-only", "download-only", "full"]).default("download-only"),
1887
+ /** Container ports to publish to 127.0.0.1 when `network=full`. */
1888
+ expose_ports: z.array(z.number().int().min(1).max(65535)).default([])
1889
+ }).default({
1890
+ cpu: 1,
1891
+ memory_mb: 1024,
1892
+ wall_seconds: 60,
1893
+ network: "download-only",
1894
+ expose_ports: []
1895
+ });
1896
+ var LocalSandboxSchema = z.object({
1897
+ sandbox_dir: z.string().min(1).optional(),
1898
+ python_bin: z.string().min(1).optional(),
1899
+ inherit_env: z.boolean().optional(),
1900
+ limits: SandboxLimitsSchema
1901
+ }).default(() => ({ limits: defaultSandboxLimits() }));
1902
+ var DockerSandboxSchema = z.object({
1903
+ image: z.string().default("python:3.11-slim"),
1904
+ workdir: z.string().default("/workspace"),
1905
+ pull: z.boolean().default(false),
1906
+ docker_bin: z.string().default("docker"),
1907
+ extra_run_args: z.array(z.string()).default([]),
1908
+ sandbox_dir: z.string().min(1).optional(),
1909
+ limits: SandboxLimitsSchema
1910
+ }).default({
1911
+ image: "python:3.11-slim",
1912
+ workdir: "/workspace",
1913
+ pull: false,
1914
+ docker_bin: "docker",
1915
+ extra_run_args: [],
1916
+ limits: defaultSandboxLimits()
1917
+ });
1918
+ var LanguageSandboxSchema = z.object({
1919
+ mode: SandboxModeSchema.default("subprocess"),
1920
+ local: LocalSandboxSchema,
1921
+ docker: DockerSandboxSchema
1922
+ }).default(() => ({
1923
+ mode: "subprocess",
1924
+ local: { limits: defaultSandboxLimits() },
1925
+ docker: {
1926
+ image: "python:3.11-slim",
1927
+ workdir: "/workspace",
1928
+ pull: false,
1929
+ docker_bin: "docker",
1930
+ extra_run_args: [],
1931
+ limits: defaultSandboxLimits()
1932
+ }
1933
+ }));
1934
+ var SandboxesSchema = z.object({
1935
+ python: LanguageSandboxSchema.optional(),
1936
+ typescript: LanguageSandboxSchema.optional()
1937
+ }).default({});
1861
1938
  var LlmSchema = z.object({
1862
1939
  default: z.string(),
1863
1940
  providers: z.record(z.string(), ProviderSchema),
@@ -1865,7 +1942,7 @@ var LlmSchema = z.object({
1865
1942
  * 角色 → provider 数组的映射。
1866
1943
  * 兼容旧格式:单字符串 `Coder: ollama_code` 自动归一化为 `[ollama_code]`。
1867
1944
  * 数组形式 `Coder: [ollama_code, openai]` 表示该角色的候选 LLM 池;
1868
- * 实际选择顺序由 `llm.scores` 评分降序决定;用户显式设置评分=0 的 provider 直接跳过。
1945
+ * 实际选择顺序由 ScoreStore 有效评分降序决定;有效评分为用户覆盖优先,否则使用动态评分。
1869
1946
  */
1870
1947
  roles: z.record(z.string(), z.union([z.string(), z.array(z.string())])).default({}).transform((obj) => {
1871
1948
  const out = {};
@@ -1879,9 +1956,8 @@ var LlmSchema = z.object({
1879
1956
  /** 可选:按角色指定 fallback 链(覆盖全局) */
1880
1957
  role_fallbacks: z.record(z.string(), z.array(z.string())).default({}),
1881
1958
  /**
1882
- * Provider 评分快照(启动时从 sidecar 文件加载,运行时由 ScoreStore 维护并自动落盘)。
1883
- * 配置文件里也可以手工设置初始值;不存在的 provider 默认评分 = 1。
1884
- * 用户手工设置评分 = 0 表示禁用;运行时自动评分只在 0.1~1.0 范围内调整。
1959
+ * Provider 兼容初值。运行时动态评分由 ScoreStore 写入 llm_scores.yaml;
1960
+ * 用户手动覆盖应写入 llm_scores_user.yaml。旧配置里显式 0 仍表示用户禁用。
1885
1961
  */
1886
1962
  scores: z.record(z.string(), z.number().min(0)).default({}),
1887
1963
  /**
@@ -1901,65 +1977,107 @@ var LlmSchema = z.object({
1901
1977
  });
1902
1978
  }
1903
1979
  });
1980
+ var AgentSchema = z.object({
1981
+ /** @deprecated Target project language is inferred from topic/baseline. Kept for legacy configs only. */
1982
+ language: TargetLanguageSchema.optional(),
1983
+ max_steps: z.number().int().positive().default(50),
1984
+ max_debug_retries: z.number().int().positive().default(3),
1985
+ /** Debugger 滑动窗口的硬上限(默认 = max(max_debug_retries*4, 10))。 */
1986
+ max_debug_retries_cap: z.number().int().positive().optional(),
1987
+ max_rounds_per_step: z.number().int().positive().default(6),
1988
+ max_debug_rounds_per_step: z.number().int().positive().optional(),
1989
+ max_edit_lines_per_step: z.union([z.literal("auto"), z.number().int().positive()]).default("auto"),
1990
+ max_write_chunk_bytes: z.union([z.literal("auto"), z.number().int().positive()]).default("auto"),
1991
+ /** @deprecated Use agent.sandboxes.<language>.mode. */
1992
+ sandbox: SandboxModeSchema.optional(),
1993
+ /** @deprecated Use agent.sandboxes.<language>.<local|docker>.limits. */
1994
+ sandbox_limits: SandboxLimitsSchema.optional(),
1995
+ /** @deprecated Use agent.sandboxes.<language>.docker. */
1996
+ sandbox_docker: DockerSandboxSchema.optional(),
1997
+ sandboxes: SandboxesSchema
1998
+ }).transform((agent) => {
1999
+ const legacyLanguage = agent.language ?? "python";
2000
+ const legacyMode = agent.sandbox ?? "subprocess";
2001
+ const legacyLimits = agent.sandbox_limits ?? defaultSandboxLimits();
2002
+ const defaults = {
2003
+ python: defaultLanguageSandbox("python", legacyMode, legacyLimits),
2004
+ typescript: defaultLanguageSandbox("typescript", legacyMode, legacyLimits)
2005
+ };
2006
+ const sandboxes = {
2007
+ python: mergeLanguageSandbox(
2008
+ defaults.python,
2009
+ agent.sandboxes.python,
2010
+ legacyLanguage === "python" ? agent.sandbox_docker : void 0
2011
+ ),
2012
+ typescript: mergeLanguageSandbox(
2013
+ defaults.typescript,
2014
+ agent.sandboxes.typescript,
2015
+ legacyLanguage === "typescript" ? agent.sandbox_docker : void 0
2016
+ )
2017
+ };
2018
+ return {
2019
+ ...agent,
2020
+ language: legacyLanguage,
2021
+ sandbox: sandboxes.python.mode,
2022
+ sandbox_limits: sandboxes.python.local.limits,
2023
+ sandbox_docker: sandboxes.python.docker,
2024
+ sandboxes
2025
+ };
2026
+ });
1904
2027
  var ConfigSchema = z.object({
1905
2028
  /** CLI / prompt locale. Accepts 'en' (default) or 'zh'. */
1906
2029
  locale: LocaleSchema.optional(),
1907
2030
  /** @deprecated use `locale` instead. Kept as a backwards-compatible alias. */
1908
2031
  ui_language: LocaleSchema.optional(),
1909
2032
  llm: LlmSchema,
1910
- agent: z.object({
1911
- language: z.enum(["python", "typescript"]).default("python"),
1912
- max_steps: z.number().int().positive().default(50),
1913
- max_debug_retries: z.number().int().positive().default(3),
1914
- /** Debugger 滑动窗口的硬上限(默认 = max(max_debug_retries*4, 10))。 */
1915
- max_debug_retries_cap: z.number().int().positive().optional(),
1916
- max_rounds_per_step: z.number().int().positive().default(6),
1917
- max_debug_rounds_per_step: z.number().int().positive().optional(),
1918
- max_edit_lines_per_step: z.union([z.literal("auto"), z.number().int().positive()]).default("auto"),
1919
- max_write_chunk_bytes: z.union([z.literal("auto"), z.number().int().positive()]).default("auto"),
1920
- sandbox: z.enum(["subprocess", "docker", "firejail"]).default("subprocess"),
1921
- sandbox_limits: z.object({
1922
- cpu: z.number().positive().default(1),
1923
- memory_mb: z.number().int().positive().default(1024),
1924
- wall_seconds: z.number().int().positive().default(60),
1925
- /**
1926
- * Sandbox network policy.
1927
- * - `off` — no network at all (`docker --network none`).
1928
- * - `download-only` — outbound traffic allowed, no inbound port publishing
1929
- * (default; lets python pip-install / fetch web pages).
1930
- * - `pypi-only` — legacy value; rejected at sandbox creation rather than silently
1931
- * allowing unrestricted outbound traffic.
1932
- * - `full` — outbound + every port in `expose_ports` is published
1933
- * to `127.0.0.1` so host-side tests can reach the app.
1934
- */
1935
- network: z.enum(["off", "pypi-only", "download-only", "full"]).default("download-only"),
1936
- /** Container ports to publish to 127.0.0.1 when `network=full`. */
1937
- expose_ports: z.array(z.number().int().min(1).max(65535)).default([])
1938
- }).default({
1939
- cpu: 1,
1940
- memory_mb: 1024,
1941
- wall_seconds: 60,
1942
- network: "download-only",
1943
- expose_ports: []
1944
- }),
1945
- sandbox_docker: z.object({
1946
- image: z.string().default("python:3.11-slim"),
1947
- workdir: z.string().default("/workspace"),
1948
- pull: z.boolean().default(false),
1949
- docker_bin: z.string().default("docker"),
1950
- extra_run_args: z.array(z.string()).default([])
1951
- }).default({
1952
- image: "python:3.11-slim",
1953
- workdir: "/workspace",
1954
- pull: false,
1955
- docker_bin: "docker",
1956
- extra_run_args: []
1957
- })
1958
- })
2033
+ agent: AgentSchema
1959
2034
  }).transform(({ locale, ui_language, ...rest }) => ({
1960
2035
  locale: locale ?? ui_language ?? "en",
1961
2036
  ...rest
1962
2037
  }));
2038
+ function defaultSandboxLimits() {
2039
+ return {
2040
+ cpu: 1,
2041
+ memory_mb: 1024,
2042
+ wall_seconds: 60,
2043
+ network: "download-only",
2044
+ expose_ports: []
2045
+ };
2046
+ }
2047
+ function defaultLanguageSandbox(language, mode, limits) {
2048
+ return {
2049
+ mode,
2050
+ local: {
2051
+ sandbox_dir: `.sandbox/${language}`,
2052
+ limits: { ...limits, expose_ports: [...limits.expose_ports ?? []] }
2053
+ },
2054
+ docker: {
2055
+ image: language === "typescript" ? "node:24-slim" : "python:3.11-slim",
2056
+ workdir: "/workspace",
2057
+ pull: false,
2058
+ docker_bin: "docker",
2059
+ extra_run_args: [],
2060
+ sandbox_dir: `.sandbox/${language}`,
2061
+ limits: { ...limits, expose_ports: [...limits.expose_ports ?? []] }
2062
+ }
2063
+ };
2064
+ }
2065
+ function mergeLanguageSandbox(defaults, override, legacyDocker) {
2066
+ const dockerOverride = legacyDocker ?? override?.docker;
2067
+ return {
2068
+ mode: override?.mode ?? defaults.mode,
2069
+ local: {
2070
+ ...defaults.local,
2071
+ ...override?.local ?? {},
2072
+ limits: override?.local?.limits ?? defaults.local.limits
2073
+ },
2074
+ docker: {
2075
+ ...defaults.docker,
2076
+ ...dockerOverride ?? {},
2077
+ limits: dockerOverride?.limits ?? defaults.docker.limits
2078
+ }
2079
+ };
2080
+ }
1963
2081
  function getXCompilerPath() {
1964
2082
  const env = xcEnv("PATH");
1965
2083
  if (env && env.trim()) return path.resolve(env.trim());
@@ -2023,6 +2141,11 @@ var LOOP_REPEATS = 12;
2023
2141
  var LOOP_MIN_LEN = 1500;
2024
2142
  var LOOP_MIN_WINDOW = 96;
2025
2143
  var LOOP_MAX_PERIOD = 256;
2144
+ var TEXT_LOOP_MIN_LEN = 6e3;
2145
+ var TEXT_LOOP_TAIL = 14e3;
2146
+ var TEXT_LOOP_SAMPLE_LEN = 48;
2147
+ var TEXT_LOOP_STRIDE = 8;
2148
+ var TEXT_LOOP_REPEATS = 5;
2026
2149
  function detectCyclicTokenLoop(aggregate) {
2027
2150
  if (aggregate.length < LOOP_MIN_LEN) return false;
2028
2151
  const maxPeriod = Math.min(LOOP_MAX_PERIOD, Math.floor(aggregate.length / LOOP_REPEATS));
@@ -2044,6 +2167,35 @@ function detectCyclicTokenLoop(aggregate) {
2044
2167
  }
2045
2168
  return false;
2046
2169
  }
2170
+ function detectRepeatedTextLoop(aggregate) {
2171
+ if (aggregate.length < TEXT_LOOP_MIN_LEN) return false;
2172
+ const tail = aggregate.slice(-TEXT_LOOP_TAIL).replace(/\s+/gu, " ").trim();
2173
+ if (tail.length < TEXT_LOOP_MIN_LEN) return false;
2174
+ const seen = /* @__PURE__ */ new Map();
2175
+ for (let i = 0; i + TEXT_LOOP_SAMPLE_LEN <= tail.length; i += TEXT_LOOP_STRIDE) {
2176
+ const sample = tail.slice(i, i + TEXT_LOOP_SAMPLE_LEN);
2177
+ if (!isHighSignalLoopSample(sample)) continue;
2178
+ const prior = seen.get(sample);
2179
+ if (!prior) {
2180
+ seen.set(sample, { count: 1, first: i, last: i });
2181
+ continue;
2182
+ }
2183
+ const next = { count: prior.count + 1, first: prior.first, last: i };
2184
+ seen.set(sample, next);
2185
+ if (next.count >= TEXT_LOOP_REPEATS && next.last - next.first >= TEXT_LOOP_SAMPLE_LEN * 3) {
2186
+ return true;
2187
+ }
2188
+ }
2189
+ return false;
2190
+ }
2191
+ function isHighSignalLoopSample(sample) {
2192
+ const chars = [...sample];
2193
+ const signalChars = chars.filter((ch) => /[\p{L}\p{N}]/u.test(ch));
2194
+ if (signalChars.length < 20) return false;
2195
+ if (signalChars.length / Math.max(1, chars.length) < 0.4) return false;
2196
+ const unique = new Set(signalChars.map((ch) => ch.toLowerCase())).size;
2197
+ return unique >= 10;
2198
+ }
2047
2199
  var RepeatTokenDetector = class {
2048
2200
  constructor(threshold = 40) {
2049
2201
  this.threshold = threshold;
@@ -2222,6 +2374,12 @@ function streamPostNdjson(url, body, watchdog, onLine, shouldStopWhen) {
2222
2374
  );
2223
2375
  return;
2224
2376
  }
2377
+ if (detectRepeatedTextLoop(aggregate)) {
2378
+ fail(
2379
+ new Error("detected repeated text loop in stream (semantic tail repetition); aborting")
2380
+ );
2381
+ return;
2382
+ }
2225
2383
  }
2226
2384
  if (obj.error) {
2227
2385
  fail(new Error(`Ollama error: ${obj.error}`));
@@ -2250,6 +2408,10 @@ function streamPostNdjson(url, body, watchdog, onLine, shouldStopWhen) {
2250
2408
  fail(new Error("detected token loop in stream; aborting"));
2251
2409
  return;
2252
2410
  }
2411
+ if (detectRepeatedTextLoop(aggregate)) {
2412
+ fail(new Error("detected repeated text loop in stream; aborting"));
2413
+ return;
2414
+ }
2253
2415
  }
2254
2416
  });
2255
2417
  res.on("end", () => {
@@ -2520,6 +2682,9 @@ var OpenAIClient = class {
2520
2682
  if (detectCyclicTokenLoop(aggregate)) {
2521
2683
  throw new Error("detected cyclic token loop in OpenAI stream (periodic tail); aborting");
2522
2684
  }
2685
+ if (detectRepeatedTextLoop(aggregate)) {
2686
+ throw new Error("detected repeated text loop in OpenAI stream (semantic tail repetition); aborting");
2687
+ }
2523
2688
  if (maxOutputChars > 0 && expectsJsonObject && aggregate.length > maxOutputChars && hasInvalidJsonPrefix(aggregate)) {
2524
2689
  throw new Error(`OpenAI stream exceeded ${maxOutputChars} chars without a valid JSON prefix; aborting`);
2525
2690
  }
@@ -3060,18 +3225,24 @@ import { promises as fs2 } from "fs";
3060
3225
  import path2 from "path";
3061
3226
  import YAML2 from "yaml";
3062
3227
  var CLUSTER_PROVIDER_TAG = "cluster";
3228
+ var LLM_DYNAMIC_SCORES_FILE = "llm_scores.yaml";
3229
+ var LLM_USER_SCORES_FILE = "llm_scores_user.yaml";
3063
3230
  var ScoreStore = class _ScoreStore {
3064
3231
  constructor(configPath, initial = {}, audit, opts = {}) {
3065
3232
  this.audit = audit;
3066
- this.sidecarPath = path2.join(path2.dirname(configPath), "llm_scores.yaml");
3233
+ const configDir = path2.dirname(configPath);
3234
+ this.sidecarPath = path2.join(configDir, LLM_DYNAMIC_SCORES_FILE);
3235
+ this.userScoresPath = path2.join(configDir, LLM_USER_SCORES_FILE);
3067
3236
  this.clusterProviders = new Set(opts.clusterProviderNames ?? []);
3068
3237
  const bounds = normalizeClusterBounds(opts.clusterScoreMin, opts.clusterScoreMax);
3069
3238
  this.clusterMin = bounds.min;
3070
3239
  this.clusterMax = bounds.max;
3071
3240
  for (const [k, v] of Object.entries(initial)) {
3072
- const score = this.clampConfigured(k, v);
3073
- this.scores.set(k, score);
3074
- if (score === _ScoreStore.DISABLED) this.userDisabled.add(k);
3241
+ if (v === _ScoreStore.DISABLED) {
3242
+ this.userScores.set(k, _ScoreStore.DISABLED);
3243
+ } else {
3244
+ this.dynamicScores.set(k, this.clampDynamic(k, v));
3245
+ }
3075
3246
  }
3076
3247
  }
3077
3248
  audit;
@@ -3083,24 +3254,28 @@ var ScoreStore = class _ScoreStore {
3083
3254
  static CLUSTER_MAX = 0.5;
3084
3255
  static DECAY = 0.5;
3085
3256
  static BOOST = 0.1;
3086
- scores = /* @__PURE__ */ new Map();
3087
- userDisabled = /* @__PURE__ */ new Set();
3257
+ dynamicScores = /* @__PURE__ */ new Map();
3258
+ userScores = /* @__PURE__ */ new Map();
3088
3259
  clusterProviders;
3089
3260
  clusterMin;
3090
3261
  clusterMax;
3091
3262
  dirty = false;
3092
3263
  writeQueue = Promise.resolve();
3093
3264
  sidecarPath;
3094
- /** 异步加载 sidecar 文件;失败/不存在不抛错,使用 ctor 提供的初值。 */
3265
+ userScoresPath;
3266
+ /** 异步加载动态 sidecar 与用户覆盖文件;失败/不存在不抛错,使用 ctor 提供的初值。 */
3095
3267
  async load() {
3268
+ await this.loadDynamicScores();
3269
+ await this.loadUserScores();
3270
+ }
3271
+ async loadDynamicScores() {
3096
3272
  try {
3097
3273
  const raw = await fs2.readFile(this.sidecarPath, "utf8");
3098
3274
  const parsed = YAML2.parse(raw);
3099
3275
  if (parsed && typeof parsed === "object") {
3100
3276
  for (const [k, v] of Object.entries(parsed)) {
3101
3277
  if (typeof v === "number" && Number.isFinite(v)) {
3102
- if (this.userDisabled.has(k)) continue;
3103
- this.scores.set(k, this.clampDynamic(k, v));
3278
+ this.dynamicScores.set(k, this.clampPersistedDynamic(k, v));
3104
3279
  }
3105
3280
  }
3106
3281
  }
@@ -3113,44 +3288,73 @@ var ScoreStore = class _ScoreStore {
3113
3288
  }
3114
3289
  }
3115
3290
  }
3291
+ async loadUserScores() {
3292
+ try {
3293
+ const raw = await fs2.readFile(this.userScoresPath, "utf8");
3294
+ const parsed = YAML2.parse(raw);
3295
+ if (parsed && typeof parsed === "object") {
3296
+ for (const [k, v] of Object.entries(parsed)) {
3297
+ if (typeof v === "number" && Number.isFinite(v)) {
3298
+ this.userScores.set(k, this.clampUserScore(v));
3299
+ }
3300
+ }
3301
+ }
3302
+ } catch (err) {
3303
+ if (err.code !== "ENOENT") {
3304
+ await this.audit?.event("llm.error", t().llm.scoreReadFailed(this.userScoresPath, err.message), {
3305
+ messageId: "llm.user_score_read_failed",
3306
+ sidecarPath: this.userScoresPath
3307
+ });
3308
+ }
3309
+ }
3310
+ }
3116
3311
  get(name) {
3117
- return this.scores.has(name) ? this.scores.get(name) : this.defaultFor(name);
3312
+ if (this.userScores.has(name)) return this.userScores.get(name);
3313
+ return this.dynamicScores.has(name) ? this.dynamicScores.get(name) : this.defaultFor(name);
3118
3314
  }
3119
- /** 主动设置评分;value=0 仅用于显式禁用,其它值会归一到动态范围。 */
3315
+ /** 主动设置动态评分;用户覆盖只从配置或 llm_scores_user.yaml 读取。 */
3120
3316
  set(name, value, reason) {
3121
- if (this.userDisabled.has(name) && value !== _ScoreStore.DISABLED) return;
3122
3317
  const v = value === _ScoreStore.DISABLED ? _ScoreStore.DISABLED : this.clampDynamic(name, value);
3123
- const prev = this.scores.get(name);
3318
+ const prev = this.dynamicScores.get(name);
3124
3319
  if (prev === v) return;
3125
- this.scores.set(name, v);
3320
+ this.dynamicScores.set(name, v);
3126
3321
  this.dirty = true;
3127
- void this.audit?.event("llm.score", t().llm.scoreChanged(name, v.toFixed(2), (prev ?? _ScoreStore.DEFAULT).toFixed(2)), {
3322
+ void this.audit?.event("llm.score", t().llm.scoreChanged(name, v.toFixed(2), (prev ?? this.defaultFor(name)).toFixed(2)), {
3128
3323
  messageId: "llm.score_changed",
3129
3324
  provider: name,
3130
3325
  score: v,
3131
- previous: prev ?? _ScoreStore.DEFAULT,
3326
+ previous: prev ?? this.defaultFor(name),
3132
3327
  reason: reason ?? "set"
3133
3328
  });
3134
3329
  this.scheduleSave();
3135
3330
  }
3136
3331
  decay(name, reason) {
3137
- if (this.get(name) === _ScoreStore.DISABLED) return;
3138
- this.set(name, Math.max(this.minFor(name), this.get(name) - _ScoreStore.DECAY), reason);
3332
+ if (this.isUserDisabled(name)) return;
3333
+ const cur = this.dynamicScore(name);
3334
+ if (cur === _ScoreStore.DISABLED) return;
3335
+ this.set(name, Math.max(this.minFor(name), cur - _ScoreStore.DECAY), reason);
3139
3336
  }
3140
3337
  boost(name, reason) {
3141
- const cur = this.get(name);
3338
+ if (this.isUserDisabled(name)) return;
3339
+ const cur = this.dynamicScore(name);
3142
3340
  if (cur === _ScoreStore.DISABLED) return;
3143
3341
  if (cur >= this.maxFor(name)) return;
3144
3342
  this.set(name, Math.min(this.maxFor(name), cur + _ScoreStore.BOOST), reason);
3145
3343
  }
3146
- /** True only for providers explicitly disabled by user config (`llm.scores.<provider>: 0`). */
3344
+ /** True only for providers explicitly disabled by user config or llm_scores_user.yaml. */
3147
3345
  isUserDisabled(name) {
3148
- return this.userDisabled.has(name);
3346
+ return this.userScores.get(name) === _ScoreStore.DISABLED;
3149
3347
  }
3150
- /** 全量快照(用于测试与 audit 输出)。 */
3348
+ /** 动态评分快照(用于持久化、测试与 audit 输出;不包含用户覆盖)。 */
3151
3349
  snapshot() {
3152
3350
  const out = {};
3153
- for (const [k, v] of this.scores) out[k] = v;
3351
+ for (const [k, v] of this.dynamicScores) out[k] = v;
3352
+ return out;
3353
+ }
3354
+ /** 用户覆盖快照(用于测试/诊断;运行时不会改写用户文件)。 */
3355
+ userSnapshot() {
3356
+ const out = {};
3357
+ for (const [k, v] of this.userScores) out[k] = v;
3154
3358
  return out;
3155
3359
  }
3156
3360
  /** 等待待写入完成(CLI 退出前调用,确保评分已落盘)。 */
@@ -3188,10 +3392,8 @@ ${t().llm.scoreFileSemantics}
3188
3392
  isClusterProvider(name) {
3189
3393
  return this.clusterProviders.has(name);
3190
3394
  }
3191
- clampConfigured(name, v) {
3192
- if (!Number.isFinite(v)) return this.defaultFor(name);
3193
- if (v === _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
3194
- return this.clampDynamic(name, v);
3395
+ dynamicScore(name) {
3396
+ return this.dynamicScores.has(name) ? this.dynamicScores.get(name) : this.defaultFor(name);
3195
3397
  }
3196
3398
  clampDynamic(name, v) {
3197
3399
  if (!Number.isFinite(v)) return this.defaultFor(name);
@@ -3200,6 +3402,18 @@ ${t().llm.scoreFileSemantics}
3200
3402
  if (v > this.maxFor(name)) return this.maxFor(name);
3201
3403
  return v;
3202
3404
  }
3405
+ clampPersistedDynamic(name, v) {
3406
+ if (!Number.isFinite(v)) return this.defaultFor(name);
3407
+ if (v === _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
3408
+ return this.clampDynamic(name, v);
3409
+ }
3410
+ clampUserScore(v) {
3411
+ if (!Number.isFinite(v)) return _ScoreStore.DEFAULT;
3412
+ if (v <= _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
3413
+ if (v < _ScoreStore.MIN) return _ScoreStore.MIN;
3414
+ if (v > _ScoreStore.MAX) return _ScoreStore.MAX;
3415
+ return v;
3416
+ }
3203
3417
  };
3204
3418
  function normalizeClusterBounds(rawMin, rawMax) {
3205
3419
  const min = clampBound(rawMin ?? ScoreStore.CLUSTER_MIN);
@@ -3618,6 +3832,9 @@ var PlanSchema = z2.object({
3618
3832
  dependencies: dependencies ?? pythonRequirements ?? []
3619
3833
  }));
3620
3834
 
3835
+ // src/core/language.ts
3836
+ import { promises as fs5 } from "fs";
3837
+
3621
3838
  // src/core/entry_gate.ts
3622
3839
  import path5 from "path";
3623
3840
 
@@ -3644,8 +3861,10 @@ function detectNetworkApiFailure(text) {
3644
3861
  function isTestAssertionDiagnosticLine(line) {
3645
3862
  const text = line.trim();
3646
3863
  if (!text) return false;
3864
+ if (/^\d+\|\s/u.test(text)) return true;
3647
3865
  if (/^(?:[→>-]\s*)?expected\b/iu.test(text)) return true;
3648
3866
  if (/\bAssertionError\b/iu.test(text) && /\bexpected\b/iu.test(text)) return true;
3867
+ if (/\bexpect\s*\(/u.test(text) && /\.(?:to|not)\w*\s*\(/u.test(text)) return true;
3649
3868
  return /\bexpected\b[\s\S]{0,240}\b(?:got|received|to\s+(?:be|equal|throw|contain|have|match))\b/iu.test(text);
3650
3869
  }
3651
3870
  function isTestRunnerStatusLine(line) {
@@ -3692,8 +3911,8 @@ async function autoFixSrcImports(ws, audit) {
3692
3911
  const candidates = [];
3693
3912
  if (await ws.exists("src/main.py")) candidates.push("src/main.py");
3694
3913
  try {
3695
- const fs27 = await import("fs/promises");
3696
- const entries = await fs27.readdir(ws.abs("src"), { withFileTypes: true });
3914
+ const fs29 = await import("fs/promises");
3915
+ const entries = await fs29.readdir(ws.abs("src"), { withFileTypes: true });
3697
3916
  for (const e of entries) {
3698
3917
  if (!e.isDirectory()) continue;
3699
3918
  const rel = `src/${e.name}/__main__.py`;
@@ -3844,8 +4063,8 @@ async function detectPythonEntrypoint(ws) {
3844
4063
  if (await ws.exists("src/main.py")) return fileCandidate("src/main.py");
3845
4064
  if (await ws.exists("src/__main__.py")) return fileCandidate("src/__main__.py");
3846
4065
  try {
3847
- const fs27 = await import("fs/promises");
3848
- const entries = await fs27.readdir(ws.abs("src"), { withFileTypes: true });
4066
+ const fs29 = await import("fs/promises");
4067
+ const entries = await fs29.readdir(ws.abs("src"), { withFileTypes: true });
3849
4068
  for (const e of entries) {
3850
4069
  if (!e.isDirectory()) continue;
3851
4070
  const rel = `src/${e.name}/__main__.py`;
@@ -3904,7 +4123,7 @@ var typescriptProfile = {
3904
4123
  manifestFile: "package.json",
3905
4124
  codeExtensions: [".ts", ".tsx"],
3906
4125
  seedManifestFromDeps: false,
3907
- defaultDockerImage: "node:20-slim",
4126
+ defaultDockerImage: "node:24-slim",
3908
4127
  renderManifest(deps) {
3909
4128
  const pkg = {
3910
4129
  name: "app",
@@ -3933,6 +4152,9 @@ var typescriptProfile = {
3933
4152
  },
3934
4153
  plannerPromptOverride: "",
3935
4154
  executorPromptOverride: "",
4155
+ async autoFixImports(ws, audit) {
4156
+ return autoFixTypeScriptTypeOnlyImports(ws, audit);
4157
+ },
3936
4158
  async probeEntry(ws, sandbox) {
3937
4159
  return probeTsEntrypoint(ws, sandbox);
3938
4160
  }
@@ -4082,6 +4304,99 @@ async function readJsonFile(ws, rel) {
4082
4304
  return null;
4083
4305
  }
4084
4306
  }
4307
+ var TYPE_ONLY_IMPORTS_BY_PACKAGE = {
4308
+ axios: /* @__PURE__ */ new Set([
4309
+ "AxiosAdapter",
4310
+ "AxiosBasicCredentials",
4311
+ "AxiosHeaderValue",
4312
+ "AxiosInstance",
4313
+ "AxiosInterceptorManager",
4314
+ "AxiosPromise",
4315
+ "AxiosProxyConfig",
4316
+ "AxiosRequestConfig",
4317
+ "AxiosRequestHeaders",
4318
+ "AxiosResponse",
4319
+ "AxiosResponseHeaders",
4320
+ "CreateAxiosDefaults",
4321
+ "InternalAxiosRequestConfig",
4322
+ "RawAxiosRequestHeaders"
4323
+ ])
4324
+ };
4325
+ async function autoFixTypeScriptTypeOnlyImports(ws, audit) {
4326
+ const files = await listTypeScriptSourceFiles(ws, "src");
4327
+ const fixed = [];
4328
+ for (const rel of files) {
4329
+ const original = await ws.readFile(rel);
4330
+ const next = rewriteKnownTypeOnlyImports(original);
4331
+ if (next === original) continue;
4332
+ await ws.writeFile(rel, next);
4333
+ fixed.push(rel);
4334
+ await audit.event("note", `fixed type-only imports in ${rel}`, {
4335
+ messageId: "audit.typescript_imports_autofix",
4336
+ path: rel
4337
+ });
4338
+ }
4339
+ return fixed;
4340
+ }
4341
+ async function listTypeScriptSourceFiles(ws, dir) {
4342
+ const abs = ws.abs(dir);
4343
+ let entries;
4344
+ try {
4345
+ entries = await fs5.readdir(abs, { withFileTypes: true });
4346
+ } catch {
4347
+ return [];
4348
+ }
4349
+ const files = [];
4350
+ for (const entry of entries) {
4351
+ const rel = `${dir}/${entry.name}`.replace(/\\/g, "/");
4352
+ if (entry.isDirectory()) {
4353
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
4354
+ files.push(...await listTypeScriptSourceFiles(ws, rel));
4355
+ } else if (entry.isFile() && /\.tsx?$/.test(entry.name)) {
4356
+ files.push(rel);
4357
+ }
4358
+ }
4359
+ return files.sort();
4360
+ }
4361
+ function rewriteKnownTypeOnlyImports(source) {
4362
+ const importRe = /^import\s+([^'"\n]+?)\s+from\s+(['"])([^'"]+)\2\s*;?$/gm;
4363
+ return source.replace(importRe, (full, clauseRaw, quote, specifier) => {
4364
+ const knownTypes = TYPE_ONLY_IMPORTS_BY_PACKAGE[specifier];
4365
+ if (!knownTypes) return full;
4366
+ if (clauseRaw.trim().startsWith("type ")) return full;
4367
+ const parsed = splitImportClause(clauseRaw);
4368
+ if (!parsed?.named.length) return full;
4369
+ const valueNamed = [];
4370
+ const typeNamed = [];
4371
+ for (const item of parsed.named) {
4372
+ const importedName = item.replace(/^type\s+/u, "").split(/\s+as\s+/iu)[0]?.trim() ?? "";
4373
+ if (knownTypes.has(importedName)) typeNamed.push(item.replace(/^type\s+/u, "").trim());
4374
+ else valueNamed.push(item);
4375
+ }
4376
+ if (typeNamed.length === 0) return full;
4377
+ const lines = [];
4378
+ if (parsed.defaultImport && valueNamed.length > 0) {
4379
+ lines.push(`import ${parsed.defaultImport}, { ${valueNamed.join(", ")} } from ${quote}${specifier}${quote};`);
4380
+ } else if (parsed.defaultImport) {
4381
+ lines.push(`import ${parsed.defaultImport} from ${quote}${specifier}${quote};`);
4382
+ } else if (valueNamed.length > 0) {
4383
+ lines.push(`import { ${valueNamed.join(", ")} } from ${quote}${specifier}${quote};`);
4384
+ }
4385
+ lines.push(`import type { ${typeNamed.join(", ")} } from ${quote}${specifier}${quote};`);
4386
+ return lines.join("\n");
4387
+ });
4388
+ }
4389
+ function splitImportClause(clauseRaw) {
4390
+ const clause = clauseRaw.trim();
4391
+ const namedMatch = clause.match(/\{([\s\S]*)\}$/u);
4392
+ if (!namedMatch) return void 0;
4393
+ const beforeNamed = clause.slice(0, namedMatch.index).replace(/,\s*$/u, "").trim();
4394
+ const named = (namedMatch[1] ?? "").split(",").map((item) => item.trim()).filter(Boolean);
4395
+ return {
4396
+ defaultImport: beforeNamed || void 0,
4397
+ named
4398
+ };
4399
+ }
4085
4400
 
4086
4401
  // src/core/architecture.ts
4087
4402
  var COMPLEXITY_SURFACES = [
@@ -4273,7 +4588,7 @@ function validateArchitectureContract(modules, steps, language, demand) {
4273
4588
  }
4274
4589
  allTestPaths.add(testPath);
4275
4590
  }
4276
- const owners = codeSteps.filter((step) => module.sourcePaths.every((path30) => pathCoveredByOutputs(path30, step.outputs))).sort((a, b) => (stepIndex.get(a.id) ?? 0) - (stepIndex.get(b.id) ?? 0));
4591
+ const owners = codeSteps.filter((step) => module.sourcePaths.every((path31) => pathCoveredByOutputs(path31, step.outputs))).sort((a, b) => (stepIndex.get(a.id) ?? 0) - (stepIndex.get(b.id) ?? 0));
4277
4592
  if (owners.length === 0) {
4278
4593
  issues.push({
4279
4594
  message: `${module.id} must map all sourcePaths to a CODE macro step; found 0.`
@@ -4303,7 +4618,7 @@ function validateArchitectureContract(modules, steps, language, demand) {
4303
4618
  ownedModules.push(module);
4304
4619
  modulesByCodeOwner.set(codeOwner.id, ownedModules);
4305
4620
  const matchingTests = testSteps.filter(
4306
- (step) => module.testPaths.some((path30) => pathCoveredByOutputs(path30, step.outputs))
4621
+ (step) => module.testPaths.some((path31) => pathCoveredByOutputs(path31, step.outputs))
4307
4622
  );
4308
4623
  if (matchingTests.length === 0) {
4309
4624
  issues.push({ message: `${module.id} testPaths are not produced by any MODULE_TEST step.` });
@@ -4360,21 +4675,21 @@ function flattenSubTasks(step) {
4360
4675
  }
4361
4676
  return out;
4362
4677
  }
4363
- function pathCoveredByOutputs(path30, outputs) {
4364
- const normalizedPath = normalizePath(path30);
4678
+ function pathCoveredByOutputs(path31, outputs) {
4679
+ const normalizedPath = normalizePath(path31);
4365
4680
  return outputs.some((output) => {
4366
4681
  const normalizedOutput = normalizePath(output);
4367
4682
  return normalizedPath === normalizedOutput || normalizedPath.startsWith(`${normalizedOutput}/`);
4368
4683
  });
4369
4684
  }
4370
- function isSourcePath(path30, extensions) {
4371
- return path30.startsWith("src/") && extensions.some((extension) => path30.endsWith(extension));
4685
+ function isSourcePath(path31, extensions) {
4686
+ return path31.startsWith("src/") && extensions.some((extension) => path31.endsWith(extension));
4372
4687
  }
4373
- function isTestPath(path30, extensions) {
4374
- return path30.startsWith("tests/") && extensions.some((extension) => path30.endsWith(extension));
4688
+ function isTestPath(path31, extensions) {
4689
+ return path31.startsWith("tests/") && extensions.some((extension) => path31.endsWith(extension));
4375
4690
  }
4376
- function normalizePath(path30) {
4377
- return path30.replace(/\\/g, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
4691
+ function normalizePath(path31) {
4692
+ return path31.replace(/\\/g, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
4378
4693
  }
4379
4694
  function transitivelyDependsOn(step, targetId, byId) {
4380
4695
  const seen = /* @__PURE__ */ new Set();
@@ -4666,6 +4981,18 @@ var DOC_PATH_ALIASES = {
4666
4981
  "docs/integration-test.md": DOC_NAMES.integrationTest,
4667
4982
  "docs/module-test.md": DOC_NAMES.moduleTest,
4668
4983
  "docs/functional-test.md": DOC_NAMES.functionalTest,
4984
+ "docs/unit-test-plan.md": DOC_NAMES.unitTestPlan,
4985
+ "docs/unit_test_plan.md": DOC_NAMES.unitTestPlan,
4986
+ "docs/tests/unit_test_plan.md": DOC_NAMES.unitTestPlan,
4987
+ "docs/integration-test-plan.md": DOC_NAMES.integrationTestPlan,
4988
+ "docs/integration_test_plan.md": DOC_NAMES.integrationTestPlan,
4989
+ "docs/tests/integration_test_plan.md": DOC_NAMES.integrationTestPlan,
4990
+ "docs/module-test-plan.md": DOC_NAMES.moduleTestPlan,
4991
+ "docs/module_test_plan.md": DOC_NAMES.moduleTestPlan,
4992
+ "docs/tests/module_test_plan.md": DOC_NAMES.moduleTestPlan,
4993
+ "docs/functional-test-plan.md": DOC_NAMES.functionalTestPlan,
4994
+ "docs/functional_test_plan.md": DOC_NAMES.functionalTestPlan,
4995
+ "docs/tests/functional_test_plan.md": DOC_NAMES.functionalTestPlan,
4669
4996
  "docs/quick-start.md": DOC_NAMES.quickstart,
4670
4997
  "docs/quick_start.md": DOC_NAMES.quickstart,
4671
4998
  "docs/quickstart.md": DOC_NAMES.quickstart,
@@ -4673,13 +5000,25 @@ var DOC_PATH_ALIASES = {
4673
5000
  "docs/api_guide.md": DOC_NAMES.apiGuide,
4674
5001
  "docs/api-guide.md": DOC_NAMES.apiGuide
4675
5002
  };
5003
+ function canonicalTestPlanAlias(path31) {
5004
+ const normalized = path31.replace(/\\/g, "/");
5005
+ const match = normalized.match(
5006
+ /^docs\/(?:tests\/)?(?:\d{1,2}[-_])?(functional|integration|module|unit)[-_]?test[-_]?plan\.md$/iu
5007
+ );
5008
+ const kind = match?.[1]?.toLowerCase();
5009
+ if (kind === "functional") return DOC_NAMES.functionalTestPlan;
5010
+ if (kind === "integration") return DOC_NAMES.integrationTestPlan;
5011
+ if (kind === "module") return DOC_NAMES.moduleTestPlan;
5012
+ if (kind === "unit") return DOC_NAMES.unitTestPlan;
5013
+ return void 0;
5014
+ }
4676
5015
  function calibrateDocPaths(steps, projectType = "application") {
4677
- const remap = (p) => DOC_PATH_ALIASES[p] ?? p;
5016
+ const remap = (p) => DOC_PATH_ALIASES[p] ?? canonicalTestPlanAlias(p) ?? p;
4678
5017
  const dropTopic = (p) => p !== DOC_NAMES.topic;
4679
5018
  return steps.map((s) => {
4680
5019
  const iterationId = s.iterationId ?? "P1";
4681
- const inputs = (s.inputs ?? []).map((p) => iterationScopedInput(remap(p), s.phase, iterationId));
4682
- let outputs = (s.outputs ?? []).map((p) => iterationScopedDoc(remap(p), s.phase, iterationId)).filter(dropTopic);
5020
+ const inputs = dedup((s.inputs ?? []).map((p) => iterationScopedInput(remap(p), s.phase, iterationId)));
5021
+ let outputs = dedup((s.outputs ?? []).map((p) => iterationScopedDoc(remap(p), s.phase, iterationId)).filter(dropTopic));
4683
5022
  outputs = outputs.filter((out) => {
4684
5023
  const ownerPhase = testPlanOwnerPhase(out, iterationId);
4685
5024
  return !ownerPhase || ownerPhase === s.phase;
@@ -4723,6 +5062,31 @@ function calibrateVModelDependencies(steps) {
4723
5062
  }
4724
5063
  return out;
4725
5064
  }
5065
+ function calibrateArchitectureModuleDependencies(modules, dependencies) {
5066
+ const architectureModules = (modules ?? []).map((module) => ({
5067
+ ...module,
5068
+ dependencies: [...module.dependencies ?? []]
5069
+ }));
5070
+ const moduleIds = new Set(architectureModules.map((module) => module.id));
5071
+ const projectDependencies = [...dependencies ?? []];
5072
+ for (const module of architectureModules) {
5073
+ const internalDependencies = [];
5074
+ for (const rawDependency of module.dependencies) {
5075
+ const dependency = String(rawDependency ?? "").trim();
5076
+ if (!dependency) continue;
5077
+ if (moduleIds.has(dependency) || /^M\d{3,}$/u.test(dependency)) {
5078
+ internalDependencies.push(dependency);
5079
+ } else {
5080
+ projectDependencies.push(dependency);
5081
+ }
5082
+ }
5083
+ module.dependencies = dedup(internalDependencies);
5084
+ }
5085
+ return {
5086
+ architectureModules,
5087
+ dependencies: dedup(projectDependencies.map((dependency) => dependency.trim()).filter(Boolean))
5088
+ };
5089
+ }
4726
5090
  function calibrateLanguageStepOwnership(steps, args) {
4727
5091
  const profile = getLanguageProfile(args.language);
4728
5092
  const out = steps.map((step) => ({
@@ -4784,35 +5148,35 @@ function preferredTestOwnerPhase(output, modules) {
4784
5148
  function findIterationStep(steps, iterationId, phase) {
4785
5149
  return steps.find((step) => (step.iterationId ?? "P1") === iterationId && step.phase === phase);
4786
5150
  }
4787
- function testPlanOwnerPhase(path30, iterationId) {
5151
+ function testPlanOwnerPhase(path31, iterationId) {
4788
5152
  for (const [sourcePhase, testPhase] of Object.entries(V_MODEL_SOURCE_TO_TEST_PHASE)) {
4789
- if (path30 === testPlanDocForIteration(testPhase, iterationId)) {
5153
+ if (path31 === testPlanDocForIteration(testPhase, iterationId)) {
4790
5154
  return sourcePhase;
4791
5155
  }
4792
5156
  }
4793
5157
  return void 0;
4794
5158
  }
4795
- function iterationScopedDoc(path30, phase, iterationId) {
4796
- if (iterationId === "P1") return path30;
4797
- if (path30 === DOC_NAMES.readme && phase === "FUNCTIONAL_TEST") {
5159
+ function iterationScopedDoc(path31, phase, iterationId) {
5160
+ if (iterationId === "P1") return path31;
5161
+ if (path31 === DOC_NAMES.readme && phase === "FUNCTIONAL_TEST") {
4798
5162
  return `docs/iterations/${iterationId}/README.md`;
4799
5163
  }
4800
- if (path30 === DOC_NAMES.quickstart && phase === "FUNCTIONAL_TEST") {
5164
+ if (path31 === DOC_NAMES.quickstart && phase === "FUNCTIONAL_TEST") {
4801
5165
  return `docs/iterations/${iterationId}/quickstart.md`;
4802
5166
  }
4803
- if (path30 === DOC_NAMES.apiGuide && phase === "FUNCTIONAL_TEST") {
5167
+ if (path31 === DOC_NAMES.apiGuide && phase === "FUNCTIONAL_TEST") {
4804
5168
  return `docs/iterations/${iterationId}/api-guide.md`;
4805
5169
  }
4806
5170
  for (const [docPhase, canonical] of Object.entries(PHASE_DOC)) {
4807
- if (path30 === canonical) {
4808
- return phaseDocForIteration(docPhase, iterationId) ?? path30;
5171
+ if (path31 === canonical) {
5172
+ return phaseDocForIteration(docPhase, iterationId) ?? path31;
4809
5173
  }
4810
5174
  }
4811
- return path30;
5175
+ return path31;
4812
5176
  }
4813
- function iterationScopedInput(path30, phase, iterationId) {
4814
- if (iterationId === "P1" || phase === "REQUIREMENT_ANALYSIS") return path30;
4815
- return iterationScopedDoc(path30, phase, iterationId);
5177
+ function iterationScopedInput(path31, phase, iterationId) {
5178
+ if (iterationId === "P1" || phase === "REQUIREMENT_ANALYSIS") return path31;
5179
+ return iterationScopedDoc(path31, phase, iterationId);
4816
5180
  }
4817
5181
  function calibrateStepIds(steps) {
4818
5182
  const map = /* @__PURE__ */ new Map();
@@ -5143,8 +5507,8 @@ function dedupModules(modules) {
5143
5507
  }
5144
5508
  return out;
5145
5509
  }
5146
- function isTestImplementationPath(path30) {
5147
- return /^tests\/.+\.(?:py|ts|tsx)$/i.test(path30);
5510
+ function isTestImplementationPath(path31) {
5511
+ return /^tests\/.+\.(?:py|ts|tsx)$/i.test(path31);
5148
5512
  }
5149
5513
  function isNonModuleTestPhase(phase) {
5150
5514
  return phase === "UNIT_TEST" || phase === "INTEGRATION_TEST" || phase === "FUNCTIONAL_TEST";
@@ -5167,7 +5531,7 @@ function testPathPrefixForPhase(phase) {
5167
5531
  return "unit";
5168
5532
  }
5169
5533
  function inferUnitTestExtension(modules) {
5170
- return modules.some((module) => module.testPaths.some((path30) => /\.tsx?$/i.test(path30))) ? ".test.ts" : ".py";
5534
+ return modules.some((module) => module.testPaths.some((path31) => /\.tsx?$/i.test(path31))) ? ".test.ts" : ".py";
5171
5535
  }
5172
5536
  function collectTransitiveDependencyIds(step, stepById) {
5173
5537
  const seen = /* @__PURE__ */ new Set();
@@ -5358,7 +5722,8 @@ var Planner = class {
5358
5722
  intent: opts.intent ?? "greenfield",
5359
5723
  hasBaseline: !!opts.hasBaseline,
5360
5724
  complex: demand.nonTrivial,
5361
- projectShapeAmbiguous
5725
+ projectShapeAmbiguous,
5726
+ languageAmbiguous: !!opts.languageAmbiguous
5362
5727
  });
5363
5728
  const rep = makeStreamReporter("Planner.clarify", this.llm.name);
5364
5729
  let provider;
@@ -5382,7 +5747,8 @@ var Planner = class {
5382
5747
  // 在 provider fallback 层校验问题集质量,避免“只有两三个泛泛问题”直接进入 Gate 1。
5383
5748
  validate: (t2) => validateClarifyJson(t2, demand.nonTrivial, {
5384
5749
  projectShapeAmbiguous,
5385
- externalApiRequired
5750
+ externalApiRequired,
5751
+ languageAmbiguous: !!opts.languageAmbiguous
5386
5752
  })
5387
5753
  }
5388
5754
  );
@@ -5453,14 +5819,14 @@ ${t().prompts.plannerSelfMode}` : "")
5453
5819
  },
5454
5820
  { role: "user", content: prompt + feedback }
5455
5821
  ],
5456
- validate: (t2) => parsePhaseStepPlanJson(t2, parseContext, phasePlan, currentPhase.id)
5822
+ validate: (t2) => parsePhaseStepPlanJson(t2, parseContext, phasePlan, currentPhase)
5457
5823
  });
5458
5824
  await this.audit?.plannerThought("decompose", text, {
5459
5825
  qaCount: input2.clarifications.length,
5460
5826
  provider,
5461
5827
  phaseId: currentPhase.id
5462
5828
  });
5463
- return parsePhaseStepPlanJson(text, parseContext, phasePlan, currentPhase.id);
5829
+ return parsePhaseStepPlanJson(text, parseContext, phasePlan, currentPhase);
5464
5830
  }
5465
5831
  async chatWithStructuredValidationRetry(input2) {
5466
5832
  const maxAttempts = plannerStructuredRepairAttemptLimit(input2.context);
@@ -5539,7 +5905,7 @@ function formatPlannerValidationFeedback(err) {
5539
5905
  function isPlannerStructuredValidationError(err) {
5540
5906
  const message = errorMessage2(err);
5541
5907
  if (isPlannerTransportFailure(message)) return false;
5542
- return /^Planner (?:architecture|phase|JSON|draft|complexityAssessment|implementationPhases|iteration)/u.test(message);
5908
+ return /^Planner (?:architecture|phase|PhasePlan|JSON|draft|complexityAssessment|implementationPhases|iteration)/u.test(message);
5543
5909
  }
5544
5910
  function isPlannerTransportFailure(message) {
5545
5911
  const text = message.toLowerCase();
@@ -5580,6 +5946,12 @@ function buildPlan(draft, opts = {}) {
5580
5946
  draft.requirementDigest
5581
5947
  );
5582
5948
  const phaseId = implementationPhases.find((phase) => phase.status === "current")?.id ?? draft.steps.find((step) => step.iterationId)?.iterationId ?? "P1";
5949
+ const architectureDependencyCalibration = calibrateArchitectureModuleDependencies(
5950
+ draft.architectureModules ?? [],
5951
+ draft.dependencies
5952
+ );
5953
+ const architectureModules = architectureDependencyCalibration.architectureModules;
5954
+ const draftDependencies = architectureDependencyCalibration.dependencies;
5583
5955
  const iterated = normalizeStepIterations(draft.steps, implementationPhases);
5584
5956
  const shaped = calibrateLanguageStepOwnership(
5585
5957
  calibrateVModelDependencies(
@@ -5588,14 +5960,14 @@ function buildPlan(draft, opts = {}) {
5588
5960
  {
5589
5961
  language,
5590
5962
  intent: opts.intent ?? "greenfield",
5591
- architectureModules: draft.architectureModules ?? []
5963
+ architectureModules
5592
5964
  }
5593
5965
  );
5594
- const mapped = calibrateArchitectureStepMappings(shaped, draft.architectureModules ?? []);
5595
- const contracted = injectArchitectureContractPrompts(mapped, draft.architectureModules ?? []);
5966
+ const mapped = calibrateArchitectureStepMappings(shaped, architectureModules);
5967
+ const contracted = injectArchitectureContractPrompts(mapped, architectureModules);
5596
5968
  const languageContracted = injectLanguageContractPrompts(contracted, language);
5597
5969
  const steps = calibratePlanCoverage(languageContracted, language);
5598
- const dependencies = language === "python" ? calibratePythonRequirements(draft.dependencies) : [...new Set((draft.dependencies ?? []).map((d) => d.trim()).filter(Boolean))];
5970
+ const dependencies = language === "python" ? calibratePythonRequirements(draftDependencies) : [...new Set((draftDependencies ?? []).map((d) => d.trim()).filter(Boolean))];
5599
5971
  return {
5600
5972
  version: "1",
5601
5973
  language,
@@ -5605,7 +5977,7 @@ function buildPlan(draft, opts = {}) {
5605
5977
  requirementDigest: draft.requirementDigest,
5606
5978
  complexityAssessment,
5607
5979
  implementationPhases,
5608
- architectureModules: draft.architectureModules,
5980
+ architectureModules,
5609
5981
  globalPrompt: draft.globalPrompt,
5610
5982
  baselineSummary: opts.baselineSummary ?? "",
5611
5983
  dependencies,
@@ -5815,6 +6187,11 @@ function validateClarifyJson(text, complex, opts = {}) {
5815
6187
  "clarify missing required external API credential question: ask whether the user has an API/key/token; if not, default to open no-key APIs"
5816
6188
  );
5817
6189
  }
6190
+ if (opts.languageAmbiguous && !parsed.some(isDevelopmentLanguageClarification)) {
6191
+ throw new Error(
6192
+ "clarify missing required development language question: ask whether the project should use Python or TypeScript/Node.js, with Python as the default option"
6193
+ );
6194
+ }
5818
6195
  return parsed;
5819
6196
  }
5820
6197
  function parsePhasePlanJson(text, context) {
@@ -5882,7 +6259,7 @@ function parsePhasePlanJson(text, context) {
5882
6259
  implementationPhases
5883
6260
  };
5884
6261
  }
5885
- function parsePhaseStepPlanJson(text, context, phasePlan, currentIterationId) {
6262
+ function parsePhaseStepPlanJson(text, context, phasePlan, currentPhase) {
5886
6263
  const data = safeJson(text);
5887
6264
  if (!data || typeof data !== "object") {
5888
6265
  throw new Error("Planner did not return a JSON object for phase Step planning.");
@@ -5890,7 +6267,7 @@ function parsePhaseStepPlanJson(text, context, phasePlan, currentIterationId) {
5890
6267
  const obj = data;
5891
6268
  const rawDeps = Array.isArray(obj.dependencies) ? obj.dependencies : Array.isArray(obj.pythonRequirements) ? obj.pythonRequirements : [];
5892
6269
  const draft = {
5893
- requirementDigest: typeof obj.requirementDigest === "string" && obj.requirementDigest.trim() ? obj.requirementDigest : phasePlan.requirementDigest,
6270
+ requirementDigest: typeof obj.requirementDigest === "string" && obj.requirementDigest.trim() ? obj.requirementDigest : currentPhase.objective || phasePlan.requirementDigest,
5894
6271
  globalPrompt: typeof obj.globalPrompt === "string" && obj.globalPrompt.trim() ? obj.globalPrompt : phasePlan.globalPrompt,
5895
6272
  projectType: phasePlan.projectType,
5896
6273
  complexityAssessment: phasePlan.complexityAssessment,
@@ -5899,7 +6276,11 @@ function parsePhaseStepPlanJson(text, context, phasePlan, currentIterationId) {
5899
6276
  architectureModules: obj.architectureModules,
5900
6277
  steps: obj.steps
5901
6278
  };
5902
- const parsed = parseDraftPlanJson(JSON.stringify(draft), context);
6279
+ const parsed = parseDraftPlanJson(JSON.stringify(draft), {
6280
+ ...context,
6281
+ phaseDemandText: phaseDemandText(currentPhase)
6282
+ });
6283
+ const currentIterationId = currentPhase.id;
5903
6284
  const wrongIteration = parsed.steps.find((step) => (step.iterationId ?? "P1") !== currentIterationId);
5904
6285
  if (wrongIteration) {
5905
6286
  throw new Error(
@@ -5934,7 +6315,7 @@ function parseDraftPlanJson(text, context) {
5934
6315
  context?.baselineSummary ?? ""
5935
6316
  ].join("\n"));
5936
6317
  const rawDeps = Array.isArray(obj.dependencies) ? obj.dependencies : Array.isArray(obj.pythonRequirements) ? obj.pythonRequirements : [];
5937
- const dependencies = rawDeps.filter((s) => typeof s === "string");
6318
+ let dependencies = rawDeps.filter((s) => typeof s === "string");
5938
6319
  if (context) {
5939
6320
  const validPhaseNames = new Set(PHASES);
5940
6321
  const nonCanonical = steps.map((rawStep, index) => {
@@ -5967,7 +6348,12 @@ function parseDraftPlanJson(text, context) {
5967
6348
  if (!architectureResult.success) {
5968
6349
  throw new Error(`Planner architectureModules invalid: ${architectureResult.error.issues.map((i) => i.message).join("; ")}`);
5969
6350
  }
5970
- const architectureModules = architectureResult.data;
6351
+ const architectureDependencyCalibration = calibrateArchitectureModuleDependencies(
6352
+ architectureResult.data,
6353
+ dependencies
6354
+ );
6355
+ const architectureModules = architectureDependencyCalibration.architectureModules;
6356
+ dependencies = architectureDependencyCalibration.dependencies;
5971
6357
  const parsedComplexityAssessment = parseComplexityAssessment(obj.complexityAssessment);
5972
6358
  if (context && !parsedComplexityAssessment) {
5973
6359
  throw new Error("Planner JSON missing valid complexityAssessment; complexity must be assessed during plan decomposition.");
@@ -6001,14 +6387,7 @@ function parseDraftPlanJson(text, context) {
6001
6387
  }
6002
6388
  if (context) {
6003
6389
  const demand = analyzeArchitectureDemand(
6004
- {
6005
- requirementDigest: digest,
6006
- rawRequirement: context.rawRequirement,
6007
- userAddenda: context.userAddenda,
6008
- globalPrompt,
6009
- baselineSummary: context.baselineSummary,
6010
- intent: context.intent
6011
- },
6390
+ architectureDemandInputForDraft(context, digest, globalPrompt),
6012
6391
  context.language
6013
6392
  );
6014
6393
  const forcedPhaseSplit = hasForcedPhaseSplit([
@@ -6058,6 +6437,34 @@ function parseDraftPlanJson(text, context) {
6058
6437
  steps: stepsWithIterations
6059
6438
  };
6060
6439
  }
6440
+ function architectureDemandInputForDraft(context, digest, globalPrompt) {
6441
+ if (!context.phaseDemandText) {
6442
+ return {
6443
+ requirementDigest: digest,
6444
+ rawRequirement: context.rawRequirement,
6445
+ userAddenda: context.userAddenda,
6446
+ globalPrompt,
6447
+ baselineSummary: context.baselineSummary,
6448
+ intent: context.intent
6449
+ };
6450
+ }
6451
+ return {
6452
+ requirementDigest: context.phaseDemandText,
6453
+ baselineSummary: context.baselineSummary,
6454
+ intent: context.intent
6455
+ };
6456
+ }
6457
+ function phaseDemandText(currentPhase) {
6458
+ const gate = currentPhase.verificationGate;
6459
+ return [
6460
+ currentPhase.title,
6461
+ currentPhase.objective,
6462
+ currentPhase.scope.join("\n"),
6463
+ currentPhase.deliverables.join("\n"),
6464
+ gate?.summary ?? "",
6465
+ gate?.checks.join("\n") ?? ""
6466
+ ].map((part) => part.trim()).filter(Boolean).join("\n");
6467
+ }
6061
6468
  function parseComplexityAssessment(value) {
6062
6469
  const result = ComplexityAssessmentSchema.safeParse(value);
6063
6470
  return result.success ? result.data : void 0;
@@ -6144,6 +6551,15 @@ ${question.options.map((option) => option.answer).join("\n")}`.toLowerCase();
6144
6551
  const externalApiContext = /\b(api|url|endpoint|provider|external|third[- ]party|fetch|request)\b/u.test(text) || /外部|第三方|接口|数据源|天气|节假日|联网/u.test(text);
6145
6552
  return externalApiContext && asksCredential && mentionsNoKeyFallback;
6146
6553
  }
6554
+ function isDevelopmentLanguageClarification(question) {
6555
+ const text = `${question.question}
6556
+ ${question.why}
6557
+ ${question.options.map((option) => option.answer).join("\n")}`.toLowerCase();
6558
+ const mentionsPython = /\bpython\b/u.test(text) || /python\s*脚本|python\s*项目/u.test(text);
6559
+ const mentionsTypeScript = /\btypescript\b|\btype\s*script\b|\bnode(?:\.js)?\b/u.test(text) || /type\s*script|ts\s*(程序|工程|项目|脚本|语言|实现)|typescript\s*项目/u.test(text);
6560
+ const asksLanguage = /\b(language|runtime|implementation stack|programming language)\b/u.test(text) || /开发语言|编程语言|实现语言|运行时|技术栈/u.test(text);
6561
+ return asksLanguage && mentionsPython && mentionsTypeScript;
6562
+ }
6147
6563
  function projectShapeSignals(text) {
6148
6564
  const lower = text.toLowerCase();
6149
6565
  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);
@@ -6271,7 +6687,7 @@ ${text.slice(0, 500)}`);
6271
6687
  }
6272
6688
 
6273
6689
  // src/core/incremental.ts
6274
- import { promises as fs7 } from "fs";
6690
+ import { promises as fs8 } from "fs";
6275
6691
  import path9 from "path";
6276
6692
 
6277
6693
  // src/core/phase_plan.ts
@@ -6351,11 +6767,11 @@ function relativeFrom(base, target) {
6351
6767
  }
6352
6768
 
6353
6769
  // src/core/project_memory.ts
6354
- import { promises as fs6 } from "fs";
6770
+ import { promises as fs7 } from "fs";
6355
6771
  import path8 from "path";
6356
6772
 
6357
6773
  // src/core/storage.ts
6358
- import { promises as fs5 } from "fs";
6774
+ import { promises as fs6 } from "fs";
6359
6775
  import path7 from "path";
6360
6776
 
6361
6777
  // src/core/lint.ts
@@ -6791,21 +7207,21 @@ function parseLoadedPlan(json) {
6791
7207
  }
6792
7208
  async function savePlan(planPath, plan) {
6793
7209
  PlanSchema.parse(plan);
6794
- await fs5.mkdir(path7.dirname(planPath), { recursive: true });
6795
- await fs5.writeFile(planPath, JSON.stringify(plan, null, 2) + "\n", "utf8");
7210
+ await fs6.mkdir(path7.dirname(planPath), { recursive: true });
7211
+ await fs6.writeFile(planPath, JSON.stringify(plan, null, 2) + "\n", "utf8");
6796
7212
  }
6797
7213
  async function loadPhasePlan(phasePlanPath) {
6798
- const raw = await fs5.readFile(phasePlanPath, "utf8");
7214
+ const raw = await fs6.readFile(phasePlanPath, "utf8");
6799
7215
  return PhasePlanSchema.parse(JSON.parse(raw));
6800
7216
  }
6801
7217
  async function savePhasePlan(phasePlanPath, phasePlan) {
6802
7218
  PhasePlanSchema.parse(phasePlan);
6803
- await fs5.mkdir(path7.dirname(phasePlanPath), { recursive: true });
6804
- await fs5.writeFile(phasePlanPath, JSON.stringify(phasePlan, null, 2) + "\n", "utf8");
7219
+ await fs6.mkdir(path7.dirname(phasePlanPath), { recursive: true });
7220
+ await fs6.writeFile(phasePlanPath, JSON.stringify(phasePlan, null, 2) + "\n", "utf8");
6805
7221
  }
6806
7222
  async function loadPlanTarget(inputPath) {
6807
7223
  const requestedPath = path7.resolve(inputPath);
6808
- const raw = await fs5.readFile(requestedPath, "utf8");
7224
+ const raw = await fs6.readFile(requestedPath, "utf8");
6809
7225
  const json = JSON.parse(raw);
6810
7226
  const phasePlanResult = PhasePlanSchema.safeParse(json);
6811
7227
  if (phasePlanResult.success) {
@@ -6815,7 +7231,7 @@ async function loadPlanTarget(inputPath) {
6815
7231
  throw new Error(`phasePlan ${requestedPath} has no planPath for current phase ${phasePlan.currentPhaseId}`);
6816
7232
  }
6817
7233
  const planPath = path7.resolve(path7.dirname(requestedPath), phase.planPath);
6818
- const loaded2 = parseLoadedPlan(JSON.parse(await fs5.readFile(planPath, "utf8")));
7234
+ const loaded2 = parseLoadedPlan(JSON.parse(await fs6.readFile(planPath, "utf8")));
6819
7235
  return {
6820
7236
  plan: loaded2.plan,
6821
7237
  planPath,
@@ -6982,7 +7398,7 @@ async function defaultPlanMetadataPath(root) {
6982
7398
  }
6983
7399
  async function fileExists(filePath) {
6984
7400
  try {
6985
- await fs6.stat(filePath);
7401
+ await fs7.stat(filePath);
6986
7402
  return true;
6987
7403
  } catch {
6988
7404
  return false;
@@ -7010,7 +7426,7 @@ async function readWorkspaceText(ws, rel, maxChars) {
7010
7426
  }
7011
7427
  async function readAbsoluteText(fullPath, maxChars) {
7012
7428
  try {
7013
- const raw = await fs6.readFile(fullPath, "utf8");
7429
+ const raw = await fs7.readFile(fullPath, "utf8");
7014
7430
  return truncate(raw, maxChars);
7015
7431
  } catch {
7016
7432
  return "";
@@ -7032,7 +7448,7 @@ async function walk(abs, rel, out, depth) {
7032
7448
  if (depth > 6 || out.length >= 160) return;
7033
7449
  let entries;
7034
7450
  try {
7035
- entries = await fs6.readdir(abs, { withFileTypes: true });
7451
+ entries = await fs7.readdir(abs, { withFileTypes: true });
7036
7452
  } catch {
7037
7453
  return;
7038
7454
  }
@@ -7214,20 +7630,20 @@ function extractImportedStems(text) {
7214
7630
  function normalizeImportStem(value) {
7215
7631
  return value.replace(/^\.\//u, "").replace(/^\.\.\//u, "").replace(/\.(js|ts|tsx|py)$/u, "").replace(/\/index$/u, "").trim();
7216
7632
  }
7217
- function extractContractsFromDoc(path30, text) {
7633
+ function extractContractsFromDoc(path31, text) {
7218
7634
  const contracts = [];
7219
7635
  for (const rawLine of text.split("\n")) {
7220
7636
  const line = rawLine.replace(/^[-*#\d.\s]+/u, "").trim();
7221
7637
  if (line.length < 18) continue;
7222
7638
  const lower = line.toLowerCase();
7223
7639
  if (INVARIANT_RE.test(lower)) {
7224
- contracts.push({ kind: "invariant", subject: path30, path: path30, detail: trimContractLine(line) });
7640
+ contracts.push({ kind: "invariant", subject: path31, path: path31, detail: trimContractLine(line) });
7225
7641
  } else if (LIMITATION_RE.test(lower)) {
7226
- contracts.push({ kind: "limitation", subject: path30, path: path30, detail: trimContractLine(line) });
7642
+ contracts.push({ kind: "limitation", subject: path31, path: path31, detail: trimContractLine(line) });
7227
7643
  } else if (EXTENSION_RE.test(lower)) {
7228
- contracts.push({ kind: "extension-point", subject: path30, path: path30, detail: trimContractLine(line) });
7644
+ contracts.push({ kind: "extension-point", subject: path31, path: path31, detail: trimContractLine(line) });
7229
7645
  } else if (INTEGRATION_RE.test(lower)) {
7230
- contracts.push({ kind: "integration", subject: path30, path: path30, detail: trimContractLine(line) });
7646
+ contracts.push({ kind: "integration", subject: path31, path: path31, detail: trimContractLine(line) });
7231
7647
  }
7232
7648
  }
7233
7649
  return contracts;
@@ -7326,7 +7742,7 @@ ${sec.slice(0, remain)}
7326
7742
  }
7327
7743
  async function loadPlanSummary(_ws, planPath, label) {
7328
7744
  try {
7329
- const raw = await fs7.readFile(planPath, "utf8");
7745
+ const raw = await fs8.readFile(planPath, "utf8");
7330
7746
  const json = JSON.parse(raw);
7331
7747
  const phasePlanParsed = PhasePlanSchema.safeParse(json);
7332
7748
  if (phasePlanParsed.success) {
@@ -7394,7 +7810,7 @@ async function defaultBaselinePlanPath(root) {
7394
7810
  }
7395
7811
  async function fileExists2(filePath) {
7396
7812
  try {
7397
- await fs7.stat(filePath);
7813
+ await fs8.stat(filePath);
7398
7814
  return true;
7399
7815
  } catch {
7400
7816
  return false;
@@ -7458,7 +7874,7 @@ async function walk2(abs, rel, out, depth) {
7458
7874
  if (depth > 6 || out.length >= 120) return;
7459
7875
  let entries;
7460
7876
  try {
7461
- entries = await fs7.readdir(abs, { withFileTypes: true });
7877
+ entries = await fs8.readdir(abs, { withFileTypes: true });
7462
7878
  } catch {
7463
7879
  return;
7464
7880
  }
@@ -7636,7 +8052,7 @@ function renderSubTasks(tasks, depth) {
7636
8052
  }
7637
8053
 
7638
8054
  // src/core/project_file.ts
7639
- import { promises as fs8 } from "fs";
8055
+ import { promises as fs9 } from "fs";
7640
8056
  import path10 from "path";
7641
8057
  import { z as z4 } from "zod";
7642
8058
  var XCOMPILER_PROJECT_KIND = "xcompiler.project";
@@ -7697,7 +8113,7 @@ async function findProjectFile(workspace) {
7697
8113
  const ws = path10.resolve(workspace);
7698
8114
  let entries;
7699
8115
  try {
7700
- entries = await fs8.readdir(ws, { withFileTypes: true });
8116
+ entries = await fs9.readdir(ws, { withFileTypes: true });
7701
8117
  } catch {
7702
8118
  return void 0;
7703
8119
  }
@@ -7747,8 +8163,8 @@ async function updateProjectFile(opts) {
7747
8163
  history: nextHistory
7748
8164
  };
7749
8165
  XCompilerProjectFileSchema.parse(data);
7750
- await fs8.mkdir(path10.dirname(filePath), { recursive: true });
7751
- await fs8.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
8166
+ await fs9.mkdir(path10.dirname(filePath), { recursive: true });
8167
+ await fs9.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
7752
8168
  return filePath;
7753
8169
  }
7754
8170
  function buildProjectProgress(plan) {
@@ -7791,7 +8207,7 @@ function buildProjectProgress(plan) {
7791
8207
  }
7792
8208
  async function readExistingProjectFile(filePath) {
7793
8209
  try {
7794
- const raw = await fs8.readFile(filePath, "utf8");
8210
+ const raw = await fs9.readFile(filePath, "utf8");
7795
8211
  return XCompilerProjectFileSchema.parse(JSON.parse(raw));
7796
8212
  } catch {
7797
8213
  return void 0;
@@ -7820,7 +8236,7 @@ function assertProjectFileExtension(filePath) {
7820
8236
  }
7821
8237
 
7822
8238
  // src/audit/audit.ts
7823
- import { promises as fs9, appendFileSync, mkdirSync, existsSync, writeFileSync } from "fs";
8239
+ import { promises as fs10, appendFileSync, mkdirSync, existsSync, writeFileSync } from "fs";
7824
8240
  import { createHash } from "crypto";
7825
8241
  import path11 from "path";
7826
8242
  var AuditLogger = class {
@@ -7977,8 +8393,8 @@ var AuditLogger = class {
7977
8393
  });
7978
8394
  }
7979
8395
  /**
7980
- * 记录一轮 Executor 思考:thoughts 文本、计划调用的 actions、是否完成。
7981
- * 写入 jsonl + markdown 折叠块,交付时可作为"AI 思考过程完整记录"。
8396
+ * 记录一轮 Executor 执行摘要:简短 intent、issue 处理方案、计划调用的 actions、是否完成。
8397
+ * 写入 jsonl + markdown 折叠块,交付时可追溯每轮决策和动作。
7982
8398
  */
7983
8399
  async executorTurn(stepId, role, round, payload) {
7984
8400
  const storedPayload = protectAuditContent(payload, this.contentMode);
@@ -8050,8 +8466,8 @@ ${t().audit.processLogPreamble}
8050
8466
  }
8051
8467
  async appendMd(text) {
8052
8468
  this.mdQueue = this.mdQueue.then(
8053
- () => fs9.appendFile(this.mdAbs, text, "utf8"),
8054
- () => fs9.appendFile(this.mdAbs, text, "utf8")
8469
+ () => fs10.appendFile(this.mdAbs, text, "utf8"),
8470
+ () => fs10.appendFile(this.mdAbs, text, "utf8")
8055
8471
  ).catch((err) => {
8056
8472
  console.warn(t().audit.markdownAppendFailed(err.message));
8057
8473
  });
@@ -8133,7 +8549,7 @@ function redactText(value) {
8133
8549
  }
8134
8550
 
8135
8551
  // src/core/lock.ts
8136
- import { promises as fs10, unlinkSync } from "fs";
8552
+ import { promises as fs11, unlinkSync } from "fs";
8137
8553
  import path12 from "path";
8138
8554
  import os from "os";
8139
8555
  var LockError = class extends Error {
@@ -8160,7 +8576,7 @@ function isAlive(pid) {
8160
8576
  }
8161
8577
  async function readLock(file) {
8162
8578
  try {
8163
- const raw = await fs10.readFile(file, "utf8");
8579
+ const raw = await fs11.readFile(file, "utf8");
8164
8580
  return JSON.parse(raw);
8165
8581
  } catch {
8166
8582
  return null;
@@ -8168,7 +8584,7 @@ async function readLock(file) {
8168
8584
  }
8169
8585
  async function acquireLock(workspace, command, options = {}) {
8170
8586
  const file = lockPath(workspace);
8171
- await fs10.mkdir(path12.dirname(file), { recursive: true });
8587
+ await fs11.mkdir(path12.dirname(file), { recursive: true });
8172
8588
  const info = {
8173
8589
  pid: process.pid,
8174
8590
  host: os.hostname(),
@@ -8178,7 +8594,7 @@ async function acquireLock(workspace, command, options = {}) {
8178
8594
  const payload = JSON.stringify(info, null, 2);
8179
8595
  const tryCreate = async () => {
8180
8596
  try {
8181
- const fh = await fs10.open(file, "wx");
8597
+ const fh = await fs11.open(file, "wx");
8182
8598
  await fh.writeFile(payload, "utf8");
8183
8599
  await fh.close();
8184
8600
  return true;
@@ -8191,14 +8607,14 @@ async function acquireLock(workspace, command, options = {}) {
8191
8607
  return makeReleaser(file);
8192
8608
  }
8193
8609
  if (options.force) {
8194
- await fs10.writeFile(file, payload, "utf8");
8610
+ await fs11.writeFile(file, payload, "utf8");
8195
8611
  return makeReleaser(file);
8196
8612
  }
8197
8613
  const existing = await readLock(file);
8198
8614
  if (existing) {
8199
8615
  const sameHost = existing.host === info.host;
8200
8616
  if (sameHost && !isAlive(existing.pid)) {
8201
- await fs10.writeFile(file, payload, "utf8");
8617
+ await fs11.writeFile(file, payload, "utf8");
8202
8618
  return makeReleaser(file);
8203
8619
  }
8204
8620
  throw new LockError(
@@ -8207,7 +8623,7 @@ async function acquireLock(workspace, command, options = {}) {
8207
8623
  existing
8208
8624
  );
8209
8625
  }
8210
- await fs10.writeFile(file, payload, "utf8");
8626
+ await fs11.writeFile(file, payload, "utf8");
8211
8627
  return makeReleaser(file);
8212
8628
  }
8213
8629
  function makeReleaser(file) {
@@ -8216,7 +8632,7 @@ function makeReleaser(file) {
8216
8632
  if (released) return;
8217
8633
  released = true;
8218
8634
  try {
8219
- await fs10.unlink(file);
8635
+ await fs11.unlink(file);
8220
8636
  } catch {
8221
8637
  }
8222
8638
  };
@@ -8241,7 +8657,7 @@ function makeReleaser(file) {
8241
8657
  }
8242
8658
 
8243
8659
  // src/version.ts
8244
- var XCOMPILER_VERSION = "0.2.3";
8660
+ var XCOMPILER_VERSION = "0.2.4";
8245
8661
  var XCOMPILER_PLUGIN_API_VERSION = 1;
8246
8662
 
8247
8663
  // src/plugins/compatibility.ts
@@ -8584,8 +9000,51 @@ function resolveClarificationAnswer(q, rawAnswer) {
8584
9000
  }
8585
9001
  return answer;
8586
9002
  }
8587
- function resolveCompileLanguage(configuredLanguage, intent, baseline) {
8588
- return isIncrementalIntent(intent) ? baseline.language ?? configuredLanguage : configuredLanguage;
9003
+ function resolveCompileLanguage(inputOrConfigured, intent, baseline) {
9004
+ if (typeof inputOrConfigured === "string") {
9005
+ return isIncrementalIntent(intent ?? "greenfield") ? baseline?.language ?? inputOrConfigured : inputOrConfigured;
9006
+ }
9007
+ const input2 = inputOrConfigured;
9008
+ if (isIncrementalIntent(input2.intent ?? "greenfield") && input2.baseline?.language) {
9009
+ return { language: input2.baseline.language, source: "baseline", ambiguous: false };
9010
+ }
9011
+ const topicInferred = inferCompileLanguageFromText(input2.rawRequirement);
9012
+ if (topicInferred) {
9013
+ return {
9014
+ language: topicInferred,
9015
+ source: "topic",
9016
+ ambiguous: false
9017
+ };
9018
+ }
9019
+ const clarificationText = formatLanguageClarificationText(input2.clarifications ?? []);
9020
+ const clarifiedInferred = inferCompileLanguageFromText([
9021
+ clarificationText,
9022
+ input2.userAddenda ?? ""
9023
+ ].join("\n"));
9024
+ if (clarifiedInferred) {
9025
+ return { language: clarifiedInferred, source: "clarification", ambiguous: false };
9026
+ }
9027
+ return { language: "python", source: "default", ambiguous: true };
9028
+ }
9029
+ function inferCompileLanguageFromText(text) {
9030
+ const normalized = text.toLowerCase().replace(/[,。;:、()【】]/gu, " ").replace(/\s+/gu, " ");
9031
+ 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);
9032
+ 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);
9033
+ if (pythonStrong && !typescriptStrong) return "python";
9034
+ if (typescriptStrong && !pythonStrong) return "typescript";
9035
+ const pythonWeak = /\bopenpyxl\b/u.test(normalized) || /\bpandas\b/u.test(normalized) || /\bfastapi\b/u.test(normalized) || /\bflask\b/u.test(normalized);
9036
+ 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);
9037
+ if ((pythonStrong || pythonWeak) && !(typescriptStrong || typescriptWeak)) return "python";
9038
+ if ((typescriptStrong || typescriptWeak) && !(pythonStrong || pythonWeak)) return "typescript";
9039
+ return void 0;
9040
+ }
9041
+ function formatLanguageClarificationText(input2) {
9042
+ return input2.map((item) => [
9043
+ item.question,
9044
+ item.answer,
9045
+ item.why ?? "",
9046
+ ...(item.options ?? []).map((option) => option.answer)
9047
+ ].join("\n")).join("\n\n");
8589
9048
  }
8590
9049
  async function runCompile(opts) {
8591
9050
  const io = opts.io ?? silentRuntimeIO;
@@ -8662,15 +9121,7 @@ async function runCompile(opts) {
8662
9121
  if (baseline.summary) {
8663
9122
  await runtimeLog(io, "success", M.compile.baselineLoaded(intent, baseline.sources.join(", ")));
8664
9123
  }
8665
- const language = resolveCompileLanguage(cfg.agent.language, intent, baseline);
8666
- if (isIncrementalIntent(intent) && baseline.language && baseline.language !== cfg.agent.language) {
8667
- await runtimeLog(
8668
- io,
8669
- "warning",
8670
- M.compile.baselineLanguageOverride(baseline.language, baseline.languageSource ?? "baseline", cfg.agent.language)
8671
- );
8672
- }
8673
- const planner = new Planner(router.for("Planner"), audit, language);
9124
+ const plannerClient = router.for("Planner");
8674
9125
  const trace = (msg) => {
8675
9126
  if (xcEnv("TRACE") === "1") {
8676
9127
  void runtimeLog(io, "dim", t().audit.traceLine("xcompiler-trace", msg));
@@ -8679,7 +9130,7 @@ async function runCompile(opts) {
8679
9130
  let rawRequirement;
8680
9131
  if (topicMode) {
8681
9132
  trace("topic.read");
8682
- rawRequirement = await fs11.readFile(path13.resolve(opts.topicFile), "utf8");
9133
+ rawRequirement = await fs12.readFile(path13.resolve(opts.topicFile), "utf8");
8683
9134
  if (!rawRequirement.trim()) {
8684
9135
  await runtimeLog(io, "error", M.compile.topicEmptyExit);
8685
9136
  await audit.end({ status: "aborted", reason: "empty topic file" });
@@ -8700,19 +9151,26 @@ async function runCompile(opts) {
8700
9151
  await audit.userInput(M.compile.auditOriginalRequirement, rawRequirement);
8701
9152
  trace("audit.userInput.intake.done");
8702
9153
  }
9154
+ const initialLanguage = resolveCompileLanguage({
9155
+ rawRequirement,
9156
+ intent,
9157
+ baseline
9158
+ });
8703
9159
  trace("clarify.section.enter");
8704
9160
  const clarifications = [];
8705
9161
  let clarificationQuestions = [];
8706
9162
  trace(`clarify.section.flag yes=${opts.yes} topicMode=${topicMode}`);
8707
9163
  if (!opts.yes && !topicMode) {
9164
+ const clarifyPlanner = new Planner(plannerClient, audit, initialLanguage.language);
8708
9165
  trace("ora.clarify.start");
8709
9166
  const spin = io.progress(M.compile.spinClarify, { animate: false });
8710
9167
  trace("ora.clarify.started");
8711
9168
  try {
8712
9169
  trace("planner.clarify.call");
8713
- clarificationQuestions = await planner.clarify(rawRequirement, {
9170
+ clarificationQuestions = await clarifyPlanner.clarify(rawRequirement, {
8714
9171
  intent,
8715
- hasBaseline: !!baseline.summary
9172
+ hasBaseline: !!baseline.summary,
9173
+ languageAmbiguous: initialLanguage.ambiguous
8716
9174
  });
8717
9175
  trace(`planner.clarify.return n=${clarificationQuestions.length}`);
8718
9176
  spin.succeed(M.compile.clarifySucceed(clarificationQuestions.length));
@@ -8801,6 +9259,20 @@ ${M.compile.topicPreviewHeader}`);
8801
9259
  }
8802
9260
  trace("ws.readFile.finalTopic");
8803
9261
  const finalTopicMd = await ws.readFile(draftTopic);
9262
+ const languageResolution = resolveCompileLanguage({
9263
+ rawRequirement: finalTopicMd,
9264
+ clarifications,
9265
+ userAddenda,
9266
+ intent,
9267
+ baseline
9268
+ });
9269
+ const language = languageResolution.language;
9270
+ await audit.event("note", `target language resolved: ${language}`, {
9271
+ messageId: "compile.language_resolved",
9272
+ language,
9273
+ source: languageResolution.source,
9274
+ ambiguous: languageResolution.ambiguous
9275
+ });
8804
9276
  await archiveIfExists(ws, DOC_NAMES.topic, audit);
8805
9277
  await ws.writeFile(DOC_NAMES.topic, finalTopicMd);
8806
9278
  await audit.event("topic.persist", M.compile.auditTopicPersisted(ws.abs(DOC_NAMES.topic)), {
@@ -8814,6 +9286,7 @@ ${M.compile.topicPreviewHeader}`);
8814
9286
  trace("ora.spin2.started");
8815
9287
  let draft;
8816
9288
  try {
9289
+ const planner = new Planner(plannerClient, audit, language);
8817
9290
  const plannerInput = {
8818
9291
  rawRequirement: finalTopicMd,
8819
9292
  clarifications,
@@ -8951,7 +9424,7 @@ async function tryLoadPhasePlan(phasePlanPath) {
8951
9424
  }
8952
9425
  async function intake(inputFile, io) {
8953
9426
  if (inputFile) {
8954
- return fs11.readFile(path13.resolve(inputFile), "utf8");
9427
+ return fs12.readFile(path13.resolve(inputFile), "utf8");
8955
9428
  }
8956
9429
  return requireRuntimeInteraction(io, "requirement intake").readMultiline({
8957
9430
  message: t().compile.requirementInputHint
@@ -8995,14 +9468,14 @@ function renderTopicDraft(raw, qa, addenda = "") {
8995
9468
  }
8996
9469
 
8997
9470
  // src/runtime/commands.ts
8998
- import path29 from "path";
8999
- import { promises as fs26 } from "fs";
9471
+ import path30 from "path";
9472
+ import { promises as fs28 } from "fs";
9000
9473
 
9001
9474
  // src/runtime/run.ts
9002
- import path27 from "path";
9475
+ import path28 from "path";
9003
9476
 
9004
9477
  // src/workspace/git.ts
9005
- import { promises as fs12 } from "fs";
9478
+ import { promises as fs13 } from "fs";
9006
9479
  import path14 from "path";
9007
9480
  import { simpleGit } from "simple-git";
9008
9481
 
@@ -9011,7 +9484,7 @@ import { existsSync as existsSync2, readFileSync } from "fs";
9011
9484
 
9012
9485
  // src/sandbox/subprocess.ts
9013
9486
  import { spawn } from "child_process";
9014
- import { promises as fs13 } from "fs";
9487
+ import { promises as fs14 } from "fs";
9015
9488
  import path15 from "path";
9016
9489
  import crypto from "crypto";
9017
9490
 
@@ -9042,13 +9515,16 @@ function formatCommand(cmd, argv) {
9042
9515
  return [cmd, ...argv].filter(Boolean).join(" ");
9043
9516
  }
9044
9517
 
9518
+ // src/sandbox/subprocess.ts
9519
+ var INSTALL_IDLE_TIMEOUT_FLOOR_MS = 15 * 6e4;
9520
+
9045
9521
  // src/sandbox/docker.ts
9046
- import { promises as fs14 } from "fs";
9522
+ import { promises as fs15 } from "fs";
9047
9523
  import path16 from "path";
9048
9524
  import crypto2 from "crypto";
9049
9525
 
9050
9526
  // src/core/engine.ts
9051
- import path26 from "path";
9527
+ import path27 from "path";
9052
9528
  import chalk2 from "chalk";
9053
9529
 
9054
9530
  // src/util/spinner.ts
@@ -9134,10 +9610,10 @@ init_types();
9134
9610
  // src/tools/fs.ts
9135
9611
  init_types();
9136
9612
  import path18 from "path";
9137
- import { promises as fs16 } from "fs";
9613
+ import { promises as fs17 } from "fs";
9138
9614
 
9139
9615
  // src/tools/path_guard.ts
9140
- import { promises as fs15 } from "fs";
9616
+ import { promises as fs16 } from "fs";
9141
9617
  import path17 from "path";
9142
9618
  async function resolveWorkspacePath(ws, rawPath, operation, opts = {}) {
9143
9619
  const raw = rawPath && rawPath.trim() ? rawPath : ".";
@@ -9146,19 +9622,19 @@ async function resolveWorkspacePath(ws, rawPath, operation, opts = {}) {
9146
9622
  if (!isInside(root, abs)) return deny(operation, raw);
9147
9623
  if (opts.mustExist) {
9148
9624
  try {
9149
- const real = await fs15.realpath(abs);
9625
+ const real = await fs16.realpath(abs);
9150
9626
  if (!isInside(root, real)) return deny(operation, raw);
9151
9627
  } catch (err) {
9152
9628
  return { ok: false, error: `${operation} failed: ${err.message}` };
9153
9629
  }
9154
9630
  } else if (opts.forWrite) {
9155
- const existingTarget = await fs15.realpath(abs).catch(() => void 0);
9631
+ const existingTarget = await fs16.realpath(abs).catch(() => void 0);
9156
9632
  if (existingTarget && !isInside(root, existingTarget)) return deny(operation, raw);
9157
9633
  const parent = await nearestExistingParent(path17.dirname(abs));
9158
- const realParent = await fs15.realpath(parent).catch(() => parent);
9634
+ const realParent = await fs16.realpath(parent).catch(() => parent);
9159
9635
  if (!isInside(root, realParent)) return deny(operation, raw);
9160
9636
  } else {
9161
- const existing = await fs15.realpath(abs).catch(() => void 0);
9637
+ const existing = await fs16.realpath(abs).catch(() => void 0);
9162
9638
  if (existing && !isInside(root, existing)) return deny(operation, raw);
9163
9639
  }
9164
9640
  return { ok: true, abs };
@@ -9168,13 +9644,13 @@ function isInside(root, candidate) {
9168
9644
  return rel === "" || !!rel && !rel.startsWith("..") && !path17.isAbsolute(rel);
9169
9645
  }
9170
9646
  async function realpathOrResolve(p) {
9171
- return fs15.realpath(p).catch(() => path17.resolve(p));
9647
+ return fs16.realpath(p).catch(() => path17.resolve(p));
9172
9648
  }
9173
9649
  async function nearestExistingParent(start) {
9174
9650
  let current2 = path17.resolve(start);
9175
9651
  while (true) {
9176
9652
  try {
9177
- const stat = await fs15.stat(current2);
9653
+ const stat = await fs16.stat(current2);
9178
9654
  if (stat.isDirectory()) return current2;
9179
9655
  } catch {
9180
9656
  }
@@ -9193,7 +9669,7 @@ function deny(operation, rawPath) {
9193
9669
  // src/tools/patch.ts
9194
9670
  init_types();
9195
9671
  import path19 from "path";
9196
- import { promises as fs17 } from "fs";
9672
+ import { promises as fs18 } from "fs";
9197
9673
 
9198
9674
  // src/tools/sandbox.ts
9199
9675
  function tailLines(text, n) {
@@ -9268,13 +9744,13 @@ var pipInstallTool = {
9268
9744
  // src/tools/edit.ts
9269
9745
  init_types();
9270
9746
  import path20 from "path";
9271
- import { promises as fs18 } from "fs";
9747
+ import { promises as fs19 } from "fs";
9272
9748
 
9273
9749
  // src/tools/deps.ts
9274
- import { promises as fs19 } from "fs";
9750
+ import { promises as fs20 } from "fs";
9275
9751
 
9276
9752
  // src/tools/net.ts
9277
- import { promises as fs20 } from "fs";
9753
+ import { promises as fs21 } from "fs";
9278
9754
  import path21 from "path";
9279
9755
  var DEFAULT_MAX_BYTES = 256 * 1024;
9280
9756
 
@@ -9282,25 +9758,31 @@ var DEFAULT_MAX_BYTES = 256 * 1024;
9282
9758
  init_types();
9283
9759
 
9284
9760
  // src/tools/guard.ts
9285
- import { promises as fs21 } from "fs";
9761
+ import { promises as fs22 } from "fs";
9286
9762
  import path22 from "path";
9287
9763
 
9288
9764
  // src/agents/executor.ts
9289
9765
  import path23 from "path";
9290
- import { promises as fs22 } from "fs";
9766
+ import { promises as fs23 } from "fs";
9291
9767
  import { jsonrepair } from "jsonrepair";
9292
9768
 
9293
9769
  // src/core/debug_cache.ts
9294
- import { promises as fs23 } from "fs";
9770
+ import { promises as fs24 } from "fs";
9295
9771
  import path24 from "path";
9296
9772
 
9297
- // src/core/project_audit.ts
9298
- import { promises as fs24 } from "fs";
9773
+ // src/core/debug_wiki.ts
9774
+ import { promises as fs25 } from "fs";
9299
9775
  import path25 from "path";
9776
+ import { fileURLToPath } from "url";
9777
+ import YAML3 from "yaml";
9778
+
9779
+ // src/core/project_audit.ts
9780
+ import { promises as fs26 } from "fs";
9781
+ import path26 from "path";
9300
9782
 
9301
9783
  // src/runtime/workspace.ts
9302
- import path28 from "path";
9303
- import { promises as fs25 } from "fs";
9784
+ import path29 from "path";
9785
+ import { promises as fs27 } from "fs";
9304
9786
  function defaultProjectName(now = /* @__PURE__ */ new Date()) {
9305
9787
  const pad = (n) => n.toString().padStart(2, "0");
9306
9788
  return `xcompiler-${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
@@ -9308,14 +9790,14 @@ function defaultProjectName(now = /* @__PURE__ */ new Date()) {
9308
9790
  async function resolveCompileWorkspace(opts) {
9309
9791
  const explicit = opts.output ?? opts.workspace;
9310
9792
  if (explicit) {
9311
- const ws2 = path28.resolve(explicit);
9312
- await fs25.mkdir(ws2, { recursive: true });
9793
+ const ws2 = path29.resolve(explicit);
9794
+ await fs27.mkdir(ws2, { recursive: true });
9313
9795
  return ws2;
9314
9796
  }
9315
- const base = opts.baseDir ? path28.resolve(opts.baseDir) : "/tmp";
9797
+ const base = opts.baseDir ? path29.resolve(opts.baseDir) : "/tmp";
9316
9798
  const name = opts.name ?? defaultProjectName();
9317
- const ws = path28.join(base, name);
9318
- await fs25.mkdir(ws, { recursive: true });
9799
+ const ws = path29.join(base, name);
9800
+ await fs27.mkdir(ws, { recursive: true });
9319
9801
  return ws;
9320
9802
  }
9321
9803