micro-models-agent 0.35.4 → 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 +1247 -913
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2472,6 +2472,10 @@ Command: {command}`,
2472
2472
  "plan.step_already_done": "Step {step} is already done — nothing to do. Plan progress:",
2473
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.',
2474
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).",
2475
2479
  "plan.show_header": "Plan status:",
2476
2480
  "plan.show_empty": "(plan has no steps)",
2477
2481
  "plan.list_header": "Plans:",
@@ -2768,6 +2772,7 @@ Use this knowledge to answer the user's question.`,
2768
2772
  "exec.audit_pass": "[✓] Task complete: {done}/{total} steps done, {files} files verified",
2769
2773
  "exec.audit_pending": "[✗] Task incomplete: {done}/{total} steps done — remaining steps are not marked done",
2770
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",
2771
2776
  "exec.audit_fail_tests": "[✗] Task incomplete: {done}/{total} steps done, tests FAILING: {failed} failed / {passed} passed — {detail}",
2772
2777
  "exec.audit_fail_typecheck": "[✗] Task incomplete: {done}/{total} steps done, {missing} files missing, typecheck error: {typeError}",
2773
2778
  "exec.audit_incomplete": "[⚠ Final audit incomplete: {summary}. Task is NOT finished — continue working. Remaining steps: {steps}]",
@@ -2779,6 +2784,7 @@ Use this knowledge to answer the user's question.`,
2779
2784
  [Hints]
2780
2785
  {hints}`,
2781
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.',
2782
2788
  "hall.max_retries_exhausted": "Model returned empty or insufficient responses after multiple retries",
2783
2789
  "hall.short_response": "Response too short or empty",
2784
2790
  "hall.repetitive": "Response too repetitive ({pct}% overlap)",
@@ -3064,6 +3070,10 @@ var init_ru = __esm(() => {
3064
3070
  "plan.step_already_done": "Шаг {step} уже выполнен — ничего делать не нужно. Прогресс плана:",
3065
3071
  "plan.order_blocked": "Нельзя отметить шаг {step} выполненным: шаг {first} («{desc}») ещё не завершён. Сначала завершите предыдущие шаги или отметьте шаг {first} status=skipped, если он не нужен.",
3066
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, если они должны остаться).",
3067
3077
  "plan.show_header": "Статус плана:",
3068
3078
  "plan.show_empty": "(в плане нет шагов)",
3069
3079
  "plan.list_header": "Планы:",
@@ -3360,6 +3370,7 @@ var init_ru = __esm(() => {
3360
3370
  "exec.audit_pass": "[✓] Задача выполнена: {done}/{total} шагов, {files} файлов проверено",
3361
3371
  "exec.audit_pending": "[✗] Задача не выполнена: {done}/{total} шагов — оставшиеся шаги не отмечены выполненными",
3362
3372
  "exec.audit_fail": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов отсутствует",
3373
+ "exec.audit_leftovers": "[✗] Задача не выполнена: {done}/{total} шагов, {files} файлов из delete-шагов всё ещё существуют",
3363
3374
  "exec.audit_fail_tests": "[✗] Задача не выполнена: {done}/{total} шагов, тесты ПАДАЮТ: {failed} failed / {passed} passed — {detail}",
3364
3375
  "exec.audit_fail_typecheck": "[✗] Задача не выполнена: {done}/{total} шагов, {missing} файлов отсутствует, ошибка typecheck: {typeError}",
3365
3376
  "exec.audit_incomplete": "[⚠ Финальная проверка не пройдена: {summary}. Задача НЕ завершена — продолжайте работу. Оставшиеся шаги: {steps}]",
@@ -3371,6 +3382,7 @@ var init_ru = __esm(() => {
3371
3382
  [Подсказки]
3372
3383
  {hints}`,
3373
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 для этого.',
3374
3386
  "hall.max_retries_exhausted": "Модель вернула пустой или недостаточный ответ после нескольких попыток",
3375
3387
  "hall.short_response": "Слишком короткий или пустой ответ",
3376
3388
  "hall.repetitive": "Слишком повторяющийся ответ ({pct}% совпадение)",
