micro-models-agent 0.35.2 → 0.35.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +1710 -1216
  2. package/package.json +3 -3
package/dist/main.js CHANGED
@@ -2096,47 +2096,55 @@ var init_config = __esm(() => {
2096
2096
  command: "npx",
2097
2097
  args: ["typescript-language-server", "--stdio"],
2098
2098
  timeout: 20000,
2099
- autoInstall: true
2099
+ autoInstall: true,
2100
+ workspaceMarkers: ["package.json", "tsconfig.json"]
2100
2101
  },
2101
2102
  javascript: {
2102
2103
  command: "npx",
2103
2104
  args: ["typescript-language-server", "--stdio"],
2104
2105
  timeout: 15000,
2105
- autoInstall: true
2106
+ autoInstall: true,
2107
+ workspaceMarkers: ["package.json", "tsconfig.json"]
2106
2108
  },
2107
2109
  python: {
2108
2110
  command: "npx",
2109
2111
  args: ["pyright", "--stdio"],
2110
2112
  timeout: 15000,
2111
- autoInstall: true
2113
+ autoInstall: true,
2114
+ workspaceMarkers: ["pyproject.toml", "setup.py", "requirements.txt"]
2112
2115
  },
2113
2116
  rust: {
2114
2117
  command: "rust-analyzer",
2115
2118
  timeout: 15000,
2116
- autoInstall: false
2119
+ autoInstall: false,
2120
+ workspaceMarkers: ["Cargo.toml"]
2117
2121
  },
2118
2122
  go: {
2119
2123
  command: "gopls",
2120
2124
  timeout: 15000,
2121
- autoInstall: false
2125
+ autoInstall: false,
2126
+ workspaceMarkers: ["go.mod"]
2122
2127
  },
2123
2128
  json: {
2124
2129
  command: "npx",
2125
2130
  args: ["vscode-json-languageserver", "--stdio"],
2126
2131
  timeout: 1e4,
2127
- autoInstall: true
2132
+ autoInstall: true,
2133
+ workspaceMarkers: ["package.json"]
2128
2134
  },
2129
2135
  html: {
2130
2136
  command: "npx",
2131
2137
  args: ["vscode-html-languageserver", "--stdio"],
2132
2138
  timeout: 1e4,
2133
- autoInstall: true
2139
+ autoInstall: true,
2140
+ workspaceMarkers: ["package.json"]
2134
2141
  },
2135
2142
  css: {
2136
2143
  command: "npx",
2137
2144
  args: ["vscode-css-languageserver", "--stdio"],
2138
2145
  timeout: 1e4,
2139
- autoInstall: true
2146
+ autoInstall: true,
2147
+ workspaceMarkers: ["package.json"]
2140
2148
  }
2141
2149
  }
2142
2150
  };
@@ -2464,6 +2472,10 @@ Command: {command}`,
2464
2472
  "plan.step_already_done": "Step {step} is already done — nothing to do. Plan progress:",
2465
2473
  "plan.order_blocked": 'Cannot mark step {step} done: step {first} ("{desc}") is not finished yet. Complete earlier steps first, or mark step {first} status=skipped if it is not needed.',
2466
2474
  "plan.deliverables_missing": "Cannot mark step {step} done: the files it names do not exist yet: {files}. Create these files first (or mark the step status=skipped if they are not actually needed).",
2475
+ "plan.kinds_mismatch": "kinds array length must match steps",
2476
+ "plan.kinds_invalid": 'Invalid step kind(s): {kinds}. Use "create" or "delete".',
2477
+ "plan.kinds_not_array": 'kinds must be an array of "create" or "delete"',
2478
+ "plan.deliverables_remain": "Cannot mark step {step} done: the files it names still exist: {files}. This step is kind=delete — delete these files first (or set status=skipped if they should stay).",
2467
2479
  "plan.show_header": "Plan status:",
2468
2480
  "plan.show_empty": "(plan has no steps)",
2469
2481
  "plan.list_header": "Plans:",
@@ -2487,6 +2499,7 @@ Command: {command}`,
2487
2499
  "verify.script_passed": "Script '{script}' passed",
2488
2500
  "verify.script_failed": "Script '{script}' failed: {message}",
2489
2501
  "verify.syntax_error": "Syntax error in: {path}",
2502
+ "lsp.unavailable": "static checks are unavailable (server not found). Verify via the project's own build/test instead.",
2490
2503
  "cli.description": "Micro Models Agent — AI coding agent for small models",
2491
2504
  "cli.init": "Run interactive setup wizard",
2492
2505
  "cli.config_saved": "Configuration saved to ~/.mma/config.json",
@@ -2759,6 +2772,7 @@ Use this knowledge to answer the user's question.`,
2759
2772
  "exec.audit_pass": "[✓] Task complete: {done}/{total} steps done, {files} files verified",
2760
2773
  "exec.audit_pending": "[✗] Task incomplete: {done}/{total} steps done — remaining steps are not marked done",
2761
2774
  "exec.audit_fail": "[✗] Task incomplete: {done}/{total} steps done, {files} files missing",
2775
+ "exec.audit_leftovers": "[✗] Task incomplete: {done}/{total} steps done, {files} files from delete-steps still exist",
2762
2776
  "exec.audit_fail_tests": "[✗] Task incomplete: {done}/{total} steps done, tests FAILING: {failed} failed / {passed} passed — {detail}",
2763
2777
  "exec.audit_fail_typecheck": "[✗] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
2764
2778
  "exec.audit_incomplete": "[⚠ Final audit incomplete: {summary}. Task is NOT finished — continue working. Remaining steps: {steps}]",
@@ -2770,6 +2784,7 @@ Use this knowledge to answer the user's question.`,
2770
2784
  [Hints]
2771
2785
  {hints}`,
2772
2786
  "exec.file_rewrite_warning": "⚠️ File {file} has been rewritten {count} times. Consider a different approach — the current fix strategy is not working.",
2787
+ "exec.forbidden_cmd": 'STOP using "{cmd}" via the bash tool — it is not a native Windows cmd.exe command and has failed repeatedly this session. Use the dedicated tool instead: grep → the grep tool, ls/dir → list_dir, find → glob, rm → delete_file, sed → edit_file, touch → write_file, which → `where`, cp/mv → move_file, diff → read_file. Do NOT call bash for this purpose again.',
2773
2788
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2774
2789
  "hall.short_response": "Response too short or empty",
2775
2790
  "hall.repetitive": "Response too repetitive ({pct}% overlap)",
@@ -2830,6 +2845,7 @@ Use this knowledge to answer the user's question.`,
2830
2845
  "indexer.find_results": `Found {count} matching files:
2831
2846
  {results}`,
2832
2847
  "indexer.no_matches": 'No matching files for "{query}"',
2848
+ "indexer.duplicates": "Duplicate basenames (verify which file is the real source): {names}",
2833
2849
  "indexer.stack_deps": "deps",
2834
2850
  "indexer.stack_dev": "dev",
2835
2851
  "indexer.stack_scripts": "scripts",