@@ -4902,6 +4914,30 @@ var exports_openai_compat = {};
4902
4914
  __export(exports_openai_compat, {
4903
4915
  OpenAICompatProvider: () => OpenAICompatProvider
4904
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
+ }
4905
4941
 
4906
4942
  class OpenAICompatProvider {
4907
4943
  model;
@@ -4949,26 +4985,14 @@ class OpenAICompatProvider {
4949
4985
  }
4950
4986
  }
4951
4987
  async* doStream(messages, tools, signal, options) {
4952
- const body = {
4988
+ const body = buildRequestBody({
4953
4989
  model: this.model,
4954
4990
  messages,
4991
+ tools,
4955
4992
  stream: true,
4956
- max_tokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096
4957
- };
4958
- if (options?.reasoningEffort) {
4959
- body.reasoning_effort = options.reasoningEffort;
4960
- }
4961
- if (tools && tools.length > 0) {
4962
- body.tools = tools.map((t2) => ({
4963
- type: "function",
4964
- function: {
4965
- name: t2.name,
4966
- description: t2.description,
4967
- parameters: t2.parameters
4968
- }
4969
- }));
4970
- body.tool_choice = "auto";
4971
- }
4993
+ maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
4994
+ reasoningEffort: options?.reasoningEffort
4995
+ });
4972
4996
  const headers = {
4973
4997
  "Content-Type": "application/json"
4974
4998
  };
@@ -5094,26 +5118,14 @@ class OpenAICompatProvider {
5094
5118
  }
5095
5119
  }
5096
5120
  async doNonStreaming(messages, tools, signal, options) {
5097
- const body = {
5121
+ const body = buildRequestBody({
5098
5122
  model: this.model,
5099
5123
  messages,
5124
+ tools,
5100
5125
  stream: false,
5101
- max_tokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096
5102
- };
5103
- if (options?.reasoningEffort) {
5104
- body.reasoning_effort = options.reasoningEffort;
5105
- }
5106
- if (tools && tools.length > 0) {
5107
- body.tools = tools.map((t2) => ({
5108
- type: "function",
5109
- function: {
5110
- name: t2.name,
5111
- description: t2.description,
5112
- parameters: t2.parameters
5113
- }
5114
- }));
5115
- body.tool_choice = "auto";
5116
- }
5126
+ maxTokens: options?.maxTokens ?? this.config.maxCompletionTokens ?? 4096,
5127
+ reasoningEffort: options?.reasoningEffort
5128
+ });
5117
5129
  const headers = {
5118
5130
  "Content-Type": "application/json"
5119
5131
  };
@@ -9374,13 +9386,14 @@ class StuckDetector {
9374
9386
  if (desc.includes("deploy") || desc.includes("publish")) {
9375
9387
  hints.push("Verify credentials and network access before deploying.");
9376
9388
  }
9377
- if (this.hasRepetitiveToolCalls()) {
9389
+ const primary = this.getPrimaryReason();
9390
+ if (this.hasRepetitiveToolCalls() && primary !== "repetitive") {
9378
9391
  hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
9379
9392
  }
9380
- if (this.hasConsecutiveFailures()) {
9393
+ if (this.hasConsecutiveFailures() && primary !== "consecutive") {
9381
9394
  hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
9382
9395
  }
9383
- if (this.hasReadOnlyLoop()) {
9396
+ if (this.hasReadOnlyLoop() && primary !== "read-only") {
9384
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.");
9385
9398
  }
9386
9399
  return hints;
@@ -9506,35 +9519,50 @@ class StuckDetector {
9506
9519
  return "";
9507
9520
  }
9508
9521
  getRecoveryMessage() {
9509
- if (this.isStuck()) {
9522
+ const reason = this.getPrimaryReason();
9523
+ if (reason === "stuck") {
9510
9524
  return t("exec.stuck_recovery", {
9511
9525
  iterations: this.iterationsOnCurrentStep,
9512
9526
  stepId: String(this.currentStepId ?? "?"),
9513
9527
  description: this.currentStepDescription
9514
9528
  });
9515
9529
  }
9516
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
9517
- if (errorTool) {
9530
+ if (reason === "tool-errors") {
9531
+ const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
9518
9532
  return t("exec.tool_errors_recovery", {
9519
9533
  tool: errorTool[0],
9520
9534
  count: errorTool[1]
9521
9535
  });
9522
9536
  }
9523
- if (this.hasConsecutiveFailures()) {
9537
+ if (reason === "consecutive") {
9524
9538
  return t("exec.consecutive_failures_recovery", {
9525
9539
  count: this.consecutiveFailures
9526
9540
  });
9527
9541
  }
9528
- if (this.hasRepetitiveToolCalls()) {
9542
+ if (reason === "repetitive") {
9529
9543
  return this.getRepetitiveToolMessage();
9530
9544
  }
9531
- if (this.hasReadOnlyLoop()) {
9545
+ if (reason === "read-only") {
9532
9546
  return t("exec.read_only_loop_recovery", {
9533
9547
  count: String(this.readOnlyStreak)
9534
9548
  });
9535
9549
  }
9536
9550
  return "";
9537
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
+ }
9538
9566
  reset() {
9539
9567
  this.currentStepId = null;
9540
9568
  this.iterationsOnCurrentStep = 0;
@@ -11009,6 +11037,105 @@ var init_agent = __esm(() => {
11009
11037
  init_verifier();
11010
11038
  });
11011
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
+
11012
11139
  // src/modules/context/manager.ts
11013
11140
  function summarizeArgs(args) {
11014
11141
  if (!args)
@@ -11027,6 +11154,18 @@ function truncate(s, max) {
11027
11154
  return s;
11028
11155
  return s.slice(0, max - 3) + "...";
11029
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
+ }
11030
11169
  function extractTriedAndFailed(messages) {
11031
11170
  const failures = new Map;
11032
11171
  for (const msg of messages) {
@@ -11058,11 +11197,10 @@ class ContextManager {
11058
11197
  peakTokens = 0;
11059
11198
  budget;
11060
11199
  compactionThreshold;
11061
- fileFacts = [];
11062
- decisionFacts = [];
11063
- errorFacts = [];
11200
+ facts = new FactExtractor;
11064
11201
  lastUserTask = "";
11065
- static MAX_FACTS = 20;
11202
+ sessionMission = "";
11203
+ lastUserFeedback = "";
11066
11204
  tokenCounter;
11067
11205
  pendingImageParts = [];
11068
11206
  toolTokens = 0;
@@ -11101,6 +11239,7 @@ class ContextManager {
11101
11239
  resetUserTurn() {
11102
11240
  this.iterationsSinceCompaction = 0;
11103
11241
  this.lastUserTask = "";
11242
+ this.lastUserFeedback = "";
11104
11243
  }
11105
11244
  getQuality() {
11106
11245
  const usedTokens = this.getEstimatedTokens();
@@ -11108,16 +11247,22 @@ class ContextManager {
11108
11247
  const COMPACTION_PENALTY_FLOOR = 0.5;
11109
11248
  const compactionLoss = Math.max(COMPACTION_PENALTY_FLOOR, 1 - this.compactionCount * 0.15);
11110
11249
  const msgCount = this.messages.length || 1;
11111
- 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));
11112
11251
  const freshness = Math.max(0, 1 - this.iterationsSinceCompaction / COMPACTION_INTERVAL);
11113
11252
  const score = tokenLoad * 0.4 + compactionLoss * 0.25 + errorDensity * 0.2 + freshness * 0.15;
11114
11253
  return Math.round(Math.min(100, Math.max(0, score * 100)));
11115
11254
  }
11116
11255
  addMessage(msg) {
11117
- if (msg.role === "user" && !this.lastUserTask) {
11256
+ if (msg.role === "user") {
11118
11257
  const text = typeof msg.content === "string" ? msg.content : getMessageText(msg.content);
11119
11258
  if (text.trim() && !text.startsWith("<system-summary>") && !text.includes("[Compressed:")) {
11120
- 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
+ }
11121
11266
  }
11122
11267
  }
11123
11268
  if (msg.role === "user" && this.pendingImageParts.length > 0) {
@@ -11206,22 +11351,27 @@ class ContextManager {
11206
11351
  const cutoff = this.messages.length - KEEP_LAST_N * 2;
11207
11352
  const oldTurns = this.messages.slice(0, cutoff);
11208
11353
  const recentTurns = this.messages.slice(cutoff);
11209
- this.extractFacts(oldTurns);
11354
+ this.facts.extract(oldTurns);
11210
11355
  const parts = [];
11211
11356
  parts.push(`[Compressed: ${oldTurns.length} old turns removed]`);
11212
- if (this.lastUserTask) {
11213
- const task = this.lastUserTask.slice(0, 400);
11357
+ const task = (this.lastUserTask || this.sessionMission).slice(0, 400);
11358
+ if (task)
11214
11359
  parts.push(`[Task: ${task}]`);
11215
- }
11216
- if (this.fileFacts.length > 0) {
11217
- parts.push(`[Files: ${this.fileFacts.slice(-15).join("; ")}]`);
11218
- }
11219
- if (this.decisionFacts.length > 0) {
11220
- parts.push(`[Decisions: ${this.decisionFacts.slice(-5).join("; ")}]`);
11221
- }
11222
- if (this.errorFacts.length > 0) {
11223
- parts.push(`[Errors: ${this.errorFacts.slice(-3).join("; ")}]`);
11224
- }
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);
11225
11375
  const triedAndFailed = extractTriedAndFailed(oldTurns);
11226
11376
  if (triedAndFailed.length > 0) {
11227
11377
  const lines = triedAndFailed.map((t2) => `- ${t2.tool}(${t2.args}): ${t2.error} (failed ${t2.count}x)`);
@@ -11251,55 +11401,6 @@ ${lines.join(`
11251
11401
  this.onCompact(summary);
11252
11402
  }
11253
11403
  }
11254
- extractFacts(turns) {
11255
- const DRIVE = "(?:[a-zA-Z]:[\\\\/])?";
11256
- const filePatterns = [
11257
- new RegExp(`(?:Created|Updated|Written|Deleted|Moved) ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11258
- new RegExp(`Replaced in ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11259
- new RegExp(`Файл (?:создан|обновлён|записан|удалён|перемещён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11260
- new RegExp(`file (?:created|updated|written|deleted|moved):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
11261
- ];
11262
- const newFiles = [];
11263
- const newDecisions = [];
11264
- const newErrors = [];
11265
- for (const msg of turns) {
11266
- const content = getMessageText(msg.content);
11267
- if (msg.role === "user" && (content.startsWith("<system-summary>") || content.includes("[Compressed:"))) {
11268
- continue;
11269
- }
11270
- if (msg.role === "tool") {
11271
- for (const pattern of filePatterns) {
11272
- for (const match of content.matchAll(pattern)) {
11273
- newFiles.push(match[1]);
11274
- }
11275
- }
11276
- if (content.includes("Plan:") && content.includes("[")) {
11277
- const planLine = content.split(`
11278
- `).find((line) => line.includes("Plan:"));
11279
- if (planLine)
11280
- newDecisions.push(planLine.trim());
11281
- }
11282
- }
11283
- if (msg.role === "assistant") {
11284
- if (content.includes("decided:") || content.includes("decision:")) {
11285
- const line = content.split(`
11286
- `).find((l) => l.includes("decided:") || l.includes("decision:"));
11287
- if (line)
11288
- newDecisions.push(line.trim().slice(0, 200));
11289
- }
11290
- }
11291
- if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
11292
- const line = content.split(`
11293
- `).find((l) => l.includes("Error:") || l.includes("failed") || l.includes("Ошибка:") || l.includes("не удалось"));
11294
- if (line)
11295
- newErrors.push(line.trim().slice(0, 250));
11296
- }
11297
- }
11298
- const dedup = (arr) => [...new Set(arr)];
11299
- this.fileFacts = dedup([...this.fileFacts, ...newFiles]).slice(-ContextManager.MAX_FACTS);
11300
- this.decisionFacts = dedup([...this.decisionFacts, ...newDecisions]).slice(-ContextManager.MAX_FACTS);
11301
- this.errorFacts = dedup([...this.errorFacts, ...newErrors]).slice(-ContextManager.MAX_FACTS);
11302
- }
11303
11404
  updateSystemPrompt(content) {
11304
11405
  const idx = this.messages.findIndex((m) => m.role === "system");
11305
11406
  if (idx >= 0) {
@@ -11318,6 +11419,8 @@ ${lines.join(`
11318
11419
  this.compactionCount = 0;
11319
11420
  this.peakTokens = 0;
11320
11421
  this.lastUserTask = "";
11422
+ this.lastUserFeedback = "";
11423
+ this.sessionMission = "";
11321
11424
  }
11322
11425
  getEstimatedTokens() {
11323
11426
  return this.messages.reduce((sum, m) => sum + this.estimateMessageTokens(m), 0) + this.toolTokens;
@@ -11334,7 +11437,9 @@ ${lines.join(`
11334
11437
  }
11335
11438
  }
11336
11439
  var COMPACTION_INTERVAL = 15, KEEP_LAST_N = 6;
11337
- var init_manager = () => {};
11440
+ var init_manager = __esm(() => {
11441
+ init_fact_extractor();
11442
+ });
11338
11443
 
11339
11444
  // src/modules/hallucination/consistency.ts
11340
11445
  class ConsistencyCheck {
@@ -11445,6 +11550,10 @@ function isFilePathLike(s) {
11445
11550
  return false;
11446
11551
  return FILE_EXTENSIONS.has(ext);
11447
11552
  }
11553
+ const last = s.split(/[\\/]/).pop() ?? "";
11554
+ const lastDot = last.lastIndexOf(".");
11555
+ if (lastDot <= 0)
11556
+ return false;
11448
11557
  return true;
11449
11558
  }
11450
11559
  function extractFileLikeTokens(text) {
@@ -15708,7 +15817,7 @@ class PlanCreator {
15708
15817
  const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
15709
15818
  return hasActionWord && words.length > 8;
15710
15819
  }
15711
- static createPlan(title, stepDescriptions, baseDir) {
15820
+ static createPlan(title, stepDescriptions, baseDir, kinds) {
15712
15821
  const stepCount = stepDescriptions.length;
15713
15822
  return {
15714
15823
  id: generatePlanId(),
@@ -15716,7 +15825,8 @@ class PlanCreator {
15716
15825
  steps: stepDescriptions.map((desc, i) => ({
15717
15826
  id: i + 1,
15718
15827
  description: desc,
15719
- status: "pending"
15828
+ status: "pending",
15829
+ kind: kinds?.[i] ?? "create"
15720
15830
  })),
15721
15831
  createdAt: new Date().toISOString(),
15722
15832
  baseDir
@@ -15730,7 +15840,8 @@ class PlanCreator {
15730
15840
  const added = newSteps.map((desc, i) => ({
15731
15841
  id: kept.length + i + 1,
15732
15842
  description: desc,
15733
- status: "pending"
15843
+ status: "pending",
15844
+ kind: "create"
15734
15845
  }));
15735
15846
  return {
15736
15847
  id: plan.id,
@@ -15822,42 +15933,9 @@ class PlanTracker {
15822
15933
  }
15823
15934
  var init_tracker = () => {};
15824
15935
 
15825
- // src/modules/execution/auditor.ts
15826
- import { existsSync as existsSync28, readdirSync as readdirSync8 } from "fs";
15827
- import { resolve as resolve17, join as join23, basename as basename2 } from "path";
15828
- function findExistingFile(baseDir, filePath) {
15829
- const direct = resolve17(baseDir, filePath);
15830
- if (existsSync28(direct))
15831
- return direct;
15832
- const name = basename2(filePath).toLowerCase();
15833
- const suffix = filePath.replace(/\\/g, "/").toLowerCase();
15834
- let found = null;
15835
- const walk = (dir, depth) => {
15836
- if (found || depth > RESOLVE_MAX_DEPTH)
15837
- return;
15838
- let entries;
15839
- try {
15840
- entries = readdirSync8(dir, { withFileTypes: true });
15841
- } catch {
15842
- return;
15843
- }
15844
- for (const e of entries) {
15845
- if (found)
15846
- return;
15847
- const full = join23(dir, e.name);
15848
- if (e.isDirectory()) {
15849
- if (SKIP_DIRS.has(e.name))
15850
- continue;
15851
- walk(full, depth + 1);
15852
- } else if (e.name.toLowerCase() === name || full.replace(/\\/g, "/").toLowerCase().endsWith("/" + suffix)) {
15853
- found = full;
15854
- return;
15855
- }
15856
- }
15857
- };
15858
- walk(baseDir, 0);
15859
- return found;
15860
- }
15936
+ // src/modules/execution/audit-runners.ts
15937
+ import { readdirSync as readdirSync8 } from "fs";
15938
+ import { join as join23 } from "path";
15861
15939
  function findTestFile(dir, depth = 0) {
15862
15940
  if (depth > 5)
15863
15941
  return null;
@@ -15932,6 +16010,76 @@ async function runTests(baseDir) {
15932
16010
  command: "bun test"
15933
16011
  };
15934
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
+ }
15935
16083
 
15936
16084
  class Auditor {
15937
16085
  baseDir;
@@ -15939,18 +16087,27 @@ class Auditor {
15939
16087
  this.baseDir = baseDir;
15940
16088
  }
15941
16089
  async audit(plan) {
15942
- const allStepText = plan.steps.filter((s) => s.status === "done").map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
15943
- 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));
15944
16096
  const missingFiles = [];
15945
16097
  const existingFiles = [];
15946
- for (const filePath of uniqueFiles) {
15947
- const resolved = findExistingFile(this.baseDir, filePath);
15948
- if (resolved) {
16098
+ const leftoverFiles = [];
16099
+ for (const filePath of createFiles) {
16100
+ if (findExistingFile(this.baseDir, filePath)) {
15949
16101
  existingFiles.push(filePath);
15950
16102
  } else {
15951
16103
  missingFiles.push(filePath);
15952
16104
  }
15953
16105
  }
16106
+ for (const filePath of deleteFiles) {
16107
+ if (findExistingFile(this.baseDir, filePath)) {
16108
+ leftoverFiles.push(filePath);
16109
+ }
16110
+ }
15954
16111
  let testRun = null;
15955
16112
  if (hasTestStep(plan) && findTestFile(this.baseDir)) {
15956
16113
  try {
@@ -15959,17 +16116,27 @@ class Auditor {
15959
16116
  testRun = null;
15960
16117
  }
15961
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
+ }
15962
16127
  const doneSteps = plan.steps.filter((s) => s.status === "done").length;
15963
16128
  const terminalSteps = plan.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
15964
16129
  const totalSteps = plan.steps.length;
16130
+ const auditedFiles = [...new Set([...createFiles, ...deleteFiles])];
15965
16131
  let massEditWarning = null;
15966
- if (uniqueFiles.length > MASS_EDIT_THRESHOLD) {
16132
+ if (auditedFiles.length > MASS_EDIT_THRESHOLD) {
15967
16133
  massEditWarning = t("exec.mass_edit_warning", {
15968
- count: String(uniqueFiles.length)
16134
+ count: String(auditedFiles.length)
15969
16135
  });
15970
16136
  }
15971
16137
  const testsFailing = testRun !== null && testRun.failed > 0;
15972
- const passed = missingFiles.length === 0 && !testsFailing;
16138
+ const typecheckFailing = typecheckError !== null;
16139
+ const passed = missingFiles.length === 0 && leftoverFiles.length === 0 && !testsFailing && !typecheckFailing;
15973
16140
  const stepsPending = terminalSteps < totalSteps;
15974
16141
  let summary;
15975
16142
  if (missingFiles.length > 0) {
@@ -15978,6 +16145,12 @@ class Auditor {
15978
16145
  total: totalSteps,
15979
16146
  files: missingFiles.length
15980
16147
  });
16148
+ } else if (leftoverFiles.length > 0) {
16149
+ summary = t("exec.audit_leftovers", {
16150
+ done: doneSteps,
16151
+ total: totalSteps,
16152
+ files: leftoverFiles.length
16153
+ });
15981
16154
  } else if (testsFailing) {
15982
16155
  summary = t("exec.audit_fail_tests", {
15983
16156
  done: doneSteps,
@@ -15986,6 +16159,13 @@ class Auditor {
15986
16159
  passed: String(testRun.passedCount),
15987
16160
  detail: testRun.detail
15988
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
+ });
15989
16169
  } else if (stepsPending) {
15990
16170
  summary = t("exec.audit_pending", {
15991
16171
  done: doneSteps,
@@ -16001,38 +16181,27 @@ class Auditor {
16001
16181
  return {
16002
16182
  passed,
16003
16183
  missingFiles,
16184
+ leftoverFiles,
16004
16185
  createdFiles: existingFiles,
16005
16186
  modifiedFiles: [],
16006
16187
  summary,
16007
16188
  massEditWarning,
16008
- testRun
16189
+ testRun,
16190
+ typecheckError
16009
16191
  };
16010
16192
  }
16011
16193
  }
16012
- 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;
16013
16195
  var init_auditor = __esm(() => {
16014
16196
  init_i18n();
16015
16197
  init_js_identifiers();
16016
- init_bash();
16017
- init_processes();
16018
- SKIP_DIRS = new Set([
16019
- "node_modules",
16020
- ".git",
16021
- ".mma",
16022
- "dist",
16023
- "build",
16024
- "coverage",
16025
- ".next",
16026
- ".nuxt",
16027
- "vendor"
16028
- ]);
16029
- TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
16030
- 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();
16031
16200
  });
16032
16201
 
16033
16202
  // src/modules/execution/plan-store.ts
16034
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync29, readdirSync as readdirSync9, rmSync } from "fs";
16035
- import { join as join24 } 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";
16036
16205
  function readPlanFile(path, fallbackBaseDir) {
16037
16206
  try {
16038
16207
  const raw = readFileSync16(path, "utf-8");
@@ -16059,8 +16228,8 @@ function writePlanFile(path, plan) {
16059
16228
  function listDir(dir, baseDir) {
16060
16229
  if (!existsSync29(dir))
16061
16230
  return [];
16062
- const files = readdirSync9(dir).filter((f) => f.endsWith(".json"));
16063
- return files.map((f) => readPlanFile(join24(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);
16064
16233
  }
16065
16234
  function toMeta(plan, status) {
16066
16235
  return {
@@ -16081,21 +16250,21 @@ class PlanStore {
16081
16250
  archiveDir;
16082
16251
  legacyPath;
16083
16252
  constructor(baseDir) {
16084
- const mmaDir = join24(baseDir, ".mma");
16253
+ const mmaDir = join25(baseDir, ".mma");
16085
16254
  if (!existsSync29(mmaDir))
16086
16255
  mkdirSync14(mmaDir, { recursive: true });
16087
16256
  this.baseDir = baseDir;
16088
- this.plansDir = join24(mmaDir, "plans");
16089
- this.draftsDir = join24(this.plansDir, "drafts");
16090
- this.archiveDir = join24(this.plansDir, "archive");
16091
- this.legacyPath = join24(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);
16092
16261
  for (const dir of [this.plansDir, this.draftsDir, this.archiveDir]) {
16093
16262
  if (!existsSync29(dir))
16094
16263
  mkdirSync14(dir, { recursive: true });
16095
16264
  }
16096
16265
  }
16097
16266
  activePath() {
16098
- return join24(this.plansDir, "active.json");
16267
+ return join25(this.plansDir, "active.json");
16099
16268
  }
16100
16269
  saveActive(plan) {
16101
16270
  writePlanFile(this.activePath(), plan);
@@ -16125,14 +16294,14 @@ class PlanStore {
16125
16294
  rmSync(p, { force: true });
16126
16295
  }
16127
16296
  saveDraft(plan) {
16128
- writePlanFile(join24(this.draftsDir, `${plan.id}.json`), plan);
16297
+ writePlanFile(join25(this.draftsDir, `${plan.id}.json`), plan);
16129
16298
  }
16130
16299
  loadDraft(id) {
16131
- const p = join24(this.draftsDir, `${id}.json`);
16300
+ const p = join25(this.draftsDir, `${id}.json`);
16132
16301
  return existsSync29(p) ? readPlanFile(p, this.baseDir) : null;
16133
16302
  }
16134
16303
  removeDraft(id) {
16135
- const p = join24(this.draftsDir, `${id}.json`);
16304
+ const p = join25(this.draftsDir, `${id}.json`);
16136
16305
  if (existsSync29(p))
16137
16306
  rmSync(p, { force: true });
16138
16307
  }
@@ -16140,7 +16309,7 @@ class PlanStore {
16140
16309
  return listDir(this.draftsDir, this.baseDir);
16141
16310
  }
16142
16311
  archivePlan(plan) {
16143
- writePlanFile(join24(this.archiveDir, `${plan.id}.json`), plan);
16312
+ writePlanFile(join25(this.archiveDir, `${plan.id}.json`), plan);
16144
16313
  this.removeDraft(plan.id);
16145
16314
  const active = this.loadActive();
16146
16315
  if (active && active.id === plan.id) {
@@ -16151,7 +16320,7 @@ class PlanStore {
16151
16320
  return listDir(this.archiveDir, this.baseDir);
16152
16321
  }
16153
16322
  removeArchived(id) {
16154
- const p = join24(this.archiveDir, `${id}.json`);
16323
+ const p = join25(this.archiveDir, `${id}.json`);
16155
16324
  if (existsSync29(p))
16156
16325
  rmSync(p, { force: true });
16157
16326
  }
@@ -16245,91 +16414,12 @@ var init_plan_coverage = __esm(() => {
16245
16414
  ]);
16246
16415
  });
16247
16416
 
16248
- // src/modules/execution/module.ts
16249
- import { existsSync as existsSync30, readFileSync as readFileSync17 } from "fs";
16250
- import { resolve as resolve18 } from "path";
16251
-
16252
- class ExecutionModule {
16253
- name = "execution";
16254
- tracker = null;
16255
- verifier;
16256
- stuckDetector;
16257
- auditor;
16258
- store;
16259
- baseDir;
16260
- lastRecoveryIteration = -STUCK_RECOVERY_COOLDOWN;
16261
- consecutivePlanWarnings = 0;
16262
- lastStepId = -1;
16263
- depsGateHints = new Map;
16264
- _auditSkipsRemaining = 0;
16265
- stuckNotified = false;
16266
- pendingMessages = [];
16267
- constructor(baseDir, stuckThreshold = 6) {
16268
- this.baseDir = baseDir;
16269
- this.verifier = new StepVerifier(baseDir);
16270
- this.stuckDetector = new StuckDetector(stuckThreshold);
16271
- this.auditor = new Auditor(baseDir);
16272
- this.store = new PlanStore(baseDir);
16273
- }
16274
- setPlan(plan) {
16275
- this.tracker = new PlanTracker(plan);
16276
- this.store.saveActive(plan);
16277
- this._auditSkipsRemaining = 0;
16278
- }
16279
- restorePlan() {
16280
- const plan = this.store.loadActive();
16281
- if (!plan)
16282
- return false;
16283
- this.tracker = new PlanTracker(plan);
16284
- this.tracker.syncCurrentStep();
16285
- this._auditSkipsRemaining = 1;
16286
- return true;
16287
- }
16288
- getTracker() {
16289
- return this.tracker;
16290
- }
16291
- getStore() {
16292
- return this.store;
16293
- }
16294
- getStuckDetector() {
16295
- return this.stuckDetector;
16296
- }
16297
- async runFinalAudit() {
16298
- if (!this.tracker)
16299
- return null;
16300
- if (this._auditSkipsRemaining > 0) {
16301
- this._auditSkipsRemaining--;
16302
- return null;
16303
- }
16304
- const plan = this.tracker.getPlan();
16305
- const audit = await this.auditor.audit(plan);
16306
- const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
16307
- const done = plan.steps.filter((s) => s.status === "done").length;
16308
- const passed = audit.passed && pendingSteps.length === 0;
16309
- return {
16310
- passed,
16311
- done,
16312
- total: plan.steps.length,
16313
- pendingSteps,
16314
- missingFiles: audit.missingFiles,
16315
- summary: audit.summary
16316
- };
16317
- }
16318
- getSystemPromptBlock() {
16319
- if (!this.tracker)
16320
- return null;
16321
- return {
16322
- content: this.tracker.toPromptBlock(),
16323
- priority: "critical",
16324
- essential: true,
16325
- estimatedTokens: 200
16326
- };
16327
- }
16328
- getToolDefinitions() {
16329
- return [
16330
- {
16331
- name: "plan",
16332
- 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.
16333
16423
 
16334
16424
  Actions:
16335
16425
  - create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.
@@ -16346,198 +16436,222 @@ Write CONCRETE steps with exact file paths and commands:
16346
16436
  - Specify WHICH CLI commands to run with exact arguments
16347
16437
  - Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)
16348
16438
  - Good: "Создать src/components/Header.tsx с навигацией и логотипом"
16349
- - Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)`,
16350
- parameters: {
16351
- type: "object",
16352
- properties: {
16353
- action: {
16354
- type: "string",
16355
- enum: [
16356
- "create",
16357
- "update",
16358
- "show",
16359
- "abort",
16360
- "list",
16361
- "switch",
16362
- "re-plan"
16363
- ]
16364
- },
16365
- title: { type: "string" },
16366
- steps: { type: "array", items: { type: "string" } },
16367
- step: { type: "number" },
16368
- status: { type: "string", enum: ["done", "failed", "skipped"] },
16369
- note: { type: "string" },
16370
- 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
+ ]
16455
+ },
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".'
16371
16462
  },
16372
- required: ["action"]
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)" }
16373
16467
  },
16374
- handler: async (_ctx, args) => {
16375
- const action = String(args.action);
16376
- if (action === "list") {
16377
- const metas = this.store.listAll();
16378
- if (metas.length === 0) {
16379
- return { success: true, output: t("plan.list_empty") };
16380
- }
16381
- const lines = metas.map((m) => {
16382
- const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
16383
- const namePart = m.name ? ` (${m.name})` : "";
16384
- return `${icon} ${m.id}${namePart} ${m.title} ${m.doneCount}/${m.stepCount}`;
16385
- });
16386
- return {
16387
- success: true,
16388
- 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")}
16389
16485
  ${lines.join(`
16390
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 })
16391
16499
  };
16392
16500
  }
16393
- if (action === "switch") {
16394
- const planId = String(args.id || "");
16395
- if (!planId) {
16396
- 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}`;
16397
16571
  }
16398
- 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);
16399
16583
  if (!found) {
16400
16584
  return {
16401
16585
  success: false,
16402
16586
  output: t("plan.not_found", { id: planId })
16403
16587
  };
16404
16588
  }
16405
- this.preserveActive();
16406
- if (found.status === "draft") {
16407
- this.store.removeDraft(found.plan.id);
16408
- } else if (found.status === "archived") {
16409
- this.store.removeArchived(found.plan.id);
16410
- }
16411
- this.setPlan(found.plan);
16412
- const display = PlanCreator.toPromptBlock(found.plan, 0);
16589
+ const display2 = PlanCreator.toPromptBlock(found.plan, 0);
16413
16590
  return {
16414
16591
  success: true,
16415
- output: t("plan.switched", {
16416
- id: found.plan.id,
16417
- title: found.plan.title
16418
- }),
16419
- display
16592
+ output: `${t("plan.show_header")}
16593
+ ${display2}`,
16594
+ display: display2
16420
16595
  };
16421
16596
  }
16422
- if (action === "re-plan") {
16423
- if (!this.tracker) {
16424
- return { success: false, output: t("plan.no_active") };
16425
- }
16426
- const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
16427
- if (newSteps.length === 0) {
16428
- return { success: false, output: t("plan.replan_no_steps") };
16429
- }
16430
- const oldPlan = this.tracker.getPlan();
16431
- const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
16432
- const keptCount = replanned.steps.length - newSteps.length;
16433
- this.setPlan(replanned);
16434
- const display = PlanCreator.toPromptBlock(replanned, keptCount);
16435
- return {
16436
- success: true,
16437
- output: t("plan.replanned", {
16438
- kept: String(keptCount),
16439
- steps: String(newSteps.length)
16440
- }),
16441
- display
16442
- };
16597
+ if (!deps.trackerRef.current) {
16598
+ return { success: false, output: t("plan.no_active") };
16443
16599
  }
16444
- if (action === "create") {
16445
- const title = String(args.title || "Task Plan");
16446
- const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
16447
- if (steps.length === 0) {
16448
- return { success: false, output: t("plan.no_steps") };
16449
- }
16450
- this.preserveActive();
16451
- const plan = PlanCreator.createPlan(title, steps, this.baseDir);
16452
- this.setPlan(plan);
16453
- const display = PlanCreator.toPromptBlock(plan, 0);
16454
- let output = t("plan.created", {
16455
- title,
16456
- steps: String(steps.length)
16457
- });
16458
- let displayOut = display;
16459
- const taskText = this.getTaskText(_ctx);
16460
- if (taskText) {
16461
- const { missing } = checkPlanCoverage(taskText, steps);
16462
- if (missing.length > 0) {
16463
- const warn = t("plan.coverage_warning", {
16464
- missing: missing.join(", ")
16465
- });
16466
- output += `
16467
- ⚠️ ${warn}`;
16468
- displayOut = `${display}
16469
- ⚠️ ${warn}`;
16470
- }
16471
- }
16472
- return {
16473
- success: true,
16474
- output,
16475
- display: displayOut
16476
- };
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") };
16477
16614
  }
16478
- if (action === "show") {
16479
- const planId = args.id ? String(args.id) : "";
16480
- if (planId && planId !== this.tracker?.getPlan()?.id) {
16481
- const found = this.store.find(planId);
16482
- if (!found) {
16483
- return {
16484
- success: false,
16485
- output: t("plan.not_found", { id: planId })
16486
- };
16487
- }
16488
- const display2 = PlanCreator.toPromptBlock(found.plan, 0);
16489
- return {
16490
- success: true,
16491
- output: `${t("plan.show_header")}
16492
- ${display2}`,
16493
- display: display2
16494
- };
16495
- }
16496
- if (!this.tracker) {
16497
- return { success: false, output: t("plan.no_active") };
16498
- }
16499
- 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();
16500
16619
  return {
16501
16620
  success: true,
16502
- output: `${t("plan.show_header")}
16503
- ${display}`,
16504
- display
16621
+ output: `${t("plan.step_already_done", {
16622
+ step: String(stepId)
16623
+ })}
16624
+ ${progress2}`,
16625
+ display: display2
16505
16626
  };
16506
16627
  }
16507
- if (action === "update" && args.step && this.tracker) {
16508
- const stepId = Number(args.step);
16509
- const target = this.tracker.getStep(stepId);
16510
- if (!target) {
16511
- return { success: false, output: t("plan.step_not_found") };
16512
- }
16513
- const status = String(args.status || "done");
16514
- if (status === "done" && target.status === "done") {
16515
- const progress2 = this.tracker.getProgressString();
16516
- 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) {
16517
16631
  return {
16518
- success: true,
16519
- output: `${t("plan.step_already_done", {
16520
- step: String(stepId)
16521
- })}
16522
- ${progress2}`,
16523
- 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
+ })
16524
16638
  };
16525
16639
  }
16526
- if (status === "done") {
16527
- const blockers = this.tracker.getPlan().steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
16528
- 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) {
16529
16645
  return {
16530
16646
  success: false,
16531
- output: t("plan.order_blocked", {
16647
+ output: t("plan.deliverables_remain", {
16532
16648
  step: String(stepId),
16533
- first: String(blockers[0].id),
16534
- desc: blockers[0].description
16649
+ files: leftovers.join(", ")
16535
16650
  })
16536
16651
  };
16537
16652
  }
16538
- }
16539
- if (status === "done") {
16540
- const missing = this.missingStepDeliverables(target);
16653
+ } else {
16654
+ const missing = deps.missingStepDeliverables(target);
16541
16655
  if (missing.length > 0) {
16542
16656
  return {
16543
16657
  success: false,
@@ -16548,373 +16662,559 @@ ${progress2}`,
16548
16662
  };
16549
16663
  }
16550
16664
  }
16551
- this.tracker.updateStepStatus(stepId, status);
16552
- if (args.note)
16553
- this.tracker.addNote(Number(args.step), String(args.note));
16554
- this.tracker.syncCurrentStep();
16555
- this.store.saveActive(this.tracker.getPlan());
16556
- const progress = this.tracker.getProgressString();
16557
- const display = this.tracker.toPromptBlock();
16558
- _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}`);
16559
- return {
16560
- success: true,
16561
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}
16562
- ${progress}`,
16563
- display
16564
- };
16565
16665
  }
16566
- if (action === "update" && Array.isArray(args.steps) && args.steps.length > 0 && this.tracker) {
16567
- const title = String(args.title || this.tracker.getPlan().title);
16568
- const steps = args.steps.map(String);
16569
- const plan = PlanCreator.createPlan(title, steps, this.baseDir);
16570
- this.setPlan(plan);
16571
- const display = PlanCreator.toPromptBlock(plan, 0);
16572
- const output = t("plan.updated", {
16573
- title,
16574
- steps: String(steps.length)
16575
- });
16576
- return {
16577
- success: true,
16578
- output,
16579
- display
16580
- };
16581
- }
16582
- if (action === "abort") {
16583
- if (this.tracker) {
16584
- this.store.archivePlan(this.tracker.getPlan());
16585
- }
16586
- this.tracker = null;
16587
- this.store.clearActive();
16588
- return { success: true, output: t("plan.aborted") };
16589
- }
16590
- if (!this.tracker) {
16591
- 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 };
16592
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
+ });
16593
16696
  return {
16594
- success: false,
16595
- output: t("plan.unknown_action", { action })
16697
+ success: true,
16698
+ output,
16699
+ display
16596
16700
  };
16597
16701
  }
16598
- },
16599
- {
16600
- name: "todo",
16601
- description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
16602
- parameters: {
16603
- type: "object",
16604
- properties: {
16605
- action: { type: "string", enum: ["add", "done", "list"] },
16606
- items: { type: "array", items: { type: "string" } }
16607
- },
16608
- 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" } }
16609
16728
  },
16610
- handler: async (_ctx, args) => {
16611
- if (!this.tracker) {
16612
- 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 });
16613
16747
  }
16614
- const action = String(args.action);
16615
- const currentStep = this.tracker.getCurrentStep();
16616
- if (!currentStep) {
16617
- 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
+ };
16618
16767
  }
16619
- if (action === "add" && Array.isArray(args.items)) {
16620
- const items = args.items.map(String);
16621
- const existing = currentStep.subtasks ?? [];
16622
- let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
16623
- for (const item of items) {
16624
- 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++;
16625
16775
  }
16626
- currentStep.subtasks = existing;
16627
- this.store.saveActive(this.tracker.getPlan());
16628
- const display = this.tracker.toPromptBlock();
16776
+ }
16777
+ if (marked === 0) {
16629
16778
  return {
16630
- success: true,
16631
- output: t("todo.added", {
16632
- count: String(items.length),
16779
+ success: false,
16780
+ output: t("todo.subtask_not_found", {
16633
16781
  items: items.join(", ")
16634
- }),
16635
- display
16782
+ })
16636
16783
  };
16637
16784
  }
16638
- if (action === "done") {
16639
- const items = Array.isArray(args.items) ? args.items.map(String) : [];
16640
- if (items.length === 0) {
16641
- return {
16642
- success: false,
16643
- output: t("todo.no_items")
16644
- };
16645
- }
16646
- const subs = currentStep.subtasks ?? [];
16647
- let marked = 0;
16648
- for (const item of items) {
16649
- const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
16650
- if (sub) {
16651
- sub.done = true;
16652
- marked++;
16653
- }
16654
- }
16655
- if (marked === 0) {
16656
- return {
16657
- success: false,
16658
- output: t("todo.subtask_not_found", {
16659
- items: items.join(", ")
16660
- })
16661
- };
16662
- }
16663
- currentStep.subtasks = subs;
16664
- this.store.saveActive(this.tracker.getPlan());
16665
- const doneCount = subs.filter((s) => s.done).length;
16666
- const display = this.tracker.toPromptBlock();
16667
- let output = t("todo.marked_done", {
16668
- count: String(marked)
16669
- });
16670
- output += ` (${doneCount}/${subs.length})`;
16671
- if (doneCount === subs.length) {
16672
- 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 += `
16673
16795
  All sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
16674
- }
16675
- return { success: true, output, display };
16676
16796
  }
16677
- if (action === "list") {
16678
- const subs = currentStep.subtasks ?? [];
16679
- const lines = subs.length ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`) : ["(no sub-tasks)"];
16680
- return {
16681
- success: true,
16682
- 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}
16683
16805
  ${lines.join(`
16684
16806
  `)}`
16685
- };
16686
- }
16687
- return {
16688
- success: false,
16689
- output: t("todo.unknown_action", { action })
16690
16807
  };
16691
16808
  }
16692
- },
16693
- {
16694
- name: "verify",
16695
- description: "Run verification for the current step. Checks files mentioned in the step description.",
16696
- parameters: {
16697
- type: "object",
16698
- properties: {
16699
- step: { type: "number", description: "Step number to verify" }
16700
- }
16701
- },
16702
- handler: async (_ctx, args) => {
16703
- if (!this.tracker)
16704
- return { success: false, output: t("plan.no_active") };
16705
- const step = args.step ? this.tracker.getStep(Number(args.step)) : this.tracker.getCurrentStep();
16706
- if (!step)
16707
- return { success: false, output: t("plan.step_not_found") };
16708
- const result = await this.verifier.verifyStep(step.description);
16709
- return {
16710
- success: result.passed,
16711
- output: result.passed ? t("verify.passed") : t("verify.failed", {
16712
- details: result.failed.map((f) => f.message).join("; ")
16713
- })
16714
- };
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" }
16715
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
+ };
16716
16838
  }
16717
- ];
16718
- }
16719
- getPlugin() {
16720
- return {
16721
- name: "execution",
16722
- onBeforeThink: (ctx) => {
16723
- if (ctx.contextManager && this.pendingMessages.length > 0) {
16724
- for (const m of this.pendingMessages.splice(0)) {
16725
- 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;
16726
16900
  }
16727
- }
16728
- if (this.tracker?.isComplete()) {
16729
- this.stuckDetector.resetStepProgress();
16730
- this.consecutivePlanWarnings = 0;
16731
- this.lastStepId = -1;
16732
- this.stuckNotified = false;
16901
+ deps.stuckDetector.setCurrentStep(step.id, step.description);
16902
+ deps.stuckDetector.recordIteration(step.id);
16733
16903
  } else {
16734
- const step = this.tracker?.getCurrentStep();
16735
- if (this.tracker && step) {
16736
- if (step.id !== this.lastStepId) {
16737
- this.consecutivePlanWarnings = 0;
16738
- this.lastStepId = step.id;
16739
- this.stuckNotified = false;
16740
- }
16741
- this.stuckDetector.setCurrentStep(step.id, step.description);
16742
- this.stuckDetector.recordIteration(step.id);
16743
- } else {
16744
- this.stuckDetector.reset();
16745
- this.consecutivePlanWarnings = 0;
16746
- this.lastStepId = -1;
16747
- this.stuckNotified = false;
16748
- const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
16749
- if (iter === 3 && !this.tracker && ctx.contextManager) {
16750
- ctx.contextManager.addMessage({
16751
- role: "user",
16752
- 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>`
16753
- });
16754
- }
16755
- if (iter >= 6 && !this.tracker && ctx.contextManager) {
16756
- ctx.contextManager.addMessage({
16757
- role: "user",
16758
- 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>`
16759
- });
16760
- }
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
+ });
16761
16914
  }
16762
- }
16763
- const stuckReason = this.stuckDetector.getStuckReason();
16764
- if (stuckReason) {
16765
- const logIt = this.stuckDetector.isStuck() ? !this.stuckNotified : true;
16766
- if (logIt) {
16767
- ctx.logger?.warn(stuckReason);
16768
- ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
16769
- if (this.stuckDetector.isStuck())
16770
- 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
+ });
16771
16920
  }
16772
16921
  }
16773
- if (this.stuckDetector.isStuck() || this.stuckDetector.hasRepetitiveToolCalls() || this.stuckDetector.hasReadOnlyLoop()) {
16774
- const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
16775
- if (currentIter - this.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
16776
- const recovery = this.stuckDetector.getRecoveryMessage();
16777
- if (recovery && ctx.contextManager) {
16778
- const lastError = this.stuckDetector.getLastErrorOutput();
16779
- 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 ? `
16780
16940
  If you have relevant skills available, consider loading one with load_skill for expert guidance.` : "";
16781
- const actionableHints = this.stuckDetector.getActionableHints();
16782
- const actionableHintStr = actionableHints.length > 0 ? `
16941
+ const actionableHints = deps.stuckDetector.getActionableHints();
16942
+ const actionableHintStr = actionableHints.length > 0 ? `
16783
16943
  ${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join(`
16784
16944
  `) })}` : "";
16785
- const alternative = this.stuckDetector.getToolAlternative();
16786
- const altHint = alternative ? `
16787
- Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.` : "";
16788
- ctx.contextManager.addMessage({
16789
- role: "user",
16790
- content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`
16791
- });
16792
- }
16793
- const hints = this.stuckDetector.getHints();
16794
- if (hints.length > 0 && ctx.contextManager) {
16795
- const hintMsg = t("exec.hints", {
16796
- 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(`
16797
16957
  `)
16798
- });
16799
- ctx.contextManager.addMessage({
16800
- role: "user",
16801
- content: `<system-summary>${hintMsg}</system-summary>`
16802
- });
16803
- }
16804
- this.stuckDetector.recordEscalation();
16805
- this.lastRecoveryIteration = currentIter;
16806
- if (this.stuckDetector.shouldEscalate() && ctx.onMeta) {
16807
- const escalation = t("exec.escalation", {
16808
- stepId: String(this.tracker?.getCurrentStep()?.id ?? "?"),
16809
- description: this.tracker?.getCurrentStep()?.description ?? ""
16810
- });
16811
- ctx.onMeta(escalation);
16812
- }
16813
- if (this.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD && ctx.contextManager) {
16814
- const step = this.tracker?.getCurrentStep();
16815
- 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({
16816
17030
  role: "user",
16817
- 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>`
16818
17032
  });
16819
17033
  }
16820
17034
  }
16821
17035
  }
16822
- },
16823
- onBeforeTool: (_ctx, call) => {
16824
- const warning = this.checkPlanAlignment(call);
16825
- if (warning) {
16826
- 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({
16827
17045
  role: "user",
16828
- content: `<system-summary>${warning}</system-summary>`
17046
+ content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
17047
+ `) })}</system-summary>`
16829
17048
  });
16830
- this.consecutivePlanWarnings++;
16831
- if (this.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
16832
- 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({
16833
17057
  role: "user",
16834
- 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>`
16835
17059
  });
16836
- return t("exec.plan_blocked", {
16837
- step: String(this.tracker?.getCurrentStep()?.id ?? "?"),
16838
- 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>`
16839
17064
  });
16840
- }
16841
- } else {
16842
- this.consecutivePlanWarnings = 0;
16843
- }
16844
- return true;
16845
- },
16846
- onToolCall: (ctx) => {
16847
- const toolName = ctx?.toolName;
16848
- const args = ctx?.args;
16849
- if (toolName && args) {
16850
- this.stuckDetector.recordToolCall(toolName, args);
16851
- }
16852
- },
16853
- onAfterTool: (ctx, call, result) => {
16854
- if (call.name === "bash") {
16855
- this.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
16856
- }
16857
- const toolText = String(result.output ?? "");
16858
- if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
16859
- this.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
16860
- }
16861
- if (!result.success) {
16862
- this.stuckDetector.recordToolError(call.name, result.output);
16863
- const actionableHints = this.stuckDetector.getActionableHints();
16864
- const alternative = this.stuckDetector.getToolAlternative();
16865
- if (actionableHints.length > 0 || alternative) {
16866
- const parts = [...actionableHints];
16867
- if (alternative) {
16868
- parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
16869
- }
16870
- this.pendingMessages.push({
17065
+ } else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
17066
+ deps.pendingMessages.push({
16871
17067
  role: "user",
16872
- content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join(`
16873
- `) })}</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>`
16874
17069
  });
16875
17070
  }
16876
- } else {
16877
- this.stuckDetector.recordToolSuccess();
16878
- if (call.name === "bash" && result.success) {
16879
- const cmd = String(call.arguments?.command ?? "");
16880
- const testRun = detectTestResults(String(result.output ?? ""));
16881
- if (testRun && testRun.failed > 0) {
16882
- this.pendingMessages.push({
16883
- role: "user",
16884
- 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>`
16885
- });
16886
- } else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
16887
- this.pendingMessages.push({
16888
- role: "user",
16889
- 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>`
16890
- });
16891
- } else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
16892
- this.pendingMessages.push({
16893
- role: "user",
16894
- 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>`
16895
- });
16896
- }
16897
- }
16898
17071
  }
16899
- if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
16900
- const filePath = call.arguments?.path;
16901
- if (filePath) {
16902
- this.stuckDetector.recordFileRewrite(filePath);
16903
- if (this.stuckDetector.hasExcessiveRewrites()) {
16904
- const file = this.stuckDetector.getExcessiveRewriteFile();
16905
- const count = this.stuckDetector.getFileRewriteCount(file);
16906
- if (ctx.onMeta) {
16907
- ctx.onMeta(t("exec.file_rewrite_warning", {
16908
- file,
16909
- count: String(count)
16910
- }));
16911
- }
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
+ }));
16912
17085
  }
16913
17086
  }
16914
- this.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
16915
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;
16916
17130
  }
16917
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
+ });
16918
17218
  }
16919
17219
  preserveActive() {
16920
17220
  if (!this.tracker)
@@ -16945,19 +17245,18 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
16945
17245
  ];
16946
17246
  if (allowedAlways.includes(call.name))
16947
17247
  return null;
16948
- 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());
16949
17249
  if (stepPaths.length === 0)
16950
17250
  return null;
16951
- const argStr = JSON.stringify(call.arguments).toLowerCase();
16952
- 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());
16953
17253
  if (callPaths.length === 0)
16954
17254
  return null;
16955
17255
  const plan = this.tracker.getPlan();
16956
17256
  const finishedPaths = new Set;
16957
17257
  for (const s of plan.steps) {
16958
17258
  if (s.status === "done" || s.status === "skipped") {
16959
- const ps = s.description.match(/\b[\w./\\-]+\.[a-z]+/gi)?.map((p) => p.toLowerCase()) ?? [];
16960
- ps.forEach((p) => finishedPaths.add(p));
17259
+ extractFileLikeTokens(stripUrls(s.description)).map((p) => p.toLowerCase()).forEach((p) => finishedPaths.add(p));
16961
17260
  }
16962
17261
  }
16963
17262
  const offPath = callPaths.some((p) => {
@@ -16979,7 +17278,7 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
16979
17278
  if (!step)
16980
17279
  return;
16981
17280
  const stepText = step.description.toLowerCase();
16982
- const stepPaths = step.description.match(/\b[\w./\\-]+\.[a-z]+/gi) || [];
17281
+ const stepPaths = extractFileLikeTokens(stripUrls(step.description)) || [];
16983
17282
  const isDepsStep = stepText.includes("install") || stepText.includes("зависим") || stepText.includes("init") || stepText.includes("инициализац");
16984
17283
  if (isDepsStep) {
16985
17284
  const lockFiles = [
@@ -16997,8 +17296,8 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
16997
17296
  const hasLockFile = lockFiles.some((f) => existsSync30(resolve18(this.baseDir, f)));
16998
17297
  if (!hasLockFile) {
16999
17298
  if (contextManager) {
17000
- const hints = this.depsGateHints.get(step.id) || 0;
17001
- 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);
17002
17301
  const force = hints >= 1;
17003
17302
  const content = force ? t("exec.step_gate_deps_force", { step: String(step.id) }) : t("exec.step_gate_deps", {
17004
17303
  step: String(step.id),
@@ -17015,23 +17314,27 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17015
17314
  if (stepPaths.length === 0)
17016
17315
  return;
17017
17316
  const allExist = stepPaths.every((p) => existsSync30(resolve18(this.baseDir, p)));
17018
- if (!allExist)
17019
- return;
17020
- const emptyFiles = [];
17021
- for (const p of stepPaths) {
17022
- try {
17023
- const content = readFileSync17(resolve18(this.baseDir, p), "utf-8");
17024
- if (content.trim().length < 10) {
17025
- emptyFiles.push(p);
17026
- }
17027
- } catch {}
17028
- }
17029
- if (emptyFiles.length > 0 && contextManager) {
17030
- contextManager.addMessage({
17031
- role: "user",
17032
- content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`
17033
- });
17317
+ const allGone = stepPaths.every((p) => !existsSync30(resolve18(this.baseDir, p)));
17318
+ const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
17319
+ if (!satisfied)
17034
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
+ }
17035
17338
  }
17036
17339
  this.tracker?.updateStepStatus(step.id, "done");
17037
17340
  const hadNext = this.tracker?.advance() ?? false;
@@ -17058,18 +17361,49 @@ Tool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}
17058
17361
  return [];
17059
17362
  return tokens.filter((p) => !findExistingFile(this.baseDir, p));
17060
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
+ }
17061
17391
  getTaskText(ctx) {
17062
17392
  const manager = ctx?.contextManager;
17063
17393
  if (!manager)
17064
17394
  return "";
17065
17395
  const history = manager.getActiveHistory();
17066
- 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
+ });
17067
17402
  if (!firstUser)
17068
17403
  return "";
17069
17404
  return getMessageText(firstUser.content).trim();
17070
17405
  }
17071
17406
  }
17072
- var STUCK_RECOVERY_COOLDOWN = 5, MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3, FORCE_SKIP_THRESHOLD = 10;
17073
17407
  var init_module = __esm(() => {
17074
17408
  init_i18n();
17075
17409
  init_tracker();
@@ -17077,9 +17411,9 @@ var init_module = __esm(() => {
17077
17411
  init_stuck_detector();
17078
17412
  init_auditor();
17079
17413
  init_plan_store();
17080
- init_plan_coverage();
17081
- init_bash();
17082
17414
  init_js_identifiers();
17415
+ init_plan_tool();
17416
+ init_execution_plugin();
17083
17417
  });
17084
17418
 
17085
17419
  // src/modules/security/session-encryption.ts
@@ -17087,10 +17421,10 @@ import {
17087
17421
  readFileSync as readFileSync18,
17088
17422
  writeFileSync as writeFileSync11,
17089
17423
  existsSync as existsSync31,
17090
- readdirSync as readdirSync10,
17424
+ readdirSync as readdirSync11,
17091
17425
  unlinkSync as unlinkSync4
17092
17426
  } from "fs";
17093
- import { join as join25 } from "path";
17427
+ import { join as join26 } from "path";
17094
17428
  import { homedir as homedir8 } from "os";
17095
17429
 
17096
17430
  class SessionFileEncryptor {
@@ -17099,7 +17433,7 @@ class SessionFileEncryptor {
17099
17433
  constructor(config) {
17100
17434
  this.config = { ...DEFAULT_SESSION_ENCRYPTION, ...config };
17101
17435
  this.encryptor = new ConfigEncryptor({
17102
- keyPath: config?.keyPath || join25(homedir8(), ".mma", ".session-encryption-key")
17436
+ keyPath: config?.keyPath || join26(homedir8(), ".mma", ".session-encryption-key")
17103
17437
  });
17104
17438
  }
17105
17439
  isEnabled() {
@@ -17183,9 +17517,9 @@ class SessionFileEncryptor {
17183
17517
  encryptSessionDirectory(sessionDir) {
17184
17518
  if (!this.config.enabled)
17185
17519
  return;
17186
- const files = readdirSync10(sessionDir);
17520
+ const files = readdirSync11(sessionDir);
17187
17521
  for (const file of files) {
17188
- const filePath = join25(sessionDir, file);
17522
+ const filePath = join26(sessionDir, file);
17189
17523
  if (existsSync31(filePath) && !file.endsWith(".enc")) {
17190
17524
  try {
17191
17525
  const content = readFileSync18(filePath, "utf8");
@@ -17199,10 +17533,10 @@ class SessionFileEncryptor {
17199
17533
  decryptSessionDirectory(sessionDir) {
17200
17534
  if (!this.config.enabled)
17201
17535
  return;
17202
- const files = readdirSync10(sessionDir);
17536
+ const files = readdirSync11(sessionDir);
17203
17537
  for (const file of files) {
17204
17538
  if (file.endsWith(".enc")) {
17205
- const encFilePath = join25(sessionDir, file);
17539
+ const encFilePath = join26(sessionDir, file);
17206
17540
  const decFilePath = encFilePath.slice(0, -4);
17207
17541
  try {
17208
17542
  const content = readFileSync18(encFilePath, "utf8");
@@ -17229,13 +17563,13 @@ var init_session_encryption = __esm(() => {
17229
17563
  import {
17230
17564
  existsSync as existsSync32,
17231
17565
  mkdirSync as mkdirSync15,
17232
- readdirSync as readdirSync11,
17566
+ readdirSync as readdirSync12,
17233
17567
  readFileSync as readFileSync19,
17234
17568
  rmSync as rmSync2,
17235
17569
  writeFileSync as writeFileSync12,
17236
17570
  appendFileSync as appendFileSync6
17237
17571
  } from "fs";
17238
- import { join as join26 } from "path";
17572
+ import { join as join27 } from "path";
17239
17573
  import { gzipSync } from "zlib";
17240
17574
 
17241
17575
  class SessionStore {
@@ -17249,7 +17583,7 @@ class SessionStore {
17249
17583
  }
17250
17584
  }
17251
17585
  getSessionDir(id) {
17252
- return join26(this.baseDir, id);
17586
+ return join27(this.baseDir, id);
17253
17587
  }
17254
17588
  updateEncryption(config) {
17255
17589
  if (config?.enabled) {
@@ -17265,16 +17599,16 @@ class SessionStore {
17265
17599
  mkdirSync15(this.baseDir, { recursive: true });
17266
17600
  }
17267
17601
  sessionDir(id) {
17268
- return join26(this.baseDir, id);
17602
+ return join27(this.baseDir, id);
17269
17603
  }
17270
17604
  metaPath(id) {
17271
- return join26(this.sessionDir(id), "meta.json");
17605
+ return join27(this.sessionDir(id), "meta.json");
17272
17606
  }
17273
17607
  historyPath(id) {
17274
- return join26(this.sessionDir(id), "history.jsonl");
17608
+ return join27(this.sessionDir(id), "history.jsonl");
17275
17609
  }
17276
17610
  sessionLogPath(id) {
17277
- return join26(this.sessionDir(id), "session.jsonl");
17611
+ return join27(this.sessionDir(id), "session.jsonl");
17278
17612
  }
17279
17613
  sessionExists(id) {
17280
17614
  return existsSync32(this.metaPath(id));
@@ -17398,7 +17732,7 @@ class SessionStore {
17398
17732
  listSessions() {
17399
17733
  if (!existsSync32(this.baseDir))
17400
17734
  return [];
17401
- const entries = readdirSync11(this.baseDir, { withFileTypes: true });
17735
+ const entries = readdirSync12(this.baseDir, { withFileTypes: true });
17402
17736
  const sessions = [];
17403
17737
  for (const entry of entries) {
17404
17738
  if (entry.isDirectory()) {
@@ -17428,7 +17762,7 @@ class SessionStore {
17428
17762
  if (existsSync32(historyPath)) {
17429
17763
  const content = readFileSync19(historyPath, "utf-8");
17430
17764
  const compressed = gzipSync(content);
17431
- const gzPath = join26(this.baseDir, `${session2.id}.jsonl.gz`);
17765
+ const gzPath = join27(this.baseDir, `${session2.id}.jsonl.gz`);
17432
17766
  writeFileSync12(gzPath, compressed);
17433
17767
  rmSync2(historyPath);
17434
17768
  }
@@ -17639,8 +17973,8 @@ class ProfileCompressor {
17639
17973
 
17640
17974
  // src/modules/user-profile/profile.ts
17641
17975
  import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as existsSync33, mkdirSync as mkdirSync16 } from "fs";
17642
- import { join as join27 } from "path";
17643
- import { homedir as homedir9, hostname, platform as platform4, type } from "os";
17976
+ import { join as join28 } from "path";
17977
+ import { homedir as homedir9, hostname, platform as platform5, type } from "os";
17644
17978
  import { env } from "process";
17645
17979
 
17646
17980
  class UserProfile {
@@ -17652,7 +17986,7 @@ class UserProfile {
17652
17986
  }
17653
17987
  collect() {
17654
17988
  this.info = {
17655
- platform: platform4(),
17989
+ platform: platform5(),
17656
17990
  os: `${type()} ${hostname()}`,
17657
17991
  hostname: hostname(),
17658
17992
  shell: env.SHELL || env.ComSpec || "unknown",
@@ -17666,10 +18000,10 @@ class UserProfile {
17666
18000
  if (!existsSync33(this.profileDir)) {
17667
18001
  mkdirSync16(this.profileDir, { recursive: true });
17668
18002
  }
17669
- writeFileSync13(join27(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");
17670
18004
  }
17671
18005
  load() {
17672
- const path = join27(this.profileDir, "profile.json");
18006
+ const path = join28(this.profileDir, "profile.json");
17673
18007
  if (!existsSync33(path))
17674
18008
  return null;
17675
18009
  try {
@@ -17708,8 +18042,8 @@ class UserProfile {
17708
18042
  var init_profile = () => {};
17709
18043
 
17710
18044
  // src/modules/skills/loader.ts
17711
- import { readdirSync as readdirSync12, readFileSync as readFileSync21, existsSync as existsSync34, statSync as statSync6 } from "fs";
17712
- import { join as join28 } 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";
17713
18047
 
17714
18048
  class SkillsLoader {
17715
18049
  loadFromDir(dirPath) {
@@ -17720,9 +18054,9 @@ class SkillsLoader {
17720
18054
  return skills;
17721
18055
  }
17722
18056
  scanDir(dirPath, skills) {
17723
- const entries = readdirSync12(dirPath);
18057
+ const entries = readdirSync13(dirPath);
17724
18058
  for (const entry of entries) {
17725
- const fullPath = join28(dirPath, entry);
18059
+ const fullPath = join29(dirPath, entry);
17726
18060
  const stat = statSync6(fullPath);
17727
18061
  if (stat.isDirectory()) {
17728
18062
  this.scanDir(fullPath, skills);
@@ -17966,10 +18300,10 @@ var init_browser2 = __esm(() => {
17966
18300
  });
17967
18301
 
17968
18302
  // src/modules/lsp/command.ts
17969
- import { delimiter, join as join29 } from "path";
18303
+ import { delimiter, join as join30 } from "path";
17970
18304
  import { existsSync as existsSync35 } from "fs";
17971
- import { platform as platform5 } from "os";
17972
- function resolveSpawnCommand(command, platformName = platform5(), pathEnv = process.env.PATH ?? "") {
18305
+ import { platform as platform6 } from "os";
18306
+ function resolveSpawnCommand(command, platformName = platform6(), pathEnv = process.env.PATH ?? "") {
17973
18307
  if (platformName !== "win32")
17974
18308
  return command;
17975
18309
  if (command.includes("/") || command.includes("\\") || WIN_EXTS.some((ext) => command.toLowerCase().endsWith(ext))) {
@@ -17978,7 +18312,7 @@ function resolveSpawnCommand(command, platformName = platform5(), pathEnv = proc
17978
18312
  const dirs = pathEnv.split(delimiter).filter(Boolean);
17979
18313
  for (const dir of dirs) {
17980
18314
  for (const ext of WIN_EXTS) {
17981
- const candidate = join29(dir, `${command}${ext}`);
18315
+ const candidate = join30(dir, `${command}${ext}`);
17982
18316
  if (existsSync35(candidate))
17983
18317
  return `${command}${ext}`;
17984
18318
  }
@@ -18006,7 +18340,7 @@ import {
18006
18340
  execSync as execSync2
18007
18341
  } from "child_process";
18008
18342
  import { resolve as resolve19 } from "path";
18009
- import { platform as platform6 } from "os";
18343
+ import { platform as platform7 } from "os";
18010
18344
 
18011
18345
  class LspClient {
18012
18346
  process = null;
@@ -18069,7 +18403,7 @@ class LspClient {
18069
18403
  }
18070
18404
  return new Promise((resolve20, reject) => {
18071
18405
  const args = config.args ?? [];
18072
- const isWin = platform6() === "win32";
18406
+ const isWin = platform7() === "win32";
18073
18407
  let spawnCommand = resolveSpawnCommand(config.command);
18074
18408
  let spawnArgs = args;
18075
18409
  const spawnOpts = {
@@ -18359,8 +18693,8 @@ var init_lsp = __esm(() => {
18359
18693
  });
18360
18694
 
18361
18695
  // src/modules/indexer/walker.ts
18362
- import { readdirSync as readdirSync13, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync37, watch } from "fs";
18363
- import { join as join30, 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";
18364
18698
 
18365
18699
  class Indexer {
18366
18700
  baseDir;
@@ -18391,14 +18725,14 @@ class Indexer {
18391
18725
  return;
18392
18726
  let entries;
18393
18727
  try {
18394
- entries = readdirSync13(dir);
18728
+ entries = readdirSync14(dir);
18395
18729
  } catch {
18396
18730
  return;
18397
18731
  }
18398
18732
  for (const entry of entries) {
18399
18733
  if (count >= this.MAX_FILES)
18400
18734
  return;
18401
- const fullPath = join30(dir, entry);
18735
+ const fullPath = join31(dir, entry);
18402
18736
  const relPath = relative2(this.baseDir, fullPath);
18403
18737
  const stat = statSync7(fullPath);
18404
18738
  if (stat.isDirectory()) {
@@ -18462,13 +18796,13 @@ var init_walker = __esm(() => {
18462
18796
 
18463
18797
  // src/modules/indexer/cache.ts
18464
18798
  import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as existsSync38, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
18465
- import { join as join31 } from "path";
18799
+ import { join as join32 } from "path";
18466
18800
 
18467
18801
  class IndexCache {
18468
18802
  cachePath;
18469
18803
  cache = null;
18470
18804
  constructor(cacheDir) {
18471
- this.cachePath = join31(cacheDir, "index-cache.json");
18805
+ this.cachePath = join32(cacheDir, "index-cache.json");
18472
18806
  }
18473
18807
  load() {
18474
18808
  if (this.cache)
@@ -18484,7 +18818,7 @@ class IndexCache {
18484
18818
  }
18485
18819
  save(result) {
18486
18820
  this.cache = result;
18487
- const dir = join31(this.cachePath, "..");
18821
+ const dir = join32(this.cachePath, "..");
18488
18822
  if (!existsSync38(dir))
18489
18823
  mkdirSync17(dir, { recursive: true });
18490
18824
  writeFileSync14(this.cachePath, JSON.stringify(result), "utf-8");
@@ -18502,10 +18836,10 @@ var init_cache = () => {};
18502
18836
 
18503
18837
  // src/modules/indexer/project-profile.ts
18504
18838
  import { readFileSync as readFileSync24, existsSync as existsSync39 } from "fs";
18505
- import { join as join32 } from "path";
18839
+ import { join as join33 } from "path";
18506
18840
  function detectManifest(baseDir) {
18507
18841
  for (const manifest of MANIFEST_ORDER) {
18508
- if (existsSync39(join32(baseDir, manifest)))
18842
+ if (existsSync39(join33(baseDir, manifest)))
18509
18843
  return manifest;
18510
18844
  }
18511
18845
  return null;
@@ -18522,7 +18856,7 @@ function cleanDependency(entry) {
18522
18856
  }
18523
18857
  function readPackageJson(baseDir) {
18524
18858
  try {
18525
- const raw = JSON.parse(readFileSync24(join32(baseDir, "package.json"), "utf-8"));
18859
+ const raw = JSON.parse(readFileSync24(join33(baseDir, "package.json"), "utf-8"));
18526
18860
  if (!raw || typeof raw !== "object")
18527
18861
  return null;
18528
18862
  const profile = {
@@ -18546,7 +18880,7 @@ function readPackageJson(baseDir) {
18546
18880
  }
18547
18881
  function readPyproject(baseDir) {
18548
18882
  try {
18549
- const content = readFileSync24(join32(baseDir, "pyproject.toml"), "utf-8");
18883
+ const content = readFileSync24(join33(baseDir, "pyproject.toml"), "utf-8");
18550
18884
  const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
18551
18885
  const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
18552
18886
  if (nameMatch)
@@ -18562,7 +18896,7 @@ function readPyproject(baseDir) {
18562
18896
  }
18563
18897
  function readCargo(baseDir) {
18564
18898
  try {
18565
- const content = readFileSync24(join32(baseDir, "Cargo.toml"), "utf-8");
18899
+ const content = readFileSync24(join33(baseDir, "Cargo.toml"), "utf-8");
18566
18900
  const profile = { runtime: "rust", deps: [], devDeps: [], scripts: {} };
18567
18901
  const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
18568
18902
  if (nameMatch)
@@ -18586,7 +18920,7 @@ function readCargo(baseDir) {
18586
18920
  }
18587
18921
  function readGoMod(baseDir) {
18588
18922
  try {
18589
- const content = readFileSync24(join32(baseDir, "go.mod"), "utf-8");
18923
+ const content = readFileSync24(join33(baseDir, "go.mod"), "utf-8");
18590
18924
  const profile = { runtime: "go", deps: [], devDeps: [], scripts: {} };
18591
18925
  const moduleMatch = content.match(/^module\s+(\S+)/m);
18592
18926
  if (moduleMatch)
@@ -18604,7 +18938,7 @@ function readGoMod(baseDir) {
18604
18938
  }
18605
18939
  function readRequirements(baseDir) {
18606
18940
  try {
18607
- const content = readFileSync24(join32(baseDir, "requirements.txt"), "utf-8");
18941
+ const content = readFileSync24(join33(baseDir, "requirements.txt"), "utf-8");
18608
18942
  const profile = { runtime: "python", deps: [], devDeps: [], scripts: {} };
18609
18943
  for (const line of content.split(`
18610
18944
  `)) {
@@ -19058,7 +19392,7 @@ var init_mcp = __esm(() => {
19058
19392
 
19059
19393
  // src/modules/memory/module.ts
19060
19394
  import { homedir as homedir10 } from "os";
19061
- import { join as join33 } from "path";
19395
+ import { join as join34 } from "path";
19062
19396
 
19063
19397
  class MemoryModule {
19064
19398
  name = "memory";
@@ -19067,7 +19401,7 @@ class MemoryModule {
19067
19401
  if (storeOrDir instanceof MemoryStore) {
19068
19402
  this.store = storeOrDir;
19069
19403
  } else {
19070
- const dir = storeOrDir || join33(homedir10(), ".mma", "memory");
19404
+ const dir = storeOrDir || join34(homedir10(), ".mma", "memory");
19071
19405
  this.store = new MemoryStore(dir);
19072
19406
  }
19073
19407
  }
@@ -19159,7 +19493,7 @@ __export(exports_bootstrap, {
19159
19493
  bootstrap: () => bootstrap
19160
19494
  });
19161
19495
  import { homedir as homedir11 } from "os";
19162
- import { join as join34, resolve as resolve21 } from "path";
19496
+ import { join as join35, resolve as resolve21 } from "path";
19163
19497
  import { existsSync as existsSync40, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "fs";
19164
19498
  function buildSystemInfo(config, baseDir, profileCompressed) {
19165
19499
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
@@ -19186,8 +19520,8 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
19186
19520
  `);
19187
19521
  }
19188
19522
  async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19189
- const dir = configDir || join34(homedir11(), ".mma");
19190
- const projectConfigPath = projectDir ? join34(projectDir, ".mmrc") : join34(process.cwd(), ".mmrc");
19523
+ const dir = configDir || join35(homedir11(), ".mma");
19524
+ const projectConfigPath = projectDir ? join35(projectDir, ".mmrc") : join35(process.cwd(), ".mmrc");
19191
19525
  const config = loadConfig({ configDir: dir, projectConfigPath });
19192
19526
  setLocale(config.locale);
19193
19527
  try {
@@ -19197,7 +19531,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19197
19531
  }
19198
19532
  } catch {}
19199
19533
  const logger = new Logger(config.logLevel);
19200
- logger.setLogDir(join34(dir, "logs"));
19534
+ logger.setLogDir(join35(dir, "logs"));
19201
19535
  logger.debug("MMA bootstrap", {
19202
19536
  version: config.version,
19203
19537
  model: config.model
@@ -19219,7 +19553,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19219
19553
  logger.info(`Model ${config.model} loaded in ${loadResult.loadTime}s`);
19220
19554
  }
19221
19555
  }
19222
- const profile = new UserProfile(join34(dir));
19556
+ const profile = new UserProfile(join35(dir));
19223
19557
  profile.load() || profile.collect();
19224
19558
  profile.save();
19225
19559
  const llmProvider = new OpenAICompatProvider({
@@ -19231,7 +19565,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19231
19565
  rateLimits: config.security?.rateLimits
19232
19566
  });
19233
19567
  const baseDir = projectDir ? resolve21(projectDir) : process.cwd();
19234
- const projectMapCacheDir = join34(baseDir, ".mma");
19568
+ const projectMapCacheDir = join35(baseDir, ".mma");
19235
19569
  const indexerModule = new IndexerModule({
19236
19570
  baseDir,
19237
19571
  cacheDir: projectMapCacheDir
@@ -19242,9 +19576,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19242
19576
  logger.warn(`Project indexing failed: ${err.message}`);
19243
19577
  }
19244
19578
  const skillsLoader = new SkillsLoader;
19245
- const builtinDir = join34(import.meta.dirname, "skills", "builtin");
19246
- const globalDir = join34(homedir11(), ".agents", "skills");
19247
- const projectSkillsDir = join34(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");
19248
19582
  const availableSkills = skillsLoader.loadFromAllSources(builtinDir, globalDir, projectSkillsDir);
19249
19583
  const skillsBudget = Math.floor(config.contextWindow * config.skills.budget);
19250
19584
  const skillsModule = new SkillsModule(availableSkills, skillsBudget);
@@ -19258,11 +19592,11 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19258
19592
  essential: true,
19259
19593
  estimatedTokens: Math.ceil(systemInfoContent.length / 4)
19260
19594
  };
19261
- const agentsMdGlobal = join34(dir, "AGENTS.md");
19595
+ const agentsMdGlobal = join35(dir, "AGENTS.md");
19262
19596
  if (!existsSync40(agentsMdGlobal)) {
19263
19597
  writeFileSync15(agentsMdGlobal, "", "utf-8");
19264
19598
  }
19265
- const sessionDir = join34(dir, "sessions");
19599
+ const sessionDir = join35(dir, "sessions");
19266
19600
  const sessionStore = new SessionStore(sessionDir);
19267
19601
  sessionStore.init();
19268
19602
  const sessionManager = new SessionManager(sessionStore, {
@@ -19329,7 +19663,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19329
19663
  const mcpModule = new MCPModule(config);
19330
19664
  await mcpModule.initialize();
19331
19665
  moduleRegistry.register(mcpModule);
19332
- const memoryStore = new MemoryStore(join34(dir, "memory"));
19666
+ const memoryStore = new MemoryStore(join35(dir, "memory"));
19333
19667
  const memoryModule = new MemoryModule(memoryStore);
19334
19668
  moduleRegistry.register(memoryModule);
19335
19669
  if (config.browser.enabled) {
@@ -19380,8 +19714,8 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19380
19714
  pluginManager.register(plugin);
19381
19715
  pluginManager.register(plugin2);
19382
19716
  const pluginLoader = new PluginLoader;
19383
- const globalPluginsDir = join34(homedir11(), ".mma", "plugins");
19384
- const projectPluginsDir = join34(baseDir, ".mma", "plugins");
19717
+ const globalPluginsDir = join35(homedir11(), ".mma", "plugins");
19718
+ const projectPluginsDir = join35(baseDir, ".mma", "plugins");
19385
19719
  pluginLoader.loadFromDir(globalPluginsDir, pluginManager, logger);
19386
19720
  pluginLoader.loadFromDir(projectPluginsDir, pluginManager, logger);
19387
19721
  contextManager.onCompact = (summary) => {
@@ -19399,9 +19733,9 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19399
19733
  const skipAgentsMd = noAgentsMd === true;
19400
19734
  if (!skipAgentsMd) {
19401
19735
  const agentsMdCandidates = [
19402
- join34(baseDir, "AGENTS.md"),
19403
- join34(baseDir, ".mma", "AGENTS.md"),
19404
- join34(dir, "AGENTS.md")
19736
+ join35(baseDir, "AGENTS.md"),
19737
+ join35(baseDir, ".mma", "AGENTS.md"),
19738
+ join35(dir, "AGENTS.md")
19405
19739
  ];
19406
19740
  for (const p of agentsMdCandidates) {
19407
19741
  if (existsSync40(p)) {
@@ -20261,7 +20595,7 @@ __export(exports_manifest, {
20261
20595
  });
20262
20596
  import { existsSync as existsSync41, readFileSync as readFileSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
20263
20597
  import { homedir as homedir13 } from "os";
20264
- import { join as join36 } from "path";
20598
+ import { join as join37 } from "path";
20265
20599
  function readManifest(path = MANIFEST_PATH) {
20266
20600
  try {
20267
20601
  if (existsSync41(path)) {
@@ -20272,7 +20606,7 @@ function readManifest(path = MANIFEST_PATH) {
20272
20606
  return { version: 1, certifications: [] };
20273
20607
  }
20274
20608
  function saveManifest(m, path = MANIFEST_PATH) {
20275
- mkdirSync18(join36(homedir13(), ".mma"), { recursive: true });
20609
+ mkdirSync18(join37(homedir13(), ".mma"), { recursive: true });
20276
20610
  writeFileSync16(path, JSON.stringify(m, null, 2), "utf-8");
20277
20611
  }
20278
20612
  function upsertCertification(entry, path = MANIFEST_PATH) {
@@ -20307,7 +20641,7 @@ function getCertMark(model, providerUrl, currentVersion, path = MANIFEST_PATH) {
20307
20641
  }
20308
20642
  var MANIFEST_PATH;
20309
20643
  var init_manifest = __esm(() => {
20310
- MANIFEST_PATH = join36(homedir13(), ".mma", "certifications.json");
20644
+ MANIFEST_PATH = join37(homedir13(), ".mma", "certifications.json");
20311
20645
  });
20312
20646
 
20313
20647
  // node_modules/yaml/dist/nodes/identity.js
@@ -27430,8 +27764,8 @@ var init_scenarios = __esm(() => {
27430
27764
  });
27431
27765
 
27432
27766
  // src/modules/certification/loader.ts
27433
- import { existsSync as existsSync42, readdirSync as readdirSync14, readFileSync as readFileSync27 } from "fs";
27434
- import { join as join37 } from "path";
27767
+ import { existsSync as existsSync42, readdirSync as readdirSync15, readFileSync as readFileSync27 } from "fs";
27768
+ import { join as join38 } from "path";
27435
27769
  function validateScenario(s) {
27436
27770
  const errors2 = [];
27437
27771
  const isSkip = s.mode === "skip";
@@ -27481,11 +27815,11 @@ function loadScenarios(userDir) {
27481
27815
  scenarios.push(s);
27482
27816
  }
27483
27817
  if (userDir && existsSync42(userDir)) {
27484
- for (const file of readdirSync14(userDir)) {
27818
+ for (const file of readdirSync15(userDir)) {
27485
27819
  if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
27486
27820
  continue;
27487
27821
  try {
27488
- const raw = readFileSync27(join37(userDir, file), "utf-8");
27822
+ const raw = readFileSync27(join38(userDir, file), "utf-8");
27489
27823
  const data = $parse(raw);
27490
27824
  const parsed = normalizeScenario(data, file);
27491
27825
  const errs = validateScenario(parsed);
@@ -27539,7 +27873,7 @@ var init_loader3 = __esm(() => {
27539
27873
 
27540
27874
  // src/modules/certification/fact-checker.ts
27541
27875
  import { existsSync as existsSync43, readFileSync as readFileSync28, statSync as statSync8 } from "fs";
27542
- import { join as join38 } from "path";
27876
+ import { join as join39 } from "path";
27543
27877
  function checkSandbox(sandboxDir, checks, exitCode, output) {
27544
27878
  const failures = [];
27545
27879
  for (const check of checks) {
@@ -27556,13 +27890,13 @@ function runCheck(sandboxDir, check, exitCode, output) {
27556
27890
  case "outputContains":
27557
27891
  return output.includes(check.text);
27558
27892
  case "fileExists":
27559
- return isFile(join38(sandboxDir, check.path));
27893
+ return isFile(join39(sandboxDir, check.path));
27560
27894
  case "fileNotExists":
27561
- return !existsSync43(join38(sandboxDir, check.path));
27895
+ return !existsSync43(join39(sandboxDir, check.path));
27562
27896
  case "dirExists":
27563
- return isDir(join38(sandboxDir, check.path));
27897
+ return isDir(join39(sandboxDir, check.path));
27564
27898
  case "fileContent": {
27565
- const abs = join38(sandboxDir, check.path);
27899
+ const abs = join39(sandboxDir, check.path);
27566
27900
  if (!isFile(abs))
27567
27901
  return false;
27568
27902
  const content = readFileSync28(abs, "utf-8");
@@ -27573,7 +27907,7 @@ function runCheck(sandboxDir, check, exitCode, output) {
27573
27907
  return false;
27574
27908
  }
27575
27909
  case "fileRegex": {
27576
- const abs = join38(sandboxDir, check.path);
27910
+ const abs = join39(sandboxDir, check.path);
27577
27911
  if (!isFile(abs))
27578
27912
  return false;
27579
27913
  return new RegExp(check.pattern).test(readFileSync28(abs, "utf-8"));
@@ -27623,8 +27957,8 @@ var init_fact_checker = () => {};
27623
27957
  // src/modules/certification/runner.ts
27624
27958
  import { spawn as spawn7 } from "child_process";
27625
27959
  import { existsSync as existsSync44, mkdirSync as mkdirSync19, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
27626
- import { platform as platform7 } from "os";
27627
- import { join as join39, resolve as resolve22, dirname as dirname12 } from "path";
27960
+ import { platform as platform8 } from "os";
27961
+ import { join as join40, resolve as resolve22, dirname as dirname12 } from "path";
27628
27962
  async function runScenario(scenario, opts) {
27629
27963
  if (scenario.mode === "skip") {
27630
27964
  return {
@@ -27643,7 +27977,7 @@ async function runScenario(scenario, opts) {
27643
27977
  let passed = 0;
27644
27978
  let firstError;
27645
27979
  for (let i = 1;i <= reps; i++) {
27646
- const sandbox = join39(opts.sandboxBase, `run-${scenario.id}-${i}`);
27980
+ const sandbox = join40(opts.sandboxBase, `run-${scenario.id}-${i}`);
27647
27981
  let failures = [];
27648
27982
  let exitCode = -1;
27649
27983
  let output = "";
@@ -27704,20 +28038,20 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
27704
28038
  rmSync4(sandbox, { recursive: true, force: true });
27705
28039
  mkdirSync19(sandbox, { recursive: true });
27706
28040
  for (const f of scenario.fixtures ?? []) {
27707
- const src = join39(mmaRoot, f.source);
28041
+ const src = join40(mmaRoot, f.source);
27708
28042
  if (!existsSync44(src)) {
27709
28043
  throw new Error(`fixture missing: ${f.source}`);
27710
28044
  }
27711
- const dest = join39(sandbox, f.dest);
28045
+ const dest = join40(sandbox, f.dest);
27712
28046
  mkdirSync19(dirname12(dest), { recursive: true });
27713
28047
  cpSync2(src, dest);
27714
28048
  }
27715
28049
  }
27716
28050
  function resolveMmaEntry(mmaRoot) {
27717
- const dev = join39(mmaRoot, "src", "cli", "main.ts");
28051
+ const dev = join40(mmaRoot, "src", "cli", "main.ts");
27718
28052
  if (existsSync44(dev))
27719
28053
  return dev;
27720
- return join39(mmaRoot, "dist", "main.js");
28054
+ return join40(mmaRoot, "dist", "main.js");
27721
28055
  }
27722
28056
  function findMmaRoot(fromDir) {
27723
28057
  const candidates = [
@@ -27725,7 +28059,7 @@ function findMmaRoot(fromDir) {
27725
28059
  resolve22(fromDir, "..")
27726
28060
  ];
27727
28061
  for (const c of candidates) {
27728
- if (existsSync44(join39(c, "package.json")))
28062
+ if (existsSync44(join40(c, "package.json")))
27729
28063
  return c;
27730
28064
  }
27731
28065
  return process.cwd();
@@ -27734,7 +28068,7 @@ function killTree2(child) {
27734
28068
  const pid = child.pid;
27735
28069
  if (!pid)
27736
28070
  return;
27737
- if (platform7() === "win32") {
28071
+ if (platform8() === "win32") {
27738
28072
  spawn7("taskkill", ["/pid", String(pid), "/T", "/F"], {
27739
28073
  windowsHide: true,
27740
28074
  stdio: "ignore"
@@ -27793,11 +28127,11 @@ __export(exports_cli, {
27793
28127
  });
27794
28128
  import { rmSync as rmSync5 } from "fs";
27795
28129
  import { homedir as homedir14 } from "os";
27796
- import { join as join40, dirname as dirname13 } from "path";
28130
+ import { join as join41, dirname as dirname13 } from "path";
27797
28131
  import { fileURLToPath as fileURLToPath2 } from "url";
27798
28132
  import { existsSync as existsSync45, readFileSync as readFileSync29 } from "fs";
27799
28133
  function readVersion() {
27800
- const candidates = [join40(MMA_ROOT, "package.json")];
28134
+ const candidates = [join41(MMA_ROOT, "package.json")];
27801
28135
  for (const p of candidates) {
27802
28136
  if (existsSync45(p)) {
27803
28137
  try {
@@ -27837,7 +28171,7 @@ async function certify(opts) {
27837
28171
  return;
27838
28172
  }
27839
28173
  console.log(t("cli.cert_started", { model: opts.name, provider: providerUrl }));
27840
- const sandboxBase = join40(process.cwd(), ".mma", "certification");
28174
+ const sandboxBase = join41(process.cwd(), ".mma", "certification");
27841
28175
  const results = [];
27842
28176
  const total = selected.length;
27843
28177
  let idx = 0;
@@ -27951,7 +28285,7 @@ var init_cli = __esm(() => {
27951
28285
  init_manifest();
27952
28286
  HERE = dirname13(fileURLToPath2(import.meta.url));
27953
28287
  MMA_ROOT = findMmaRoot(HERE);
27954
- USER_SCENARIO_DIR = join40(homedir14(), ".mma", "certification", "scenarios");
28288
+ USER_SCENARIO_DIR = join41(homedir14(), ".mma", "certification", "scenarios");
27955
28289
  });
27956
28290
 
27957
28291
  // src/cli/repl-commands.ts
@@ -27960,15 +28294,15 @@ __export(exports_repl_commands, {
27960
28294
  registerAllCommands: () => registerAllCommands,
27961
28295
  COMMAND_GROUPS: () => COMMAND_GROUPS
27962
28296
  });
27963
- import { join as join42, dirname as dirname15 } from "path";
28297
+ import { join as join43, dirname as dirname15 } from "path";
27964
28298
  import { homedir as homedir16 } from "os";
27965
28299
  import { existsSync as existsSync47, readFileSync as readFileSync31 } from "fs";
27966
28300
  import { fileURLToPath as fileURLToPath4 } from "url";
27967
28301
  function readVersion3() {
27968
28302
  const here = dirname15(fileURLToPath4(import.meta.url));
27969
28303
  const candidates = [
27970
- join42(here, "..", "..", "package.json"),
27971
- join42(here, "..", "package.json")
28304
+ join43(here, "..", "..", "package.json"),
28305
+ join43(here, "..", "package.json")
27972
28306
  ];
27973
28307
  for (const p of candidates) {
27974
28308
  if (existsSync47(p)) {
@@ -28136,7 +28470,7 @@ function registerMmaCommands(ctx) {
28136
28470
  console.log(pc2.yellow(t("repl.wizard_running")));
28137
28471
  await ctx.withExclusiveInput(async () => {
28138
28472
  const answers = await runSetup(ctx.rl);
28139
- const configPath = join42(homedir16(), ".mma", "config.json");
28473
+ const configPath = join43(homedir16(), ".mma", "config.json");
28140
28474
  ctx.config.provider.type = answers.provider;
28141
28475
  ctx.config.provider.baseUrl = answers.apiBase;
28142
28476
  ctx.config.provider.apiKey = answers.apiKey;
@@ -28190,7 +28524,7 @@ Excluded blocks: ${info.excluded.length}`));
28190
28524
  return;
28191
28525
  }
28192
28526
  ctx.config.provider.type = name;
28193
- const configPath = join42(homedir16(), ".mma", "config.json");
28527
+ const configPath = join43(homedir16(), ".mma", "config.json");
28194
28528
  saveConfig(ctx.config, configPath);
28195
28529
  await ctx.agent.reconfigure(ctx.config);
28196
28530
  console.log(pc2.green(t("repl.provider_set", { name })));
@@ -28246,7 +28580,7 @@ Excluded blocks: ${info.excluded.length}`));
28246
28580
  return;
28247
28581
  }
28248
28582
  ctx.config.model = name;
28249
- const configPath = join42(homedir16(), ".mma", "config.json");
28583
+ const configPath = join43(homedir16(), ".mma", "config.json");
28250
28584
  saveConfig(ctx.config, configPath);
28251
28585
  await ctx.agent.reconfigure(ctx.config);
28252
28586
  console.log(pc2.green(t("repl.model_set", { name })));
@@ -28271,7 +28605,7 @@ Excluded blocks: ${info.excluded.length}`));
28271
28605
  return;
28272
28606
  }
28273
28607
  ctx.config.contextWindow = size;
28274
- const configPath = join42(homedir16(), ".mma", "config.json");
28608
+ const configPath = join43(homedir16(), ".mma", "config.json");
28275
28609
  saveConfig(ctx.config, configPath);
28276
28610
  await ctx.agent.reconfigure(ctx.config);
28277
28611
  console.log(pc2.green(t("cli.context_set", { size })));
@@ -28290,10 +28624,10 @@ Excluded blocks: ${info.excluded.length}`));
28290
28624
  ctx.agent.shutdown();
28291
28625
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
28292
28626
  const { homedir: homedir17 } = await import("os");
28293
- const { join: join43 } = await import("path");
28627
+ const { join: join44 } = await import("path");
28294
28628
  const configDir = ctx.configDir;
28295
28629
  const baseDir = ctx.baseDir;
28296
- const projectConfigPath = join43(baseDir, ".mmrc");
28630
+ const projectConfigPath = join44(baseDir, ".mmrc");
28297
28631
  const freshConfig = loadConfig2({ configDir, projectConfigPath });
28298
28632
  Object.assign(ctx.config, freshConfig);
28299
28633
  const { bootstrap: bootstrap2 } = await Promise.resolve().then(() => (init_bootstrap(), exports_bootstrap));
@@ -28593,14 +28927,14 @@ init_bootstrap();
28593
28927
  init_config2();
28594
28928
  init_setup();
28595
28929
  init_i18n();
28596
- import { join as join41, dirname as dirname14 } from "path";
28930
+ import { join as join42, dirname as dirname14 } from "path";
28597
28931
  import { homedir as homedir15 } from "os";
28598
28932
  import { existsSync as existsSync46, readFileSync as readFileSync30 } from "fs";
28599
28933
 
28600
28934
  // src/cli/security-commands.ts
28601
28935
  init_bootstrap();
28602
28936
  init_config2();
28603
- import { join as join35 } from "path";
28937
+ import { join as join36 } from "path";
28604
28938
  import { homedir as homedir12 } from "os";
28605
28939
 
28606
28940
  // src/modules/security/security-policies.ts
@@ -29130,7 +29464,7 @@ function createSecurityCommand(program2) {
29130
29464
  }
29131
29465
  });
29132
29466
  securityCmd.command("set-policy").argument("<preset>", t("cli.security.preset")).description(t("cli.security.set_policy")).action(async (preset) => {
29133
- const configPath = join35(homedir12(), ".mma", "config.json");
29467
+ const configPath = join36(homedir12(), ".mma", "config.json");
29134
29468
  const { config: appConfig } = await bootstrap();
29135
29469
  const validPresets = ["strict", "balanced", "permissive"];
29136
29470
  if (!validPresets.includes(preset)) {
@@ -29145,7 +29479,7 @@ function createSecurityCommand(program2) {
29145
29479
  console.log(t("cli.security.policy_description", { description: policy.description }));
29146
29480
  });
29147
29481
  securityCmd.command("enable-encryption").description(t("cli.security.enable_encryption")).action(async () => {
29148
- const configPath = join35(homedir12(), ".mma", "config.json");
29482
+ const configPath = join36(homedir12(), ".mma", "config.json");
29149
29483
  const { config: appConfig } = await bootstrap();
29150
29484
  appConfig.security = appConfig.security || {};
29151
29485
  appConfig.security.sessionEncryption = {
@@ -29157,7 +29491,7 @@ function createSecurityCommand(program2) {
29157
29491
  console.log(t("cli.security.encryption_enabled"));
29158
29492
  });
29159
29493
  securityCmd.command("disable-encryption").description(t("cli.security.disable_encryption")).action(async () => {
29160
- const configPath = join35(homedir12(), ".mma", "config.json");
29494
+ const configPath = join36(homedir12(), ".mma", "config.json");
29161
29495
  const { config: appConfig } = await bootstrap();
29162
29496
  appConfig.security = appConfig.security || {};
29163
29497
  appConfig.security.sessionEncryption = {
@@ -29169,7 +29503,7 @@ function createSecurityCommand(program2) {
29169
29503
  console.log(t("cli.security.encryption_disabled"));
29170
29504
  });
29171
29505
  securityCmd.command("enable-audit").description(t("cli.security.enable_audit")).action(async () => {
29172
- const configPath = join35(homedir12(), ".mma", "config.json");
29506
+ const configPath = join36(homedir12(), ".mma", "config.json");
29173
29507
  const { config: appConfig } = await bootstrap();
29174
29508
  appConfig.security = appConfig.security || {};
29175
29509
  appConfig.security.auditNotifier = {
@@ -29183,7 +29517,7 @@ function createSecurityCommand(program2) {
29183
29517
  console.log(t("cli.security.audit_enabled"));
29184
29518
  });
29185
29519
  securityCmd.command("disable-audit").description(t("cli.security.disable_audit")).action(async () => {
29186
- const configPath = join35(homedir12(), ".mma", "config.json");
29520
+ const configPath = join36(homedir12(), ".mma", "config.json");
29187
29521
  const { config: appConfig } = await bootstrap();
29188
29522
  appConfig.security = appConfig.security || {};
29189
29523
  appConfig.security.auditNotifier = {
@@ -29219,8 +29553,8 @@ import { fileURLToPath as fileURLToPath3 } from "url";
29219
29553
  function readVersion2() {
29220
29554
  const here = dirname14(fileURLToPath3(import.meta.url));
29221
29555
  const candidates = [
29222
- join41(here, "..", "..", "package.json"),
29223
- join41(here, "..", "package.json")
29556
+ join42(here, "..", "..", "package.json"),
29557
+ join42(here, "..", "package.json")
29224
29558
  ];
29225
29559
  for (const p of candidates) {
29226
29560
  if (existsSync46(p)) {
@@ -29238,7 +29572,7 @@ function createProgram() {
29238
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"));
29239
29573
  program2.command("init").description(t("cli.init")).action(async () => {
29240
29574
  const answers = await runSetup();
29241
- const configPath = join41(homedir15(), ".mma", "config.json");
29575
+ const configPath = join42(homedir15(), ".mma", "config.json");
29242
29576
  const { config } = await bootstrap();
29243
29577
  config.provider.type = answers.provider;
29244
29578
  config.provider.baseUrl = answers.apiBase;
@@ -29283,7 +29617,7 @@ function createProgram() {
29283
29617
  });
29284
29618
  const configCmd = program2.command("config").description(t("cli.manage_config"));
29285
29619
  configCmd.command("set").argument("<key>", t("cli.config_key")).argument("<value>", "Config value").description(t("cli.set_value")).action(async (key, value) => {
29286
- const configPath = join41(homedir15(), ".mma", "config.json");
29620
+ const configPath = join42(homedir15(), ".mma", "config.json");
29287
29621
  const { config } = await bootstrap();
29288
29622
  const keys = key.split(".");
29289
29623
  let obj = config;
@@ -29346,7 +29680,7 @@ function createProgram() {
29346
29680
  console.log(t("cli.model_hint"));
29347
29681
  });
29348
29682
  model.command("use").argument("<name>", "Model name").description(t("cli.set_model")).action(async (name) => {
29349
- const configPath = join41(homedir15(), ".mma", "config.json");
29683
+ const configPath = join42(homedir15(), ".mma", "config.json");
29350
29684
  const { config } = await bootstrap();
29351
29685
  config.model = name;
29352
29686
  saveConfig(config, configPath);
@@ -29382,7 +29716,7 @@ function createProgram() {
29382
29716
  await uncertify2(name, config);
29383
29717
  });
29384
29718
  program2.command("context").description(t("cli.manage_context")).argument("<size>", "Context window size in tokens").action(async (size) => {
29385
- const configPath = join41(homedir15(), ".mma", "config.json");
29719
+ const configPath = join42(homedir15(), ".mma", "config.json");
29386
29720
  const { config } = await bootstrap();
29387
29721
  const contextWindow = parseInt(size, 10);
29388
29722
  if (isNaN(contextWindow) || contextWindow < 1024) {
@@ -29400,7 +29734,7 @@ function createProgram() {
29400
29734
  console.log(t("cli.base_url"), config.provider.baseUrl);
29401
29735
  });
29402
29736
  provider.command("use").argument("<name>", "Provider name").description(t("cli.set_provider")).action(async (name) => {
29403
- const configPath = join41(homedir15(), ".mma", "config.json");
29737
+ const configPath = join42(homedir15(), ".mma", "config.json");
29404
29738
  const { config } = await bootstrap();
29405
29739
  config.provider.type = name;
29406
29740
  saveConfig(config, configPath);
@@ -30171,7 +30505,7 @@ class LineEditor {
30171
30505
 
30172
30506
  // src/cli/repl.ts
30173
30507
  import { existsSync as existsSync48, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
30174
- import { join as join43, dirname as dirname16 } from "path";
30508
+ import { join as join44, dirname as dirname16 } from "path";
30175
30509
  import { homedir as homedir17 } from "os";
30176
30510
  import { fileURLToPath as fileURLToPath5 } from "url";
30177
30511
 
@@ -30677,8 +31011,8 @@ init_repl_commands();
30677
31011
  function readVersion4() {
30678
31012
  const here = dirname16(fileURLToPath5(import.meta.url));
30679
31013
  const candidates = [
30680
- join43(here, "..", "..", "package.json"),
30681
- join43(here, "..", "package.json")
31014
+ join44(here, "..", "..", "package.json"),
31015
+ join44(here, "..", "package.json")
30682
31016
  ];
30683
31017
  for (const p of candidates) {
30684
31018
  if (existsSync48(p)) {
@@ -30739,10 +31073,10 @@ class Repl {
30739
31073
  this.skillsModule = skillsModule;
30740
31074
  this.pluginManager = pluginManager;
30741
31075
  this.logger = logger;
30742
- this.configDir = configDir || join43(homedir17(), ".mma");
31076
+ this.configDir = configDir || join44(homedir17(), ".mma");
30743
31077
  this.baseDir = baseDir || process.cwd();
30744
31078
  this.noAgentsMd = noAgentsMd === true;
30745
- this.historyPath = join43(homedir17(), ".mma", "repl-history");
31079
+ this.historyPath = join44(homedir17(), ".mma", "repl-history");
30746
31080
  this.loadHistory();
30747
31081
  this.rl = process.stdin.isTTY ? new LineEditor({
30748
31082
  input: process.stdin,
@@ -31134,9 +31468,9 @@ ${t("image.clipboard_empty")}`));
31134
31468
  row(t("repl.agents_label"), pc2.red(t("repl.disabled")));
31135
31469
  } else {
31136
31470
  const agentsMdCandidates = [
31137
- join43(this.baseDir, "AGENTS.md"),
31138
- join43(this.baseDir, ".mma", "AGENTS.md"),
31139
- join43(this.configDir, "AGENTS.md")
31471
+ join44(this.baseDir, "AGENTS.md"),
31472
+ join44(this.baseDir, ".mma", "AGENTS.md"),
31473
+ join44(this.configDir, "AGENTS.md")
31140
31474
  ];
31141
31475
  const foundAgents = agentsMdCandidates.filter((p) => existsSync48(p));
31142
31476
  if (foundAgents.length > 0) {
@@ -31149,7 +31483,7 @@ ${t("image.clipboard_empty")}`));
31149
31483
  }
31150
31484
  const meta = this.sessionManager?.getActiveMeta();
31151
31485
  if (meta) {
31152
- const sessionPath = join43(this.configDir, "sessions", meta.id);
31486
+ const sessionPath = join44(this.configDir, "sessions", meta.id);
31153
31487
  row(t("repl.session_label"), `${pc2.cyan(meta.name)} ${pc2.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc2.dim(sessionPath)}`);
31154
31488
  }
31155
31489
  const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
@@ -31191,13 +31525,13 @@ init_config2();
31191
31525
  init_i18n();
31192
31526
  init_colors();
31193
31527
  import { existsSync as existsSync49, readFileSync as readFileSync33 } from "fs";
31194
- import { join as join44, dirname as dirname17 } from "path";
31528
+ import { join as join45, dirname as dirname17 } from "path";
31195
31529
  import { homedir as homedir18 } from "os";
31196
31530
  import { fileURLToPath as fileURLToPath6 } from "url";
31197
31531
 
31198
31532
  // src/modules/updater/checker.ts
31199
31533
  init_command();
31200
- import { platform as platform8 } from "os";
31534
+ import { platform as platform9 } from "os";
31201
31535
  var defaultRunner2 = async (command, args, options) => {
31202
31536
  const { execFile } = await import("child_process");
31203
31537
  return new Promise((resolve23) => {
@@ -31259,7 +31593,7 @@ class Updater {
31259
31593
  buildInstallCommand(latest) {
31260
31594
  const npmArgs = ["install", "-g", `${this.packageName}@${latest}`];
31261
31595
  const command = resolveSpawnCommand("npm");
31262
- if (platform8() === "win32" && /\.(cmd|bat)$/i.test(command)) {
31596
+ if (platform9() === "win32" && /\.(cmd|bat)$/i.test(command)) {
31263
31597
  return { command: "cmd.exe", args: ["/c", command, ...npmArgs] };
31264
31598
  }
31265
31599
  return { command, args: npmArgs };
@@ -31355,8 +31689,8 @@ class UpdaterModule {
31355
31689
  function readVersion5() {
31356
31690
  const here = dirname17(fileURLToPath6(import.meta.url));
31357
31691
  const candidates = [
31358
- join44(here, "..", "..", "package.json"),
31359
- join44(here, "..", "package.json")
31692
+ join45(here, "..", "..", "package.json"),
31693
+ join45(here, "..", "package.json")
31360
31694
  ];
31361
31695
  for (const p of candidates) {
31362
31696
  if (existsSync49(p)) {
@@ -31432,15 +31766,15 @@ async function main() {
31432
31766
  agent.shutdown();
31433
31767
  process.exit(exitCode);
31434
31768
  } else {
31435
- const configPath = join44(homedir18(), ".mma", "config.json");
31769
+ const configPath = join45(homedir18(), ".mma", "config.json");
31436
31770
  if (!existsSync49(configPath)) {
31437
31771
  console.log(pc2.yellow(`
31438
31772
  ` + t("cli.first_run") + `
31439
31773
  `));
31440
31774
  const answers = await runSetup();
31441
31775
  const config2 = loadConfig({
31442
- configDir: join44(homedir18(), ".mma"),
31443
- projectConfigPath: projectDir ? join44(projectDir, ".mmrc") : join44(process.cwd(), ".mmrc")
31776
+ configDir: join45(homedir18(), ".mma"),
31777
+ projectConfigPath: projectDir ? join45(projectDir, ".mmrc") : join45(process.cwd(), ".mmrc")
31444
31778
  });
31445
31779
  config2.provider.type = answers.provider;
31446
31780
  config2.provider.baseUrl = answers.apiBase;