@@ -3054,6 +3070,10 @@ var init_ru = __esm(() => {
3054
3070
  "plan.step_already_done": "Шаг {step} уже выполнен — ничего делать не нужно. Прогресс плана:",
3055
3071
  "plan.order_blocked": "Нельзя отметить шаг {step} выполненным: шаг {first} («{desc}») ещё не завершён. Сначала завершите предыдущие шаги или отметьте шаг {first} status=skipped, если он не нужен.",
3056
3072
  "plan.deliverables_missing": "Нельзя отметить шаг {step} выполненным: указанные в нём файлы ещё не существуют: {files}. Сначала создайте эти файлы (или отметьте шаг status=skipped, если они на самом деле не нужны).",
3073
+ "plan.kinds_mismatch": "Длина kinds должна совпадать с числом шагов",
3074
+ "plan.kinds_invalid": 'Недопустимый kind шага: {kinds}. Используй "create" или "delete".',
3075
+ "plan.kinds_not_array": 'kinds должен быть массивом значений "create" или "delete"',
3076
+ "plan.deliverables_remain": "Нельзя пометить шаг {step} как выполненный: указанные файлы всё ещё существуют: {files}. Шаг имеет kind=delete — сначала удали их (или поставь status=skipped, если они должны остаться).",
3057
3077
  "plan.show_header": "Статус плана:",
3058
3078
  "plan.show_empty": "(в плане нет шагов)",
3059
3079
  "plan.list_header": "Планы:",
@@ -3077,6 +3097,7 @@ var init_ru = __esm(() => {
3077
3097
  "verify.script_passed": "Скрипт '{script}' пройден",
3078
3098
  "verify.script_failed": "Скрипт '{script}' не пройден: {message}",
3079
3099
  "verify.syntax_error": "Синтаксическая ошибка в: {path}",
3100
+ "lsp.unavailable": "статические проверки недоступны (сервер не найден). Проверяйте через собственный build/test проекта.",
3080
3101
  "cli.description": "Micro Models Agent — ИИ-агент для кодинга на малых моделях",
3081
3102
  "cli.init": "Запустить мастер настройки",
3082
3103
  "cli.config_saved": "Конфигурация сохранена в ~/.mma/config.json",
@@ -3349,6 +3370,7 @@ var init_ru = __esm(() => {
3349
3370
  "exec.audit_pass": "[✓] Задача выполнена: {done}/{total} шагов, {files} файлов проверено",
3350
3371
  "exec.audit_pending": "[✗] Задача не выполнена: {done}/{total} шагов — оставшиеся шаги не отмечены выполненными",
3351
3372
  "exec.audit_fail": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов отсутствует",
3373
+ "exec.audit_leftovers": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов из delete-шагов всё ещё существуют",
3352
3374
  "exec.audit_fail_tests": "[✗] Задача не выполнена: {done}/{total} шагов, тесты ПАДАЮТ: {failed} failed / {passed} passed — {detail}",
3353
3375
  "exec.audit_fail_typecheck": "[✗] Задача не выполнена: {done}/{total} шагов, {missing} файлов отсутствует, ошибка typecheck: {typeError}",
3354
3376
  "exec.audit_incomplete": "[⚠ Финальная проверка не пройдена: {summary}. Задача НЕ завершена — продолжайте работу. Оставшиеся шаги: {steps}]",
@@ -3360,6 +3382,7 @@ var init_ru = __esm(() => {
3360
3382
  [Подсказки]
3361
3383
  {hints}`,
3362
3384
  "exec.file_rewrite_warning": "⚠️ Файл {file} был перезаписан {count} раз. Попробуйте другой подход — текущая стратегия исправлений не работает.",
3385
+ "exec.forbidden_cmd": 'ПРЕКРАТИ использовать "{cmd}" через bash — это не команда Windows cmd.exe, и она уже неоднократно падала в этой сессии. Используй предназначенный тул: grep → тул grep, ls/dir → list_dir, find → glob, rm → delete_file, sed → edit_file, touch → write_file, which → `where`, cp/mv → move_file, diff → read_file. Больше не вызывай bash для этого.',
3363
3386
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3364
3387
  "hall.short_response": "Слишком короткий или пустой ответ",
3365
3388
  "hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
@@ -3420,6 +3443,7 @@ var init_ru = __esm(() => {
3420
3443
  "indexer.find_results": `Найдено {count} совпадающих файлов:
3421
3444
  {results}`,
3422
3445
  "indexer.no_matches": 'Нет совпадающих файлов для "{query}"',
3446
+ "indexer.duplicates": "Дубликаты basename (проверьте, какой файл реально используется): {names}",
3423
3447
  "indexer.stack_deps": "зависимости",
3424
3448
  "indexer.stack_dev": "dev",
3425
3449
  "indexer.stack_scripts": "скрипты",
@@ -4890,6 +4914,30 @@ var exports_openai_compat = {};
4890
4914
  __export(exports_openai_compat, {
4891
4915
  OpenAICompatProvider: () => OpenAICompatProvider
4892
4916
  });
4917
+ function buildRequestBody(opts) {
4918
+ const body = {
4919
+ model: opts.model,
4920
+ messages: opts.messages,
4921
+ stream: opts.stream
4922
+ };
4923
+ if (opts.maxTokens !== undefined)
4924
+ body.max_tokens = opts.maxTokens;
4925
+ if (opts.reasoningEffort) {
4926
+ body.reasoning_effort = opts.reasoningEffort;
4927
+ }
4928
+ if (opts.tools && opts.tools.length > 0) {
4929
+ body.tools = opts.tools.map((t2) => ({
4930
+ type: "function",
4931
+ function: {
4932
+ name: t2.name,
4933
+ description: t2.description,
4934
+ parameters: t2.parameters
4935
+ }
4936
+ }));
4937
+ body.tool_choice = "auto";
4938
+ }
4939
+ return body;
4940
+ }
4893
4941
 
4894
4942
  class OpenAICompatProvider {
4895
4943
  model;
@@ -4937,26 +4985,14 @@ class OpenAICompatProvider {
4937
4985
  }
4938
4986
  }
4939
4987
  async* doStream(messages, tools, signal, options) {
4940
- const body = {
4988
+ const body = buildRequestBody({
4941
4989
  model: this.model,
4942
4990
  messages,
4991
+ tools,
4943
4992
  stream: true,
4944
- max_tokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096
4945
- };
4946
- if (options?.reasoningEffort) {
4947
- body.reasoning_effort = options.reasoningEffort;
4948
- }
4949
- if (tools && tools.length > 0) {
4950
- body.tools = tools.map((t2) => ({
4951
- type: "function",
4952
- function: {
4953
- name: t2.name,
4954
- description: t2.description,
4955
- parameters: t2.parameters
4956
- }
4957
- }));
4958
- body.tool_choice = "auto";
4959
- }
4993
+ maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
4994
+ reasoningEffort: options?.reasoningEffort
4995
+ });
4960
4996
  const headers = {
4961
4997
  "Content-Type": "application/json"
4962
4998
  };
@@ -5082,26 +5118,14 @@ class OpenAICompatProvider {
5082
5118
  }
5083
5119
  }
5084
5120
  async doNonStreaming(messages, tools, signal, options) {
5085
- const body = {
5121
+ const body = buildRequestBody({
5086
5122
  model: this.model,
5087
5123
  messages,
5124
+ tools,
5088
5125
  stream: false,
5089
- max_tokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096
5090
- };
5091
- if (options?.reasoningEffort) {
5092
- body.reasoning_effort = options.reasoningEffort;
5093
- }
5094
- if (tools && tools.length > 0) {
5095
- body.tools = tools.map((t2) => ({
5096
- type: "function",
5097
- function: {
5098
- name: t2.name,
5099
- description: t2.description,
5100
- parameters: t2.parameters
5101
- }
5102
- }));
5103
- body.tool_choice = "auto";
5104
- }
5126
+ maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5127
+ reasoningEffort: options?.reasoningEffort
5128
+ });
5105
5129
  const headers = {
5106
5130
  "Content-Type": "application/json"
5107
5131
  };
@@ -5351,7 +5375,8 @@ class ToolRegistry {
5351
5375
  return tools.map((t2) => ({
5352
5376
  name: t2.name,
5353
5377
  description: t2.description,
5354
- parameters: t2.parameters
5378
+ parameters: t2.parameters,
5379
+ boundedOutput: t2.boundedOutput
5355
5380
  }));
5356
5381
  }
5357
5382
  }
@@ -6056,6 +6081,7 @@ var init_read_file = __esm(() => {
6056
6081
  name: "read_file",
6057
6082
  description: `Read a file from the filesystem. Reads up to ${DEFAULT_LIMIT} lines at a time by default; use offset to page through large files.`,
6058
6083
  tags: ["file", "code"],
6084
+ boundedOutput: true,
6059
6085
  parameters: {
6060
6086
  type: "object",
6061
6087
  properties: {
@@ -6688,6 +6714,7 @@ var init_list_dir = __esm(() => {
6688
6714
  name: "list_dir",
6689
6715
  description: `List files and directories in a given path. Shows up to ${MAX_PREVIEW_LINES} entries by default.`,
6690
6716
  tags: ["file"],
6717
+ boundedOutput: true,
6691
6718
  parameters: {
6692
6719
  type: "object",
6693
6720
  properties: {
@@ -6932,6 +6959,7 @@ var init_file_info = __esm(() => {
6932
6959
  name: "file_info",
6933
6960
  description: "Get metadata about a file or directory (size, creation date, modification date).",
6934
6961
  tags: ["file"],
6962
+ boundedOutput: true,
6935
6963
  parameters: {
6936
6964
  type: "object",
6937
6965
  properties: {
@@ -7290,7 +7318,7 @@ class ProcessRegistry {
7290
7318
  entry.status = "killed";
7291
7319
  }
7292
7320
  entry.spawnError = err.message;
7293
- append(Buffer.from(`[process error] ${err.message}`));
7321
+ pushLine(`[process error] ${err.message}`);
7294
7322
  resolveExit();
7295
7323
  });
7296
7324
  child.on("close", (code) => {
@@ -7689,19 +7717,19 @@ ${output2}`;
7689
7717
  output2 = `(exit code ${code})`;
7690
7718
  }
7691
7719
  if (platform2() === "win32") {
7692
- const firstWord = command.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
7693
- if (firstWord && firstWord in UNIX_TO_WIN_HINTS) {
7720
+ const originalFirstWord = originalCommand.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
7721
+ if (originalFirstWord && originalFirstWord in UNIX_TO_WIN_HINTS) {
7694
7722
  if (code === 0) {
7695
- FAILING_FIRST_WORDS.delete(firstWord);
7723
+ FAILING_FIRST_WORDS.delete(originalFirstWord);
7696
7724
  } else {
7697
- const failures = (FAILING_FIRST_WORDS.get(firstWord) || 0) + 1;
7698
- FAILING_FIRST_WORDS.set(firstWord, failures);
7725
+ const failures = (FAILING_FIRST_WORDS.get(originalFirstWord) || 0) + 1;
7726
+ FAILING_FIRST_WORDS.set(originalFirstWord, failures);
7699
7727
  if (failures >= HARD_BLOCK_THRESHOLD) {
7700
- output2 = `STOP using "${firstWord}" — it does not work in this cmd.exe shell and has failed ${failures} times in a row. ${UNIX_TO_WIN_HINTS[firstWord]}`;
7728
+ output2 = `STOP using "${originalFirstWord}" — it does not work in this cmd.exe shell and has failed ${failures} times in a row. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
7701
7729
  } else {
7702
7730
  output2 = `${output2}
7703
7731
 
7704
- Hint: "${firstWord}" may not work on Windows. ${UNIX_TO_WIN_HINTS[firstWord]}`;
7732
+ Hint: "${originalFirstWord}" may not work on Windows. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
7705
7733
  }
7706
7734
  }
7707
7735
  }
@@ -7801,6 +7829,7 @@ var init_process_log = __esm(() => {
7801
7829
  name: "process_log",
7802
7830
  description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state.`,
7803
7831
  tags: ["shell"],
7832
+ boundedOutput: true,
7804
7833
  parameters: {
7805
7834
  type: "object",
7806
7835
  properties: {
@@ -9357,13 +9386,14 @@ class StuckDetector {
9357
9386
  if (desc.includes("deploy") || desc.includes("publish")) {
9358
9387
  hints.push("Verify credentials and network access before deploying.");
9359
9388
  }
9360
- if (this.hasRepetitiveToolCalls()) {
9389
+ const primary = this.getPrimaryReason();
9390
+ if (this.hasRepetitiveToolCalls() && primary !== "repetitive") {
9361
9391
  hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
9362
9392
  }
9363
- if (this.hasConsecutiveFailures()) {
9393
+ if (this.hasConsecutiveFailures() && primary !== "consecutive") {
9364
9394
  hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
9365
9395
  }
9366
- if (this.hasReadOnlyLoop()) {
9396
+ if (this.hasReadOnlyLoop() && primary !== "read-only") {
9367
9397
  hints.push("You have made many read-only tool calls (read_file/glob/grep/browser) without writing anything. Stop exploring — make the edit/write the task needs, or use the plan tool to decide next steps.");
9368
9398
  }
9369
9399
  return hints;
@@ -9489,35 +9519,50 @@ class StuckDetector {
9489
9519
  return "";
9490
9520
  }
9491
9521
  getRecoveryMessage() {
9492
- if (this.isStuck()) {
9522
+ const reason = this.getPrimaryReason();
9523
+ if (reason === "stuck") {
9493
9524
  return t("exec.stuck_recovery", {
9494
9525
  iterations: this.iterationsOnCurrentStep,
9495
9526
  stepId: String(this.currentStepId ?? "?"),
9496
9527
  description: this.currentStepDescription
9497
9528
  });
9498
9529
  }
9499
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
9500
- if (errorTool) {
9530
+ if (reason === "tool-errors") {
9531
+ const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
9501
9532
  return t("exec.tool_errors_recovery", {
9502
9533
  tool: errorTool[0],
9503
9534
  count: errorTool[1]
9504
9535
  });
9505
9536
  }
9506
- if (this.hasConsecutiveFailures()) {
9537
+ if (reason === "consecutive") {
9507
9538
  return t("exec.consecutive_failures_recovery", {
9508
9539
  count: this.consecutiveFailures
9509
9540
  });
9510
9541
  }
9511
- if (this.hasRepetitiveToolCalls()) {
9542
+ if (reason === "repetitive") {
9512
9543
  return this.getRepetitiveToolMessage();
9513
9544
  }
9514
- if (this.hasReadOnlyLoop()) {
9545
+ if (reason === "read-only") {
9515
9546
  return t("exec.read_only_loop_recovery", {
9516
9547
  count: String(this.readOnlyStreak)
9517
9548
  });
9518
9549
  }
9519
9550
  return "";
9520
9551
  }
9552
+ getPrimaryReason() {
9553
+ if (this.isStuck())
9554
+ return "stuck";
9555
+ const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
9556
+ if (errorTool)
9557
+ return "tool-errors";
9558
+ if (this.hasConsecutiveFailures())
9559
+ return "consecutive";
9560
+ if (this.hasRepetitiveToolCalls())
9561
+ return "repetitive";
9562
+ if (this.hasReadOnlyLoop())
9563
+ return "read-only";
9564
+ return null;
9565
+ }
9521
9566
  reset() {
9522
9567
  this.currentStepId = null;
9523
9568
  this.iterationsOnCurrentStep = 0;
@@ -9941,9 +9986,31 @@ var init_moe_executor = __esm(() => {
9941
9986
  ];
9942
9987
  });
9943
9988
 
9944
- // src/modules/execution/verifier.ts
9989
+ // src/modules/lsp/project-root.ts
9945
9990
  import { existsSync as existsSync19 } from "fs";
9946
- import { resolve as resolve11, extname as extname2 } from "path";
9991
+ import { dirname as dirname6, join as join10 } from "path";
9992
+ function findProjectRoot(filePath, baseDir, markers) {
9993
+ if (!markers || markers.length === 0)
9994
+ return baseDir;
9995
+ let dir = dirname6(filePath);
9996
+ const root = baseDir.replace(/[\\/]+$/, "");
9997
+ while (true) {
9998
+ if (markers.some((m) => existsSync19(join10(dir, m)))) {
9999
+ return dir;
10000
+ }
10001
+ if (dir === root)
10002
+ return dir;
10003
+ const parent = dirname6(dir);
10004
+ if (parent === dir)
10005
+ return root;
10006
+ dir = parent;
10007
+ }
10008
+ }
10009
+ var init_project_root = () => {};
10010
+
10011
+ // src/modules/execution/verifier.ts
10012
+ import { existsSync as existsSync20 } from "fs";
10013
+ import { resolve as resolve11, extname as extname2, join as join11 } from "path";
9947
10014
  import { spawn as spawn2 } from "child_process";
9948
10015
 
9949
10016
  class StepVerifier {
@@ -9953,7 +10020,7 @@ class StepVerifier {
9953
10020
  }
9954
10021
  async checkFileExists(path) {
9955
10022
  const resolved = resolve11(this.baseDir, path);
9956
- const exists = existsSync19(resolved);
10023
+ const exists = existsSync20(resolved);
9957
10024
  return {
9958
10025
  passed: exists,
9959
10026
  message: exists ? t("verify.file_exists", { path }) : t("verify.file_not_found", { path })
@@ -9969,7 +10036,7 @@ class StepVerifier {
9969
10036
  }
9970
10037
  async runTypeCheck() {
9971
10038
  const tsconfigPath = resolve11(this.baseDir, "tsconfig.json");
9972
- if (!existsSync19(tsconfigPath)) {
10039
+ if (!existsSync20(tsconfigPath)) {
9973
10040
  return { passed: true, message: "No tsconfig.json found — skipping type check" };
9974
10041
  }
9975
10042
  try {
@@ -9980,9 +10047,26 @@ class StepVerifier {
9980
10047
  return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
9981
10048
  }
9982
10049
  }
10050
+ async runTypeCheckForFile(filePath) {
10051
+ const projectRoot = findProjectRoot(filePath, this.baseDir, [
10052
+ "tsconfig.json",
10053
+ "package.json"
10054
+ ]);
10055
+ const tsconfigPath = join11(projectRoot, "tsconfig.json");
10056
+ if (!existsSync20(tsconfigPath)) {
10057
+ return { passed: true, message: "No tsconfig.json found — skipping type check" };
10058
+ }
10059
+ try {
10060
+ await this.runAsync("npx tsc --noEmit --skipLibCheck", projectRoot, 60000);
10061
+ return { passed: true, message: "TypeScript type check passed" };
10062
+ } catch (e) {
10063
+ const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
10064
+ return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
10065
+ }
10066
+ }
9983
10067
  async runTests() {
9984
10068
  const pkgPath = resolve11(this.baseDir, "package.json");
9985
- if (!existsSync19(pkgPath)) {
10069
+ if (!existsSync20(pkgPath)) {
9986
10070
  return { passed: true, message: "No package.json found — skipping tests" };
9987
10071
  }
9988
10072
  try {
@@ -10115,6 +10199,7 @@ class StepVerifier {
10115
10199
  }
10116
10200
  var init_verifier = __esm(() => {
10117
10201
  init_i18n();
10202
+ init_project_root();
10118
10203
  });
10119
10204
 
10120
10205
  // src/core/agent-moe.ts
@@ -10231,132 +10316,7 @@ var init_agent_moe = __esm(() => {
10231
10316
  init_verifier();
10232
10317
  });
10233
10318
 
10234
- // src/modules/memory/search.ts
10235
- import { readFileSync as readFileSync10, existsSync as existsSync20 } from "fs";
10236
- import { join as join10 } from "path";
10237
-
10238
- class MemorySearch {
10239
- memoryDir;
10240
- constructor(memoryDir) {
10241
- this.memoryDir = memoryDir;
10242
- }
10243
- query(query) {
10244
- const results = [];
10245
- const lowerQuery = query.toLowerCase();
10246
- for (const name of MEMORY_FILES) {
10247
- const path = join10(this.memoryDir, `${name}.md`);
10248
- if (!existsSync20(path))
10249
- continue;
10250
- const content = readFileSync10(path, "utf-8");
10251
- const lines = content.split(`
10252
- `);
10253
- for (const line of lines) {
10254
- if (line.toLowerCase().includes(lowerQuery)) {
10255
- results.push({ file: name, match: line.trim() });
10256
- }
10257
- }
10258
- }
10259
- const prefsPath = join10(this.memoryDir, "preferences.json");
10260
- if (existsSync20(prefsPath)) {
10261
- try {
10262
- const prefs = JSON.parse(readFileSync10(prefsPath, "utf-8"));
10263
- for (const [key, value] of Object.entries(prefs)) {
10264
- const searchStr = `${key}=${value}`;
10265
- if (searchStr.toLowerCase().includes(lowerQuery)) {
10266
- results.push({ file: "preferences", match: `${key} = ${value}` });
10267
- }
10268
- }
10269
- } catch {}
10270
- }
10271
- return results;
10272
- }
10273
- }
10274
- var MEMORY_FILES;
10275
- var init_search = __esm(() => {
10276
- MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
10277
- });
10278
-
10279
- // src/modules/memory/store.ts
10280
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, appendFileSync as appendFileSync5, existsSync as existsSync21, mkdirSync as mkdirSync12 } from "fs";
10281
- import { join as join11 } from "path";
10282
-
10283
- class MemoryStore {
10284
- memoryDir;
10285
- constructor(memoryDir) {
10286
- this.memoryDir = memoryDir;
10287
- this.ensureDir();
10288
- for (const name of MEMORY_FILES2) {
10289
- const path = join11(this.memoryDir, `${name}.md`);
10290
- if (!existsSync21(path)) {
10291
- writeFileSync8(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
10292
-
10293
- `, "utf-8");
10294
- }
10295
- }
10296
- }
10297
- ensureDir() {
10298
- if (!existsSync21(this.memoryDir)) {
10299
- mkdirSync12(this.memoryDir, { recursive: true });
10300
- }
10301
- }
10302
- read(name) {
10303
- const path = join11(this.memoryDir, `${name}.md`);
10304
- if (!existsSync21(path))
10305
- return "";
10306
- return readFileSync11(path, "utf-8");
10307
- }
10308
- append(name, entry) {
10309
- const path = join11(this.memoryDir, `${name}.md`);
10310
- const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
10311
- const formatted = `- **${timestamp}** — ${entry}
10312
- `;
10313
- appendFileSync5(path, formatted, "utf-8");
10314
- }
10315
- search(query) {
10316
- const searchModule = new MemorySearch(this.memoryDir);
10317
- return searchModule.query(query);
10318
- }
10319
- prefsPath() {
10320
- return join11(this.memoryDir, "preferences.json");
10321
- }
10322
- getPreferences() {
10323
- const path = this.prefsPath();
10324
- if (!existsSync21(path))
10325
- return {};
10326
- try {
10327
- return JSON.parse(readFileSync11(path, "utf-8"));
10328
- } catch {
10329
- return {};
10330
- }
10331
- }
10332
- setPreference(key, value) {
10333
- const prefs = this.getPreferences();
10334
- prefs[key] = value;
10335
- writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
10336
- }
10337
- deletePreference(key) {
10338
- const prefs = this.getPreferences();
10339
- if (!(key in prefs))
10340
- return false;
10341
- delete prefs[key];
10342
- writeFileSync8(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
10343
- return true;
10344
- }
10345
- appendRule(category, pattern, cause, solution) {
10346
- const entry = `**pattern:** ${pattern}
10347
- **cause:** ${cause}
10348
- **solution:** ${solution}`;
10349
- this.append(category, entry);
10350
- }
10351
- }
10352
- var MEMORY_FILES2;
10353
- var init_store2 = __esm(() => {
10354
- init_search();
10355
- MEMORY_FILES2 = ["conventions", "decisions", "errors", "facts"];
10356
- });
10357
-
10358
10319
  // src/core/agent.ts
10359
- import { join as join12 } from "path";
10360
10320
  function isToolCallJson(text) {
10361
10321
  const trimmed = text.trim();
10362
10322
  if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
@@ -10369,6 +10329,12 @@ function isToolCallJson(text) {
10369
10329
  }
10370
10330
  return false;
10371
10331
  }
10332
+ function toolOutputCharLimit(remainingBudget, historyBudget, bounded) {
10333
+ if (bounded)
10334
+ return Number.MAX_SAFE_INTEGER;
10335
+ const maxCharsByRatio = Math.floor(historyBudget * TOOL_RESULT_MAX_TOKENS_RATIO * 2);
10336
+ return Math.min(remainingBudget, maxCharsByRatio, TOOL_RESULT_ABSOLUTE_MAX_CHARS);
10337
+ }
10372
10338
 
10373
10339
  class Agent {
10374
10340
  deps;
@@ -10432,11 +10398,9 @@ class Agent {
10432
10398
  this.deps.contextManager.updateSystemPrompt?.(prompt);
10433
10399
  }
10434
10400
  }
10435
- truncateToolOutput(output, budget, currentTokens) {
10436
- const remainingBudget = budget.history - currentTokens;
10437
- const maxCharsByBudget = Math.floor(remainingBudget * 0.5 * 2);
10438
- const maxCharsByRatio = Math.floor(budget.history * TOOL_RESULT_MAX_TOKENS_RATIO * 2);
10439
- const maxChars = Math.min(maxCharsByBudget, maxCharsByRatio, TOOL_RESULT_ABSOLUTE_MAX_CHARS);
10401
+ truncateToolOutput(output, budget, currentTokens, bounded) {
10402
+ const remainingBudget = Math.max(0, budget.history - currentTokens);
10403
+ const maxChars = toolOutputCharLimit(remainingBudget, budget.history, bounded);
10440
10404
  if (output.length <= maxChars)
10441
10405
  return output;
10442
10406
  const truncated = output.slice(0, maxChars);
@@ -10546,6 +10510,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
10546
10510
  let repeatedToolCount = 0;
10547
10511
  const MAX_REPEATED_TOOL_CALLS = 2;
10548
10512
  const allToolsForBudget = toolExecutor.getToolDefinitions(this.deps.toolTags);
10513
+ const boundedToolNames = new Set(allToolsForBudget.filter((t2) => t2.boundedOutput).map((t2) => t2.name));
10549
10514
  const toolTokenEstimate = allToolsForBudget.reduce((sum, t2) => sum + Math.ceil((t2.description.length + JSON.stringify(t2.parameters).length) / 4), 0);
10550
10515
  contextManager.setToolTokens(toolTokenEstimate);
10551
10516
  while (iteration < config.maxToolIterations && !this.shutdownRequested) {
@@ -10748,7 +10713,7 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
10748
10713
  }
10749
10714
  const currentTokens2 = contextManager.getEstimatedTokens();
10750
10715
  const budget3 = contextManager.getBudget();
10751
- const truncatedOutput = this.truncateToolOutput(result.output, budget3, currentTokens2);
10716
+ const truncatedOutput = this.truncateToolOutput(result.output, budget3, currentTokens2, boundedToolNames.has(call.name));
10752
10717
  contextManager.addMessage({
10753
10718
  role: "tool",
10754
10719
  content: truncatedOutput,
@@ -10782,14 +10747,10 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
10782
10747
  toolFailureCounts.set(key, prev);
10783
10748
  if (prev.count >= MIN_REPEATED_TOOL_FAILURES && !memoryRuleRecorded.has(key)) {
10784
10749
  memoryRuleRecorded.add(key);
10785
- if (sessionManager) {
10786
- const activeSession = sessionManager.getActiveMeta();
10787
- if (activeSession) {
10788
- const memDir = join12(baseDir, ".mma", "memory");
10789
- const memStore = new MemoryStore(memDir);
10790
- memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.error})`, `Repeated ${key} failures suggest a systemic problem (config, environment, or a broken tool), not a one-off`, "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool");
10791
- logger.warn(`Recorded repeated ${key} failures to memory (${prev.count}x)`);
10792
- }
10750
+ const memStore = this.deps.memoryStore;
10751
+ if (memStore) {
10752
+ memStore.appendRule("errors", `Tool ${key} failed ${prev.count}x (${prev.error})`, `Repeated ${key} failures suggest a systemic problem (config, environment, or a broken tool), not a one-off`, "Check the error message, verify the tool's dependencies are installed/configured, and consider a different tool");
10753
+ logger.warn(`Recorded repeated ${key} failures to memory (${prev.count}x)`);
10793
10754
  }
10794
10755
  }
10795
10756
  }
@@ -10811,13 +10772,9 @@ ${t("tool.truncated", { tokens: Math.ceil(removedChars / 2) })}`;
10811
10772
  content: `<system-summary>${recoveryMsg}
10812
10773
  ${taskReminder}</system-summary>`
10813
10774
  });
10814
- if (sessionManager) {
10815
- const activeSession = sessionManager.getActiveMeta();
10816
- if (activeSession) {
10817
- const memDir = join12(baseDir, ".mma", "memory");
10818
- const memStore = new MemoryStore(memDir);
10819
- memStore.appendRule("errors", `${consecutiveToolFailures} consecutive tool failures`, "Multiple tools failing suggests environment or configuration issue", "Check dependencies, verify file paths, try write_file directly instead of shell commands");
10820
- }
10775
+ const memStore = this.deps.memoryStore;
10776
+ if (memStore) {
10777
+ memStore.appendRule("errors", `${consecutiveToolFailures} consecutive tool failures`, "Multiple tools failing suggests environment or configuration issue", "Check dependencies, verify file paths, try write_file directly instead of shell commands");
10821
10778
  }
10822
10779
  }
10823
10780
  contextManager.addMessage({
@@ -11077,10 +11034,108 @@ var init_agent = __esm(() => {
11077
11034
  init_processes();
11078
11035
  init_session_logger();
11079
11036
  init_agent_moe();
11080
- init_store2();
11081
11037
  init_verifier();
11082
11038
  });
11083
11039
 
11040
+ // src/modules/context/fact-extractor.ts
11041
+ class FactExtractor {
11042
+ fileFacts = [];
11043
+ deletedFacts = [];
11044
+ decisionFacts = [];
11045
+ errorFacts = [];
11046
+ static MAX_FACTS = 20;
11047
+ get errorCount() {
11048
+ return this.errorFacts.length;
11049
+ }
11050
+ extract(turns) {
11051
+ const DRIVE = "(?:[a-zA-Z]:[\\\\/])?";
11052
+ const createdPatterns = [
11053
+ new RegExp(`(?:Created|Updated|Written|Moved|Replaced in) ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11054
+ new RegExp(`Файл (?:создан|обновлён|записан|перемещён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11055
+ new RegExp(`file (?:created|updated|written|moved):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
11056
+ ];
11057
+ const deletedPatterns = [
11058
+ new RegExp(`Deleted ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11059
+ new RegExp(`(?:Удалён|Файл удалён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
11060
+ ];
11061
+ const newFiles = [];
11062
+ const newDeleted = [];
11063
+ const newDecisions = [];
11064
+ const newErrors = [];
11065
+ const lastEvent = new Map;
11066
+ for (const msg of turns) {
11067
+ const content = getMessageText(msg.content);
11068
+ if (msg.role === "user" && (content.startsWith("<system-summary>") || content.includes("[Compressed:"))) {
11069
+ continue;
11070
+ }
11071
+ if (msg.role === "tool") {
11072
+ for (const pattern of createdPatterns) {
11073
+ for (const match of content.matchAll(pattern)) {
11074
+ newFiles.push(match[1]);
11075
+ lastEvent.set(match[1], "file");
11076
+ }
11077
+ }
11078
+ for (const pattern of deletedPatterns) {
11079
+ for (const match of content.matchAll(pattern)) {
11080
+ newDeleted.push(match[1]);
11081
+ lastEvent.set(match[1], "deleted");
11082
+ }
11083
+ }
11084
+ if (content.includes("Plan:") && content.includes("[")) {
11085
+ const planLine = content.split(`
11086
+ `).find((line) => line.includes("Plan:"));
11087
+ if (planLine)
11088
+ newDecisions.push(planLine.trim());
11089
+ }
11090
+ }
11091
+ if (msg.role === "assistant") {
11092
+ if (content.includes("decided:") || content.includes("decision:")) {
11093
+ const line = content.split(`
11094
+ `).find((l) => l.includes("decided:") || l.includes("decision:"));
11095
+ if (line)
11096
+ newDecisions.push(line.trim().slice(0, 200));
11097
+ }
11098
+ }
11099
+ if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
11100
+ const line = content.split(`
11101
+ `).find((l) => l.includes("Error:") || l.includes("failed") || l.includes("Ошибка:") || l.includes("не удалось"));
11102
+ if (line)
11103
+ newErrors.push(line.trim().slice(0, 250));
11104
+ }
11105
+ }
11106
+ const dedup = (arr) => [...new Set(arr)];
11107
+ const finalFiles = dedup(newFiles.filter((p) => lastEvent.get(p) === "file"));
11108
+ const finalDeleted = dedup(newDeleted.filter((p) => lastEvent.get(p) === "deleted"));
11109
+ this.fileFacts = dedup([...this.fileFacts, ...finalFiles].filter((p) => !finalDeleted.includes(p))).slice(-FactExtractor.MAX_FACTS);
11110
+ this.deletedFacts = dedup([...this.deletedFacts, ...finalDeleted].filter((p) => !finalFiles.includes(p))).slice(-FactExtractor.MAX_FACTS);
11111
+ this.decisionFacts = dedup([...this.decisionFacts, ...newDecisions]).slice(-FactExtractor.MAX_FACTS);
11112
+ this.errorFacts = dedup([...this.errorFacts, ...newErrors]).slice(-FactExtractor.MAX_FACTS);
11113
+ }
11114
+ filesLine() {
11115
+ const deletedSet = new Set(this.deletedFacts);
11116
+ const liveFiles = this.fileFacts.filter((p) => !deletedSet.has(p));
11117
+ if (liveFiles.length === 0)
11118
+ return null;
11119
+ return `[Files: ${liveFiles.slice(-15).join("; ")}]`;
11120
+ }
11121
+ deletedLine() {
11122
+ if (this.deletedFacts.length === 0)
11123
+ return null;
11124
+ return `[Deleted: ${this.deletedFacts.slice(-10).join("; ")}]`;
11125
+ }
11126
+ decisionsLine() {
11127
+ if (this.decisionFacts.length === 0)
11128
+ return null;
11129
+ return `[Decisions: ${this.decisionFacts.slice(-5).join("; ")}]`;
11130
+ }
11131
+ errorsLine() {
11132
+ if (this.errorFacts.length === 0)
11133
+ return null;
11134
+ return `[Errors: ${this.errorFacts.slice(-3).join("; ")}]`;
11135
+ }
11136
+ }
11137
+ var init_fact_extractor = () => {};
11138
+
11084
11139
  // src/modules/context/manager.ts
11085
11140
  function summarizeArgs(args) {
11086
11141
  if (!args)
@@ -11099,6 +11154,18 @@ function truncate(s, max) {
11099
11154
  return s;
11100
11155
  return s.slice(0, max - 3) + "...";
11101
11156
  }
11157
+ function looksLikeErrorPaste(text) {
11158
+ if (/(?:^|\s)ERROR|Error:|error TS\d+|Transform failed|\[plugin:|SyntaxError|Cannot find|Uncaught|exception/i.test(text)) {
11159
+ return true;
11160
+ }
11161
+ if (/(?:^|\n)\s*\d+\s*\|/.test(text))
11162
+ return true;
11163
+ if (/[\w./\\-]+\.[a-z]{1,6}:\d+:\d+/.test(text))
11164
+ return true;
11165
+ if (/(?:^|\n)\s*at\s+\S+/.test(text))
11166
+ return true;
11167
+ return false;
11168
+ }
11102
11169
  function extractTriedAndFailed(messages) {
11103
11170
  const failures = new Map;
11104
11171
  for (const msg of messages) {
@@ -11130,11 +11197,10 @@ class ContextManager {
11130
11197
  peakTokens = 0;
11131
11198
  budget;
11132
11199
  compactionThreshold;
11133
- fileFacts = [];
11134
- decisionFacts = [];
11135
- errorFacts = [];
11200
+ facts = new FactExtractor;
11136
11201
  lastUserTask = "";
11137
- static MAX_FACTS = 20;
11202
+ sessionMission = "";
11203
+ lastUserFeedback = "";
11138
11204
  tokenCounter;
11139
11205
  pendingImageParts = [];
11140
11206
  toolTokens = 0;
@@ -11173,6 +11239,7 @@ class ContextManager {
11173
11239
  resetUserTurn() {
11174
11240
  this.iterationsSinceCompaction = 0;
11175
11241
  this.lastUserTask = "";
11242
+ this.lastUserFeedback = "";
11176
11243
  }
11177
11244
  getQuality() {
11178
11245
  const usedTokens = this.getEstimatedTokens();
@@ -11180,16 +11247,22 @@ class ContextManager {
11180
11247
  const COMPACTION_PENALTY_FLOOR = 0.5;
11181
11248
  const compactionLoss = Math.max(COMPACTION_PENALTY_FLOOR, 1 - this.compactionCount * 0.15);
11182
11249
  const msgCount = this.messages.length || 1;
11183
- const errorDensity = Math.max(0, 1 - Math.min(1, this.errorFacts.length / msgCount));
11250
+ const errorDensity = Math.max(0, 1 - Math.min(1, this.facts.errorCount / msgCount));
11184
11251
  const freshness = Math.max(0, 1 - this.iterationsSinceCompaction / COMPACTION_INTERVAL);
11185
11252
  const score = tokenLoad * 0.4 + compactionLoss * 0.25 + errorDensity * 0.2 + freshness * 0.15;
11186
11253
  return Math.round(Math.min(100, Math.max(0, score * 100)));
11187
11254
  }
11188
11255
  addMessage(msg) {
11189
- if (msg.role === "user" && !this.lastUserTask) {
11256
+ if (msg.role === "user") {
11190
11257
  const text = typeof msg.content === "string" ? msg.content : getMessageText(msg.content);
11191
11258
  if (text.trim() && !text.startsWith("<system-summary>") && !text.includes("[Compressed:")) {
11192
- this.lastUserTask = text.trim();
11259
+ if (looksLikeErrorPaste(text) && (this.lastUserTask || this.sessionMission)) {
11260
+ this.lastUserFeedback = text.trim().slice(0, 400);
11261
+ } else if (!this.lastUserTask && !this.lastUserFeedback) {
11262
+ this.lastUserTask = text.trim();
11263
+ if (!this.sessionMission)
11264
+ this.sessionMission = text.trim();
11265
+ }
11193
11266
  }
11194
11267
  }
11195
11268
  if (msg.role === "user" && this.pendingImageParts.length > 0) {
@@ -11278,22 +11351,27 @@ class ContextManager {
11278
11351
  const cutoff = this.messages.length - KEEP_LAST_N * 2;
11279
11352
  const oldTurns = this.messages.slice(0, cutoff);
11280
11353
  const recentTurns = this.messages.slice(cutoff);
11281
- this.extractFacts(oldTurns);
11354
+ this.facts.extract(oldTurns);
11282
11355
  const parts = [];
11283
11356
  parts.push(`[Compressed: ${oldTurns.length} old turns removed]`);
11284
- if (this.lastUserTask) {
11285
- const task = this.lastUserTask.slice(0, 400);
11357
+ const task = (this.lastUserTask || this.sessionMission).slice(0, 400);
11358
+ if (task)
11286
11359
  parts.push(`[Task: ${task}]`);
11287
- }
11288
- if (this.fileFacts.length > 0) {
11289
- parts.push(`[Files: ${this.fileFacts.slice(-15).join("; ")}]`);
11290
- }
11291
- if (this.decisionFacts.length > 0) {
11292
- parts.push(`[Decisions: ${this.decisionFacts.slice(-5).join("; ")}]`);
11293
- }
11294
- if (this.errorFacts.length > 0) {
11295
- parts.push(`[Errors: ${this.errorFacts.slice(-3).join("; ")}]`);
11296
- }
11360
+ if (this.lastUserFeedback && !task.startsWith(this.lastUserFeedback)) {
11361
+ parts.push(`[User feedback: ${this.lastUserFeedback}]`);
11362
+ }
11363
+ const filesLine = this.facts.filesLine();
11364
+ if (filesLine)
11365
+ parts.push(filesLine);
11366
+ const deletedLine = this.facts.deletedLine();
11367
+ if (deletedLine)
11368
+ parts.push(deletedLine);
11369
+ const decisionsLine = this.facts.decisionsLine();
11370
+ if (decisionsLine)
11371
+ parts.push(decisionsLine);
11372
+ const errorsLine = this.facts.errorsLine();
11373
+ if (errorsLine)
11374
+ parts.push(errorsLine);
11297
11375
  const triedAndFailed = extractTriedAndFailed(oldTurns);
11298
11376
  if (triedAndFailed.length > 0) {
11299
11377
  const lines = triedAndFailed.map((t2) => `- ${t2.tool}(${t2.args}): ${t2.error} (failed ${t2.count}x)`);
@@ -11323,55 +11401,6 @@ ${lines.join(`
11323
11401
  this.onCompact(summary);
11324
11402
  }
11325
11403
  }
11326
- extractFacts(turns) {
11327
- const DRIVE = "(?:[a-zA-Z]:[\\\\/])?";
11328
- const filePatterns = [
11329
- new RegExp(`(?:Created|Updated|Written|Deleted|Moved) ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11330
- new RegExp(`Replaced in ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11331
- new RegExp(`Файл (?:создан|обновлён|записан|удалён|перемещён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11332
- new RegExp(`file (?:created|updated|written|deleted|moved):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
11333
- ];
11334
- const newFiles = [];
11335
- const newDecisions = [];
11336
- const newErrors = [];
11337
- for (const msg of turns) {
11338
- const content = getMessageText(msg.content);
11339
- if (msg.role === "user" && (content.startsWith("<system-summary>") || content.includes("[Compressed:"))) {
11340
- continue;
11341
- }
11342
- if (msg.role === "tool") {
11343
- for (const pattern of filePatterns) {
11344
- for (const match of content.matchAll(pattern)) {
11345
- newFiles.push(match[1]);
11346
- }
11347
- }
11348
- if (content.includes("Plan:") && content.includes("[")) {
11349
- const planLine = content.split(`
11350
- `).find((line) => line.includes("Plan:"));
11351
- if (planLine)
11352
- newDecisions.push(planLine.trim());
11353
- }
11354
- }
11355
- if (msg.role === "assistant") {
11356
- if (content.includes("decided:") || content.includes("decision:")) {
11357
- const line = content.split(`
11358
- `).find((l) => l.includes("decided:") || l.includes("decision:"));
11359
- if (line)
11360
- newDecisions.push(line.trim().slice(0, 200));
11361
- }
11362
- }
11363
- if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
11364
- const line = content.split(`
11365
- `).find((l) => l.includes("Error:") || l.includes("failed") || l.includes("Ошибка:") || l.includes("не удалось"));
11366
- if (line)
11367
- newErrors.push(line.trim().slice(0, 250));
11368
- }
11369
- }
11370
- const dedup = (arr) => [...new Set(arr)];
11371
- this.fileFacts = dedup([...this.fileFacts, ...newFiles]).slice(-ContextManager.MAX_FACTS);
11372
- this.decisionFacts = dedup([...this.decisionFacts, ...newDecisions]).slice(-ContextManager.MAX_FACTS);
11373
- this.errorFacts = dedup([...this.errorFacts, ...newErrors]).slice(-ContextManager.MAX_FACTS);
11374
- }
11375
11404
  updateSystemPrompt(content) {
11376
11405
  const idx = this.messages.findIndex((m) => m.role === "system");
11377
11406
  if (idx >= 0) {
@@ -11390,6 +11419,8 @@ ${lines.join(`
11390
11419
  this.compactionCount = 0;
11391
11420
  this.peakTokens = 0;
11392
11421
  this.lastUserTask = "";
11422
+ this.lastUserFeedback = "";
11423
+ this.sessionMission = "";
11393
11424
  }
11394
11425
  getEstimatedTokens() {
11395
11426
  return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0) + this.toolTokens;
@@ -11406,7 +11437,9 @@ ${lines.join(`
11406
11437
  }
11407
11438
  }
11408
11439
  var COMPACTION_INTERVAL = 15, KEEP_LAST_N = 6;
11409
- var init_manager = () => {};
11440
+ var init_manager = __esm(() => {
11441
+ init_fact_extractor();
11442
+ });
11410
11443
 
11411
11444
  // src/modules/hallucination/consistency.ts
11412
11445
  class ConsistencyCheck {
@@ -11517,6 +11550,10 @@ function isFilePathLike(s) {
11517
11550
  return false;
11518
11551
  return FILE_EXTENSIONS.has(ext);
11519
11552
  }
11553
+ const last = s.split(/[\\/]/).pop() ?? "";
11554
+ const lastDot = last.lastIndexOf(".");
11555
+ if (lastDot <= 0)
11556
+ return false;
11520
11557
  return true;
11521
11558
  }
11522
11559
  function extractFileLikeTokens(text) {
@@ -11705,8 +11742,8 @@ var init_js_identifiers = __esm(() => {
11705
11742
  });
11706
11743
 
11707
11744
  // src/modules/hallucination/factual.ts
11708
- import { existsSync as existsSync22, readdirSync as readdirSync5 } from "fs";
11709
- import { resolve as resolve12, isAbsolute as isAbsolute2, join as join13 } from "path";
11745
+ import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
11746
+ import { resolve as resolve12, isAbsolute as isAbsolute2, join as join12 } from "path";
11710
11747
 
11711
11748
  class FactualCheck {
11712
11749
  baseDir;
@@ -11741,14 +11778,14 @@ class FactualCheck {
11741
11778
  }
11742
11779
  pathExists(fp) {
11743
11780
  if (isAbsolute2(fp))
11744
- return existsSync22(fp);
11781
+ return existsSync21(fp);
11745
11782
  if (!fp.includes("/") && !fp.includes("\\")) {
11746
11783
  return this.bareNameExists(fp);
11747
11784
  }
11748
- return existsSync22(resolve12(this.baseDir, fp));
11785
+ return existsSync21(resolve12(this.baseDir, fp));
11749
11786
  }
11750
11787
  bareNameExists(name) {
11751
- if (existsSync22(resolve12(this.baseDir, name)))
11788
+ if (existsSync21(resolve12(this.baseDir, name)))
11752
11789
  return true;
11753
11790
  if (this.indexHas(name))
11754
11791
  return true;
@@ -11788,7 +11825,7 @@ class FactualCheck {
11788
11825
  for (const entry of entries) {
11789
11826
  if (count >= FactualCheck.MAX_INDEXED_FILES)
11790
11827
  break;
11791
- const full = join13(dir, entry.name);
11828
+ const full = join12(dir, entry.name);
11792
11829
  if (entry.isDirectory()) {
11793
11830
  if (!IGNORED_DIRS.has(entry.name)) {
11794
11831
  count = this.scanDir(full, index, count);
@@ -12418,7 +12455,7 @@ ${joined}` }
12418
12455
  var DEFAULT_CHUNK_SYSTEM_PROMPT = 'Answer the query using ONLY the provided text. Be concise. If the text does not contain the answer, say "NO_EVIDENCE".', DEFAULT_SYNTHESIS_SYSTEM_PROMPT = "You are given a query and per-chunk answers over a large text. Produce the final answer to the query, combining evidence from the chunks. If no chunk had evidence, say so.";
12419
12456
 
12420
12457
  // src/tools/chunk-query.ts
12421
- import { readFileSync as readFileSync12 } from "node:fs";
12458
+ import { readFileSync as readFileSync10 } from "node:fs";
12422
12459
  import { resolve as resolve14 } from "node:path";
12423
12460
  var chunkQueryTool;
12424
12461
  var init_chunk_query = __esm(() => {
@@ -12487,7 +12524,7 @@ var init_chunk_query = __esm(() => {
12487
12524
  return { success: false, output: `[SCOPE] ${check.reason || "Path not allowed"}` };
12488
12525
  }
12489
12526
  try {
12490
- content = readFileSync12(resolve14(ctx.baseDir, inputPath), "utf8");
12527
+ content = readFileSync10(resolve14(ctx.baseDir, inputPath), "utf8");
12491
12528
  } catch (e) {
12492
12529
  return { success: false, output: `Cannot read ${inputPath}: ${e.message}` };
12493
12530
  }
@@ -12836,8 +12873,8 @@ var init_web_browse = __esm(() => {
12836
12873
  });
12837
12874
 
12838
12875
  // src/tools/download-file.ts
12839
- import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync13 } from "fs";
12840
- import { dirname as dirname6 } from "path";
12876
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync12 } from "fs";
12877
+ import { dirname as dirname8 } from "path";
12841
12878
  var MAX_DOWNLOAD_BYTES, downloadFileTool;
12842
12879
  var init_download_file = __esm(() => {
12843
12880
  init_i18n();
@@ -12924,8 +12961,8 @@ var init_download_file = __esm(() => {
12924
12961
  output: t("tool.download_too_large", { max: String(maxBytes) })
12925
12962
  };
12926
12963
  }
12927
- mkdirSync13(dirname6(resolved), { recursive: true });
12928
- writeFileSync9(resolved, buffer);
12964
+ mkdirSync12(dirname8(resolved), { recursive: true });
12965
+ writeFileSync8(resolved, buffer);
12929
12966
  const contentType = response.headers.get("content-type")?.split(";")[0]?.trim() || "unknown";
12930
12967
  logNetworkRequest(ctx.sessionId, sanitizeUrl(url), true, `Status: ${response.status}`);
12931
12968
  logFileWrite(ctx.sessionId, resolved, true, `Downloaded ${buffer.byteLength} bytes`);
@@ -13760,7 +13797,7 @@ ${JSON.stringify(result, null, 2)}`
13760
13797
 
13761
13798
  // src/tools/search-history.ts
13762
13799
  import * as fs from "fs";
13763
- import { join as join14 } from "path";
13800
+ import { join as join13 } from "path";
13764
13801
  import { homedir as homedir5 } from "os";
13765
13802
  function searchFile(filePath, query, maxResults, results) {
13766
13803
  if (!fs.existsSync(filePath))
@@ -13804,7 +13841,7 @@ var init_search_history = __esm(() => {
13804
13841
  const query = String(args.query || "").toLowerCase();
13805
13842
  const maxResults = Number(args.maxResults) || 5;
13806
13843
  const sessionId = args.sessionId ? String(args.sessionId) : null;
13807
- const sessionDir = join14(homedir5(), ".mma", "sessions");
13844
+ const sessionDir = join13(homedir5(), ".mma", "sessions");
13808
13845
  const results = [];
13809
13846
  try {
13810
13847
  if (!fs.existsSync(sessionDir)) {
@@ -13819,7 +13856,7 @@ var init_search_history = __esm(() => {
13819
13856
  continue;
13820
13857
  if (sessionId && entry.name !== sessionId)
13821
13858
  continue;
13822
- const historyFile = join14(sessionDir, entry.name, "history.jsonl");
13859
+ const historyFile = join13(sessionDir, entry.name, "history.jsonl");
13823
13860
  searchFile(historyFile, query, maxResults, results);
13824
13861
  if (results.length >= maxResults)
13825
13862
  break;
@@ -13845,9 +13882,133 @@ var init_search_history = __esm(() => {
13845
13882
  };
13846
13883
  });
13847
13884
 
13885
+ // src/modules/memory/search.ts
13886
+ import { readFileSync as readFileSync12, existsSync as existsSync23 } from "fs";
13887
+ import { join as join14 } from "path";
13888
+
13889
+ class MemorySearch {
13890
+ memoryDir;
13891
+ constructor(memoryDir) {
13892
+ this.memoryDir = memoryDir;
13893
+ }
13894
+ query(query) {
13895
+ const results = [];
13896
+ const lowerQuery = query.toLowerCase();
13897
+ for (const name of MEMORY_FILES) {
13898
+ const path = join14(this.memoryDir, `${name}.md`);
13899
+ if (!existsSync23(path))
13900
+ continue;
13901
+ const content = readFileSync12(path, "utf-8");
13902
+ const lines = content.split(`
13903
+ `);
13904
+ for (const line of lines) {
13905
+ if (line.toLowerCase().includes(lowerQuery)) {
13906
+ results.push({ file: name, match: line.trim() });
13907
+ }
13908
+ }
13909
+ }
13910
+ const prefsPath = join14(this.memoryDir, "preferences.json");
13911
+ if (existsSync23(prefsPath)) {
13912
+ try {
13913
+ const prefs = JSON.parse(readFileSync12(prefsPath, "utf-8"));
13914
+ for (const [key, value] of Object.entries(prefs)) {
13915
+ const searchStr = `${key}=${value}`;
13916
+ if (searchStr.toLowerCase().includes(lowerQuery)) {
13917
+ results.push({ file: "preferences", match: `${key} = ${value}` });
13918
+ }
13919
+ }
13920
+ } catch {}
13921
+ }
13922
+ return results;
13923
+ }
13924
+ }
13925
+ var MEMORY_FILES;
13926
+ var init_search = __esm(() => {
13927
+ MEMORY_FILES = ["conventions", "decisions", "errors", "facts"];
13928
+ });
13929
+
13930
+ // src/modules/memory/store.ts
13931
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync24, mkdirSync as mkdirSync13 } from "fs";
13932
+ import { join as join15 } from "path";
13933
+
13934
+ class MemoryStore {
13935
+ memoryDir;
13936
+ constructor(memoryDir) {
13937
+ this.memoryDir = memoryDir;
13938
+ this.ensureDir();
13939
+ for (const name of MEMORY_FILES2) {
13940
+ const path = join15(this.memoryDir, `${name}.md`);
13941
+ if (!existsSync24(path)) {
13942
+ writeFileSync9(path, `# ${name.charAt(0).toUpperCase() + name.slice(1)}
13943
+
13944
+ `, "utf-8");
13945
+ }
13946
+ }
13947
+ }
13948
+ ensureDir() {
13949
+ if (!existsSync24(this.memoryDir)) {
13950
+ mkdirSync13(this.memoryDir, { recursive: true });
13951
+ }
13952
+ }
13953
+ read(name) {
13954
+ const path = join15(this.memoryDir, `${name}.md`);
13955
+ if (!existsSync24(path))
13956
+ return "";
13957
+ return readFileSync13(path, "utf-8");
13958
+ }
13959
+ append(name, entry) {
13960
+ const path = join15(this.memoryDir, `${name}.md`);
13961
+ const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
13962
+ const formatted = `- **${timestamp}** — ${entry}
13963
+ `;
13964
+ appendFileSync5(path, formatted, "utf-8");
13965
+ }
13966
+ search(query) {
13967
+ const searchModule = new MemorySearch(this.memoryDir);
13968
+ return searchModule.query(query);
13969
+ }
13970
+ prefsPath() {
13971
+ return join15(this.memoryDir, "preferences.json");
13972
+ }
13973
+ getPreferences() {
13974
+ const path = this.prefsPath();
13975
+ if (!existsSync24(path))
13976
+ return {};
13977
+ try {
13978
+ return JSON.parse(readFileSync13(path, "utf-8"));
13979
+ } catch {
13980
+ return {};
13981
+ }
13982
+ }
13983
+ setPreference(key, value) {
13984
+ const prefs = this.getPreferences();
13985
+ prefs[key] = value;
13986
+ writeFileSync9(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
13987
+ }
13988
+ deletePreference(key) {
13989
+ const prefs = this.getPreferences();
13990
+ if (!(key in prefs))
13991
+ return false;
13992
+ delete prefs[key];
13993
+ writeFileSync9(this.prefsPath(), JSON.stringify(prefs, null, 2), "utf-8");
13994
+ return true;
13995
+ }
13996
+ appendRule(category, pattern, cause, solution) {
13997
+ const entry = `**pattern:** ${pattern}
13998
+ **cause:** ${cause}
13999
+ **solution:** ${solution}`;
14000
+ this.append(category, entry);
14001
+ }
14002
+ }
14003
+ var MEMORY_FILES2;
14004
+ var init_store2 = __esm(() => {
14005
+ init_search();
14006
+ MEMORY_FILES2 = ["conventions", "decisions", "errors", "facts"];
14007
+ });
14008
+
13848
14009
  // src/tools/remember.ts
13849
14010
  import { homedir as homedir6 } from "os";
13850
- import { join as join15 } from "path";
14011
+ import { join as join16 } from "path";
13851
14012
  var CATEGORIES, rememberTool;
13852
14013
  var init_remember = __esm(() => {
13853
14014
  init_i18n();
@@ -13885,7 +14046,7 @@ var init_remember = __esm(() => {
13885
14046
  if (!CATEGORIES.includes(category)) {
13886
14047
  return { success: false, output: t("tool.invalid_params") };
13887
14048
  }
13888
- const memoryDir = join15(homedir6(), ".mma", "memory");
14049
+ const memoryDir = join16(homedir6(), ".mma", "memory");
13889
14050
  const store = new MemoryStore(memoryDir);
13890
14051
  try {
13891
14052
  if (category === "preferences") {
@@ -13918,7 +14079,7 @@ var init_remember = __esm(() => {
13918
14079
 
13919
14080
  // src/tools/recall.ts
13920
14081
  import { homedir as homedir7 } from "os";
13921
- import { join as join16 } from "path";
14082
+ import { join as join17 } from "path";
13922
14083
  function formatAll(store) {
13923
14084
  const parts = [];
13924
14085
  const prefs = store.getPreferences();
@@ -14001,7 +14162,7 @@ var init_recall = __esm(() => {
14001
14162
  handler: async (_ctx, args) => {
14002
14163
  const query = args.query ? String(args.query) : "";
14003
14164
  const category = args.category ? String(args.category) : "";
14004
- const memoryDir = join16(homedir7(), ".mma", "memory");
14165
+ const memoryDir = join17(homedir7(), ".mma", "memory");
14005
14166
  const store = new MemoryStore(memoryDir);
14006
14167
  try {
14007
14168
  if (!query && !category) {
@@ -14039,10 +14200,10 @@ __export(exports_bridge_client, {
14039
14200
  });
14040
14201
  import { spawn as spawn4 } from "child_process";
14041
14202
  import { createInterface } from "readline";
14042
- import { dirname as dirname7, join as join17 } from "path";
14203
+ import { dirname as dirname9, join as join18 } from "path";
14043
14204
  import { fileURLToPath } from "url";
14044
14205
  function bridgeScriptPath() {
14045
- return join17(dirname7(fileURLToPath(import.meta.url)), "bridge-server.mjs");
14206
+ return join18(dirname9(fileURLToPath(import.meta.url)), "bridge-server.mjs");
14046
14207
  }
14047
14208
 
14048
14209
  class BridgeDriver {
@@ -14578,15 +14739,15 @@ function buildTextExtractionScript() {
14578
14739
 
14579
14740
  // src/modules/browser/cookie-store.ts
14580
14741
  import { readFile, writeFile, mkdir } from "fs/promises";
14581
- import { join as join18 } from "path";
14742
+ import { join as join19 } from "path";
14582
14743
 
14583
14744
  class CookieStore {
14584
14745
  filePath;
14585
14746
  constructor(cookieDir) {
14586
- this.filePath = join18(cookieDir, "cookies.json");
14747
+ this.filePath = join19(cookieDir, "cookies.json");
14587
14748
  }
14588
14749
  async save(cookies) {
14589
- await mkdir(join18(this.filePath, ".."), { recursive: true });
14750
+ await mkdir(join19(this.filePath, ".."), { recursive: true });
14590
14751
  await writeFile(this.filePath, JSON.stringify(cookies, null, 2), "utf-8");
14591
14752
  }
14592
14753
  async load() {
@@ -14941,10 +15102,10 @@ var init_session = __esm(() => {
14941
15102
  });
14942
15103
 
14943
15104
  // src/tools/browser.ts
14944
- import { join as join19 } from "path";
15105
+ import { join as join20 } from "path";
14945
15106
  function getSession(ctx) {
14946
15107
  if (!session) {
14947
- const cookieDir = join19(ctx.baseDir, ".mma", "browser");
15108
+ const cookieDir = join20(ctx.baseDir, ".mma", "browser");
14948
15109
  session = new BrowserSession({
14949
15110
  ...DEFAULT_BROWSER_CONFIG,
14950
15111
  headless: ctx.config.browser?.headless ?? true,
@@ -15080,8 +15241,8 @@ async function readClipboardFallback() {
15080
15241
  const { platform: platform3 } = await import("os");
15081
15242
  const { execSync } = await import("child_process");
15082
15243
  const { readFileSync: readFileSync15, unlinkSync: unlinkSync4 } = await import("fs");
15083
- const { join: join20 } = await import("path");
15084
- const tmpPath = join20(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
15244
+ const { join: join21 } = await import("path");
15245
+ const tmpPath = join21(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
15085
15246
  try {
15086
15247
  if (platform3() === "linux") {
15087
15248
  execSync(`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`, { timeout: 5000 });
@@ -15159,7 +15320,7 @@ var init_image_utils = __esm(() => {
15159
15320
  });
15160
15321
 
15161
15322
  // src/tools/attach-image.ts
15162
- import { existsSync as existsSync24 } from "fs";
15323
+ import { existsSync as existsSync25 } from "fs";
15163
15324
  import { resolve as resolve15 } from "path";
15164
15325
  var attachImageTool;
15165
15326
  var init_attach_image = __esm(() => {
@@ -15202,7 +15363,7 @@ var init_attach_image = __esm(() => {
15202
15363
  dataUrl = result.dataUrl;
15203
15364
  } else {
15204
15365
  const absPath = resolve15(ctx.baseDir, source);
15205
- if (!existsSync24(absPath)) {
15366
+ if (!existsSync25(absPath)) {
15206
15367
  return {
15207
15368
  success: false,
15208
15369
  output: t("image.not_found", { path: source })
@@ -15364,16 +15525,16 @@ class ModuleRegistry {
15364
15525
  }
15365
15526
 
15366
15527
  // src/modules/plugins/loader.ts
15367
- import { readdirSync as readdirSync7, existsSync as existsSync25, statSync as statSync5 } from "fs";
15368
- import { join as join20 } from "path";
15528
+ import { readdirSync as readdirSync7, existsSync as existsSync26, statSync as statSync5 } from "fs";
15529
+ import { join as join21 } from "path";
15369
15530
 
15370
15531
  class PluginLoader {
15371
15532
  loadFromDir(dirPath, pluginManager, logger) {
15372
- if (!existsSync25(dirPath))
15533
+ if (!existsSync26(dirPath))
15373
15534
  return;
15374
15535
  const entries = readdirSync7(dirPath);
15375
15536
  for (const entry of entries) {
15376
- const fullPath = join20(dirPath, entry);
15537
+ const fullPath = join21(dirPath, entry);
15377
15538
  if (!statSync5(fullPath).isFile())
15378
15539
  continue;
15379
15540
  if (!entry.endsWith(".ts") && !entry.endsWith(".js"))
@@ -15397,8 +15558,8 @@ var init_loader = __esm(() => {
15397
15558
 
15398
15559
  // src/modules/plugins/builtin/lint-on-write.ts
15399
15560
  import { spawn as spawn5, execSync } from "child_process";
15400
- import { existsSync as existsSync26, readFileSync as readFileSync15 } from "fs";
15401
- import { resolve as resolve16, extname as extname4, join as join21 } from "path";
15561
+ import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
15562
+ import { resolve as resolve16, extname as extname4, join as join22 } from "path";
15402
15563
  import { platform as platform3 } from "os";
15403
15564
  function contentHash(content) {
15404
15565
  let h = 5381;
@@ -15444,7 +15605,7 @@ class LintOnWritePlugin {
15444
15605
  if (!path)
15445
15606
  return;
15446
15607
  const fullPath = resolve16(ctx.baseDir, path);
15447
- if (!existsSync26(fullPath))
15608
+ if (!existsSync27(fullPath))
15448
15609
  return;
15449
15610
  const ext = extname4(fullPath);
15450
15611
  const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
@@ -15455,7 +15616,7 @@ class LintOnWritePlugin {
15455
15616
  return;
15456
15617
  }
15457
15618
  await this.runProjectLint(ctx, result);
15458
- await this.runProjectTypeCheck(ctx, result);
15619
+ await this.runProjectTypeCheck(fullPath, ctx.baseDir, result);
15459
15620
  }
15460
15621
  async checkSyntax(filePath, ext, baseDir) {
15461
15622
  if (ext === ".ts" || ext === ".tsx") {
@@ -15500,8 +15661,8 @@ class LintOnWritePlugin {
15500
15661
  }
15501
15662
  async runProjectLint(ctx, result) {
15502
15663
  try {
15503
- const packageJsonPath = join21(ctx.baseDir, "package.json");
15504
- if (!existsSync26(packageJsonPath)) {
15664
+ const packageJsonPath = join22(ctx.baseDir, "package.json");
15665
+ if (!existsSync27(packageJsonPath)) {
15505
15666
  return;
15506
15667
  }
15507
15668
  const packageJson = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
@@ -15519,9 +15680,13 @@ class LintOnWritePlugin {
15519
15680
  [Project lint failed]: ${err.message}`;
15520
15681
  }
15521
15682
  }
15522
- async runProjectTypeCheck(ctx, result) {
15523
- const tsconfigPath = join21(ctx.baseDir, "tsconfig.json");
15524
- if (!existsSync26(tsconfigPath)) {
15683
+ async runProjectTypeCheck(filePath, baseDir, result) {
15684
+ const projectRoot = findProjectRoot(filePath, baseDir, [
15685
+ "tsconfig.json",
15686
+ "package.json"
15687
+ ]);
15688
+ const tsconfigPath = join22(projectRoot, "tsconfig.json");
15689
+ if (!existsSync27(tsconfigPath)) {
15525
15690
  return;
15526
15691
  }
15527
15692
  const now = Date.now();
@@ -15535,7 +15700,7 @@ class LintOnWritePlugin {
15535
15700
  return;
15536
15701
  }
15537
15702
  this._checkTimestamp = now;
15538
- this._checkPromise = this.runTscCheck(ctx.baseDir);
15703
+ this._checkPromise = this.runTscCheck(projectRoot);
15539
15704
  const error = await this._checkPromise;
15540
15705
  if (error) {
15541
15706
  result.output += `
@@ -15604,6 +15769,7 @@ function runAsync(command, cwd, timeoutMs) {
15604
15769
  }
15605
15770
  var TYPE_CHECK_DEBOUNCE_MS = 2000, syntaxCache, _winDecoder, plugin;
15606
15771
  var init_lint_on_write = __esm(() => {
15772
+ init_project_root();
15607
15773
  syntaxCache = new Map;
15608
15774
  plugin = new LintOnWritePlugin;
15609
15775
  });
@@ -15651,7 +15817,7 @@ class PlanCreator {
15651
15817
  const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
15652
15818
  return hasActionWord && words.length > 8;
15653
15819
  }
15654
- static createPlan(title, stepDescriptions, baseDir) {
15820
+ static createPlan(title, stepDescriptions, baseDir, kinds) {
15655
15821
  const stepCount = stepDescriptions.length;
15656
15822
  return {
15657
15823
  id: generatePlanId(),
@@ -15659,7 +15825,8 @@ class PlanCreator {
15659
15825
  steps: stepDescriptions.map((desc, i) => ({
15660
15826
  id: i + 1,
15661
15827
  description: desc,
15662
- status: "pending"
15828
+ status: "pending",
15829
+ kind: kinds?.[i] ?? "create"
15663
15830
  })),
15664
15831
  createdAt: new Date().toISOString(),
15665
15832
  baseDir
@@ -15673,7 +15840,8 @@ class PlanCreator {
15673
15840
  const added = newSteps.map((desc, i) => ({
15674
15841
  id: kept.length + i + 1,
15675
15842
  description: desc,
15676
- status: "pending"
15843
+ status: "pending",
15844
+ kind: "create"
15677
15845
  }));
15678
15846
  return {
15679
15847
  id: plan.id,
@@ -15765,42 +15933,9 @@ class PlanTracker {
15765
15933
  }
15766
15934
  var init_tracker = () => {};
15767
15935
 
15768
- // src/modules/execution/auditor.ts
15769
- import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
15770
- import { resolve as resolve17, join as join22, basename as basename2 } from "path";
15771
- function findExistingFile(baseDir, filePath) {
15772
- const direct = resolve17(baseDir, filePath);
15773
- if (existsSync27(direct))
15774
- return direct;
15775
- const name = basename2(filePath).toLowerCase();
15776
- const suffix = filePath.replace(/\\/g, "/").toLowerCase();
15777
- let found = null;
15778
- const walk = (dir, depth) => {
15779
- if (found || depth > RESOLVE_MAX_DEPTH)
15780
- return;
15781
- let entries;
15782
- try {
15783
- entries = readdirSync8(dir, { withFileTypes: true });
15784
- } catch {
15785
- return;
15786
- }
15787
- for (const e of entries) {
15788
- if (found)
15789
- return;
15790
- const full = join22(dir, e.name);
15791
- if (e.isDirectory()) {
15792
- if (SKIP_DIRS.has(e.name))
15793
- continue;
15794
- walk(full, depth + 1);
15795
- } else if (e.name.toLowerCase() === name || full.replace(/\\/g, "/").toLowerCase().endsWith("/" + suffix)) {
15796
- found = full;
15797
- return;
15798
- }
15799
- }
15800
- };
15801
- walk(baseDir, 0);
15802
- return found;
15803
- }
15936
+ // src/modules/execution/audit-runners.ts
15937
+ import { readdirSync as readdirSync8 } from "fs";
15938
+ import { join as join23 } from "path";
15804
15939
  function findTestFile(dir, depth = 0) {
15805
15940
  if (depth > 5)
15806
15941
  return null;
@@ -15811,7 +15946,7 @@ function findTestFile(dir, depth = 0) {
15811
15946
  return null;
15812
15947
  }
15813
15948
  for (const e of entries) {
15814
- const full = join22(dir, e.name);
15949
+ const full = join23(dir, e.name);
15815
15950
  if (e.isDirectory()) {
15816
15951
  if (SKIP_DIRS.has(e.name))
15817
15952
  continue;
@@ -15875,6 +16010,76 @@ async function runTests(baseDir) {
15875
16010
  command: "bun test"
15876
16011
  };
15877
16012
  }
16013
+ function parseTypecheckErrors(output) {
16014
+ const line = output.split(`
16015
+ `).find((l) => /error TS\d+/.test(l));
16016
+ return line ? line.trim().slice(0, 300) : null;
16017
+ }
16018
+ async function runTypecheck(baseDir) {
16019
+ const entry = processRegistry.start("npx --no-install tsc --noEmit --skipLibCheck", baseDir);
16020
+ const exited = await processRegistry.waitForExit(entry.id, 90000);
16021
+ const output = entry.log.join(`
16022
+ `);
16023
+ processRegistry.remove(entry.id);
16024
+ if (!exited)
16025
+ return null;
16026
+ return parseTypecheckErrors(output);
16027
+ }
16028
+ var SKIP_DIRS, TEST_EXT_RE, TEST_STEP_RE;
16029
+ var init_audit_runners = __esm(() => {
16030
+ init_bash();
16031
+ init_processes();
16032
+ SKIP_DIRS = new Set([
16033
+ "node_modules",
16034
+ ".git",
16035
+ ".mma",
16036
+ "dist",
16037
+ "build",
16038
+ "coverage",
16039
+ ".next",
16040
+ ".nuxt",
16041
+ "vendor"
16042
+ ]);
16043
+ TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
16044
+ TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
16045
+ });
16046
+
16047
+ // src/modules/execution/auditor.ts
16048
+ import { existsSync as existsSync28, readdirSync as readdirSync9 } from "fs";
16049
+ import { resolve as resolve17, join as join24, basename as basename2 } from "path";
16050
+ function findExistingFile(baseDir, filePath) {
16051
+ const direct = resolve17(baseDir, filePath);
16052
+ if (existsSync28(direct))
16053
+ return direct;
16054
+ const name = basename2(filePath).toLowerCase();
16055
+ const suffix = filePath.replace(/\\/g, "/").toLowerCase();
16056
+ let found = null;
16057
+ const walk = (dir, depth) => {
16058
+ if (found || depth > RESOLVE_MAX_DEPTH)
16059
+ return;
16060
+ let entries;
16061
+ try {
16062
+ entries = readdirSync9(dir, { withFileTypes: true });
16063
+ } catch {
16064
+ return;
16065
+ }
16066
+ for (const e of entries) {
16067
+ if (found)
16068
+ return;
16069
+ const full = join24(dir, e.name);
16070
+ if (e.isDirectory()) {
16071
+ if (SKIP_DIRS.has(e.name))
16072
+ continue;
16073
+ walk(full, depth + 1);
16074
+ } else if (e.name.toLowerCase() === name || full.replace(/\\/g, "/").toLowerCase().endsWith("/" + suffix)) {
16075
+ found = full;
16076
+ return;
16077
+ }
16078
+ }
16079
+ };
16080
+ walk(baseDir, 0);
16081
+ return found;
16082
+ }
15878
16083
 
15879
16084
  class Auditor {
15880
16085
  baseDir;
@@ -15882,18 +16087,27 @@ class Auditor {
15882
16087
  this.baseDir = baseDir;
15883
16088
  }
15884
16089
  async audit(plan) {
15885
- const allStepText = plan.steps.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
15886
- const uniqueFiles = extractFileLikeTokens(stripUrls(allStepText));
16090
+ const doneCreate = plan.steps.filter((s) => s.status === "done" && s.kind !== "delete");
16091
+ const doneDelete = plan.steps.filter((s) => s.status === "done" && s.kind === "delete");
16092
+ const createText = doneCreate.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
16093
+ const deleteText = doneDelete.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
16094
+ const createFiles = extractFileLikeTokens(stripUrls(createText));
16095
+ const deleteFiles = extractFileLikeTokens(stripUrls(deleteText));
15887
16096
  const missingFiles = [];
15888
16097
  const existingFiles = [];
15889
- for (const filePath of uniqueFiles) {
15890
- const resolved = findExistingFile(this.baseDir, filePath);
15891
- if (resolved) {
16098
+ const leftoverFiles = [];
16099
+ for (const filePath of createFiles) {
16100
+ if (findExistingFile(this.baseDir, filePath)) {
15892
16101
  existingFiles.push(filePath);
15893
16102
  } else {
15894
16103
  missingFiles.push(filePath);
15895
16104
  }
15896
16105
  }
16106
+ for (const filePath of deleteFiles) {
16107
+ if (findExistingFile(this.baseDir, filePath)) {
16108
+ leftoverFiles.push(filePath);
16109
+ }
16110
+ }
15897
16111
  let testRun = null;
15898
16112
  if (hasTestStep(plan) && findTestFile(this.baseDir)) {
15899
16113
  try {
@@ -15902,17 +16116,28 @@ class Auditor {
15902
16116
  testRun = null;
15903
16117
  }
15904
16118
  }
16119
+ let typecheckError = null;
16120
+ if (missingFiles.length === 0 && leftoverFiles.length === 0 && existsSync28(join24(this.baseDir, "tsconfig.json"))) {
16121
+ try {
16122
+ typecheckError = await runTypecheck(this.baseDir);
16123
+ } catch {
16124
+ typecheckError = null;
16125
+ }
16126
+ }
15905
16127
  const doneSteps = plan.steps.filter((s) => s.status === "done").length;
16128
+ const terminalSteps = plan.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
15906
16129
  const totalSteps = plan.steps.length;
16130
+ const auditedFiles = [...new Set([...createFiles, ...deleteFiles])];
15907
16131
  let massEditWarning = null;
15908
- if (uniqueFiles.length > MASS_EDIT_THRESHOLD) {
16132
+ if (auditedFiles.length > MASS_EDIT_THRESHOLD) {
15909
16133
  massEditWarning = t("exec.mass_edit_warning", {
15910
- count: String(uniqueFiles.length)
16134
+ count: String(auditedFiles.length)
15911
16135
  });
15912
16136
  }
15913
16137
  const testsFailing = testRun !== null && testRun.failed > 0;
15914
- const passed = missingFiles.length === 0 && !testsFailing;
15915
- const stepsPending = doneSteps < totalSteps;
16138
+ const typecheckFailing = typecheckError !== null;
16139
+ const passed = missingFiles.length === 0 && leftoverFiles.length === 0 && !testsFailing && !typecheckFailing;
16140
+ const stepsPending = terminalSteps < totalSteps;
15916
16141
  let summary;
15917
16142
  if (missingFiles.length > 0) {
15918
16143
  summary = t("exec.audit_fail", {
@@ -15920,6 +16145,12 @@ class Auditor {
15920
16145
  total: totalSteps,
15921
16146
  files: missingFiles.length
15922
16147
  });
16148
+ } else if (leftoverFiles.length > 0) {
16149
+ summary = t("exec.audit_leftovers", {
16150
+ done: doneSteps,
16151
+ total: totalSteps,
16152
+ files: leftoverFiles.length
16153
+ });
15923
16154
  } else if (testsFailing) {
15924
16155
  summary = t("exec.audit_fail_tests", {
15925
16156
  done: doneSteps,
@@ -15928,6 +16159,13 @@ class Auditor {
15928
16159
  passed: String(testRun.passedCount),
15929
16160
  detail: testRun.detail
15930
16161
  });
16162
+ } else if (typecheckFailing) {
16163
+ summary = t("exec.audit_fail_typecheck", {
16164
+ done: doneSteps,
16165
+ total: totalSteps,
16166
+ missing: String(missingFiles.length),
16167
+ typeError: typecheckError
16168
+ });
15931
16169
  } else if (stepsPending) {
15932
16170
  summary = t("exec.audit_pending", {
15933
16171
  done: doneSteps,
@@ -15943,38 +16181,27 @@ class Auditor {
15943
16181
  return {
15944
16182
  passed,
15945
16183
  missingFiles,
16184
+ leftoverFiles,
15946
16185
  createdFiles: existingFiles,
15947
16186
  modifiedFiles: [],
15948
16187
  summary,
15949
16188
  massEditWarning,
15950
- testRun
16189
+ testRun,
16190
+ typecheckError
15951
16191
  };
15952
16192
  }
15953
16193
  }
15954
- var MASS_EDIT_THRESHOLD = 10, SKIP_DIRS, TEST_EXT_RE, TEST_STEP_RE, RESOLVE_MAX_DEPTH = 8;
16194
+ var MASS_EDIT_THRESHOLD = 10, RESOLVE_MAX_DEPTH = 8;
15955
16195
  var init_auditor = __esm(() => {
15956
16196
  init_i18n();
15957
16197
  init_js_identifiers();
15958
- init_bash();
15959
- init_processes();
15960
- SKIP_DIRS = new Set([
15961
- "node_modules",
15962
- ".git",
15963
- ".mma",
15964
- "dist",
15965
- "build",
15966
- "coverage",
15967
- ".next",
15968
- ".nuxt",
15969
- "vendor"
15970
- ]);
15971
- TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
15972
- TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
16198
+ init_audit_runners();
16199
+ init_audit_runners();
15973
16200
  });
15974
16201
 
15975
16202
  // src/modules/execution/plan-store.ts
15976
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync28, readdirSync as readdirSync9, rmSync } from "fs";
15977
- import { join as join23 } from "path";
16203
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync29, readdirSync as readdirSync10, rmSync } from "fs";
16204
+ import { join as join25 } from "path";
15978
16205
  function readPlanFile(path, fallbackBaseDir) {
15979
16206
  try {
15980
16207
  const raw = readFileSync16(path, "utf-8");
@@ -15999,10 +16226,10 @@ function writePlanFile(path, plan) {
15999
16226
  writeFileSync10(path, JSON.stringify(plan, null, 2), "utf-8");
16000
16227
  }
16001
16228
  function listDir(dir, baseDir) {
16002
- if (!existsSync28(dir))
16229
+ if (!existsSync29(dir))
16003
16230
  return [];
16004
- const files = readdirSync9(dir).filter((f) => f.endsWith(".json"));
16005
- return files.map((f) => readPlanFile(join23(dir, f), baseDir)).filter((p) => p !== null);
16231
+ const files = readdirSync10(dir).filter((f) => f.endsWith(".json"));
16232
+ return files.map((f) => readPlanFile(join25(dir, f), baseDir)).filter((p) => p !== null);
16006
16233
  }
16007
16234
  function toMeta(plan, status) {
16008
16235
  return {
@@ -16023,33 +16250,33 @@ class PlanStore {
16023
16250
  archiveDir;
16024
16251
  legacyPath;
16025
16252
  constructor(baseDir) {
16026
- const mmaDir = join23(baseDir, ".mma");
16027
- if (!existsSync28(mmaDir))
16253
+ const mmaDir = join25(baseDir, ".mma");
16254
+ if (!existsSync29(mmaDir))
16028
16255
  mkdirSync14(mmaDir, { recursive: true });
16029
16256
  this.baseDir = baseDir;
16030
- this.plansDir = join23(mmaDir, "plans");
16031
- this.draftsDir = join23(this.plansDir, "drafts");
16032
- this.archiveDir = join23(this.plansDir, "archive");
16033
- this.legacyPath = join23(mmaDir, LEGACY_FILE);
16257
+ this.plansDir = join25(mmaDir, "plans");
16258
+ this.draftsDir = join25(this.plansDir, "drafts");
16259
+ this.archiveDir = join25(this.plansDir, "archive");
16260
+ this.legacyPath = join25(mmaDir, LEGACY_FILE);
16034
16261
  for (const dir of [this.plansDir, this.draftsDir, this.archiveDir]) {
16035
- if (!existsSync28(dir))
16262
+ if (!existsSync29(dir))
16036
16263
  mkdirSync14(dir, { recursive: true });
16037
16264
  }
16038
16265
  }
16039
16266
  activePath() {
16040
- return join23(this.plansDir, "active.json");
16267
+ return join25(this.plansDir, "active.json");
16041
16268
  }
16042
16269
  saveActive(plan) {
16043
16270
  writePlanFile(this.activePath(), plan);
16044
16271
  }
16045
16272
  loadActive() {
16046
16273
  const activePath = this.activePath();
16047
- if (existsSync28(activePath)) {
16274
+ if (existsSync29(activePath)) {
16048
16275
  const plan = readPlanFile(activePath, this.baseDir);
16049
16276
  if (plan)
16050
16277
  return plan;
16051
16278
  }
16052
- if (existsSync28(this.legacyPath)) {
16279
+ if (existsSync29(this.legacyPath)) {
16053
16280
  const legacy = readPlanFile(this.legacyPath, this.baseDir);
16054
16281
  if (legacy) {
16055
16282
  this.saveActive(legacy);
@@ -16063,26 +16290,26 @@ class PlanStore {
16063
16290
  }
16064
16291
  clearActive() {
16065
16292
  const p = this.activePath();
16066
- if (existsSync28(p))
16293
+ if (existsSync29(p))
16067
16294
  rmSync(p, { force: true });
16068
16295
  }
16069
16296
  saveDraft(plan) {
16070
- writePlanFile(join23(this.draftsDir, `${plan.id}.json`), plan);
16297
+ writePlanFile(join25(this.draftsDir, `${plan.id}.json`), plan);
16071
16298
  }
16072
16299
  loadDraft(id) {
16073
- const p = join23(this.draftsDir, `${id}.json`);
16074
- return existsSync28(p) ? readPlanFile(p, this.baseDir) : null;
16300
+ const p = join25(this.draftsDir, `${id}.json`);
16301
+ return existsSync29(p) ? readPlanFile(p, this.baseDir) : null;
16075
16302
  }
16076
16303
  removeDraft(id) {
16077
- const p = join23(this.draftsDir, `${id}.json`);
16078
- if (existsSync28(p))
16304
+ const p = join25(this.draftsDir, `${id}.json`);
16305
+ if (existsSync29(p))
16079
16306
  rmSync(p, { force: true });
16080
16307
  }
16081
16308
  listDrafts() {
16082
16309
  return listDir(this.draftsDir, this.baseDir);
16083
16310
  }
16084
16311
  archivePlan(plan) {
16085
- writePlanFile(join23(this.archiveDir, `${plan.id}.json`), plan);
16312
+ writePlanFile(join25(this.archiveDir, `${plan.id}.json`), plan);
16086
16313
  this.removeDraft(plan.id);
16087
16314
  const active = this.loadActive();
16088
16315
  if (active && active.id === plan.id) {
@@ -16093,8 +16320,8 @@ class PlanStore {
16093
16320
  return listDir(this.archiveDir, this.baseDir);
16094
16321
  }
16095
16322
  removeArchived(id) {
16096
- const p = join23(this.archiveDir, `${id}.json`);
16097
- if (existsSync28(p))
16323
+ const p = join25(this.archiveDir, `${id}.json`);
16324
+ if (existsSync29(p))
16098
16325
  rmSync(p, { force: true });
16099
16326
  }
16100
16327
  listAll() {
@@ -16187,91 +16414,12 @@ var init_plan_coverage = __esm(() => {
16187
16414
  ]);
16188
16415
  });
16189
16416
 
16190
- // src/modules/execution/module.ts
16191
- import { existsSync as existsSync29, readFileSync as readFileSync17 } from "fs";
16192
- import { resolve as resolve18 } from "path";
16193
-
16194
- class ExecutionModule {
16195
- name = "execution";
16196
- tracker = null;
16197
- verifier;
16198
- stuckDetector;
16199
- auditor;
16200
- store;
16201
- baseDir;
16202
- lastRecoveryIteration = -STUCK_RECOVERY_COOLDOWN;
16203
- consecutivePlanWarnings = 0;
16204
- lastStepId = -1;
16205
- depsGateHints = new Map;
16206
- _auditSkipsRemaining = 0;
16207
- stuckNotified = false;
16208
- pendingMessages = [];
16209
- constructor(baseDir, stuckThreshold = 6) {
16210
- this.baseDir = baseDir;
16211
- this.verifier = new StepVerifier(baseDir);
16212
- this.stuckDetector = new StuckDetector(stuckThreshold);
16213
- this.auditor = new Auditor(baseDir);
16214
- this.store = new PlanStore(baseDir);
16215
- }
16216
- setPlan(plan) {
16217
- this.tracker = new PlanTracker(plan);
16218
- this.store.saveActive(plan);
16219
- this._auditSkipsRemaining = 0;
16220
- }
16221
- restorePlan() {
16222
- const plan = this.store.loadActive();
16223
- if (!plan)
16224
- return false;
16225
- this.tracker = new PlanTracker(plan);
16226
- this.tracker.syncCurrentStep();
16227
- this._auditSkipsRemaining = 1;
16228
- return true;
16229
- }
16230
- getTracker() {
16231
- return this.tracker;
16232
- }
16233
- getStore() {
16234
- return this.store;
16235
- }
16236
- getStuckDetector() {
16237
- return this.stuckDetector;
16238
- }
16239
- async runFinalAudit() {
16240
- if (!this.tracker)
16241
- return null;
16242
- if (this._auditSkipsRemaining > 0) {
16243
- this._auditSkipsRemaining--;
16244
- return null;
16245
- }
16246
- const plan = this.tracker.getPlan();
16247
- const audit = await this.auditor.audit(plan);
16248
- const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
16249
- const done = plan.steps.filter((s) => s.status === "done").length;
16250
- const passed = audit.passed && pendingSteps.length === 0;
16251
- return {
16252
- passed,
16253
- done,
16254
- total: plan.steps.length,
16255
- pendingSteps,
16256
- missingFiles: audit.missingFiles,
16257
- summary: audit.summary
16258
- };
16259
- }
16260
- getSystemPromptBlock() {
16261
- if (!this.tracker)
16262
- return null;
16263
- return {
16264
- content: this.tracker.toPromptBlock(),
16265
- priority: "critical",
16266
- essential: true,
16267
- estimatedTokens: 200
16268
- };
16269
- }
16270
- getToolDefinitions() {
16271
- return [
16272
- {
16273
- name: "plan",
16274
- description: `Create, update, show, abort, list, switch, or re-plan multi-step plans.
16417
+ // src/modules/execution/plan-tool.ts
16418
+ function createPlanToolDefinitions(deps) {
16419
+ return [
16420
+ {
16421
+ name: "plan",
16422
+ description: `Create, update, show, abort, list, switch, or re-plan multi-step plans.
16275
16423
 
16276
16424
  Actions:
16277
16425
  - create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.
@@ -16288,198 +16436,222 @@ Write CONCRETE steps with exact file paths and commands:
16288
16436
  - Specify WHICH CLI commands to run with exact arguments
16289
16437
  - Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)
16290
16438
  - Good: "Создать src/components/Header.tsx с навигацией и логотипом"
16291
- - Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)`,
16292
- parameters: {
16293
- type: "object",
16294
- properties: {
16295
- action: {
16296
- type: "string",
16297
- enum: [
16298
- "create",
16299
- "update",
16300
- "show",
16301
- "abort",
16302
- "list",
16303
- "switch",
16304
- "re-plan"
16305
- ]
16306
- },
16307
- title: { type: "string" },
16308
- steps: { type: "array", items: { type: "string" } },
16309
- step: { type: "number" },
16310
- status: { type: "string", enum: ["done", "failed", "skipped"] },
16311
- note: { type: "string" },
16312
- id: { type: "string", description: "Plan id (for switch action)" }
16439
+ - Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)
16440
+ - For steps that REMOVE files, pass kinds: ["delete"] aligned with steps (the done-gate verifies those files are gone instead of demanding they exist).`,
16441
+ parameters: {
16442
+ type: "object",
16443
+ properties: {
16444
+ action: {
16445
+ type: "string",
16446
+ enum: [
16447
+ "create",
16448
+ "update",
16449
+ "show",
16450
+ "abort",
16451
+ "list",
16452
+ "switch",
16453
+ "re-plan"
16454
+ ]
16313
16455
  },
16314
- required: ["action"]
16456
+ title: { type: "string" },
16457
+ steps: { type: "array", items: { type: "string" } },
16458
+ kinds: {
16459
+ type: "array",
16460
+ items: { type: "string", enum: ["create", "delete"] },
16461
+ description: 'Optional per-step intent, aligned 1:1 with steps. Pass "delete" for steps whose purpose is REMOVING files — the done-gate then requires those files to be GONE (not present). Defaults to "create".'
16462
+ },
16463
+ step: { type: "number" },
16464
+ status: { type: "string", enum: ["done", "failed", "skipped"] },
16465
+ note: { type: "string" },
16466
+ id: { type: "string", description: "Plan id (for switch action)" }
16315
16467
  },
16316
- handler: async (_ctx, args) => {
16317
- const action = String(args.action);
16318
- if (action === "list") {
16319
- const metas = this.store.listAll();
16320
- if (metas.length === 0) {
16321
- return { success: true, output: t("plan.list_empty") };
16322
- }
16323
- const lines = metas.map((m) => {
16324
- const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
16325
- const namePart = m.name ? ` (${m.name})` : "";
16326
- return `${icon} ${m.id}${namePart} ${m.title} ${m.doneCount}/${m.stepCount}`;
16327
- });
16328
- return {
16329
- success: true,
16330
- output: `${t("plan.list_header")}
16468
+ required: ["action"]
16469
+ },
16470
+ handler: async (_ctx, args) => {
16471
+ const action = String(args.action);
16472
+ if (action === "list") {
16473
+ const metas = deps.store.listAll();
16474
+ if (metas.length === 0) {
16475
+ return { success: true, output: t("plan.list_empty") };
16476
+ }
16477
+ const lines = metas.map((m) => {
16478
+ const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
16479
+ const namePart = m.name ? ` (${m.name})` : "";
16480
+ return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
16481
+ });
16482
+ return {
16483
+ success: true,
16484
+ output: `${t("plan.list_header")}
16331
16485
  ${lines.join(`
16332
16486
  `)}`
16487
+ };
16488
+ }
16489
+ if (action === "switch") {
16490
+ const planId = String(args.id || "");
16491
+ if (!planId) {
16492
+ return { success: false, output: t("plan.switch_no_id") };
16493
+ }
16494
+ const found = deps.store.find(planId);
16495
+ if (!found) {
16496
+ return {
16497
+ success: false,
16498
+ output: t("plan.not_found", { id: planId })
16333
16499
  };
16334
16500
  }
16335
- if (action === "switch") {
16336
- const planId = String(args.id || "");
16337
- if (!planId) {
16338
- return { success: false, output: t("plan.switch_no_id") };
16501
+ deps.preserveActive();
16502
+ if (found.status === "draft") {
16503
+ deps.store.removeDraft(found.plan.id);
16504
+ } else if (found.status === "archived") {
16505
+ deps.store.removeArchived(found.plan.id);
16506
+ }
16507
+ deps.setPlan(found.plan);
16508
+ const display = PlanCreator.toPromptBlock(found.plan, 0);
16509
+ return {
16510
+ success: true,
16511
+ output: t("plan.switched", {
16512
+ id: found.plan.id,
16513
+ title: found.plan.title
16514
+ }),
16515
+ display
16516
+ };
16517
+ }
16518
+ if (action === "re-plan") {
16519
+ if (!deps.trackerRef.current) {
16520
+ return { success: false, output: t("plan.no_active") };
16521
+ }
16522
+ const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
16523
+ if (newSteps.length === 0) {
16524
+ return { success: false, output: t("plan.replan_no_steps") };
16525
+ }
16526
+ const tracker = deps.trackerRef.current;
16527
+ const oldPlan = tracker.getPlan();
16528
+ const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
16529
+ const keptCount = replanned.steps.length - newSteps.length;
16530
+ deps.setPlan(replanned);
16531
+ const display = PlanCreator.toPromptBlock(replanned, keptCount);
16532
+ return {
16533
+ success: true,
16534
+ output: t("plan.replanned", {
16535
+ kept: String(keptCount),
16536
+ steps: String(newSteps.length)
16537
+ }),
16538
+ display
16539
+ };
16540
+ }
16541
+ if (action === "create") {
16542
+ const title = String(args.title || "Task Plan");
16543
+ const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
16544
+ if (steps.length === 0) {
16545
+ return { success: false, output: t("plan.no_steps") };
16546
+ }
16547
+ const parsed = deps.parseKinds(args, steps);
16548
+ if (parsed.error) {
16549
+ return { success: false, output: parsed.error };
16550
+ }
16551
+ deps.preserveActive();
16552
+ const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
16553
+ deps.setPlan(plan);
16554
+ const display = PlanCreator.toPromptBlock(plan, 0);
16555
+ let output = t("plan.created", {
16556
+ title,
16557
+ steps: String(steps.length)
16558
+ });
16559
+ let displayOut = display;
16560
+ const taskText = deps.getTaskText(_ctx);
16561
+ if (taskText) {
16562
+ const { missing } = checkPlanCoverage(taskText, steps);
16563
+ if (missing.length > 0) {
16564
+ const warn = t("plan.coverage_warning", {
16565
+ missing: missing.join(", ")
16566
+ });
16567
+ output += `
16568
+ ⚠️ ${warn}`;
16569
+ displayOut = `${display}
16570
+ ⚠️ ${warn}`;
16339
16571
  }
16340
- const found = this.store.find(planId);
16572
+ }
16573
+ return {
16574
+ success: true,
16575
+ output,
16576
+ display: displayOut
16577
+ };
16578
+ }
16579
+ if (action === "show") {
16580
+ const planId = args.id ? String(args.id) : "";
16581
+ if (planId && planId !== deps.trackerRef.current?.getPlan()?.id) {
16582
+ const found = deps.store.find(planId);
16341
16583
  if (!found) {
16342
16584
  return {
16343
16585
  success: false,
16344
16586
  output: t("plan.not_found", { id: planId })
16345
16587
  };
16346
16588
  }
16347
- this.preserveActive();
16348
- if (found.status === "draft") {
16349
- this.store.removeDraft(found.plan.id);
16350
- } else if (found.status === "archived") {
16351
- this.store.removeArchived(found.plan.id);
16352
- }
16353
- this.setPlan(found.plan);
16354
- const display = PlanCreator.toPromptBlock(found.plan, 0);
16589
+ const display2 = PlanCreator.toPromptBlock(found.plan, 0);
16355
16590
  return {
16356
16591
  success: true,
16357
- output: t("plan.switched", {
16358
- id: found.plan.id,
16359
- title: found.plan.title
16360
- }),
16361
- display
16592
+ output: `${t("plan.show_header")}
16593
+ ${display2}`,
16594
+ display: display2
16362
16595
  };
16363
16596
  }
16364
- if (action === "re-plan") {
16365
- if (!this.tracker) {
16366
- return { success: false, output: t("plan.no_active") };
16367
- }
16368
- const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
16369
- if (newSteps.length === 0) {
16370
- return { success: false, output: t("plan.replan_no_steps") };
16371
- }
16372
- const oldPlan = this.tracker.getPlan();
16373
- const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
16374
- const keptCount = replanned.steps.length - newSteps.length;
16375
- this.setPlan(replanned);
16376
- const display = PlanCreator.toPromptBlock(replanned, keptCount);
16377
- return {
16378
- success: true,
16379
- output: t("plan.replanned", {
16380
- kept: String(keptCount),
16381
- steps: String(newSteps.length)
16382
- }),
16383
- display
16384
- };
16597
+ if (!deps.trackerRef.current) {
16598
+ return { success: false, output: t("plan.no_active") };
16385
16599
  }
16386
- if (action === "create") {
16387
- const title = String(args.title || "Task Plan");
16388
- const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
16389
- if (steps.length === 0) {
16390
- return { success: false, output: t("plan.no_steps") };
16391
- }
16392
- this.preserveActive();
16393
- const plan = PlanCreator.createPlan(title, steps, this.baseDir);
16394
- this.setPlan(plan);
16395
- const display = PlanCreator.toPromptBlock(plan, 0);
16396
- let output = t("plan.created", {
16397
- title,
16398
- steps: String(steps.length)
16399
- });
16400
- let displayOut = display;
16401
- const taskText = this.getTaskText(_ctx);
16402
- if (taskText) {
16403
- const { missing } = checkPlanCoverage(taskText, steps);
16404
- if (missing.length > 0) {
16405
- const warn = t("plan.coverage_warning", {
16406
- missing: missing.join(", ")
16407
- });
16408
- output += `
16409
- ⚠️ ${warn}`;
16410
- displayOut = `${display}
16411
- ⚠️ ${warn}`;
16412
- }
16413
- }
16414
- return {
16415
- success: true,
16416
- output,
16417
- display: displayOut
16418
- };
16600
+ const display = deps.trackerRef.current.toPromptBlock();
16601
+ return {
16602
+ success: true,
16603
+ output: `${t("plan.show_header")}
16604
+ ${display}`,
16605
+ display
16606
+ };
16607
+ }
16608
+ if (action === "update" && args.step && deps.trackerRef.current) {
16609
+ const tracker = deps.trackerRef.current;
16610
+ const stepId = Number(args.step);
16611
+ const target = tracker.getStep(stepId);
16612
+ if (!target) {
16613
+ return { success: false, output: t("plan.step_not_found") };
16419
16614
  }
16420
- if (action === "show") {
16421
- const planId = args.id ? String(args.id) : "";
16422
- if (planId && planId !== this.tracker?.getPlan()?.id) {
16423
- const found = this.store.find(planId);
16424
- if (!found) {
16425
- return {
16426
- success: false,
16427
- output: t("plan.not_found", { id: planId })
16428
- };
16429
- }
16430
- const display2 = PlanCreator.toPromptBlock(found.plan, 0);
16431
- return {
16432
- success: true,
16433
- output: `${t("plan.show_header")}
16434
- ${display2}`,
16435
- display: display2
16436
- };
16437
- }
16438
- if (!this.tracker) {
16439
- return { success: false, output: t("plan.no_active") };
16440
- }
16441
- const display = this.tracker.toPromptBlock();
16615
+ const status = String(args.status || "done");
16616
+ if (status === "done" && target.status === "done") {
16617
+ const progress2 = tracker.getProgressString();
16618
+ const display2 = tracker.toPromptBlock();
16442
16619
  return {
16443
16620
  success: true,
16444
- output: `${t("plan.show_header")}
16445
- ${display}`,
16446
- display
16621
+ output: `${t("plan.step_already_done", {
16622
+ step: String(stepId)
16623
+ })}
16624
+ ${progress2}`,
16625
+ display: display2
16447
16626
  };
16448
16627
  }
16449
- if (action === "update" && args.step && this.tracker) {
16450
- const stepId = Number(args.step);
16451
- const target = this.tracker.getStep(stepId);
16452
- if (!target) {
16453
- return { success: false, output: t("plan.step_not_found") };
16454
- }
16455
- const status = String(args.status || "done");
16456
- if (status === "done" && target.status === "done") {
16457
- const progress2 = this.tracker.getProgressString();
16458
- const display2 = this.tracker.toPromptBlock();
16628
+ if (status === "done") {
16629
+ const blockers = tracker.getPlan().steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
16630
+ if (blockers.length > 0) {
16459
16631
  return {
16460
- success: true,
16461
- output: `${t("plan.step_already_done", {
16462
- step: String(stepId)
16463
- })}
16464
- ${progress2}`,
16465
- display: display2
16632
+ success: false,
16633
+ output: t("plan.order_blocked", {
16634
+ step: String(stepId),
16635
+ first: String(blockers[0].id),
16636
+ desc: blockers[0].description
16637
+ })
16466
16638
  };
16467
16639
  }
16468
- if (status === "done") {
16469
- const blockers = this.tracker.getPlan().steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
16470
- if (blockers.length > 0) {
16640
+ }
16641
+ if (status === "done") {
16642
+ if (target.kind === "delete") {
16643
+ const leftovers = deps.stillExistingDeliverables(target);
16644
+ if (leftovers.length > 0) {
16471
16645
  return {
16472
16646
  success: false,
16473
- output: t("plan.order_blocked", {
16647
+ output: t("plan.deliverables_remain", {
16474
16648
  step: String(stepId),
16475
- first: String(blockers[0].id),
16476
- desc: blockers[0].description
16649
+ files: leftovers.join(", ")
16477
16650
  })
16478
16651
  };
16479
16652
  }
16480
- }
16481
- if (status === "done") {
16482
- const missing = this.missingStepDeliverables(target);
16653
+ } else {
16654
+ const missing = deps.missingStepDeliverables(target);
16483
16655
  if (missing.length > 0) {
16484
16656
  return {
16485
16657
  success: false,
@@ -16490,373 +16662,559 @@ ${progress2}`,
16490
16662
  };
16491
16663
  }
16492
16664
  }
16493
- this.tracker.updateStepStatus(stepId, status);
16494
- if (args.note)
16495
- this.tracker.addNote(Number(args.step), String(args.note));
16496
- this.tracker.syncCurrentStep();
16497
- this.store.saveActive(this.tracker.getPlan());
16498
- const progress = this.tracker.getProgressString();
16499
- const display = this.tracker.toPromptBlock();
16500
- _ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
16501
- return {
16502
- success: true,
16503
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
16504
- ${progress}`,
16505
- display
16506
- };
16507
16665
  }
16508
- if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && this.tracker) {
16509
- const title = String(args.title || this.tracker.getPlan().title);
16510
- const steps = args.steps.map(String);
16511
- const plan = PlanCreator.createPlan(title, steps, this.baseDir);
16512
- this.setPlan(plan);
16513
- const display = PlanCreator.toPromptBlock(plan, 0);
16514
- const output = t("plan.updated", {
16515
- title,
16516
- steps: String(steps.length)
16517
- });
16518
- return {
16519
- success: true,
16520
- output,
16521
- display
16522
- };
16523
- }
16524
- if (action === "abort") {
16525
- if (this.tracker) {
16526
- this.store.archivePlan(this.tracker.getPlan());
16527
- }
16528
- this.tracker = null;
16529
- this.store.clearActive();
16530
- return { success: true, output: t("plan.aborted") };
16531
- }
16532
- if (!this.tracker) {
16533
- return { success: false, output: t("plan.no_active") };
16666
+ tracker.updateStepStatus(stepId, status);
16667
+ if (args.note)
16668
+ tracker.addNote(Number(args.step), String(args.note));
16669
+ tracker.syncCurrentStep();
16670
+ deps.store.saveActive(tracker.getPlan());
16671
+ const progress = tracker.getProgressString();
16672
+ const display = tracker.toPromptBlock();
16673
+ _ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${tracker.getCurrentStepIndex() + 1}`);
16674
+ return {
16675
+ success: true,
16676
+ output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
16677
+ ${progress}`,
16678
+ display
16679
+ };
16680
+ }
16681
+ if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && deps.trackerRef.current) {
16682
+ const tracker = deps.trackerRef.current;
16683
+ const title = String(args.title || tracker.getPlan().title);
16684
+ const steps = args.steps.map(String);
16685
+ const parsed = deps.parseKinds(args, steps);
16686
+ if (parsed.error) {
16687
+ return { success: false, output: parsed.error };
16534
16688
  }
16689
+ const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
16690
+ deps.setPlan(plan);
16691
+ const display = PlanCreator.toPromptBlock(plan, 0);
16692
+ const output = t("plan.updated", {
16693
+ title,
16694
+ steps: String(steps.length)
16695
+ });
16535
16696
  return {
16536
- success: false,
16537
- output: t("plan.unknown_action", { action })
16697
+ success: true,
16698
+ output,
16699
+ display
16538
16700
  };
16539
16701
  }
16540
- },
16541
- {
16542
- name: "todo",
16543
- description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
16544
- parameters: {
16545
- type: "object",
16546
- properties: {
16547
- action: { type: "string", enum: ["add", "done", "list"] },
16548
- items: { type: "array", items: { type: "string" } }
16549
- },
16550
- required: ["action"]
16702
+ if (action === "abort") {
16703
+ const tracker = deps.trackerRef.current;
16704
+ if (tracker) {
16705
+ deps.store.archivePlan(tracker.getPlan());
16706
+ }
16707
+ deps.trackerRef.current = null;
16708
+ deps.store.clearActive();
16709
+ return { success: true, output: t("plan.aborted") };
16710
+ }
16711
+ if (!deps.trackerRef.current) {
16712
+ return { success: false, output: t("plan.no_active") };
16713
+ }
16714
+ return {
16715
+ success: false,
16716
+ output: t("plan.unknown_action", { action })
16717
+ };
16718
+ }
16719
+ },
16720
+ {
16721
+ name: "todo",
16722
+ description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
16723
+ parameters: {
16724
+ type: "object",
16725
+ properties: {
16726
+ action: { type: "string", enum: ["add", "done", "list"] },
16727
+ items: { type: "array", items: { type: "string" } }
16551
16728
  },
16552
- handler: async (_ctx, args) => {
16553
- if (!this.tracker) {
16554
- return { success: false, output: t("plan.no_active") };
16729
+ required: ["action"]
16730
+ },
16731
+ handler: async (_ctx, args) => {
16732
+ if (!deps.trackerRef.current) {
16733
+ return { success: false, output: t("plan.no_active") };
16734
+ }
16735
+ const tracker = deps.trackerRef.current;
16736
+ const action = String(args.action);
16737
+ const currentStep = tracker.getCurrentStep();
16738
+ if (!currentStep) {
16739
+ return { success: false, output: t("plan.step_not_found") };
16740
+ }
16741
+ if (action === "add" && Array.isArray(args.items)) {
16742
+ const items = args.items.map(String);
16743
+ const existing = currentStep.subtasks ?? [];
16744
+ let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
16745
+ for (const item of items) {
16746
+ existing.push({ id: nextId++, text: item, done: false });
16555
16747
  }
16556
- const action = String(args.action);
16557
- const currentStep = this.tracker.getCurrentStep();
16558
- if (!currentStep) {
16559
- return { success: false, output: t("plan.step_not_found") };
16748
+ currentStep.subtasks = existing;
16749
+ deps.store.saveActive(tracker.getPlan());
16750
+ const display = tracker.toPromptBlock();
16751
+ return {
16752
+ success: true,
16753
+ output: t("todo.added", {
16754
+ count: String(items.length),
16755
+ items: items.join(", ")
16756
+ }),
16757
+ display
16758
+ };
16759
+ }
16760
+ if (action === "done") {
16761
+ const items = Array.isArray(args.items) ? args.items.map(String) : [];
16762
+ if (items.length === 0) {
16763
+ return {
16764
+ success: false,
16765
+ output: t("todo.no_items")
16766
+ };
16560
16767
  }
16561
- if (action === "add" && Array.isArray(args.items)) {
16562
- const items = args.items.map(String);
16563
- const existing = currentStep.subtasks ?? [];
16564
- let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
16565
- for (const item of items) {
16566
- existing.push({ id: nextId++, text: item, done: false });
16768
+ const subs = currentStep.subtasks ?? [];
16769
+ let marked = 0;
16770
+ for (const item of items) {
16771
+ const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
16772
+ if (sub) {
16773
+ sub.done = true;
16774
+ marked++;
16567
16775
  }
16568
- currentStep.subtasks = existing;
16569
- this.store.saveActive(this.tracker.getPlan());
16570
- const display = this.tracker.toPromptBlock();
16776
+ }
16777
+ if (marked === 0) {
16571
16778
  return {
16572
- success: true,
16573
- output: t("todo.added", {
16574
- count: String(items.length),
16779
+ success: false,
16780
+ output: t("todo.subtask_not_found", {
16575
16781
  items: items.join(", ")
16576
- }),
16577
- display
16782
+ })
16578
16783
  };
16579
16784
  }
16580
- if (action === "done") {
16581
- const items = Array.isArray(args.items) ? args.items.map(String) : [];
16582
- if (items.length === 0) {
16583
- return {
16584
- success: false,
16585
- output: t("todo.no_items")
16586
- };
16587
- }
16588
- const subs = currentStep.subtasks ?? [];
16589
- let marked = 0;
16590
- for (const item of items) {
16591
- const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
16592
- if (sub) {
16593
- sub.done = true;
16594
- marked++;
16595
- }
16596
- }
16597
- if (marked === 0) {
16598
- return {
16599
- success: false,
16600
- output: t("todo.subtask_not_found", {
16601
- items: items.join(", ")
16602
- })
16603
- };
16604
- }
16605
- currentStep.subtasks = subs;
16606
- this.store.saveActive(this.tracker.getPlan());
16607
- const doneCount = subs.filter((s) => s.done).length;
16608
- const display = this.tracker.toPromptBlock();
16609
- let output = t("todo.marked_done", {
16610
- count: String(marked)
16611
- });
16612
- output += ` (${doneCount}/${subs.length})`;
16613
- if (doneCount === subs.length) {
16614
- output += `
16785
+ currentStep.subtasks = subs;
16786
+ deps.store.saveActive(tracker.getPlan());
16787
+ const doneCount = subs.filter((s) => s.done).length;
16788
+ const display = tracker.toPromptBlock();
16789
+ let output = t("todo.marked_done", {
16790
+ count: String(marked)
16791
+ });
16792
+ output += ` (${doneCount}/${subs.length})`;
16793
+ if (doneCount === subs.length) {
16794
+ output += `
16615
16795
  All sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
16616
- }
16617
- return { success: true, output, display };
16618
16796
  }
16619
- if (action === "list") {
16620
- const subs = currentStep.subtasks ?? [];
16621
- const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
16622
- return {
16623
- success: true,
16624
- output: `Step ${currentStep.id}: ${currentStep.description}
16797
+ return { success: true, output, display };
16798
+ }
16799
+ if (action === "list") {
16800
+ const subs = currentStep.subtasks ?? [];
16801
+ const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
16802
+ return {
16803
+ success: true,
16804
+ output: `Step ${currentStep.id}: ${currentStep.description}
16625
16805
  ${lines.join(`
16626
16806
  `)}`
16627
- };
16628
- }
16629
- return {
16630
- success: false,
16631
- output: t("todo.unknown_action", { action })
16632
16807
  };
16633
16808
  }
16634
- },
16635
- {
16636
- name: "verify",
16637
- description: "Run verification for the current step. Checks files mentioned in the step description.",
16638
- parameters: {
16639
- type: "object",
16640
- properties: {
16641
- step: { type: "number", description: "Step number to verify" }
16642
- }
16643
- },
16644
- handler: async (_ctx, args) => {
16645
- if (!this.tracker)
16646
- return { success: false, output: t("plan.no_active") };
16647
- const step = args.step ? this.tracker.getStep(Number(args.step)) : this.tracker.getCurrentStep();
16648
- if (!step)
16649
- return { success: false, output: t("plan.step_not_found") };
16650
- const result = await this.verifier.verifyStep(step.description);
16651
- return {
16652
- success: result.passed,
16653
- output: result.passed ? t("verify.passed") : t("verify.failed", {
16654
- details: result.failed.map((f) => f.message).join("; ")
16655
- })
16656
- };
16809
+ return {
16810
+ success: false,
16811
+ output: t("todo.unknown_action", { action })
16812
+ };
16813
+ }
16814
+ },
16815
+ {
16816
+ name: "verify",
16817
+ description: "Run verification for the current step. Checks files mentioned in the step description.",
16818
+ parameters: {
16819
+ type: "object",
16820
+ properties: {
16821
+ step: { type: "number", description: "Step number to verify" }
16657
16822
  }
16823
+ },
16824
+ handler: async (_ctx, args) => {
16825
+ if (!deps.trackerRef.current)
16826
+ return { success: false, output: t("plan.no_active") };
16827
+ const tracker = deps.trackerRef.current;
16828
+ const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
16829
+ if (!step)
16830
+ return { success: false, output: t("plan.step_not_found") };
16831
+ const result = await deps.verifier.verifyStep(step.description);
16832
+ return {
16833
+ success: result.passed,
16834
+ output: result.passed ? t("verify.passed") : t("verify.failed", {
16835
+ details: result.failed.map((f) => f.message).join("; ")
16836
+ })
16837
+ };
16658
16838
  }
16659
- ];
16660
- }
16661
- getPlugin() {
16662
- return {
16663
- name: "execution",
16664
- onBeforeThink: (ctx) => {
16665
- if (ctx.contextManager && this.pendingMessages.length > 0) {
16666
- for (const m of this.pendingMessages.splice(0)) {
16667
- ctx.contextManager.addMessage(m);
16839
+ }
16840
+ ];
16841
+ }
16842
+ var init_plan_tool = __esm(() => {
16843
+ init_i18n();
16844
+ init_plan_coverage();
16845
+ });
16846
+
16847
+ // src/modules/execution/windows-commands.ts
16848
+ function firstWord(command) {
16849
+ const segs = command.trim().split(/[\s;|&]+/);
16850
+ return (segs[0] || "").replace(/[^\w-]/g, "").toLowerCase();
16851
+ }
16852
+ function forbiddenWindowsCommand(command) {
16853
+ const m = command.match(/^\s*cd\s+\S+\s*&&\s*(\S+)/);
16854
+ if (m)
16855
+ return forbiddenWindowsCommand(m[1]);
16856
+ const word = firstWord(command);
16857
+ if (word && FORBIDDEN_COMMANDS.has(word))
16858
+ return word;
16859
+ return null;
16860
+ }
16861
+ var FORBIDDEN_COMMANDS;
16862
+ var init_windows_commands = __esm(() => {
16863
+ FORBIDDEN_COMMANDS = new Set([
16864
+ "grep",
16865
+ "sed",
16866
+ "ls",
16867
+ "find",
16868
+ "rm",
16869
+ "touch",
16870
+ "which",
16871
+ "diff",
16872
+ "cp",
16873
+ "mv"
16874
+ ]);
16875
+ });
16876
+
16877
+ // src/modules/execution/execution-plugin.ts
16878
+ import { platform as platform4 } from "os";
16879
+ function createExecutionPlugin(deps) {
16880
+ return {
16881
+ name: "execution",
16882
+ onBeforeThink: (ctx) => {
16883
+ if (ctx.contextManager && deps.pendingMessages.length > 0) {
16884
+ for (const m of deps.pendingMessages.splice(0)) {
16885
+ ctx.contextManager.addMessage(m);
16886
+ }
16887
+ }
16888
+ if (deps.trackerRef.current?.isComplete()) {
16889
+ deps.stuckDetector.resetStepProgress();
16890
+ deps.state.consecutivePlanWarnings = 0;
16891
+ deps.state.lastStepId = -1;
16892
+ deps.state.stuckNotified = false;
16893
+ } else {
16894
+ const step = deps.trackerRef.current?.getCurrentStep();
16895
+ if (deps.trackerRef.current && step) {
16896
+ if (step.id !== deps.state.lastStepId) {
16897
+ deps.state.consecutivePlanWarnings = 0;
16898
+ deps.state.lastStepId = step.id;
16899
+ deps.state.stuckNotified = false;
16668
16900
  }
16669
- }
16670
- if (this.tracker?.isComplete()) {
16671
- this.stuckDetector.resetStepProgress();
16672
- this.consecutivePlanWarnings = 0;
16673
- this.lastStepId = -1;
16674
- this.stuckNotified = false;
16901
+ deps.stuckDetector.setCurrentStep(step.id, step.description);
16902
+ deps.stuckDetector.recordIteration(step.id);
16675
16903
  } else {
16676
- const step = this.tracker?.getCurrentStep();
16677
- if (this.tracker && step) {
16678
- if (step.id !== this.lastStepId) {
16679
- this.consecutivePlanWarnings = 0;
16680
- this.lastStepId = step.id;
16681
- this.stuckNotified = false;
16682
- }
16683
- this.stuckDetector.setCurrentStep(step.id, step.description);
16684
- this.stuckDetector.recordIteration(step.id);
16685
- } else {
16686
- this.stuckDetector.reset();
16687
- this.consecutivePlanWarnings = 0;
16688
- this.lastStepId = -1;
16689
- this.stuckNotified = false;
16690
- const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
16691
- if (iter === 3 && !this.tracker && ctx.contextManager) {
16692
- ctx.contextManager.addMessage({
16693
- role: "user",
16694
- content: `<system-summary>You have made 3 tool calls without creating a plan. For any task that involves creating files, installing packages, or multiple steps — you MUST use plan create BEFORE continuing. Use the plan tool now with concrete steps (exact filenames, commands, deliverables). Do NOT make any more write/edit/bash calls until you have a plan.</system-summary>`
16695
- });
16696
- }
16697
- if (iter >= 6 && !this.tracker && ctx.contextManager) {
16698
- ctx.contextManager.addMessage({
16699
- role: "user",
16700
- content: `<system-summary>STOP. ${iter} iterations without a plan. You MUST call plan create RIGHT NOW. No more tool calls until you create a plan.</system-summary>`
16701
- });
16702
- }
16904
+ deps.stuckDetector.reset();
16905
+ deps.state.consecutivePlanWarnings = 0;
16906
+ deps.state.lastStepId = -1;
16907
+ deps.state.stuckNotified = false;
16908
+ const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
16909
+ if (iter === 3 && !deps.trackerRef.current && ctx.contextManager) {
16910
+ ctx.contextManager.addMessage({
16911
+ role: "user",
16912
+ content: `<system-summary>You have made 3 tool calls without creating a plan. For any task that involves creating files, installing packages, or multiple steps — you MUST use plan create BEFORE continuing. Use the plan tool now with concrete steps (exact filenames, commands, deliverables). Do NOT make any more write/edit/bash calls until you have a plan.</system-summary>`
16913
+ });
16703
16914
  }
16704
- }
16705
- const stuckReason = this.stuckDetector.getStuckReason();
16706
- if (stuckReason) {
16707
- const logIt = this.stuckDetector.isStuck() ? !this.stuckNotified : true;
16708
- if (logIt) {
16709
- ctx.logger?.warn(stuckReason);
16710
- ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
16711
- if (this.stuckDetector.isStuck())
16712
- this.stuckNotified = true;
16915
+ if (iter >= 6 && !deps.trackerRef.current && ctx.contextManager) {
16916
+ ctx.contextManager.addMessage({
16917
+ role: "user",
16918
+ content: `<system-summary>STOP. ${iter} iterations without a plan. You MUST call plan create RIGHT NOW. No more tool calls until you create a plan.</system-summary>`
16919
+ });
16713
16920
  }
16714
16921
  }
16715
- if (this.stuckDetector.isStuck() || this.stuckDetector.hasRepetitiveToolCalls() || this.stuckDetector.hasReadOnlyLoop()) {
16716
- const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
16717
- if (currentIter - this.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
16718
- const recovery = this.stuckDetector.getRecoveryMessage();
16719
- if (recovery && ctx.contextManager) {
16720
- const lastError = this.stuckDetector.getLastErrorOutput();
16721
- const skillHint = lastError ? `
16922
+ }
16923
+ const stuckReason = deps.stuckDetector.getStuckReason();
16924
+ if (stuckReason) {
16925
+ const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
16926
+ if (logIt) {
16927
+ ctx.logger?.warn(stuckReason);
16928
+ ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
16929
+ if (deps.stuckDetector.isStuck())
16930
+ deps.state.stuckNotified = true;
16931
+ }
16932
+ }
16933
+ if (deps.stuckDetector.isStuck() || deps.stuckDetector.hasRepetitiveToolCalls() || deps.stuckDetector.hasReadOnlyLoop()) {
16934
+ const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
16935
+ if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
16936
+ const recovery = deps.stuckDetector.getRecoveryMessage();
16937
+ if (recovery && ctx.contextManager) {
16938
+ const lastError = deps.stuckDetector.getLastErrorOutput();
16939
+ const skillHint = lastError ? `
16722
16940
  If you have relevant skills available, consider loading one with load_skill for expert guidance.` : "";
16723
- const actionableHints = this.stuckDetector.getActionableHints();
16724
- const actionableHintStr = actionableHints.length > 0 ? `
16941
+ const actionableHints = deps.stuckDetector.getActionableHints();
16942
+ const actionableHintStr = actionableHints.length > 0 ? `
16725
16943
  ${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
16726
16944
  `) })}` : "";
16727
- const alternative = this.stuckDetector.getToolAlternative();
16728
- const altHint = alternative ? `
16729
- Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
16730
- ctx.contextManager.addMessage({
16731
- role: "user",
16732
- content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
16733
- });
16734
- }
16735
- const hints = this.stuckDetector.getHints();
16736
- if (hints.length > 0 && ctx.contextManager) {
16737
- const hintMsg = t("exec.hints", {
16738
- hints: hints.map((h) => `- ${h}`).join(`
16945
+ const alternative = deps.stuckDetector.getToolAlternative();
16946
+ const altHint = alternative ? `
16947
+ Tool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
16948
+ ctx.contextManager.addMessage({
16949
+ role: "user",
16950
+ content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
16951
+ });
16952
+ }
16953
+ const hints = deps.stuckDetector.getHints();
16954
+ if (hints.length > 0 && ctx.contextManager) {
16955
+ const hintMsg = t("exec.hints", {
16956
+ hints: hints.map((h) => `- ${h}`).join(`
16739
16957
  `)
16740
- });
16741
- ctx.contextManager.addMessage({
16742
- role: "user",
16743
- content: `<system-summary>${hintMsg}</system-summary>`
16744
- });
16745
- }
16746
- this.stuckDetector.recordEscalation();
16747
- this.lastRecoveryIteration = currentIter;
16748
- if (this.stuckDetector.shouldEscalate() && ctx.onMeta) {
16749
- const escalation = t("exec.escalation", {
16750
- stepId: String(this.tracker?.getCurrentStep()?.id ?? "?"),
16751
- description: this.tracker?.getCurrentStep()?.description ?? ""
16752
- });
16753
- ctx.onMeta(escalation);
16754
- }
16755
- if (this.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
16756
- const step = this.tracker?.getCurrentStep();
16757
- ctx.contextManager.addMessage({
16958
+ });
16959
+ ctx.contextManager.addMessage({
16960
+ role: "user",
16961
+ content: `<system-summary>${hintMsg}</system-summary>`
16962
+ });
16963
+ }
16964
+ deps.stuckDetector.recordEscalation();
16965
+ deps.state.lastRecoveryIteration = currentIter;
16966
+ if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
16967
+ const escalation = t("exec.escalation", {
16968
+ stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
16969
+ description: deps.trackerRef.current?.getCurrentStep()?.description ?? ""
16970
+ });
16971
+ ctx.onMeta(escalation);
16972
+ }
16973
+ if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
16974
+ const step = deps.trackerRef.current?.getCurrentStep();
16975
+ ctx.contextManager.addMessage({
16976
+ role: "user",
16977
+ content: `<system-summary>STOP. Step ${step?.id ?? "?"} ("${step?.description ?? ""}") took ${deps.stuckDetector.getIterationsOnCurrentStep()} iterations with no progress. DO NOT continue this step. Immediately call: plan update step=${step?.id ?? "?"} status=done (if code works despite warnings) OR plan update step=${step?.id ?? "?"} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.</system-summary>`
16978
+ });
16979
+ }
16980
+ }
16981
+ }
16982
+ },
16983
+ onBeforeTool: (_ctx, call) => {
16984
+ const warning = deps.checkPlanAlignment(call);
16985
+ if (warning) {
16986
+ deps.pendingMessages.push({
16987
+ role: "user",
16988
+ content: `<system-summary>${warning}</system-summary>`
16989
+ });
16990
+ deps.state.consecutivePlanWarnings++;
16991
+ if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
16992
+ deps.pendingMessages.push({
16993
+ role: "user",
16994
+ content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
16995
+ });
16996
+ return t("exec.plan_blocked", {
16997
+ step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
16998
+ max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
16999
+ });
17000
+ }
17001
+ } else {
17002
+ deps.state.consecutivePlanWarnings = 0;
17003
+ }
17004
+ return true;
17005
+ },
17006
+ onToolCall: (ctx) => {
17007
+ const toolName = ctx?.toolName;
17008
+ const args = ctx?.args;
17009
+ if (toolName && args) {
17010
+ deps.stuckDetector.recordToolCall(toolName, args);
17011
+ }
17012
+ },
17013
+ onAfterTool: (ctx, call, result) => {
17014
+ if (call.name === "bash") {
17015
+ deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
17016
+ }
17017
+ const toolText = String(result.output ?? "");
17018
+ if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
17019
+ deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
17020
+ }
17021
+ if (!result.success) {
17022
+ if (call.name === "bash" && platform4() === "win32") {
17023
+ const cmd = String(call.arguments?.command ?? "");
17024
+ const forbidden = forbiddenWindowsCommand(cmd);
17025
+ if (forbidden) {
17026
+ const n = (deps.forbiddenBashFailures.get(forbidden) || 0) + 1;
17027
+ deps.forbiddenBashFailures.set(forbidden, n);
17028
+ if (n === 2) {
17029
+ deps.pendingMessages.push({
16758
17030
  role: "user",
16759
- content: `<system-summary>STOP. Step ${step?.id ?? "?"} ("${step?.description ?? ""}") took ${this.stuckDetector.getIterationsOnCurrentStep()} iterations with no progress. DO NOT continue this step. Immediately call: plan update step=${step?.id ?? "?"} status=done (if code works despite warnings) OR plan update step=${step?.id ?? "?"} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.</system-summary>`
17031
+ content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`
16760
17032
  });
16761
17033
  }
16762
17034
  }
16763
17035
  }
16764
- },
16765
- onBeforeTool: (_ctx, call) => {
16766
- const warning = this.checkPlanAlignment(call);
16767
- if (warning) {
16768
- this.pendingMessages.push({
17036
+ deps.stuckDetector.recordToolError(call.name, result.output);
17037
+ const actionableHints = deps.stuckDetector.getActionableHints();
17038
+ const alternative = deps.stuckDetector.getToolAlternative();
17039
+ if (actionableHints.length > 0 || alternative) {
17040
+ const parts = [...actionableHints];
17041
+ if (alternative) {
17042
+ parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
17043
+ }
17044
+ deps.pendingMessages.push({
16769
17045
  role: "user",
16770
- content: `<system-summary>${warning}</system-summary>`
17046
+ content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
17047
+ `) })}</system-summary>`
16771
17048
  });
16772
- this.consecutivePlanWarnings++;
16773
- if (this.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
16774
- this.pendingMessages.push({
17049
+ }
17050
+ } else {
17051
+ deps.stuckDetector.recordToolSuccess();
17052
+ if (call.name === "bash" && result.success) {
17053
+ const cmd = String(call.arguments?.command ?? "");
17054
+ const testRun = detectTestResults(String(result.output ?? ""));
17055
+ if (testRun && testRun.failed > 0) {
17056
+ deps.pendingMessages.push({
16775
17057
  role: "user",
16776
- content: `<system-summary>${t("exec.plan_blocked", { step: String(this.tracker?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`
17058
+ content: `<system-summary>${testRun.framework} reported ${testRun.failed} FAILING test(s) (${testRun.passed} passing). Do NOT mark the current step as done — fix the failing tests (read the failure output, correct the code) and re-run them until all pass.</system-summary>`
16777
17059
  });
16778
- return t("exec.plan_blocked", {
16779
- step: String(this.tracker?.getCurrentStep()?.id ?? "?"),
16780
- max: MAX_PLAN_WARNINGS_BEFORE_BLOCK
17060
+ } else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
17061
+ deps.pendingMessages.push({
17062
+ role: "user",
17063
+ content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`
16781
17064
  });
16782
- }
16783
- } else {
16784
- this.consecutivePlanWarnings = 0;
16785
- }
16786
- return true;
16787
- },
16788
- onToolCall: (ctx) => {
16789
- const toolName = ctx?.toolName;
16790
- const args = ctx?.args;
16791
- if (toolName && args) {
16792
- this.stuckDetector.recordToolCall(toolName, args);
16793
- }
16794
- },
16795
- onAfterTool: (ctx, call, result) => {
16796
- if (call.name === "bash") {
16797
- this.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
16798
- }
16799
- const toolText = String(result.output ?? "");
16800
- if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
16801
- this.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
16802
- }
16803
- if (!result.success) {
16804
- this.stuckDetector.recordToolError(call.name, result.output);
16805
- const actionableHints = this.stuckDetector.getActionableHints();
16806
- const alternative = this.stuckDetector.getToolAlternative();
16807
- if (actionableHints.length > 0 || alternative) {
16808
- const parts = [...actionableHints];
16809
- if (alternative) {
16810
- parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
16811
- }
16812
- this.pendingMessages.push({
17065
+ } else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
17066
+ deps.pendingMessages.push({
16813
17067
  role: "user",
16814
- content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
16815
- `) })}</system-summary>`
17068
+ content: `<system-summary>The command "${cmd}" completed successfully. If this was testing your code, mark the current step as done via plan update step=N status=done.</system-summary>`
16816
17069
  });
16817
17070
  }
16818
- } else {
16819
- this.stuckDetector.recordToolSuccess();
16820
- if (call.name === "bash" && result.success) {
16821
- const cmd = String(call.arguments?.command ?? "");
16822
- const testRun = detectTestResults(String(result.output ?? ""));
16823
- if (testRun && testRun.failed > 0) {
16824
- this.pendingMessages.push({
16825
- role: "user",
16826
- content: `<system-summary>${testRun.framework} reported ${testRun.failed} FAILING test(s) (${testRun.passed} passing). Do NOT mark the current step as done — fix the failing tests (read the failure output, correct the code) and re-run them until all pass.</system-summary>`
16827
- });
16828
- } else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
16829
- this.pendingMessages.push({
16830
- role: "user",
16831
- content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`
16832
- });
16833
- } else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
16834
- this.pendingMessages.push({
16835
- role: "user",
16836
- content: `<system-summary>The command "${cmd}" completed successfully. If this was testing your code, mark the current step as done via plan update step=N status=done.</system-summary>`
16837
- });
16838
- }
16839
- }
16840
17071
  }
16841
- if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
16842
- const filePath = call.arguments?.path;
16843
- if (filePath) {
16844
- this.stuckDetector.recordFileRewrite(filePath);
16845
- if (this.stuckDetector.hasExcessiveRewrites()) {
16846
- const file = this.stuckDetector.getExcessiveRewriteFile();
16847
- const count = this.stuckDetector.getFileRewriteCount(file);
16848
- if (ctx.onMeta) {
16849
- ctx.onMeta(t("exec.file_rewrite_warning", {
16850
- file,
16851
- count: String(count)
16852
- }));
16853
- }
17072
+ }
17073
+ if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
17074
+ const filePath = call.arguments?.path;
17075
+ if (filePath) {
17076
+ deps.stuckDetector.recordFileRewrite(filePath);
17077
+ if (deps.stuckDetector.hasExcessiveRewrites()) {
17078
+ const file = deps.stuckDetector.getExcessiveRewriteFile();
17079
+ const count = deps.stuckDetector.getFileRewriteCount(file);
17080
+ if (ctx.onMeta) {
17081
+ ctx.onMeta(t("exec.file_rewrite_warning", {
17082
+ file,
17083
+ count: String(count)
17084
+ }));
16854
17085
  }
16855
17086
  }
16856
- this.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
16857
17087
  }
17088
+ deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
17089
+ }
17090
+ }
17091
+ };
17092
+ }
17093
+ var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10;
17094
+ var init_execution_plugin = __esm(() => {
17095
+ init_i18n();
17096
+ init_bash();
17097
+ init_windows_commands();
17098
+ });
17099
+
17100
+ // src/modules/execution/module.ts
17101
+ import { existsSync as existsSync30, readFileSync as readFileSync17 } from "fs";
17102
+ import { resolve as resolve18 } from "path";
17103
+
17104
+ class ExecutionModule {
17105
+ name = "execution";
17106
+ tracker = null;
17107
+ verifier;
17108
+ stuckDetector;
17109
+ auditor;
17110
+ store;
17111
+ baseDir;
17112
+ _auditSkipsRemaining = 0;
17113
+ pendingMessages = [];
17114
+ forbiddenBashFailures = new Map;
17115
+ state = {
17116
+ lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
17117
+ consecutivePlanWarnings: 0,
17118
+ lastStepId: -1,
17119
+ stuckNotified: false,
17120
+ depsGateHints: new Map
17121
+ };
17122
+ trackerRef = (() => {
17123
+ const self = this;
17124
+ return {
17125
+ get current() {
17126
+ return self.tracker;
17127
+ },
17128
+ set current(v) {
17129
+ self.tracker = v;
16858
17130
  }
16859
17131
  };
17132
+ })();
17133
+ constructor(baseDir, stuckThreshold = 6) {
17134
+ this.baseDir = baseDir;
17135
+ this.verifier = new StepVerifier(baseDir);
17136
+ this.stuckDetector = new StuckDetector(stuckThreshold);
17137
+ this.auditor = new Auditor(baseDir);
17138
+ this.store = new PlanStore(baseDir);
17139
+ }
17140
+ setPlan(plan) {
17141
+ this.tracker = new PlanTracker(plan);
17142
+ this.store.saveActive(plan);
17143
+ this._auditSkipsRemaining = 0;
17144
+ }
17145
+ restorePlan() {
17146
+ const plan = this.store.loadActive();
17147
+ if (!plan)
17148
+ return false;
17149
+ this.tracker = new PlanTracker(plan);
17150
+ this.tracker.syncCurrentStep();
17151
+ this._auditSkipsRemaining = 1;
17152
+ return true;
17153
+ }
17154
+ getTracker() {
17155
+ return this.tracker;
17156
+ }
17157
+ getStore() {
17158
+ return this.store;
17159
+ }
17160
+ getStuckDetector() {
17161
+ return this.stuckDetector;
17162
+ }
17163
+ async runFinalAudit() {
17164
+ if (!this.tracker)
17165
+ return null;
17166
+ if (this._auditSkipsRemaining > 0) {
17167
+ this._auditSkipsRemaining--;
17168
+ return null;
17169
+ }
17170
+ const plan = this.tracker.getPlan();
17171
+ const audit = await this.auditor.audit(plan);
17172
+ const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
17173
+ const done = plan.steps.filter((s) => s.status === "done").length;
17174
+ const passed = audit.passed && pendingSteps.length === 0;
17175
+ return {
17176
+ passed,
17177
+ done,
17178
+ total: plan.steps.length,
17179
+ pendingSteps,
17180
+ missingFiles: audit.missingFiles,
17181
+ summary: audit.summary
17182
+ };
17183
+ }
17184
+ getSystemPromptBlock() {
17185
+ if (!this.tracker)
17186
+ return null;
17187
+ return {
17188
+ content: this.tracker.toPromptBlock(),
17189
+ priority: "critical",
17190
+ essential: true,
17191
+ estimatedTokens: 200
17192
+ };
17193
+ }
17194
+ getToolDefinitions() {
17195
+ return createPlanToolDefinitions({
17196
+ store: this.store,
17197
+ verifier: this.verifier,
17198
+ trackerRef: this.trackerRef,
17199
+ preserveActive: () => this.preserveActive(),
17200
+ setPlan: (p) => this.setPlan(p),
17201
+ missingStepDeliverables: (s) => this.missingStepDeliverables(s),
17202
+ stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
17203
+ parseKinds: (a, st) => this.parseKinds(a, st),
17204
+ getTaskText: (c) => this.getTaskText(c),
17205
+ baseDir: this.baseDir
17206
+ });
17207
+ }
17208
+ getPlugin() {
17209
+ return createExecutionPlugin({
17210
+ stuckDetector: this.stuckDetector,
17211
+ trackerRef: this.trackerRef,
17212
+ pendingMessages: this.pendingMessages,
17213
+ forbiddenBashFailures: this.forbiddenBashFailures,
17214
+ state: this.state,
17215
+ checkPlanAlignment: (call) => this.checkPlanAlignment(call),
17216
+ advancePlanIfStepComplete: (cm, sl) => this.advancePlanIfStepComplete(cm, sl)
17217
+ });
16860
17218
  }
16861
17219
  preserveActive() {
16862
17220
  if (!this.tracker)
@@ -16887,19 +17245,18 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
16887
17245
  ];
16888
17246
  if (allowedAlways.includes(call.name))
16889
17247
  return null;
16890
- const stepPaths = (step.description.match(/\b[\w./\\-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
17248
+ const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map((p) => p.toLowerCase());
16891
17249
  if (stepPaths.length === 0)
16892
17250
  return null;
16893
- const argStr = JSON.stringify(call.arguments).toLowerCase();
16894
- const callPaths = (argStr.match(/\b[\w./\\-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
17251
+ const argStr = JSON.stringify(call.arguments);
17252
+ const callPaths = extractFileLikeTokens(stripUrls(argStr)).map((p) => p.toLowerCase());
16895
17253
  if (callPaths.length === 0)
16896
17254
  return null;
16897
17255
  const plan = this.tracker.getPlan();
16898
17256
  const finishedPaths = new Set;
16899
17257
  for (const s of plan.steps) {
16900
17258
  if (s.status === "done" || s.status === "skipped") {
16901
- const ps = s.description.match(/\b[\w./\\-]+\.[a-z]+/gi)?.map((p) => p.toLowerCase()) ?? [];
16902
- ps.forEach((p) => finishedPaths.add(p));
17259
+ extractFileLikeTokens(stripUrls(s.description)).map((p) => p.toLowerCase()).forEach((p) => finishedPaths.add(p));
16903
17260
  }
16904
17261
  }
16905
17262
  const offPath = callPaths.some((p) => {
@@ -16921,7 +17278,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
16921
17278
  if (!step)
16922
17279
  return;
16923
17280
  const stepText = step.description.toLowerCase();
16924
- const stepPaths = step.description.match(/\b[\w./\\-]+\.[a-z]+/gi) || [];
17281
+ const stepPaths = extractFileLikeTokens(stripUrls(step.description)) || [];
16925
17282
  const isDepsStep = stepText.includes("install") || stepText.includes("зависим") || stepText.includes("init") || stepText.includes("инициализац");
16926
17283
  if (isDepsStep) {
16927
17284
  const lockFiles = [
@@ -16936,11 +17293,11 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
16936
17293
  "poetry.lock",
16937
17294
  "requirements.txt"
16938
17295
  ];
16939
- const hasLockFile = lockFiles.some((f) => existsSync29(resolve18(this.baseDir, f)));
17296
+ const hasLockFile = lockFiles.some((f) => existsSync30(resolve18(this.baseDir, f)));
16940
17297
  if (!hasLockFile) {
16941
17298
  if (contextManager) {
16942
- const hints = this.depsGateHints.get(step.id) || 0;
16943
- this.depsGateHints.set(step.id, hints + 1);
17299
+ const hints = this.state.depsGateHints.get(step.id) || 0;
17300
+ this.state.depsGateHints.set(step.id, hints + 1);
16944
17301
  const force = hints >= 1;
16945
17302
  const content = force ? t("exec.step_gate_deps_force", { step: String(step.id) }) : t("exec.step_gate_deps", {
16946
17303
  step: String(step.id),
@@ -16956,24 +17313,28 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
16956
17313
  }
16957
17314
  if (stepPaths.length === 0)
16958
17315
  return;
16959
- const allExist = stepPaths.every((p) => existsSync29(resolve18(this.baseDir, p)));
16960
- if (!allExist)
16961
- return;
16962
- const emptyFiles = [];
16963
- for (const p of stepPaths) {
16964
- try {
16965
- const content = readFileSync17(resolve18(this.baseDir, p), "utf-8");
16966
- if (content.trim().length < 10) {
16967
- emptyFiles.push(p);
16968
- }
16969
- } catch {}
16970
- }
16971
- if (emptyFiles.length > 0 && contextManager) {
16972
- contextManager.addMessage({
16973
- role: "user",
16974
- content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`
16975
- });
17316
+ const allExist = stepPaths.every((p) => existsSync30(resolve18(this.baseDir, p)));
17317
+ const allGone = stepPaths.every((p) => !existsSync30(resolve18(this.baseDir, p)));
17318
+ const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
17319
+ if (!satisfied)
16976
17320
  return;
17321
+ if (step.kind !== "delete") {
17322
+ const emptyFiles = [];
17323
+ for (const p of stepPaths) {
17324
+ try {
17325
+ const content = readFileSync17(resolve18(this.baseDir, p), "utf-8");
17326
+ if (content.trim().length < 10) {
17327
+ emptyFiles.push(p);
17328
+ }
17329
+ } catch {}
17330
+ }
17331
+ if (emptyFiles.length > 0 && contextManager) {
17332
+ contextManager.addMessage({
17333
+ role: "user",
17334
+ content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`
17335
+ });
17336
+ return;
17337
+ }
16977
17338
  }
16978
17339
  this.tracker?.updateStepStatus(step.id, "done");
16979
17340
  const hadNext = this.tracker?.advance() ?? false;
@@ -17000,18 +17361,49 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17000
17361
  return [];
17001
17362
  return tokens.filter((p) => !findExistingFile(this.baseDir, p));
17002
17363
  }
17364
+ stillExistingDeliverables(step) {
17365
+ const tokens = extractFileLikeTokens(stripUrls(step.description));
17366
+ if (tokens.length === 0)
17367
+ return [];
17368
+ return tokens.filter((p) => findExistingFile(this.baseDir, p));
17369
+ }
17370
+ parseKinds(args, steps) {
17371
+ if (args.kinds === undefined)
17372
+ return { kinds: null, error: null };
17373
+ if (!Array.isArray(args.kinds)) {
17374
+ return { kinds: null, error: t("plan.kinds_not_array") };
17375
+ }
17376
+ const kindsRaw = args.kinds.map(String);
17377
+ if (kindsRaw.length !== steps.length) {
17378
+ return { kinds: null, error: t("plan.kinds_mismatch") };
17379
+ }
17380
+ const invalid = kindsRaw.filter((k) => k !== "create" && k !== "delete");
17381
+ if (invalid.length > 0) {
17382
+ return {
17383
+ kinds: null,
17384
+ error: t("plan.kinds_invalid", {
17385
+ kinds: invalid.join(", ")
17386
+ })
17387
+ };
17388
+ }
17389
+ return { kinds: kindsRaw, error: null };
17390
+ }
17003
17391
  getTaskText(ctx) {
17004
17392
  const manager = ctx?.contextManager;
17005
17393
  if (!manager)
17006
17394
  return "";
17007
17395
  const history = manager.getActiveHistory();
17008
- const firstUser = history.find((m) => m.role === "user");
17396
+ const firstUser = history.find((m) => {
17397
+ if (m.role !== "user")
17398
+ return false;
17399
+ const text = getMessageText(m.content);
17400
+ return !text.startsWith("<system-summary>") && !text.includes("[Compressed:");
17401
+ });
17009
17402
  if (!firstUser)
17010
17403
  return "";
17011
17404
  return getMessageText(firstUser.content).trim();
17012
17405
  }
17013
17406
  }
17014
- var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10;
17015
17407
  var init_module = __esm(() => {
17016
17408
  init_i18n();
17017
17409
  init_tracker();
@@ -17019,20 +17411,20 @@ var init_module = __esm(() => {
17019
17411
  init_stuck_detector();
17020
17412
  init_auditor();
17021
17413
  init_plan_store();
17022
- init_plan_coverage();
17023
- init_bash();
17024
17414
  init_js_identifiers();
17415
+ init_plan_tool();
17416
+ init_execution_plugin();
17025
17417
  });
17026
17418
 
17027
17419
  // src/modules/security/session-encryption.ts
17028
17420
  import {
17029
17421
  readFileSync as readFileSync18,
17030
17422
  writeFileSync as writeFileSync11,
17031
- existsSync as existsSync30,
17032
- readdirSync as readdirSync10,
17423
+ existsSync as existsSync31,
17424
+ readdirSync as readdirSync11,
17033
17425
  unlinkSync as unlinkSync4
17034
17426
  } from "fs";
17035
- import { join as join24 } from "path";
17427
+ import { join as join26 } from "path";
17036
17428
  import { homedir as homedir8 } from "os";
17037
17429
 
17038
17430
  class SessionFileEncryptor {
@@ -17041,7 +17433,7 @@ class SessionFileEncryptor {
17041
17433
  constructor(config) {
17042
17434
  this.config = { ...DEFAULT_SESSION_ENCRYPTION, ...config };
17043
17435
  this.encryptor = new ConfigEncryptor({
17044
- keyPath: config?.keyPath || join24(homedir8(), ".mma", ".session-encryption-key")
17436
+ keyPath: config?.keyPath || join26(homedir8(), ".mma", ".session-encryption-key")
17045
17437
  });
17046
17438
  }
17047
17439
  isEnabled() {
@@ -17125,10 +17517,10 @@ class SessionFileEncryptor {
17125
17517
  encryptSessionDirectory(sessionDir) {
17126
17518
  if (!this.config.enabled)
17127
17519
  return;
17128
- const files = readdirSync10(sessionDir);
17520
+ const files = readdirSync11(sessionDir);
17129
17521
  for (const file of files) {
17130
- const filePath = join24(sessionDir, file);
17131
- if (existsSync30(filePath) && !file.endsWith(".enc")) {
17522
+ const filePath = join26(sessionDir, file);
17523
+ if (existsSync31(filePath) && !file.endsWith(".enc")) {
17132
17524
  try {
17133
17525
  const content = readFileSync18(filePath, "utf8");
17134
17526
  const encrypted = this.encryptFileContent(content);
@@ -17141,10 +17533,10 @@ class SessionFileEncryptor {
17141
17533
  decryptSessionDirectory(sessionDir) {
17142
17534
  if (!this.config.enabled)
17143
17535
  return;
17144
- const files = readdirSync10(sessionDir);
17536
+ const files = readdirSync11(sessionDir);
17145
17537
  for (const file of files) {
17146
17538
  if (file.endsWith(".enc")) {
17147
- const encFilePath = join24(sessionDir, file);
17539
+ const encFilePath = join26(sessionDir, file);
17148
17540
  const decFilePath = encFilePath.slice(0, -4);
17149
17541
  try {
17150
17542
  const content = readFileSync18(encFilePath, "utf8");
@@ -17169,15 +17561,15 @@ var init_session_encryption = __esm(() => {
17169
17561
 
17170
17562
  // src/modules/session/store.ts
17171
17563
  import {
17172
- existsSync as existsSync31,
17564
+ existsSync as existsSync32,
17173
17565
  mkdirSync as mkdirSync15,
17174
- readdirSync as readdirSync11,
17566
+ readdirSync as readdirSync12,
17175
17567
  readFileSync as readFileSync19,
17176
17568
  rmSync as rmSync2,
17177
17569
  writeFileSync as writeFileSync12,
17178
17570
  appendFileSync as appendFileSync6
17179
17571
  } from "fs";
17180
- import { join as join25 } from "path";
17572
+ import { join as join27 } from "path";
17181
17573
  import { gzipSync } from "zlib";
17182
17574
 
17183
17575
  class SessionStore {
@@ -17191,7 +17583,7 @@ class SessionStore {
17191
17583
  }
17192
17584
  }
17193
17585
  getSessionDir(id) {
17194
- return join25(this.baseDir, id);
17586
+ return join27(this.baseDir, id);
17195
17587
  }
17196
17588
  updateEncryption(config) {
17197
17589
  if (config?.enabled) {
@@ -17207,19 +17599,19 @@ class SessionStore {
17207
17599
  mkdirSync15(this.baseDir, { recursive: true });
17208
17600
  }
17209
17601
  sessionDir(id) {
17210
- return join25(this.baseDir, id);
17602
+ return join27(this.baseDir, id);
17211
17603
  }
17212
17604
  metaPath(id) {
17213
- return join25(this.sessionDir(id), "meta.json");
17605
+ return join27(this.sessionDir(id), "meta.json");
17214
17606
  }
17215
17607
  historyPath(id) {
17216
- return join25(this.sessionDir(id), "history.jsonl");
17608
+ return join27(this.sessionDir(id), "history.jsonl");
17217
17609
  }
17218
17610
  sessionLogPath(id) {
17219
- return join25(this.sessionDir(id), "session.jsonl");
17611
+ return join27(this.sessionDir(id), "session.jsonl");
17220
17612
  }
17221
17613
  sessionExists(id) {
17222
- return existsSync31(this.metaPath(id));
17614
+ return existsSync32(this.metaPath(id));
17223
17615
  }
17224
17616
  saveMeta(id, meta) {
17225
17617
  this._metaCache.set(id, meta);
@@ -17237,7 +17629,7 @@ class SessionStore {
17237
17629
  if (cached)
17238
17630
  return cached;
17239
17631
  const path = this.metaPath(id);
17240
- if (!existsSync31(path))
17632
+ if (!existsSync32(path))
17241
17633
  return null;
17242
17634
  try {
17243
17635
  const raw = readFileSync19(path, "utf-8");
@@ -17269,7 +17661,7 @@ class SessionStore {
17269
17661
  }
17270
17662
  loadHistory(id) {
17271
17663
  const path = this.historyPath(id);
17272
- if (!existsSync31(path))
17664
+ if (!existsSync32(path))
17273
17665
  return [];
17274
17666
  try {
17275
17667
  const raw = readFileSync19(path, "utf-8");
@@ -17310,7 +17702,7 @@ class SessionStore {
17310
17702
  }
17311
17703
  loadSessionLog(id) {
17312
17704
  const path = this.sessionLogPath(id);
17313
- if (!existsSync31(path))
17705
+ if (!existsSync32(path))
17314
17706
  return [];
17315
17707
  try {
17316
17708
  const raw = readFileSync19(path, "utf-8");
@@ -17338,9 +17730,9 @@ class SessionStore {
17338
17730
  }
17339
17731
  }
17340
17732
  listSessions() {
17341
- if (!existsSync31(this.baseDir))
17733
+ if (!existsSync32(this.baseDir))
17342
17734
  return [];
17343
- const entries = readdirSync11(this.baseDir, { withFileTypes: true });
17735
+ const entries = readdirSync12(this.baseDir, { withFileTypes: true });
17344
17736
  const sessions = [];
17345
17737
  for (const entry of entries) {
17346
17738
  if (entry.isDirectory()) {
@@ -17355,7 +17747,7 @@ class SessionStore {
17355
17747
  deleteSession(id) {
17356
17748
  this._metaCache.delete(id);
17357
17749
  const dir = this.sessionDir(id);
17358
- if (existsSync31(dir)) {
17750
+ if (existsSync32(dir)) {
17359
17751
  rmSync2(dir, { recursive: true, force: true });
17360
17752
  }
17361
17753
  }
@@ -17367,10 +17759,10 @@ class SessionStore {
17367
17759
  const updatedAt = new Date(session2.updatedAt);
17368
17760
  if (updatedAt < thirtyDaysAgo) {
17369
17761
  const historyPath = this.historyPath(session2.id);
17370
- if (existsSync31(historyPath)) {
17762
+ if (existsSync32(historyPath)) {
17371
17763
  const content = readFileSync19(historyPath, "utf-8");
17372
17764
  const compressed = gzipSync(content);
17373
- const gzPath = join25(this.baseDir, `${session2.id}.jsonl.gz`);
17765
+ const gzPath = join27(this.baseDir, `${session2.id}.jsonl.gz`);
17374
17766
  writeFileSync12(gzPath, compressed);
17375
17767
  rmSync2(historyPath);
17376
17768
  }
@@ -17580,9 +17972,9 @@ class ProfileCompressor {
17580
17972
  }
17581
17973
 
17582
17974
  // src/modules/user-profile/profile.ts
17583
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as existsSync32, mkdirSync as mkdirSync16 } from "fs";
17584
- import { join as join26 } from "path";
17585
- import { homedir as homedir9, hostname, platform as platform4, type } from "os";
17975
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as existsSync33, mkdirSync as mkdirSync16 } from "fs";
17976
+ import { join as join28 } from "path";
17977
+ import { homedir as homedir9, hostname, platform as platform5, type } from "os";
17586
17978
  import { env } from "process";
17587
17979
 
17588
17980
  class UserProfile {
@@ -17594,7 +17986,7 @@ class UserProfile {
17594
17986
  }
17595
17987
  collect() {
17596
17988
  this.info = {
17597
- platform: platform4(),
17989
+ platform: platform5(),
17598
17990
  os: `${type()} ${hostname()}`,
17599
17991
  hostname: hostname(),
17600
17992
  shell: env.SHELL || env.ComSpec || "unknown",
@@ -17605,14 +17997,14 @@ class UserProfile {
17605
17997
  return this.info;
17606
17998
  }
17607
17999
  save() {
17608
- if (!existsSync32(this.profileDir)) {
18000
+ if (!existsSync33(this.profileDir)) {
17609
18001
  mkdirSync16(this.profileDir, { recursive: true });
17610
18002
  }
17611
- writeFileSync13(join26(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
18003
+ writeFileSync13(join28(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
17612
18004
  }
17613
18005
  load() {
17614
- const path = join26(this.profileDir, "profile.json");
17615
- if (!existsSync32(path))
18006
+ const path = join28(this.profileDir, "profile.json");
18007
+ if (!existsSync33(path))
17616
18008
  return null;
17617
18009
  try {
17618
18010
  const data = JSON.parse(readFileSync20(path, "utf-8"));
@@ -17650,21 +18042,21 @@ class UserProfile {
17650
18042
  var init_profile = () => {};
17651
18043
 
17652
18044
  // src/modules/skills/loader.ts
17653
- import { readdirSync as readdirSync12, readFileSync as readFileSync21, existsSync as existsSync33, statSync as statSync6 } from "fs";
17654
- import { join as join27 } from "path";
18045
+ import { readdirSync as readdirSync13, readFileSync as readFileSync21, existsSync as existsSync34, statSync as statSync6 } from "fs";
18046
+ import { join as join29 } from "path";
17655
18047
 
17656
18048
  class SkillsLoader {
17657
18049
  loadFromDir(dirPath) {
17658
- if (!existsSync33(dirPath))
18050
+ if (!existsSync34(dirPath))
17659
18051
  return [];
17660
18052
  const skills = [];
17661
18053
  this.scanDir(dirPath, skills);
17662
18054
  return skills;
17663
18055
  }
17664
18056
  scanDir(dirPath, skills) {
17665
- const entries = readdirSync12(dirPath);
18057
+ const entries = readdirSync13(dirPath);
17666
18058
  for (const entry of entries) {
17667
- const fullPath = join27(dirPath, entry);
18059
+ const fullPath = join29(dirPath, entry);
17668
18060
  const stat = statSync6(fullPath);
17669
18061
  if (stat.isDirectory()) {
17670
18062
  this.scanDir(fullPath, skills);
@@ -17908,10 +18300,10 @@ var init_browser2 = __esm(() => {
17908
18300
  });
17909
18301
 
17910
18302
  // src/modules/lsp/command.ts
17911
- import { delimiter, join as join28 } from "path";
17912
- import { existsSync as existsSync34 } from "fs";
17913
- import { platform as platform5 } from "os";
17914
- function resolveSpawnCommand(command, platformName = platform5(), pathEnv = process.env.PATH ?? "") {
18303
+ import { delimiter, join as join30 } from "path";
18304
+ import { existsSync as existsSync35 } from "fs";
18305
+ import { platform as platform6 } from "os";
18306
+ function resolveSpawnCommand(command, platformName = platform6(), pathEnv = process.env.PATH ?? "") {
17915
18307
  if (platformName !== "win32")
17916
18308
  return command;
17917
18309
  if (command.includes("/") || command.includes("\\") || WIN_EXTS.some((ext) => command.toLowerCase().endsWith(ext))) {
@@ -17920,8 +18312,8 @@ function resolveSpawnCommand(command, platformName = platform5(), pathEnv = proc
17920
18312
  const dirs = pathEnv.split(delimiter).filter(Boolean);
17921
18313
  for (const dir of dirs) {
17922
18314
  for (const ext of WIN_EXTS) {
17923
- const candidate = join28(dir, `${command}${ext}`);
17924
- if (existsSync34(candidate))
18315
+ const candidate = join30(dir, `${command}${ext}`);
18316
+ if (existsSync35(candidate))
17925
18317
  return `${command}${ext}`;
17926
18318
  }
17927
18319
  }
@@ -17948,7 +18340,7 @@ import {
17948
18340
  execSync as execSync2
17949
18341
  } from "child_process";
17950
18342
  import { resolve as resolve19 } from "path";
17951
- import { platform as platform6 } from "os";
18343
+ import { platform as platform7 } from "os";
17952
18344
 
17953
18345
  class LspClient {
17954
18346
  process = null;
@@ -17960,11 +18352,11 @@ class LspClient {
17960
18352
  diagnosticsResolve = null;
17961
18353
  diagnosticsTimer = null;
17962
18354
  initialized = false;
17963
- async checkFile(filePath, baseDir, config) {
18355
+ async checkFile(filePath, baseDir, config, projectRoot = baseDir) {
17964
18356
  const timeout = config.timeout ?? DEFAULT_TIMEOUT;
17965
18357
  try {
17966
- await this.startServer(config, baseDir);
17967
- const rootUri = this.pathToUri(resolve19(baseDir));
18358
+ await this.startServer(config, projectRoot);
18359
+ const rootUri = this.pathToUri(projectRoot);
17968
18360
  const initResult = await this.sendRequest("initialize", {
17969
18361
  processId: process.pid,
17970
18362
  rootUri,
@@ -18001,7 +18393,7 @@ class LspClient {
18001
18393
  await this.shutdown();
18002
18394
  }
18003
18395
  }
18004
- async startServer(config, baseDir) {
18396
+ async startServer(config, projectRoot) {
18005
18397
  if (config.autoInstall === false) {
18006
18398
  try {
18007
18399
  execSync2(`where ${config.command}`, { stdio: "pipe", timeout: 3000 });
@@ -18011,13 +18403,13 @@ class LspClient {
18011
18403
  }
18012
18404
  return new Promise((resolve20, reject) => {
18013
18405
  const args = config.args ?? [];
18014
- const isWin = platform6() === "win32";
18406
+ const isWin = platform7() === "win32";
18015
18407
  let spawnCommand = resolveSpawnCommand(config.command);
18016
18408
  let spawnArgs = args;
18017
18409
  const spawnOpts = {
18018
18410
  stdio: ["pipe", "pipe", "pipe"],
18019
18411
  env: { ...process.env, ...config.env },
18020
- cwd: baseDir
18412
+ cwd: projectRoot
18021
18413
  };
18022
18414
  if (isWin) {
18023
18415
  spawnCommand = buildWinCommandLine(spawnCommand, args);
@@ -18190,16 +18582,21 @@ var init_client2 = __esm(() => {
18190
18582
  });
18191
18583
 
18192
18584
  // src/modules/lsp/module.ts
18193
- import { existsSync as existsSync35 } from "fs";
18585
+ import { existsSync as existsSync36 } from "fs";
18194
18586
  import { resolve as resolve20 } from "path";
18195
18587
 
18196
18588
  class LspModule {
18197
18589
  name = "lsp";
18198
18590
  config;
18199
18591
  client = new LspClient;
18592
+ consecutiveFailures = 0;
18593
+ lspDisabled = false;
18200
18594
  constructor(config) {
18201
18595
  this.config = { ...DEFAULT_LSP_CONFIG, ...config };
18202
18596
  }
18597
+ isLspDisabled() {
18598
+ return this.lspDisabled;
18599
+ }
18203
18600
  getPlugin() {
18204
18601
  const self = this;
18205
18602
  return {
@@ -18208,6 +18605,8 @@ class LspModule {
18208
18605
  onAfterTool: async (_ctx, call, result) => {
18209
18606
  if (!self.config.enabled)
18210
18607
  return;
18608
+ if (self.lspDisabled)
18609
+ return;
18211
18610
  if (call.name !== "write_file" && call.name !== "edit_file")
18212
18611
  return;
18213
18612
  if (!result.success)
@@ -18216,13 +18615,15 @@ class LspModule {
18216
18615
  if (!filePath)
18217
18616
  return;
18218
18617
  const fullPath = resolve20(_ctx.baseDir, filePath);
18219
- if (!existsSync35(fullPath))
18618
+ if (!existsSync36(fullPath))
18220
18619
  return;
18221
18620
  const serverConfig = getServerForFile(fullPath, self.config);
18222
18621
  if (!serverConfig)
18223
18622
  return;
18623
+ const projectRoot = findProjectRoot(fullPath, _ctx.baseDir, serverConfig.workspaceMarkers ?? []);
18224
18624
  try {
18225
- const diagnostics = await self.client.checkFile(fullPath, _ctx.baseDir, serverConfig);
18625
+ const diagnostics = await self.client.checkFile(fullPath, _ctx.baseDir, serverConfig, projectRoot);
18626
+ self.consecutiveFailures = 0;
18226
18627
  const errors = diagnostics.filter((d) => d.severity === 1);
18227
18628
  const warnings = diagnostics.filter((d) => d.severity === 2);
18228
18629
  if (errors.length > 0) {
@@ -18241,12 +18642,20 @@ ${items}`;
18241
18642
  }
18242
18643
  } catch (e) {
18243
18644
  const msg = e instanceof Error ? e.message : String(e);
18645
+ self.consecutiveFailures++;
18244
18646
  try {
18245
18647
  _ctx.logger?.warn(`LSP check failed for ${filePath}: ${msg}`);
18246
18648
  } catch {}
18247
- result.output += `
18649
+ if (self.consecutiveFailures >= MAX_CONSECUTIVE_LSP_FAILURES) {
18650
+ self.lspDisabled = true;
18651
+ result.output += `
18652
+
18653
+ [LSP disabled: ${t("lsp.unavailable")}]`;
18654
+ } else {
18655
+ result.output += `
18248
18656
 
18249
18657
  [LSP check failed]: ${msg}`;
18658
+ }
18250
18659
  }
18251
18660
  }
18252
18661
  };
@@ -18262,10 +18671,12 @@ function formatDiagnostics(diagnostics, filePath) {
18262
18671
  }).join(`
18263
18672
  `);
18264
18673
  }
18265
- var severityLabels;
18674
+ var severityLabels, MAX_CONSECUTIVE_LSP_FAILURES = 3;
18266
18675
  var init_module5 = __esm(() => {
18267
18676
  init_client2();
18268
18677
  init_config();
18678
+ init_project_root();
18679
+ init_i18n();
18269
18680
  severityLabels = {
18270
18681
  1: "error",
18271
18682
  2: "warning",
@@ -18282,8 +18693,8 @@ var init_lsp = __esm(() => {
18282
18693
  });
18283
18694
 
18284
18695
  // src/modules/indexer/walker.ts
18285
- import { readdirSync as readdirSync13, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync36, watch } from "fs";
18286
- import { join as join29, relative as relative2, extname as extname5 } from "path";
18696
+ import { readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync37, watch } from "fs";
18697
+ import { join as join31, relative as relative2, extname as extname5 } from "path";
18287
18698
 
18288
18699
  class Indexer {
18289
18700
  baseDir;
@@ -18310,18 +18721,18 @@ class Indexer {
18310
18721
  let totalSize = 0;
18311
18722
  let count = 0;
18312
18723
  const walkDir = (dir) => {
18313
- if (!existsSync36(dir))
18724
+ if (!existsSync37(dir))
18314
18725
  return;
18315
18726
  let entries;
18316
18727
  try {
18317
- entries = readdirSync13(dir);
18728
+ entries = readdirSync14(dir);
18318
18729
  } catch {
18319
18730
  return;
18320
18731
  }
18321
18732
  for (const entry of entries) {
18322
18733
  if (count >= this.MAX_FILES)
18323
18734
  return;
18324
- const fullPath = join29(dir, entry);
18735
+ const fullPath = join31(dir, entry);
18325
18736
  const relPath = relative2(this.baseDir, fullPath);
18326
18737
  const stat = statSync7(fullPath);
18327
18738
  if (stat.isDirectory()) {
@@ -18384,19 +18795,19 @@ var init_walker = __esm(() => {
18384
18795
  });
18385
18796
 
18386
18797
  // src/modules/indexer/cache.ts
18387
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as existsSync37, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
18388
- import { join as join30 } from "path";
18798
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as existsSync38, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
18799
+ import { join as join32 } from "path";
18389
18800
 
18390
18801
  class IndexCache {
18391
18802
  cachePath;
18392
18803
  cache = null;
18393
18804
  constructor(cacheDir) {
18394
- this.cachePath = join30(cacheDir, "index-cache.json");
18805
+ this.cachePath = join32(cacheDir, "index-cache.json");
18395
18806
  }
18396
18807
  load() {
18397
18808
  if (this.cache)
18398
18809
  return this.cache;
18399
- if (!existsSync37(this.cachePath))
18810
+ if (!existsSync38(this.cachePath))
18400
18811
  return null;
18401
18812
  try {
18402
18813
  this.cache = JSON.parse(readFileSync23(this.cachePath, "utf-8"));
@@ -18407,14 +18818,14 @@ class IndexCache {
18407
18818
  }
18408
18819
  save(result) {
18409
18820
  this.cache = result;
18410
- const dir = join30(this.cachePath, "..");
18411
- if (!existsSync37(dir))
18821
+ const dir = join32(this.cachePath, "..");
18822
+ if (!existsSync38(dir))
18412
18823
  mkdirSync17(dir, { recursive: true });
18413
18824
  writeFileSync14(this.cachePath, JSON.stringify(result), "utf-8");
18414
18825
  }
18415
18826
  invalidate() {
18416
18827
  this.cache = null;
18417
- if (existsSync37(this.cachePath)) {
18828
+ if (existsSync38(this.cachePath)) {
18418
18829
  try {
18419
18830
  rmSync3(this.cachePath);
18420
18831
  } catch {}
@@ -18424,11 +18835,11 @@ class IndexCache {
18424
18835
  var init_cache = () => {};
18425
18836
 
18426
18837
  // src/modules/indexer/project-profile.ts
18427
- import { readFileSync as readFileSync24, existsSync as existsSync38 } from "fs";
18428
- import { join as join31 } from "path";
18838
+ import { readFileSync as readFileSync24, existsSync as existsSync39 } from "fs";
18839
+ import { join as join33 } from "path";
18429
18840
  function detectManifest(baseDir) {
18430
18841
  for (const manifest of MANIFEST_ORDER) {
18431
- if (existsSync38(join31(baseDir, manifest)))
18842
+ if (existsSync39(join33(baseDir, manifest)))
18432
18843
  return manifest;
18433
18844
  }
18434
18845
  return null;
@@ -18445,7 +18856,7 @@ function cleanDependency(entry) {
18445
18856
  }
18446
18857
  function readPackageJson(baseDir) {
18447
18858
  try {
18448
- const raw = JSON.parse(readFileSync24(join31(baseDir, "package.json"), "utf-8"));
18859
+ const raw = JSON.parse(readFileSync24(join33(baseDir, "package.json"), "utf-8"));
18449
18860
  if (!raw || typeof raw !== "object")
18450
18861
  return null;
18451
18862
  const profile = {
@@ -18469,7 +18880,7 @@ function readPackageJson(baseDir) {
18469
18880
  }
18470
18881
  function readPyproject(baseDir) {
18471
18882
  try {
18472
- const content = readFileSync24(join31(baseDir, "pyproject.toml"), "utf-8");
18883
+ const content = readFileSync24(join33(baseDir, "pyproject.toml"), "utf-8");
18473
18884
  const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
18474
18885
  const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
18475
18886
  if (nameMatch)
@@ -18485,7 +18896,7 @@ function readPyproject(baseDir) {
18485
18896
  }
18486
18897
  function readCargo(baseDir) {
18487
18898
  try {
18488
- const content = readFileSync24(join31(baseDir, "Cargo.toml"), "utf-8");
18899
+ const content = readFileSync24(join33(baseDir, "Cargo.toml"), "utf-8");
18489
18900
  const profile = { runtime: "rust", deps: [], devDeps: [], scripts: {} };
18490
18901
  const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
18491
18902
  if (nameMatch)
@@ -18509,7 +18920,7 @@ function readCargo(baseDir) {
18509
18920
  }
18510
18921
  function readGoMod(baseDir) {
18511
18922
  try {
18512
- const content = readFileSync24(join31(baseDir, "go.mod"), "utf-8");
18923
+ const content = readFileSync24(join33(baseDir, "go.mod"), "utf-8");
18513
18924
  const profile = { runtime: "go", deps: [], devDeps: [], scripts: {} };
18514
18925
  const moduleMatch = content.match(/^module\s+(\S+)/m);
18515
18926
  if (moduleMatch)
@@ -18527,7 +18938,7 @@ function readGoMod(baseDir) {
18527
18938
  }
18528
18939
  function readRequirements(baseDir) {
18529
18940
  try {
18530
- const content = readFileSync24(join31(baseDir, "requirements.txt"), "utf-8");
18941
+ const content = readFileSync24(join33(baseDir, "requirements.txt"), "utf-8");
18531
18942
  const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
18532
18943
  for (const line of content.split(`
18533
18944
  `)) {
@@ -18592,7 +19003,7 @@ var init_project_profile = __esm(() => {
18592
19003
  });
18593
19004
 
18594
19005
  // src/modules/indexer/module.ts
18595
- import { dirname as dirname9 } from "path";
19006
+ import { dirname as dirname11 } from "path";
18596
19007
 
18597
19008
  class IndexerModule {
18598
19009
  name = "indexer";
@@ -18696,22 +19107,54 @@ class IndexerModule {
18696
19107
  });
18697
19108
  const more = result.files.length > 100 ? `
18698
19109
  ${t("indexer.and_more", { count: result.files.length - 100 })}` : "";
19110
+ const duplicates = this.findDuplicateBasenames(result);
18699
19111
  return [
18700
19112
  `${t("indexer.map_header")} (${this.baseDir})`,
18701
19113
  summary,
18702
19114
  ...stackLine ? [stackLine] : [],
18703
19115
  `${t("indexer.top_directories")}: ${topDirs}`,
19116
+ ...duplicates.length > 0 ? [t("indexer.duplicates", { names: duplicates.join(", ") })] : [],
18704
19117
  `${t("indexer.files")}:` + (fileLines.length > 0 ? "" : " " + t("indexer.empty")),
18705
19118
  ...fileLines,
18706
19119
  more
18707
19120
  ].join(`
18708
19121
  `);
18709
19122
  }
19123
+ findDuplicateBasenames(result) {
19124
+ const byDir = {};
19125
+ for (const f of result.files) {
19126
+ const path = f.path.replace(/\\/g, "/");
19127
+ const idx = path.lastIndexOf("/");
19128
+ const dir = idx >= 0 ? path.slice(0, idx) : "";
19129
+ const name = path.slice(idx + 1);
19130
+ const dot = name.lastIndexOf(".");
19131
+ if (dot <= 0)
19132
+ continue;
19133
+ const base = name.slice(0, dot);
19134
+ const ext = name.slice(dot + 1);
19135
+ if (!byDir[dir])
19136
+ byDir[dir] = new Map;
19137
+ const list = byDir[dir].get(base) ?? [];
19138
+ list.push(ext);
19139
+ byDir[dir].set(base, list);
19140
+ }
19141
+ const dups = [];
19142
+ for (const dir of Object.keys(byDir).sort()) {
19143
+ for (const [base, exts] of byDir[dir]) {
19144
+ if (exts.length < 2)
19145
+ continue;
19146
+ const uniqueExts = [...new Set(exts)].sort();
19147
+ const dirPrefix = dir ? `${dir}/` : "";
19148
+ dups.push(`${dirPrefix}${base}.{${uniqueExts.join(",")}}`);
19149
+ }
19150
+ }
19151
+ return dups;
19152
+ }
18710
19153
  getDirectoryCounts(result) {
18711
19154
  const counts = {};
18712
19155
  for (const f of result.files) {
18713
19156
  const normalized = f.path.replace(/\\/g, "/");
18714
- const dir = dirname9(normalized);
19157
+ const dir = dirname11(normalized);
18715
19158
  const key = dir === "." ? "(root)" : dir;
18716
19159
  counts[key] = (counts[key] || 0) + 1;
18717
19160
  }
@@ -18949,27 +19392,65 @@ var init_mcp = __esm(() => {
18949
19392
 
18950
19393
  // src/modules/memory/module.ts
18951
19394
  import { homedir as homedir10 } from "os";
18952
- import { join as join32 } from "path";
19395
+ import { join as join34 } from "path";
18953
19396
 
18954
19397
  class MemoryModule {
18955
19398
  name = "memory";
18956
19399
  store;
18957
- constructor(memoryDir) {
18958
- const dir = memoryDir || join32(homedir10(), ".mma", "memory");
18959
- this.store = new MemoryStore(dir);
19400
+ constructor(storeOrDir) {
19401
+ if (storeOrDir instanceof MemoryStore) {
19402
+ this.store = storeOrDir;
19403
+ } else {
19404
+ const dir = storeOrDir || join34(homedir10(), ".mma", "memory");
19405
+ this.store = new MemoryStore(dir);
19406
+ }
18960
19407
  }
18961
19408
  getSystemPromptBlock() {
19409
+ const parts = [];
18962
19410
  const prefs = this.store.getPreferences();
18963
- if (Object.keys(prefs).length === 0)
19411
+ if (Object.keys(prefs).length > 0) {
19412
+ const prefStr = Object.entries(prefs).map(([k, v]) => `${k}=${v}`).join(", ");
19413
+ parts.push(`User preferences: ${prefStr}`);
19414
+ }
19415
+ const memoryTail = this.getMemoryTail();
19416
+ if (memoryTail)
19417
+ parts.push(memoryTail);
19418
+ if (parts.length === 0)
18964
19419
  return null;
18965
- const prefStr = Object.entries(prefs).map(([k, v]) => `${k}=${v}`).join(", ");
19420
+ const content = parts.join(`
19421
+
19422
+ `);
18966
19423
  return {
18967
- content: `User preferences: ${prefStr}`,
19424
+ content,
18968
19425
  priority: "normal",
18969
19426
  essential: false,
18970
- estimatedTokens: Math.ceil(prefStr.length / 4) + 10
19427
+ estimatedTokens: Math.ceil(content.length / 4) + 10
18971
19428
  };
18972
19429
  }
19430
+ getMemoryTail() {
19431
+ const sections = [];
19432
+ const tailPerFile = 3;
19433
+ for (const name of MEMORY_MD_FILES) {
19434
+ const content = this.store.read(name).trim();
19435
+ if (!content)
19436
+ continue;
19437
+ const entries = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.startsWith("- ")).slice(-tailPerFile);
19438
+ if (entries.length === 0)
19439
+ continue;
19440
+ sections.push(`[Memory ${name}]:
19441
+ ${entries.join(`
19442
+ `)}`);
19443
+ }
19444
+ if (sections.length === 0)
19445
+ return null;
19446
+ let joined = sections.join(`
19447
+
19448
+ `);
19449
+ if (joined.length > 1200)
19450
+ joined = joined.slice(0, 1200) + `
19451
+ …`;
19452
+ return joined;
19453
+ }
18973
19454
  getToolDefinitions() {
18974
19455
  return [rememberTool, recallTool];
18975
19456
  }
@@ -18978,11 +19459,18 @@ class MemoryModule {
18978
19459
  name: "memory",
18979
19460
  isBuiltin: true,
18980
19461
  onBuildPrompt: () => {
19462
+ const parts = [];
18981
19463
  const prefs = this.store.getPreferences();
18982
- if (Object.keys(prefs).length === 0)
18983
- return null;
18984
- const prefStr = Object.entries(prefs).map(([k, v]) => `${k}=${v}`).join(", ");
18985
- return `User preferences: ${prefStr}`;
19464
+ if (Object.keys(prefs).length > 0) {
19465
+ const prefStr = Object.entries(prefs).map(([k, v]) => `${k}=${v}`).join(", ");
19466
+ parts.push(`User preferences: ${prefStr}`);
19467
+ }
19468
+ const memoryTail = this.getMemoryTail();
19469
+ if (memoryTail)
19470
+ parts.push(memoryTail);
19471
+ return parts.length > 0 ? parts.join(`
19472
+
19473
+ `) : null;
18986
19474
  }
18987
19475
  };
18988
19476
  }
@@ -18990,10 +19478,12 @@ class MemoryModule {
18990
19478
  return this.store;
18991
19479
  }
18992
19480
  }
19481
+ var MEMORY_MD_FILES;
18993
19482
  var init_module8 = __esm(() => {
18994
19483
  init_store2();
18995
19484
  init_remember();
18996
19485
  init_recall();
19486
+ MEMORY_MD_FILES = ["errors", "conventions", "decisions", "facts"];
18997
19487
  });
18998
19488
 
18999
19489
  // src/core/bootstrap.ts
@@ -19003,8 +19493,8 @@ __export(exports_bootstrap, {
19003
19493
  bootstrap: () => bootstrap
19004
19494
  });
19005
19495
  import { homedir as homedir11 } from "os";
19006
- import { join as join33, resolve as resolve21 } from "path";
19007
- import { existsSync as existsSync39, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "fs";
19496
+ import { join as join35, resolve as resolve21 } from "path";
19497
+ import { existsSync as existsSync40, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "fs";
19008
19498
  function buildSystemInfo(config, baseDir, profileCompressed) {
19009
19499
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
19010
19500
  const isWin = profileCompressed.toLowerCase().includes("win32");
@@ -19021,6 +19511,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
19021
19511
  if (config.autoPlan) {
19022
19512
  lines.push(`Plan rule (MANDATORY): any task creating files, installing packages, or requiring multiple actions MUST create a plan with the "plan" tool BEFORE starting. Each step = a concrete deliverable.`);
19023
19513
  }
19514
+ lines.push(`Runtime verification: when a bug is described in terms of runtime behavior (what happens when the app runs), reproduce it — run the app / build / browser tool — and confirm the fix against actual runtime output before calling "plan update step=N status=done".`);
19024
19515
  const hasMCP = config.mcpServers && Object.values(config.mcpServers).some((s) => s.enabled !== false);
19025
19516
  if (hasMCP) {
19026
19517
  lines.push(`MCP servers are available — use the named MCP tools (prefixed mcp__) to query external services.`);
@@ -19029,8 +19520,8 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
19029
19520
  `);
19030
19521
  }
19031
19522
  async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19032
- const dir = configDir || join33(homedir11(), ".mma");
19033
- const projectConfigPath = projectDir ? join33(projectDir, ".mmrc") : join33(process.cwd(), ".mmrc");
19523
+ const dir = configDir || join35(homedir11(), ".mma");
19524
+ const projectConfigPath = projectDir ? join35(projectDir, ".mmrc") : join35(process.cwd(), ".mmrc");
19034
19525
  const config = loadConfig({ configDir: dir, projectConfigPath });
19035
19526
  setLocale(config.locale);
19036
19527
  try {
@@ -19040,7 +19531,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19040
19531
  }
19041
19532
  } catch {}
19042
19533
  const logger = new Logger(config.logLevel);
19043
- logger.setLogDir(join33(dir, "logs"));
19534
+ logger.setLogDir(join35(dir, "logs"));
19044
19535
  logger.debug("MMA bootstrap", {
19045
19536
  version: config.version,
19046
19537
  model: config.model
@@ -19062,7 +19553,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19062
19553
  logger.info(`Model ${config.model} loaded in ${loadResult.loadTime}s`);
19063
19554
  }
19064
19555
  }
19065
- const profile = new UserProfile(join33(dir));
19556
+ const profile = new UserProfile(join35(dir));
19066
19557
  profile.load() || profile.collect();
19067
19558
  profile.save();
19068
19559
  const llmProvider = new OpenAICompatProvider({
@@ -19074,7 +19565,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19074
19565
  rateLimits: config.security?.rateLimits
19075
19566
  });
19076
19567
  const baseDir = projectDir ? resolve21(projectDir) : process.cwd();
19077
- const projectMapCacheDir = join33(baseDir, ".mma");
19568
+ const projectMapCacheDir = join35(baseDir, ".mma");
19078
19569
  const indexerModule = new IndexerModule({
19079
19570
  baseDir,
19080
19571
  cacheDir: projectMapCacheDir
@@ -19085,9 +19576,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19085
19576
  logger.warn(`Project indexing failed: ${err.message}`);
19086
19577
  }
19087
19578
  const skillsLoader = new SkillsLoader;
19088
- const builtinDir = join33(import.meta.dirname, "skills", "builtin");
19089
- const globalDir = join33(homedir11(), ".agents", "skills");
19090
- const projectSkillsDir = join33(baseDir, ".mma", "skills");
19579
+ const builtinDir = join35(import.meta.dirname, "skills", "builtin");
19580
+ const globalDir = join35(homedir11(), ".agents", "skills");
19581
+ const projectSkillsDir = join35(baseDir, ".mma", "skills");
19091
19582
  const availableSkills = skillsLoader.loadFromAllSources(builtinDir, globalDir, projectSkillsDir);
19092
19583
  const skillsBudget = Math.floor(config.contextWindow * config.skills.budget);
19093
19584
  const skillsModule = new SkillsModule(availableSkills, skillsBudget);
@@ -19101,11 +19592,11 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19101
19592
  essential: true,
19102
19593
  estimatedTokens: Math.ceil(systemInfoContent.length / 4)
19103
19594
  };
19104
- const agentsMdGlobal = join33(dir, "AGENTS.md");
19105
- if (!existsSync39(agentsMdGlobal)) {
19595
+ const agentsMdGlobal = join35(dir, "AGENTS.md");
19596
+ if (!existsSync40(agentsMdGlobal)) {
19106
19597
  writeFileSync15(agentsMdGlobal, "", "utf-8");
19107
19598
  }
19108
- const sessionDir = join33(dir, "sessions");
19599
+ const sessionDir = join35(dir, "sessions");
19109
19600
  const sessionStore = new SessionStore(sessionDir);
19110
19601
  sessionStore.init();
19111
19602
  const sessionManager = new SessionManager(sessionStore, {
@@ -19172,7 +19663,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19172
19663
  const mcpModule = new MCPModule(config);
19173
19664
  await mcpModule.initialize();
19174
19665
  moduleRegistry.register(mcpModule);
19175
- const memoryModule = new MemoryModule(join33(dir, "memory"));
19666
+ const memoryStore = new MemoryStore(join35(dir, "memory"));
19667
+ const memoryModule = new MemoryModule(memoryStore);
19176
19668
  moduleRegistry.register(memoryModule);
19177
19669
  if (config.browser.enabled) {
19178
19670
  const browserModule = new BrowserModule;
@@ -19222,8 +19714,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19222
19714
  pluginManager.register(plugin);
19223
19715
  pluginManager.register(plugin2);
19224
19716
  const pluginLoader = new PluginLoader;
19225
- const globalPluginsDir = join33(homedir11(), ".mma", "plugins");
19226
- const projectPluginsDir = join33(baseDir, ".mma", "plugins");
19717
+ const globalPluginsDir = join35(homedir11(), ".mma", "plugins");
19718
+ const projectPluginsDir = join35(baseDir, ".mma", "plugins");
19227
19719
  pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
19228
19720
  pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
19229
19721
  contextManager.onCompact = (summary) => {
@@ -19241,12 +19733,12 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19241
19733
  const skipAgentsMd = noAgentsMd === true;
19242
19734
  if (!skipAgentsMd) {
19243
19735
  const agentsMdCandidates = [
19244
- join33(baseDir, "AGENTS.md"),
19245
- join33(baseDir, ".mma", "AGENTS.md"),
19246
- join33(dir, "AGENTS.md")
19736
+ join35(baseDir, "AGENTS.md"),
19737
+ join35(baseDir, ".mma", "AGENTS.md"),
19738
+ join35(dir, "AGENTS.md")
19247
19739
  ];
19248
19740
  for (const p of agentsMdCandidates) {
19249
- if (existsSync39(p)) {
19741
+ if (existsSync40(p)) {
19250
19742
  const content = readFileSync25(p, "utf-8").trim();
19251
19743
  if (content) {
19252
19744
  agentsMdBlocks.push({
@@ -19289,6 +19781,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19289
19781
  },
19290
19782
  finalAudit: () => execModule.runFinalAudit(),
19291
19783
  sessionManager,
19784
+ memoryStore,
19292
19785
  exitOnComplete
19293
19786
  };
19294
19787
  const agent = new Agent(agentDeps);
@@ -19325,6 +19818,7 @@ var init_bootstrap = __esm(() => {
19325
19818
  init_indexer();
19326
19819
  init_mcp();
19327
19820
  init_module8();
19821
+ init_store2();
19328
19822
  init_i18n();
19329
19823
  init_agent();
19330
19824
  });
@@ -20099,12 +20593,12 @@ __export(exports_manifest, {
20099
20593
  getCertMark: () => getCertMark,
20100
20594
  MANIFEST_PATH: () => MANIFEST_PATH
20101
20595
  });
20102
- import { existsSync as existsSync40, readFileSync as readFileSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
20596
+ import { existsSync as existsSync41, readFileSync as readFileSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
20103
20597
  import { homedir as homedir13 } from "os";
20104
- import { join as join35 } from "path";
20598
+ import { join as join37 } from "path";
20105
20599
  function readManifest(path = MANIFEST_PATH) {
20106
20600
  try {
20107
- if (existsSync40(path)) {
20601
+ if (existsSync41(path)) {
20108
20602
  const raw = JSON.parse(readFileSync26(path, "utf-8"));
20109
20603
  return { version: 1, certifications: raw.certifications ?? [] };
20110
20604
  }
@@ -20112,7 +20606,7 @@ function readManifest(path = MANIFEST_PATH) {
20112
20606
  return { version: 1, certifications: [] };
20113
20607
  }
20114
20608
  function saveManifest(m, path = MANIFEST_PATH) {
20115
- mkdirSync18(join35(homedir13(), ".mma"), { recursive: true });
20609
+ mkdirSync18(join37(homedir13(), ".mma"), { recursive: true });
20116
20610
  writeFileSync16(path, JSON.stringify(m, null, 2), "utf-8");
20117
20611
  }
20118
20612
  function upsertCertification(entry, path = MANIFEST_PATH) {
@@ -20147,7 +20641,7 @@ function getCertMark(model, providerUrl, currentVersion, path = MANIFEST_PATH) {
20147
20641
  }
20148
20642
  var MANIFEST_PATH;
20149
20643
  var init_manifest = __esm(() => {
20150
- MANIFEST_PATH = join35(homedir13(), ".mma", "certifications.json");
20644
+ MANIFEST_PATH = join37(homedir13(), ".mma", "certifications.json");
20151
20645
  });
20152
20646
 
20153
20647
  // node_modules/yaml/dist/nodes/identity.js
@@ -27270,8 +27764,8 @@ var init_scenarios = __esm(() => {
27270
27764
  });
27271
27765
 
27272
27766
  // src/modules/certification/loader.ts
27273
- import { existsSync as existsSync41, readdirSync as readdirSync14, readFileSync as readFileSync27 } from "fs";
27274
- import { join as join36 } from "path";
27767
+ import { existsSync as existsSync42, readdirSync as readdirSync15, readFileSync as readFileSync27 } from "fs";
27768
+ import { join as join38 } from "path";
27275
27769
  function validateScenario(s) {
27276
27770
  const errors2 = [];
27277
27771
  const isSkip = s.mode === "skip";
@@ -27320,12 +27814,12 @@ function loadScenarios(userDir) {
27320
27814
  else
27321
27815
  scenarios.push(s);
27322
27816
  }
27323
- if (userDir && existsSync41(userDir)) {
27324
- for (const file of readdirSync14(userDir)) {
27817
+ if (userDir && existsSync42(userDir)) {
27818
+ for (const file of readdirSync15(userDir)) {
27325
27819
  if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
27326
27820
  continue;
27327
27821
  try {
27328
- const raw = readFileSync27(join36(userDir, file), "utf-8");
27822
+ const raw = readFileSync27(join38(userDir, file), "utf-8");
27329
27823
  const data = $parse(raw);
27330
27824
  const parsed = normalizeScenario(data, file);
27331
27825
  const errs = validateScenario(parsed);
@@ -27378,8 +27872,8 @@ var init_loader3 = __esm(() => {
27378
27872
  });
27379
27873
 
27380
27874
  // src/modules/certification/fact-checker.ts
27381
- import { existsSync as existsSync42, readFileSync as readFileSync28, statSync as statSync8 } from "fs";
27382
- import { join as join37 } from "path";
27875
+ import { existsSync as existsSync43, readFileSync as readFileSync28, statSync as statSync8 } from "fs";
27876
+ import { join as join39 } from "path";
27383
27877
  function checkSandbox(sandboxDir, checks, exitCode, output) {
27384
27878
  const failures = [];
27385
27879
  for (const check of checks) {
@@ -27396,13 +27890,13 @@ function runCheck(sandboxDir, check, exitCode, output) {
27396
27890
  case "outputContains":
27397
27891
  return output.includes(check.text);
27398
27892
  case "fileExists":
27399
- return isFile(join37(sandboxDir, check.path));
27893
+ return isFile(join39(sandboxDir, check.path));
27400
27894
  case "fileNotExists":
27401
- return !existsSync42(join37(sandboxDir, check.path));
27895
+ return !existsSync43(join39(sandboxDir, check.path));
27402
27896
  case "dirExists":
27403
- return isDir(join37(sandboxDir, check.path));
27897
+ return isDir(join39(sandboxDir, check.path));
27404
27898
  case "fileContent": {
27405
- const abs = join37(sandboxDir, check.path);
27899
+ const abs = join39(sandboxDir, check.path);
27406
27900
  if (!isFile(abs))
27407
27901
  return false;
27408
27902
  const content = readFileSync28(abs, "utf-8");
@@ -27413,7 +27907,7 @@ function runCheck(sandboxDir, check, exitCode, output) {
27413
27907
  return false;
27414
27908
  }
27415
27909
  case "fileRegex": {
27416
- const abs = join37(sandboxDir, check.path);
27910
+ const abs = join39(sandboxDir, check.path);
27417
27911
  if (!isFile(abs))
27418
27912
  return false;
27419
27913
  return new RegExp(check.pattern).test(readFileSync28(abs, "utf-8"));
@@ -27424,14 +27918,14 @@ function runCheck(sandboxDir, check, exitCode, output) {
27424
27918
  }
27425
27919
  function isFile(p) {
27426
27920
  try {
27427
- return existsSync42(p) && statSync8(p).isFile();
27921
+ return existsSync43(p) && statSync8(p).isFile();
27428
27922
  } catch {
27429
27923
  return false;
27430
27924
  }
27431
27925
  }
27432
27926
  function isDir(p) {
27433
27927
  try {
27434
- return existsSync42(p) && statSync8(p).isDirectory();
27928
+ return existsSync43(p) && statSync8(p).isDirectory();
27435
27929
  } catch {
27436
27930
  return false;
27437
27931
  }
@@ -27462,9 +27956,9 @@ var init_fact_checker = () => {};
27462
27956
 
27463
27957
  // src/modules/certification/runner.ts
27464
27958
  import { spawn as spawn7 } from "child_process";
27465
- import { existsSync as existsSync43, mkdirSync as mkdirSync19, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
27466
- import { platform as platform7 } from "os";
27467
- import { join as join38, resolve as resolve22, dirname as dirname10 } from "path";
27959
+ import { existsSync as existsSync44, mkdirSync as mkdirSync19, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
27960
+ import { platform as platform8 } from "os";
27961
+ import { join as join40, resolve as resolve22, dirname as dirname12 } from "path";
27468
27962
  async function runScenario(scenario, opts) {
27469
27963
  if (scenario.mode === "skip") {
27470
27964
  return {
@@ -27483,7 +27977,7 @@ async function runScenario(scenario, opts) {
27483
27977
  let passed = 0;
27484
27978
  let firstError;
27485
27979
  for (let i = 1;i <= reps; i++) {
27486
- const sandbox = join38(opts.sandboxBase, `run-${scenario.id}-${i}`);
27980
+ const sandbox = join40(opts.sandboxBase, `run-${scenario.id}-${i}`);
27487
27981
  let failures = [];
27488
27982
  let exitCode = -1;
27489
27983
  let output = "";
@@ -27544,20 +28038,20 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
27544
28038
  rmSync4(sandbox, { recursive: true, force: true });
27545
28039
  mkdirSync19(sandbox, { recursive: true });
27546
28040
  for (const f of scenario.fixtures ?? []) {
27547
- const src = join38(mmaRoot, f.source);
27548
- if (!existsSync43(src)) {
28041
+ const src = join40(mmaRoot, f.source);
28042
+ if (!existsSync44(src)) {
27549
28043
  throw new Error(`fixture missing: ${f.source}`);
27550
28044
  }
27551
- const dest = join38(sandbox, f.dest);
27552
- mkdirSync19(dirname10(dest), { recursive: true });
28045
+ const dest = join40(sandbox, f.dest);
28046
+ mkdirSync19(dirname12(dest), { recursive: true });
27553
28047
  cpSync2(src, dest);
27554
28048
  }
27555
28049
  }
27556
28050
  function resolveMmaEntry(mmaRoot) {
27557
- const dev = join38(mmaRoot, "src", "cli", "main.ts");
27558
- if (existsSync43(dev))
28051
+ const dev = join40(mmaRoot, "src", "cli", "main.ts");
28052
+ if (existsSync44(dev))
27559
28053
  return dev;
27560
- return join38(mmaRoot, "dist", "main.js");
28054
+ return join40(mmaRoot, "dist", "main.js");
27561
28055
  }
27562
28056
  function findMmaRoot(fromDir) {
27563
28057
  const candidates = [
@@ -27565,7 +28059,7 @@ function findMmaRoot(fromDir) {
27565
28059
  resolve22(fromDir, "..")
27566
28060
  ];
27567
28061
  for (const c of candidates) {
27568
- if (existsSync43(join38(c, "package.json")))
28062
+ if (existsSync44(join40(c, "package.json")))
27569
28063
  return c;
27570
28064
  }
27571
28065
  return process.cwd();
@@ -27574,7 +28068,7 @@ function killTree2(child) {
27574
28068
  const pid = child.pid;
27575
28069
  if (!pid)
27576
28070
  return;
27577
- if (platform7() === "win32") {
28071
+ if (platform8() === "win32") {
27578
28072
  spawn7("taskkill", ["/pid", String(pid), "/T", "/F"], {
27579
28073
  windowsHide: true,
27580
28074
  stdio: "ignore"
@@ -27633,13 +28127,13 @@ __export(exports_cli, {
27633
28127
  });
27634
28128
  import { rmSync as rmSync5 } from "fs";
27635
28129
  import { homedir as homedir14 } from "os";
27636
- import { join as join39, dirname as dirname11 } from "path";
28130
+ import { join as join41, dirname as dirname13 } from "path";
27637
28131
  import { fileURLToPath as fileURLToPath2 } from "url";
27638
- import { existsSync as existsSync44, readFileSync as readFileSync29 } from "fs";
28132
+ import { existsSync as existsSync45, readFileSync as readFileSync29 } from "fs";
27639
28133
  function readVersion() {
27640
- const candidates = [join39(MMA_ROOT, "package.json")];
28134
+ const candidates = [join41(MMA_ROOT, "package.json")];
27641
28135
  for (const p of candidates) {
27642
- if (existsSync44(p)) {
28136
+ if (existsSync45(p)) {
27643
28137
  try {
27644
28138
  const raw = JSON.parse(readFileSync29(p, "utf-8"));
27645
28139
  if (raw.version)
@@ -27677,7 +28171,7 @@ async function certify(opts) {
27677
28171
  return;
27678
28172
  }
27679
28173
  console.log(t("cli.cert_started", { model: opts.name, provider: providerUrl }));
27680
- const sandboxBase = join39(process.cwd(), ".mma", "certification");
28174
+ const sandboxBase = join41(process.cwd(), ".mma", "certification");
27681
28175
  const results = [];
27682
28176
  const total = selected.length;
27683
28177
  let idx = 0;
@@ -27789,9 +28283,9 @@ var init_cli = __esm(() => {
27789
28283
  init_loader3();
27790
28284
  init_runner2();
27791
28285
  init_manifest();
27792
- HERE = dirname11(fileURLToPath2(import.meta.url));
28286
+ HERE = dirname13(fileURLToPath2(import.meta.url));
27793
28287
  MMA_ROOT = findMmaRoot(HERE);
27794
- USER_SCENARIO_DIR = join39(homedir14(), ".mma", "certification", "scenarios");
28288
+ USER_SCENARIO_DIR = join41(homedir14(), ".mma", "certification", "scenarios");
27795
28289
  });
27796
28290
 
27797
28291
  // src/cli/repl-commands.ts
@@ -27800,18 +28294,18 @@ __export(exports_repl_commands, {
27800
28294
  registerAllCommands: () => registerAllCommands,
27801
28295
  COMMAND_GROUPS: () => COMMAND_GROUPS
27802
28296
  });
27803
- import { join as join41, dirname as dirname13 } from "path";
28297
+ import { join as join43, dirname as dirname15 } from "path";
27804
28298
  import { homedir as homedir16 } from "os";
27805
- import { existsSync as existsSync46, readFileSync as readFileSync31 } from "fs";
28299
+ import { existsSync as existsSync47, readFileSync as readFileSync31 } from "fs";
27806
28300
  import { fileURLToPath as fileURLToPath4 } from "url";
27807
28301
  function readVersion3() {
27808
- const here = dirname13(fileURLToPath4(import.meta.url));
28302
+ const here = dirname15(fileURLToPath4(import.meta.url));
27809
28303
  const candidates = [
27810
- join41(here, "..", "..", "package.json"),
27811
- join41(here, "..", "package.json")
28304
+ join43(here, "..", "..", "package.json"),
28305
+ join43(here, "..", "package.json")
27812
28306
  ];
27813
28307
  for (const p of candidates) {
27814
- if (existsSync46(p)) {
28308
+ if (existsSync47(p)) {
27815
28309
  try {
27816
28310
  const raw = JSON.parse(readFileSync31(p, "utf8"));
27817
28311
  if (raw.version)
@@ -27877,7 +28371,7 @@ function registerMmaCommands(ctx) {
27877
28371
  }
27878
28372
  try {
27879
28373
  const { loadFileAsDataUrl: loadFileAsDataUrl2, loadUrlAsDataUrl: loadUrlAsDataUrl2, readClipboardImage: readClipboardImage2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
27880
- const { existsSync: existsSync47 } = await import("fs");
28374
+ const { existsSync: existsSync48 } = await import("fs");
27881
28375
  const { resolve: resolve23 } = await import("path");
27882
28376
  let dataUrl;
27883
28377
  let label;
@@ -27897,7 +28391,7 @@ function registerMmaCommands(ctx) {
27897
28391
  label = source;
27898
28392
  } else {
27899
28393
  const absPath = resolve23(process.cwd(), source);
27900
- if (!existsSync47(absPath)) {
28394
+ if (!existsSync48(absPath)) {
27901
28395
  console.log(pc2.red(t("image.not_found", { path: source })));
27902
28396
  return;
27903
28397
  }
@@ -27976,7 +28470,7 @@ function registerMmaCommands(ctx) {
27976
28470
  console.log(pc2.yellow(t("repl.wizard_running")));
27977
28471
  await ctx.withExclusiveInput(async () => {
27978
28472
  const answers = await runSetup(ctx.rl);
27979
- const configPath = join41(homedir16(), ".mma", "config.json");
28473
+ const configPath = join43(homedir16(), ".mma", "config.json");
27980
28474
  ctx.config.provider.type = answers.provider;
27981
28475
  ctx.config.provider.baseUrl = answers.apiBase;
27982
28476
  ctx.config.provider.apiKey = answers.apiKey;
@@ -28030,7 +28524,7 @@ Excluded blocks: ${info.excluded.length}`));
28030
28524
  return;
28031
28525
  }
28032
28526
  ctx.config.provider.type = name;
28033
- const configPath = join41(homedir16(), ".mma", "config.json");
28527
+ const configPath = join43(homedir16(), ".mma", "config.json");
28034
28528
  saveConfig(ctx.config, configPath);
28035
28529
  await ctx.agent.reconfigure(ctx.config);
28036
28530
  console.log(pc2.green(t("repl.provider_set", { name })));
@@ -28086,7 +28580,7 @@ Excluded blocks: ${info.excluded.length}`));
28086
28580
  return;
28087
28581
  }
28088
28582
  ctx.config.model = name;
28089
- const configPath = join41(homedir16(), ".mma", "config.json");
28583
+ const configPath = join43(homedir16(), ".mma", "config.json");
28090
28584
  saveConfig(ctx.config, configPath);
28091
28585
  await ctx.agent.reconfigure(ctx.config);
28092
28586
  console.log(pc2.green(t("repl.model_set", { name })));
@@ -28111,7 +28605,7 @@ Excluded blocks: ${info.excluded.length}`));
28111
28605
  return;
28112
28606
  }
28113
28607
  ctx.config.contextWindow = size;
28114
- const configPath = join41(homedir16(), ".mma", "config.json");
28608
+ const configPath = join43(homedir16(), ".mma", "config.json");
28115
28609
  saveConfig(ctx.config, configPath);
28116
28610
  await ctx.agent.reconfigure(ctx.config);
28117
28611
  console.log(pc2.green(t("cli.context_set", { size })));
@@ -28130,10 +28624,10 @@ Excluded blocks: ${info.excluded.length}`));
28130
28624
  ctx.agent.shutdown();
28131
28625
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
28132
28626
  const { homedir: homedir17 } = await import("os");
28133
- const { join: join42 } = await import("path");
28627
+ const { join: join44 } = await import("path");
28134
28628
  const configDir = ctx.configDir;
28135
28629
  const baseDir = ctx.baseDir;
28136
- const projectConfigPath = join42(baseDir, ".mmrc");
28630
+ const projectConfigPath = join44(baseDir, ".mmrc");
28137
28631
  const freshConfig = loadConfig2({ configDir, projectConfigPath });
28138
28632
  Object.assign(ctx.config, freshConfig);
28139
28633
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
@@ -28433,14 +28927,14 @@ init_bootstrap();
28433
28927
  init_config2();
28434
28928
  init_setup();
28435
28929
  init_i18n();
28436
- import { join as join40, dirname as dirname12 } from "path";
28930
+ import { join as join42, dirname as dirname14 } from "path";
28437
28931
  import { homedir as homedir15 } from "os";
28438
- import { existsSync as existsSync45, readFileSync as readFileSync30 } from "fs";
28932
+ import { existsSync as existsSync46, readFileSync as readFileSync30 } from "fs";
28439
28933
 
28440
28934
  // src/cli/security-commands.ts
28441
28935
  init_bootstrap();
28442
28936
  init_config2();
28443
- import { join as join34 } from "path";
28937
+ import { join as join36 } from "path";
28444
28938
  import { homedir as homedir12 } from "os";
28445
28939
 
28446
28940
  // src/modules/security/security-policies.ts
@@ -28970,7 +29464,7 @@ function createSecurityCommand(program2) {
28970
29464
  }
28971
29465
  });
28972
29466
  securityCmd.command("set-policy").argument("<preset>", t("cli.security.preset")).description(t("cli.security.set_policy")).action(async (preset) => {
28973
- const configPath = join34(homedir12(), ".mma", "config.json");
29467
+ const configPath = join36(homedir12(), ".mma", "config.json");
28974
29468
  const { config: appConfig } = await bootstrap();
28975
29469
  const validPresets = ["strict", "balanced", "permissive"];
28976
29470
  if (!validPresets.includes(preset)) {
@@ -28985,7 +29479,7 @@ function createSecurityCommand(program2) {
28985
29479
  console.log(t("cli.security.policy_description", { description: policy.description }));
28986
29480
  });
28987
29481
  securityCmd.command("enable-encryption").description(t("cli.security.enable_encryption")).action(async () => {
28988
- const configPath = join34(homedir12(), ".mma", "config.json");
29482
+ const configPath = join36(homedir12(), ".mma", "config.json");
28989
29483
  const { config: appConfig } = await bootstrap();
28990
29484
  appConfig.security = appConfig.security || {};
28991
29485
  appConfig.security.sessionEncryption = {
@@ -28997,7 +29491,7 @@ function createSecurityCommand(program2) {
28997
29491
  console.log(t("cli.security.encryption_enabled"));
28998
29492
  });
28999
29493
  securityCmd.command("disable-encryption").description(t("cli.security.disable_encryption")).action(async () => {
29000
- const configPath = join34(homedir12(), ".mma", "config.json");
29494
+ const configPath = join36(homedir12(), ".mma", "config.json");
29001
29495
  const { config: appConfig } = await bootstrap();
29002
29496
  appConfig.security = appConfig.security || {};
29003
29497
  appConfig.security.sessionEncryption = {
@@ -29009,7 +29503,7 @@ function createSecurityCommand(program2) {
29009
29503
  console.log(t("cli.security.encryption_disabled"));
29010
29504
  });
29011
29505
  securityCmd.command("enable-audit").description(t("cli.security.enable_audit")).action(async () => {
29012
- const configPath = join34(homedir12(), ".mma", "config.json");
29506
+ const configPath = join36(homedir12(), ".mma", "config.json");
29013
29507
  const { config: appConfig } = await bootstrap();
29014
29508
  appConfig.security = appConfig.security || {};
29015
29509
  appConfig.security.auditNotifier = {
@@ -29023,7 +29517,7 @@ function createSecurityCommand(program2) {
29023
29517
  console.log(t("cli.security.audit_enabled"));
29024
29518
  });
29025
29519
  securityCmd.command("disable-audit").description(t("cli.security.disable_audit")).action(async () => {
29026
- const configPath = join34(homedir12(), ".mma", "config.json");
29520
+ const configPath = join36(homedir12(), ".mma", "config.json");
29027
29521
  const { config: appConfig } = await bootstrap();
29028
29522
  appConfig.security = appConfig.security || {};
29029
29523
  appConfig.security.auditNotifier = {
@@ -29057,13 +29551,13 @@ function createSecurityCommand(program2) {
29057
29551
  // src/cli/commands.ts
29058
29552
  import { fileURLToPath as fileURLToPath3 } from "url";
29059
29553
  function readVersion2() {
29060
- const here = dirname12(fileURLToPath3(import.meta.url));
29554
+ const here = dirname14(fileURLToPath3(import.meta.url));
29061
29555
  const candidates = [
29062
- join40(here, "..", "..", "package.json"),
29063
- join40(here, "..", "package.json")
29556
+ join42(here, "..", "..", "package.json"),
29557
+ join42(here, "..", "package.json")
29064
29558
  ];
29065
29559
  for (const p of candidates) {
29066
- if (existsSync45(p)) {
29560
+ if (existsSync46(p)) {
29067
29561
  try {
29068
29562
  const raw = JSON.parse(readFileSync30(p, "utf8"));
29069
29563
  if (raw.version)
@@ -29078,7 +29572,7 @@ function createProgram() {
29078
29572
  const program2 = new Command().name("mma").description(t("cli.description")).version(version).option("--no-agents-md", t("cli.no_agents_md")).option("-d, --dir <path>", t("cli.dir")).option("-e, --exit-on-complete", t("cli.exit_on_complete")).option("-j, --json", t("cli.json"));
29079
29573
  program2.command("init").description(t("cli.init")).action(async () => {
29080
29574
  const answers = await runSetup();
29081
- const configPath = join40(homedir15(), ".mma", "config.json");
29575
+ const configPath = join42(homedir15(), ".mma", "config.json");
29082
29576
  const { config } = await bootstrap();
29083
29577
  config.provider.type = answers.provider;
29084
29578
  config.provider.baseUrl = answers.apiBase;
@@ -29123,7 +29617,7 @@ function createProgram() {
29123
29617
  });
29124
29618
  const configCmd = program2.command("config").description(t("cli.manage_config"));
29125
29619
  configCmd.command("set").argument("<key>", t("cli.config_key")).argument("<value>", "Config value").description(t("cli.set_value")).action(async (key, value) => {
29126
- const configPath = join40(homedir15(), ".mma", "config.json");
29620
+ const configPath = join42(homedir15(), ".mma", "config.json");
29127
29621
  const { config } = await bootstrap();
29128
29622
  const keys = key.split(".");
29129
29623
  let obj = config;
@@ -29186,7 +29680,7 @@ function createProgram() {
29186
29680
  console.log(t("cli.model_hint"));
29187
29681
  });
29188
29682
  model.command("use").argument("<name>", "Model name").description(t("cli.set_model")).action(async (name) => {
29189
- const configPath = join40(homedir15(), ".mma", "config.json");
29683
+ const configPath = join42(homedir15(), ".mma", "config.json");
29190
29684
  const { config } = await bootstrap();
29191
29685
  config.model = name;
29192
29686
  saveConfig(config, configPath);
@@ -29222,7 +29716,7 @@ function createProgram() {
29222
29716
  await uncertify2(name, config);
29223
29717
  });
29224
29718
  program2.command("context").description(t("cli.manage_context")).argument("<size>", "Context window size in tokens").action(async (size) => {
29225
- const configPath = join40(homedir15(), ".mma", "config.json");
29719
+ const configPath = join42(homedir15(), ".mma", "config.json");
29226
29720
  const { config } = await bootstrap();
29227
29721
  const contextWindow = parseInt(size, 10);
29228
29722
  if (isNaN(contextWindow) || contextWindow < 1024) {
@@ -29240,7 +29734,7 @@ function createProgram() {
29240
29734
  console.log(t("cli.base_url"), config.provider.baseUrl);
29241
29735
  });
29242
29736
  provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
29243
- const configPath = join40(homedir15(), ".mma", "config.json");
29737
+ const configPath = join42(homedir15(), ".mma", "config.json");
29244
29738
  const { config } = await bootstrap();
29245
29739
  config.provider.type = name;
29246
29740
  saveConfig(config, configPath);
@@ -30010,8 +30504,8 @@ class LineEditor {
30010
30504
  }
30011
30505
 
30012
30506
  // src/cli/repl.ts
30013
- import { existsSync as existsSync47, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
30014
- import { join as join42, dirname as dirname14 } from "path";
30507
+ import { existsSync as existsSync48, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
30508
+ import { join as join44, dirname as dirname16 } from "path";
30015
30509
  import { homedir as homedir17 } from "os";
30016
30510
  import { fileURLToPath as fileURLToPath5 } from "url";
30017
30511
 
@@ -30515,13 +31009,13 @@ init_box();
30515
31009
  init_i18n();
30516
31010
  init_repl_commands();
30517
31011
  function readVersion4() {
30518
- const here = dirname14(fileURLToPath5(import.meta.url));
31012
+ const here = dirname16(fileURLToPath5(import.meta.url));
30519
31013
  const candidates = [
30520
- join42(here, "..", "..", "package.json"),
30521
- join42(here, "..", "package.json")
31014
+ join44(here, "..", "..", "package.json"),
31015
+ join44(here, "..", "package.json")
30522
31016
  ];
30523
31017
  for (const p of candidates) {
30524
- if (existsSync47(p)) {
31018
+ if (existsSync48(p)) {
30525
31019
  try {
30526
31020
  const raw = JSON.parse(readFileSync32(p, "utf8"));
30527
31021
  if (raw.version)
@@ -30579,10 +31073,10 @@ class Repl {
30579
31073
  this.skillsModule = skillsModule;
30580
31074
  this.pluginManager = pluginManager;
30581
31075
  this.logger = logger;
30582
- this.configDir = configDir || join42(homedir17(), ".mma");
31076
+ this.configDir = configDir || join44(homedir17(), ".mma");
30583
31077
  this.baseDir = baseDir || process.cwd();
30584
31078
  this.noAgentsMd = noAgentsMd === true;
30585
- this.historyPath = join42(homedir17(), ".mma", "repl-history");
31079
+ this.historyPath = join44(homedir17(), ".mma", "repl-history");
30586
31080
  this.loadHistory();
30587
31081
  this.rl = process.stdin.isTTY ? new LineEditor({
30588
31082
  input: process.stdin,
@@ -30615,7 +31109,7 @@ class Repl {
30615
31109
  this.setupListeners();
30616
31110
  }
30617
31111
  loadHistory() {
30618
- if (existsSync47(this.historyPath)) {
31112
+ if (existsSync48(this.historyPath)) {
30619
31113
  try {
30620
31114
  const raw = readFileSync32(this.historyPath, "utf-8");
30621
31115
  this.history = raw.split(`
@@ -30974,11 +31468,11 @@ ${t("image.clipboard_empty")}`));
30974
31468
  row(t("repl.agents_label"), pc2.red(t("repl.disabled")));
30975
31469
  } else {
30976
31470
  const agentsMdCandidates = [
30977
- join42(this.baseDir, "AGENTS.md"),
30978
- join42(this.baseDir, ".mma", "AGENTS.md"),
30979
- join42(this.configDir, "AGENTS.md")
31471
+ join44(this.baseDir, "AGENTS.md"),
31472
+ join44(this.baseDir, ".mma", "AGENTS.md"),
31473
+ join44(this.configDir, "AGENTS.md")
30980
31474
  ];
30981
- const foundAgents = agentsMdCandidates.filter((p) => existsSync47(p));
31475
+ const foundAgents = agentsMdCandidates.filter((p) => existsSync48(p));
30982
31476
  if (foundAgents.length > 0) {
30983
31477
  for (const p of foundAgents) {
30984
31478
  row(t("repl.agents_label"), pc2.dim(p));
@@ -30989,7 +31483,7 @@ ${t("image.clipboard_empty")}`));
30989
31483
  }
30990
31484
  const meta = this.sessionManager?.getActiveMeta();
30991
31485
  if (meta) {
30992
- const sessionPath = join42(this.configDir, "sessions", meta.id);
31486
+ const sessionPath = join44(this.configDir, "sessions", meta.id);
30993
31487
  row(t("repl.session_label"), `${pc2.cyan(meta.name)} ${pc2.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc2.dim(sessionPath)}`);
30994
31488
  }
30995
31489
  const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
@@ -31030,14 +31524,14 @@ init_setup();
31030
31524
  init_config2();
31031
31525
  init_i18n();
31032
31526
  init_colors();
31033
- import { existsSync as existsSync48, readFileSync as readFileSync33 } from "fs";
31034
- import { join as join43, dirname as dirname15 } from "path";
31527
+ import { existsSync as existsSync49, readFileSync as readFileSync33 } from "fs";
31528
+ import { join as join45, dirname as dirname17 } from "path";
31035
31529
  import { homedir as homedir18 } from "os";
31036
31530
  import { fileURLToPath as fileURLToPath6 } from "url";
31037
31531
 
31038
31532
  // src/modules/updater/checker.ts
31039
31533
  init_command();
31040
- import { platform as platform8 } from "os";
31534
+ import { platform as platform9 } from "os";
31041
31535
  var defaultRunner2 = async (command, args, options) => {
31042
31536
  const { execFile } = await import("child_process");
31043
31537
  return new Promise((resolve23) => {
@@ -31099,7 +31593,7 @@ class Updater {
31099
31593
  buildInstallCommand(latest) {
31100
31594
  const npmArgs = ["install", "-g", `${this.packageName}@${latest}`];
31101
31595
  const command = resolveSpawnCommand("npm");
31102
- if (platform8() === "win32" && /\.(cmd|bat)$/i.test(command)) {
31596
+ if (platform9() === "win32" && /\.(cmd|bat)$/i.test(command)) {
31103
31597
  return { command: "cmd.exe", args: ["/c", command, ...npmArgs] };
31104
31598
  }
31105
31599
  return { command, args: npmArgs };
@@ -31193,13 +31687,13 @@ class UpdaterModule {
31193
31687
  }
31194
31688
  // src/cli/main.ts
31195
31689
  function readVersion5() {
31196
- const here = dirname15(fileURLToPath6(import.meta.url));
31690
+ const here = dirname17(fileURLToPath6(import.meta.url));
31197
31691
  const candidates = [
31198
- join43(here, "..", "..", "package.json"),
31199
- join43(here, "..", "package.json")
31692
+ join45(here, "..", "..", "package.json"),
31693
+ join45(here, "..", "package.json")
31200
31694
  ];
31201
31695
  for (const p of candidates) {
31202
- if (existsSync48(p)) {
31696
+ if (existsSync49(p)) {
31203
31697
  try {
31204
31698
  const raw = JSON.parse(readFileSync33(p, "utf8"));
31205
31699
  if (raw.version)
@@ -31272,15 +31766,15 @@ async function main() {
31272
31766
  agent.shutdown();
31273
31767
  process.exit(exitCode);
31274
31768
  } else {
31275
- const configPath = join43(homedir18(), ".mma", "config.json");
31276
- if (!existsSync48(configPath)) {
31769
+ const configPath = join45(homedir18(), ".mma", "config.json");
31770
+ if (!existsSync49(configPath)) {
31277
31771
  console.log(pc2.yellow(`
31278
31772
  ` + t("cli.first_run") + `
31279
31773
  `));
31280
31774
  const answers = await runSetup();
31281
31775
  const config2 = loadConfig({
31282
- configDir: join43(homedir18(), ".mma"),
31283
- projectConfigPath: projectDir ? join43(projectDir, ".mmrc") : join43(process.cwd(), ".mmrc")
31776
+ configDir: join45(homedir18(), ".mma"),
31777
+ projectConfigPath: projectDir ? join45(projectDir, ".mmrc") : join45(process.cwd(), ".mmrc")
31284
31778
  });
31285
31779
  config2.provider.type = answers.provider;
31286
31780
  config2.provider.baseUrl = answers.apiBase;