micro-models-agent 0.37.0 → 0.38.0

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 +371 -122
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2393,6 +2393,7 @@ var init_en = __esm(() => {
2393
2393
  "tool.friendly.search_history": "Searching history",
2394
2394
  "tool.friendly.pipeline_run": "Running pipeline",
2395
2395
  "tool.friendly.mcp_call": "MCP call",
2396
+ "tool.friendly.lsp_check": "Checking code",
2396
2397
  "tool.truncated": "[Truncated: {tokens} tokens removed]",
2397
2398
  "tool.subagent_queued": "Sub-agent task queued: {task}",
2398
2399
  "tool.subagent_artifact": `Sub-agent completed. Full result saved to artifact: {path}
@@ -2485,6 +2486,9 @@ Command: {command}`,
2485
2486
  "plan.switched": "Switched to plan {id}: {title}",
2486
2487
  "plan.replanned": "Plan re-planned: {kept} completed steps kept, {steps} new steps added",
2487
2488
  "plan.replan_no_steps": "Provide new steps for re-planning",
2489
+ "plan.active_in_progress": 'Cannot create a new plan: active plan {id} already has progress ({done}/{total} done, current: step {current}). Resume it — use "plan show" to view, then continue working. To replace it, call "plan abort" first, then "plan create" again.',
2490
+ "plan.id_ignored": 'note: plan ids are auto-generated; use "plan switch" to activate an existing plan by id.',
2491
+ "plan.existing_fresh": "note: the previous active plan had no progress and was preserved as a draft.",
2488
2492
  "todo.added": "Added {count} todo(s): {items}",
2489
2493
  "todo.marked_done": "Marked {count} item(s) as done",
2490
2494
  "todo.no_active": "No active todos",
@@ -2500,6 +2504,11 @@ Command: {command}`,
2500
2504
  "verify.script_failed": "Script '{script}' failed: {message}",
2501
2505
  "verify.syntax_error": "Syntax error in: {path}",
2502
2506
  "lsp.unavailable": "static checks are unavailable (server not found). Verify via the project's own build/test instead.",
2507
+ "lsp.check_disabled": "LSP checks are disabled in the config.",
2508
+ "lsp.check_no_path": "Provide a path (file or directory) to check.",
2509
+ "lsp.check_notfound": "Path not found: {path}",
2510
+ "lsp.check_unsupported": "No LSP server configured for: {path}",
2511
+ "lsp.check_clean": "No errors or warnings detected ({count} file(s) checked).",
2503
2512
  "cli.description": "Micro Models Agent — AI coding agent for small models",
2504
2513
  "cli.init": "Run interactive setup wizard",
2505
2514
  "cli.config_saved": "Configuration saved to ~/.mma/config.json",
@@ -2992,6 +3001,7 @@ var init_ru = __esm(() => {
2992
3001
  "tool.friendly.search_history": "Поиск в истории",
2993
3002
  "tool.friendly.pipeline_run": "Запуск пайплайна",
2994
3003
  "tool.friendly.mcp_call": "MCP вызов",
3004
+ "tool.friendly.lsp_check": "Проверка кода",
2995
3005
  "tool.truncated": "[Обрезано: удалено {tokens} токенов]",
2996
3006
  "tool.subagent_queued": "Задача подагенту добавлена: {task}",
2997
3007
  "tool.subagent_artifact": `Суб-агент завершён. Полный результат сохранён в артефакт: {path}
@@ -3084,6 +3094,9 @@ var init_ru = __esm(() => {
3084
3094
  "plan.switched": "Переключено на план {id}: {title}",
3085
3095
  "plan.replanned": "План перепланирован: {kept} выполненных шагов сохранено, {steps} новых шагов добавлено",
3086
3096
  "plan.replan_no_steps": "Укажите новые шаги для перепланирования",
3097
+ "plan.active_in_progress": 'Нельзя создать новый план: активный план {id} уже имеет прогресс ({done}/{total} выполнено, текущий: шаг {current}). Продолжай его — вызови "plan show", чтобы увидеть, и продолжай работу. Чтобы заменить план, сначала вызови "plan abort", затем "plan create".',
3098
+ "plan.id_ignored": 'примечание: id плана генерируется автоматически; используй "plan switch", чтобы активировать существующий план по id.',
3099
+ "plan.existing_fresh": "примечание: предыдущий активный план не имел прогресса и сохранён как черновик.",
3087
3100
  "todo.added": "Добавлено {count} задач: {items}",
3088
3101
  "todo.marked_done": "Отмечено выполненными: {count}",
3089
3102
  "todo.no_active": "Нет активных задач",
@@ -3099,6 +3112,11 @@ var init_ru = __esm(() => {
3099
3112
  "verify.script_failed": "Скрипт '{script}' не пройден: {message}",
3100
3113
  "verify.syntax_error": "Синтаксическая ошибка в: {path}",
3101
3114
  "lsp.unavailable": "статические проверки недоступны (сервер не найден). Проверяйте через собственный build/test проекта.",
3115
+ "lsp.check_disabled": "LSP-проверки отключены в конфигурации.",
3116
+ "lsp.check_no_path": "Укажите путь (файл или каталог) для проверки.",
3117
+ "lsp.check_notfound": "Путь не найден: {path}",
3118
+ "lsp.check_unsupported": "Для файла не настроен LSP-сервер: {path}",
3119
+ "lsp.check_clean": "Ошибок и предупреждений не обнаружено (проверено файлов: {count}).",
3102
3120
  "cli.description": "Micro Models Agent — ИИ-агент для кодинга на малых моделях",
3103
3121
  "cli.init": "Запустить мастер настройки",
3104
3122
  "cli.config_saved": "Конфигурация сохранена в ~/.mma/config.json",
@@ -11196,6 +11214,7 @@ class FactExtractor {
11196
11214
  deletedFacts = [];
11197
11215
  decisionFacts = [];
11198
11216
  errorFacts = [];
11217
+ readFacts = [];
11199
11218
  static MAX_FACTS = 20;
11200
11219
  get errorCount() {
11201
11220
  return this.errorFacts.length;
@@ -11211,13 +11230,19 @@ class FactExtractor {
11211
11230
  new RegExp(`Deleted ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi"),
11212
11231
  new RegExp(`(?:Удалён|Файл удалён):? ${DRIVE}([\\w./\\\\-]+\\.[a-z]+)`, "gi")
11213
11232
  ];
11233
+ const readPatterns = [
11234
+ new RegExp(`── (${DRIVE}[\\w./\\\\-]+\\.[a-z]+) \\(`, "gi")
11235
+ ];
11214
11236
  const newFiles = [];
11215
11237
  const newDeleted = [];
11216
11238
  const newDecisions = [];
11217
11239
  const newErrors = [];
11240
+ const newReads = [];
11218
11241
  const lastEvent = new Map;
11219
11242
  for (const msg of turns) {
11220
11243
  const content = getMessageText(msg.content);
11244
+ if (msg.role === "system")
11245
+ continue;
11221
11246
  if (msg.role === "user" && (content.startsWith("<system-summary>") || content.includes("[Compressed:"))) {
11222
11247
  continue;
11223
11248
  }
@@ -11234,6 +11259,11 @@ class FactExtractor {
11234
11259
  lastEvent.set(match[1], "deleted");
11235
11260
  }
11236
11261
  }
11262
+ for (const pattern of readPatterns) {
11263
+ for (const match of content.matchAll(pattern)) {
11264
+ newReads.push(match[1]);
11265
+ }
11266
+ }
11237
11267
  if (content.includes("Plan:") && content.includes("[")) {
11238
11268
  const planLine = content.split(`
11239
11269
  `).find((line) => line.includes("Plan:"));
@@ -11249,11 +11279,13 @@ class FactExtractor {
11249
11279
  newDecisions.push(line.trim().slice(0, 200));
11250
11280
  }
11251
11281
  }
11252
- if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
11253
- const line = content.split(`
11282
+ if (msg.role === "tool" || msg.role === "assistant") {
11283
+ if (content.includes("Error:") || content.includes("failed") || content.includes("Ошибка:") || content.includes("не удалось")) {
11284
+ const line = content.split(`
11254
11285
  `).find((l) => l.includes("Error:") || l.includes("failed") || l.includes("Ошибка:") || l.includes("не удалось"));
11255
- if (line)
11256
- newErrors.push(line.trim().slice(0, 250));
11286
+ if (line)
11287
+ newErrors.push(line.trim().slice(0, 250));
11288
+ }
11257
11289
  }
11258
11290
  }
11259
11291
  const dedup = (arr) => [...new Set(arr)];
@@ -11263,6 +11295,7 @@ class FactExtractor {
11263
11295
  this.deletedFacts = dedup([...this.deletedFacts, ...finalDeleted].filter((p) => !finalFiles.includes(p))).slice(-FactExtractor.MAX_FACTS);
11264
11296
  this.decisionFacts = dedup([...this.decisionFacts, ...newDecisions]).slice(-FactExtractor.MAX_FACTS);
11265
11297
  this.errorFacts = dedup([...this.errorFacts, ...newErrors]).slice(-FactExtractor.MAX_FACTS);
11298
+ this.readFacts = dedup([...this.readFacts, ...newReads]).slice(-FactExtractor.MAX_FACTS);
11266
11299
  }
11267
11300
  filesLine() {
11268
11301
  const deletedSet = new Set(this.deletedFacts);
@@ -11286,6 +11319,11 @@ class FactExtractor {
11286
11319
  return null;
11287
11320
  return `[Errors: ${this.errorFacts.slice(-3).join("; ")}]`;
11288
11321
  }
11322
+ readLine() {
11323
+ if (this.readFacts.length === 0)
11324
+ return null;
11325
+ return `[Read: ${this.readFacts.slice(-12).join("; ")}]`;
11326
+ }
11289
11327
  }
11290
11328
  var init_fact_extractor = () => {};
11291
11329
 
@@ -11358,12 +11396,16 @@ class ContextManager {
11358
11396
  pendingImageParts = [];
11359
11397
  toolTokens = 0;
11360
11398
  onCompact = null;
11399
+ planSummaryProvider = null;
11361
11400
  constructor(contextWindow, contextBudget, tokenCounter) {
11362
11401
  this.contextWindow = contextWindow;
11363
11402
  this.compactionThreshold = contextBudget?.compactionThreshold ?? 0.75;
11364
11403
  this.budget = this.calculateBudget(contextWindow, contextBudget);
11365
11404
  this.tokenCounter = tokenCounter ?? null;
11366
11405
  }
11406
+ setPlanSummaryProvider(fn) {
11407
+ this.planSummaryProvider = fn;
11408
+ }
11367
11409
  calculateBudget(window, contextBudget) {
11368
11410
  if (contextBudget) {
11369
11411
  const systemPrompt = Math.floor(window * contextBudget.systemPrompt);
@@ -11516,12 +11558,18 @@ class ContextManager {
11516
11558
  if (this.lastUserFeedback && !task.startsWith(this.lastUserFeedback)) {
11517
11559
  parts.push(`[User feedback: ${this.lastUserFeedback}]`);
11518
11560
  }
11561
+ const planLine = this.planSummaryProvider?.();
11562
+ if (planLine)
11563
+ parts.push(`[Plan: ${planLine}]`);
11519
11564
  const filesLine = this.facts.filesLine();
11520
11565
  if (filesLine)
11521
11566
  parts.push(filesLine);
11522
11567
  const deletedLine = this.facts.deletedLine();
11523
11568
  if (deletedLine)
11524
11569
  parts.push(deletedLine);
11570
+ const readLine = this.facts.readLine();
11571
+ if (readLine)
11572
+ parts.push(readLine);
11525
11573
  const decisionsLine = this.facts.decisionsLine();
11526
11574
  if (decisionsLine)
11527
11575
  parts.push(decisionsLine);
@@ -14372,6 +14420,13 @@ var init_recall = __esm(() => {
14372
14420
  };
14373
14421
  });
14374
14422
 
14423
+ // src/modules/browser/bridge-path.ts
14424
+ import { existsSync as existsSync25 } from "fs";
14425
+ function pickExistingPath(candidates, fallback = candidates[0]) {
14426
+ return candidates.find((p) => existsSync25(p)) ?? fallback;
14427
+ }
14428
+ var init_bridge_path = () => {};
14429
+
14375
14430
  // src/modules/browser/bridge-client.ts
14376
14431
  var exports_bridge_client = {};
14377
14432
  __export(exports_bridge_client, {
@@ -14382,7 +14437,12 @@ import { createInterface } from "readline";
14382
14437
  import { dirname as dirname9, join as join18 } from "path";
14383
14438
  import { fileURLToPath } from "url";
14384
14439
  function bridgeScriptPath() {
14385
- return join18(dirname9(fileURLToPath(import.meta.url)), "bridge-server.mjs");
14440
+ const dir = dirname9(fileURLToPath(import.meta.url));
14441
+ const candidates = [
14442
+ join18(dir, "bridge-server.mjs"),
14443
+ join18(dir, "modules", "browser", "bridge-server.mjs")
14444
+ ];
14445
+ return pickExistingPath(candidates);
14386
14446
  }
14387
14447
 
14388
14448
  class BridgeDriver {
@@ -14560,6 +14620,7 @@ class BridgeDriver {
14560
14620
  var BRIDGE_REQUEST_TIMEOUT_MS = 30000;
14561
14621
  var init_bridge_client = __esm(() => {
14562
14622
  init_session();
14623
+ init_bridge_path();
14563
14624
  });
14564
14625
 
14565
14626
  // src/modules/browser/driver.ts
@@ -15520,7 +15581,7 @@ var init_image_utils = __esm(() => {
15520
15581
  });
15521
15582
 
15522
15583
  // src/tools/attach-image.ts
15523
- import { existsSync as existsSync25 } from "fs";
15584
+ import { existsSync as existsSync26 } from "fs";
15524
15585
  import { resolve as resolve15 } from "path";
15525
15586
  var attachImageTool;
15526
15587
  var init_attach_image = __esm(() => {
@@ -15563,7 +15624,7 @@ var init_attach_image = __esm(() => {
15563
15624
  dataUrl = result.dataUrl;
15564
15625
  } else {
15565
15626
  const absPath = resolve15(ctx.baseDir, source);
15566
- if (!existsSync25(absPath)) {
15627
+ if (!existsSync26(absPath)) {
15567
15628
  return {
15568
15629
  success: false,
15569
15630
  output: t("image.not_found", { path: source })
@@ -15725,12 +15786,12 @@ class ModuleRegistry {
15725
15786
  }
15726
15787
 
15727
15788
  // src/modules/plugins/loader.ts
15728
- import { readdirSync as readdirSync7, existsSync as existsSync26, statSync as statSync5 } from "fs";
15789
+ import { readdirSync as readdirSync7, existsSync as existsSync27, statSync as statSync5 } from "fs";
15729
15790
  import { join as join21, basename as basename2 } from "path";
15730
15791
 
15731
15792
  class PluginLoader {
15732
15793
  loadFromDir(dirPath, pluginManager, logger) {
15733
- if (!existsSync26(dirPath))
15794
+ if (!existsSync27(dirPath))
15734
15795
  return;
15735
15796
  const entries = readdirSync7(dirPath).sort();
15736
15797
  for (const entry of entries) {
@@ -15751,7 +15812,7 @@ class PluginLoader {
15751
15812
  findEntryFile(dir) {
15752
15813
  for (const name of FOLDER_ENTRY_NAMES) {
15753
15814
  const candidate = join21(dir, name);
15754
- if (existsSync26(candidate))
15815
+ if (existsSync27(candidate))
15755
15816
  return candidate;
15756
15817
  }
15757
15818
  return null;
@@ -15777,7 +15838,7 @@ var init_loader = __esm(() => {
15777
15838
 
15778
15839
  // src/modules/plugins/builtin/lint-on-write.ts
15779
15840
  import { spawn as spawn5, execSync } from "child_process";
15780
- import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
15841
+ import { existsSync as existsSync28, readFileSync as readFileSync15 } from "fs";
15781
15842
  import { resolve as resolve16, extname as extname4, join as join22 } from "path";
15782
15843
  import { platform as platform3 } from "os";
15783
15844
  function contentHash(content) {
@@ -15824,7 +15885,7 @@ class LintOnWritePlugin {
15824
15885
  if (!path)
15825
15886
  return;
15826
15887
  const fullPath = resolve16(ctx.baseDir, path);
15827
- if (!existsSync27(fullPath))
15888
+ if (!existsSync28(fullPath))
15828
15889
  return;
15829
15890
  const ext = extname4(fullPath);
15830
15891
  const syntaxError = await this.checkSyntax(fullPath, ext, ctx.baseDir);
@@ -15881,7 +15942,7 @@ class LintOnWritePlugin {
15881
15942
  async runProjectLint(ctx, result) {
15882
15943
  try {
15883
15944
  const packageJsonPath = join22(ctx.baseDir, "package.json");
15884
- if (!existsSync27(packageJsonPath)) {
15945
+ if (!existsSync28(packageJsonPath)) {
15885
15946
  return;
15886
15947
  }
15887
15948
  const packageJson = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
@@ -15905,7 +15966,7 @@ class LintOnWritePlugin {
15905
15966
  "package.json"
15906
15967
  ]);
15907
15968
  const tsconfigPath = join22(projectRoot, "tsconfig.json");
15908
- if (!existsSync27(tsconfigPath)) {
15969
+ if (!existsSync28(tsconfigPath)) {
15909
15970
  return;
15910
15971
  }
15911
15972
  const now = Date.now();
@@ -16153,7 +16214,7 @@ class PlanTracker {
16153
16214
  var init_tracker = () => {};
16154
16215
 
16155
16216
  // src/modules/execution/audit-runners.ts
16156
- import { existsSync as existsSync28, readdirSync as readdirSync8 } from "fs";
16217
+ import { existsSync as existsSync29, readdirSync as readdirSync8 } from "fs";
16157
16218
  import { dirname as dirname10, join as join23, resolve as resolve17 } from "path";
16158
16219
  function findTestFile(dir, depth = 0) {
16159
16220
  if (depth > 5)
@@ -16240,7 +16301,7 @@ function findTypecheckRoot(baseDir, existingFiles = []) {
16240
16301
  for (const start of candidates) {
16241
16302
  let dir = resolve17(start);
16242
16303
  for (let depth = 0;depth <= 10; depth++) {
16243
- if (existsSync28(join23(dir, "tsconfig.json"))) {
16304
+ if (existsSync29(join23(dir, "tsconfig.json"))) {
16244
16305
  if (!best || depth < best.depth)
16245
16306
  best = { depth, root: dir };
16246
16307
  break;
@@ -16283,11 +16344,11 @@ var init_audit_runners = __esm(() => {
16283
16344
  });
16284
16345
 
16285
16346
  // src/modules/execution/auditor.ts
16286
- import { existsSync as existsSync29, readdirSync as readdirSync9 } from "fs";
16347
+ import { existsSync as existsSync30, readdirSync as readdirSync9 } from "fs";
16287
16348
  import { resolve as resolve18, join as join24, basename as basename3 } from "path";
16288
16349
  function findExistingFile(baseDir, filePath) {
16289
16350
  const direct = resolve18(baseDir, filePath);
16290
- if (existsSync29(direct))
16351
+ if (existsSync30(direct))
16291
16352
  return direct;
16292
16353
  const name = basename3(filePath).toLowerCase();
16293
16354
  const suffix = filePath.replace(/\\/g, "/").toLowerCase();
@@ -16442,7 +16503,7 @@ var init_auditor = __esm(() => {
16442
16503
  });
16443
16504
 
16444
16505
  // src/modules/execution/plan-store.ts
16445
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync30, readdirSync as readdirSync10, rmSync } from "fs";
16506
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync14, existsSync as existsSync31, readdirSync as readdirSync10, rmSync } from "fs";
16446
16507
  import { join as join25 } from "path";
16447
16508
  function readPlanFile(path, fallbackBaseDir) {
16448
16509
  try {
@@ -16468,7 +16529,7 @@ function writePlanFile(path, plan) {
16468
16529
  writeFileSync10(path, JSON.stringify(plan, null, 2), "utf-8");
16469
16530
  }
16470
16531
  function listDir(dir, baseDir) {
16471
- if (!existsSync30(dir))
16532
+ if (!existsSync31(dir))
16472
16533
  return [];
16473
16534
  const files = readdirSync10(dir).filter((f) => f.endsWith(".json"));
16474
16535
  return files.map((f) => readPlanFile(join25(dir, f), baseDir)).filter((p) => p !== null);
@@ -16493,7 +16554,7 @@ class PlanStore {
16493
16554
  legacyPath;
16494
16555
  constructor(baseDir) {
16495
16556
  const mmaDir = join25(baseDir, ".mma");
16496
- if (!existsSync30(mmaDir))
16557
+ if (!existsSync31(mmaDir))
16497
16558
  mkdirSync14(mmaDir, { recursive: true });
16498
16559
  this.baseDir = baseDir;
16499
16560
  this.plansDir = join25(mmaDir, "plans");
@@ -16501,7 +16562,7 @@ class PlanStore {
16501
16562
  this.archiveDir = join25(this.plansDir, "archive");
16502
16563
  this.legacyPath = join25(mmaDir, LEGACY_FILE);
16503
16564
  for (const dir of [this.plansDir, this.draftsDir, this.archiveDir]) {
16504
- if (!existsSync30(dir))
16565
+ if (!existsSync31(dir))
16505
16566
  mkdirSync14(dir, { recursive: true });
16506
16567
  }
16507
16568
  }
@@ -16513,12 +16574,12 @@ class PlanStore {
16513
16574
  }
16514
16575
  loadActive() {
16515
16576
  const activePath = this.activePath();
16516
- if (existsSync30(activePath)) {
16577
+ if (existsSync31(activePath)) {
16517
16578
  const plan = readPlanFile(activePath, this.baseDir);
16518
16579
  if (plan)
16519
16580
  return plan;
16520
16581
  }
16521
- if (existsSync30(this.legacyPath)) {
16582
+ if (existsSync31(this.legacyPath)) {
16522
16583
  const legacy = readPlanFile(this.legacyPath, this.baseDir);
16523
16584
  if (legacy) {
16524
16585
  this.saveActive(legacy);
@@ -16532,7 +16593,7 @@ class PlanStore {
16532
16593
  }
16533
16594
  clearActive() {
16534
16595
  const p = this.activePath();
16535
- if (existsSync30(p))
16596
+ if (existsSync31(p))
16536
16597
  rmSync(p, { force: true });
16537
16598
  }
16538
16599
  saveDraft(plan) {
@@ -16540,11 +16601,11 @@ class PlanStore {
16540
16601
  }
16541
16602
  loadDraft(id) {
16542
16603
  const p = join25(this.draftsDir, `${id}.json`);
16543
- return existsSync30(p) ? readPlanFile(p, this.baseDir) : null;
16604
+ return existsSync31(p) ? readPlanFile(p, this.baseDir) : null;
16544
16605
  }
16545
16606
  removeDraft(id) {
16546
16607
  const p = join25(this.draftsDir, `${id}.json`);
16547
- if (existsSync30(p))
16608
+ if (existsSync31(p))
16548
16609
  rmSync(p, { force: true });
16549
16610
  }
16550
16611
  listDrafts() {
@@ -16563,7 +16624,7 @@ class PlanStore {
16563
16624
  }
16564
16625
  removeArchived(id) {
16565
16626
  const p = join25(this.archiveDir, `${id}.json`);
16566
- if (existsSync30(p))
16627
+ if (existsSync31(p))
16567
16628
  rmSync(p, { force: true });
16568
16629
  }
16569
16630
  listAll() {
@@ -16790,6 +16851,23 @@ ${lines.join(`
16790
16851
  if (parsed.error) {
16791
16852
  return { success: false, output: parsed.error };
16792
16853
  }
16854
+ const existing = deps.trackerRef.current;
16855
+ if (existing) {
16856
+ const activePlan = existing.getPlan();
16857
+ const progressed = activePlan.steps.some((s) => s.status === "done" || s.status === "failed" || s.status === "skipped");
16858
+ if (progressed) {
16859
+ const done = activePlan.steps.filter((s) => s.status === "done").length;
16860
+ return {
16861
+ success: false,
16862
+ output: t("plan.active_in_progress", {
16863
+ id: activePlan.id,
16864
+ done: String(done),
16865
+ total: String(activePlan.steps.length),
16866
+ current: String(existing.getCurrentStepIndex() + 1)
16867
+ })
16868
+ };
16869
+ }
16870
+ }
16793
16871
  deps.preserveActive();
16794
16872
  const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
16795
16873
  deps.setPlan(plan);
@@ -16798,6 +16876,14 @@ ${lines.join(`
16798
16876
  title,
16799
16877
  steps: String(steps.length)
16800
16878
  });
16879
+ if (args.id) {
16880
+ output += `
16881
+ ${t("plan.id_ignored")}`;
16882
+ }
16883
+ if (existing) {
16884
+ output += `
16885
+ ${t("plan.existing_fresh")}`;
16886
+ }
16801
16887
  let displayOut = display;
16802
16888
  const taskText = deps.getTaskText(_ctx);
16803
16889
  if (taskText) {
@@ -17340,7 +17426,7 @@ var init_execution_plugin = __esm(() => {
17340
17426
  });
17341
17427
 
17342
17428
  // src/modules/execution/module.ts
17343
- import { existsSync as existsSync31, readFileSync as readFileSync17 } from "fs";
17429
+ import { existsSync as existsSync32, readFileSync as readFileSync17 } from "fs";
17344
17430
  import { resolve as resolve19 } from "path";
17345
17431
 
17346
17432
  class ExecutionModule {
@@ -17433,6 +17519,14 @@ class ExecutionModule {
17433
17519
  estimatedTokens: 200
17434
17520
  };
17435
17521
  }
17522
+ getPlanSummary() {
17523
+ if (!this.tracker)
17524
+ return null;
17525
+ const plan = this.tracker.getPlan();
17526
+ const done = plan.steps.filter((s) => s.status === "done").length;
17527
+ const current = this.tracker.getCurrentStepIndex() + 1;
17528
+ return `[${plan.id}] "${plan.title}" ${done}/${plan.steps.length} done, current: step ${current} — continue it (plan show / re-plan), do NOT create a new plan`;
17529
+ }
17436
17530
  getToolDefinitions() {
17437
17531
  return createPlanToolDefinitions({
17438
17532
  store: this.store,
@@ -17535,7 +17629,7 @@ class ExecutionModule {
17535
17629
  "poetry.lock",
17536
17630
  "requirements.txt"
17537
17631
  ];
17538
- const hasLockFile = lockFiles.some((f) => existsSync31(resolve19(this.baseDir, f)));
17632
+ const hasLockFile = lockFiles.some((f) => existsSync32(resolve19(this.baseDir, f)));
17539
17633
  if (!hasLockFile) {
17540
17634
  if (contextManager) {
17541
17635
  const hints = this.state.depsGateHints.get(step.id) || 0;
@@ -17555,8 +17649,8 @@ class ExecutionModule {
17555
17649
  }
17556
17650
  if (stepPaths.length === 0)
17557
17651
  return;
17558
- const allExist = stepPaths.every((p) => existsSync31(resolve19(this.baseDir, p)));
17559
- const allGone = stepPaths.every((p) => !existsSync31(resolve19(this.baseDir, p)));
17652
+ const allExist = stepPaths.every((p) => existsSync32(resolve19(this.baseDir, p)));
17653
+ const allGone = stepPaths.every((p) => !existsSync32(resolve19(this.baseDir, p)));
17560
17654
  const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
17561
17655
  if (!satisfied)
17562
17656
  return;
@@ -17662,7 +17756,7 @@ var init_module = __esm(() => {
17662
17756
  import {
17663
17757
  readFileSync as readFileSync18,
17664
17758
  writeFileSync as writeFileSync11,
17665
- existsSync as existsSync32,
17759
+ existsSync as existsSync33,
17666
17760
  readdirSync as readdirSync11,
17667
17761
  unlinkSync as unlinkSync4
17668
17762
  } from "fs";
@@ -17762,7 +17856,7 @@ class SessionFileEncryptor {
17762
17856
  const files = readdirSync11(sessionDir);
17763
17857
  for (const file of files) {
17764
17858
  const filePath = join26(sessionDir, file);
17765
- if (existsSync32(filePath) && !file.endsWith(".enc")) {
17859
+ if (existsSync33(filePath) && !file.endsWith(".enc")) {
17766
17860
  try {
17767
17861
  const content = readFileSync18(filePath, "utf8");
17768
17862
  const encrypted = this.encryptFileContent(content);
@@ -17803,7 +17897,7 @@ var init_session_encryption = __esm(() => {
17803
17897
 
17804
17898
  // src/modules/session/store.ts
17805
17899
  import {
17806
- existsSync as existsSync33,
17900
+ existsSync as existsSync34,
17807
17901
  mkdirSync as mkdirSync15,
17808
17902
  readdirSync as readdirSync12,
17809
17903
  readFileSync as readFileSync19,
@@ -17853,7 +17947,7 @@ class SessionStore {
17853
17947
  return join27(this.sessionDir(id), "session.jsonl");
17854
17948
  }
17855
17949
  sessionExists(id) {
17856
- return existsSync33(this.metaPath(id));
17950
+ return existsSync34(this.metaPath(id));
17857
17951
  }
17858
17952
  saveMeta(id, meta) {
17859
17953
  this._metaCache.set(id, meta);
@@ -17871,7 +17965,7 @@ class SessionStore {
17871
17965
  if (cached)
17872
17966
  return cached;
17873
17967
  const path = this.metaPath(id);
17874
- if (!existsSync33(path))
17968
+ if (!existsSync34(path))
17875
17969
  return null;
17876
17970
  try {
17877
17971
  const raw = readFileSync19(path, "utf-8");
@@ -17903,7 +17997,7 @@ class SessionStore {
17903
17997
  }
17904
17998
  loadHistory(id) {
17905
17999
  const path = this.historyPath(id);
17906
- if (!existsSync33(path))
18000
+ if (!existsSync34(path))
17907
18001
  return [];
17908
18002
  try {
17909
18003
  const raw = readFileSync19(path, "utf-8");
@@ -17944,7 +18038,7 @@ class SessionStore {
17944
18038
  }
17945
18039
  loadSessionLog(id) {
17946
18040
  const path = this.sessionLogPath(id);
17947
- if (!existsSync33(path))
18041
+ if (!existsSync34(path))
17948
18042
  return [];
17949
18043
  try {
17950
18044
  const raw = readFileSync19(path, "utf-8");
@@ -17972,7 +18066,7 @@ class SessionStore {
17972
18066
  }
17973
18067
  }
17974
18068
  listSessions() {
17975
- if (!existsSync33(this.baseDir))
18069
+ if (!existsSync34(this.baseDir))
17976
18070
  return [];
17977
18071
  const entries = readdirSync12(this.baseDir, { withFileTypes: true });
17978
18072
  const sessions = [];
@@ -17989,7 +18083,7 @@ class SessionStore {
17989
18083
  deleteSession(id) {
17990
18084
  this._metaCache.delete(id);
17991
18085
  const dir = this.sessionDir(id);
17992
- if (existsSync33(dir)) {
18086
+ if (existsSync34(dir)) {
17993
18087
  rmSync2(dir, { recursive: true, force: true });
17994
18088
  }
17995
18089
  }
@@ -18001,7 +18095,7 @@ class SessionStore {
18001
18095
  const updatedAt = new Date(session2.updatedAt);
18002
18096
  if (updatedAt < thirtyDaysAgo) {
18003
18097
  const historyPath = this.historyPath(session2.id);
18004
- if (existsSync33(historyPath)) {
18098
+ if (existsSync34(historyPath)) {
18005
18099
  const content = readFileSync19(historyPath, "utf-8");
18006
18100
  const compressed = gzipSync(content);
18007
18101
  const gzPath = join27(this.baseDir, `${session2.id}.jsonl.gz`);
@@ -18214,7 +18308,7 @@ class ProfileCompressor {
18214
18308
  }
18215
18309
 
18216
18310
  // src/modules/user-profile/profile.ts
18217
- import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as existsSync34, mkdirSync as mkdirSync16 } from "fs";
18311
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync13, existsSync as existsSync35, mkdirSync as mkdirSync16 } from "fs";
18218
18312
  import { join as join28 } from "path";
18219
18313
  import { homedir as homedir9, hostname, platform as platform5, type } from "os";
18220
18314
  import { env } from "process";
@@ -18239,14 +18333,14 @@ class UserProfile {
18239
18333
  return this.info;
18240
18334
  }
18241
18335
  save() {
18242
- if (!existsSync34(this.profileDir)) {
18336
+ if (!existsSync35(this.profileDir)) {
18243
18337
  mkdirSync16(this.profileDir, { recursive: true });
18244
18338
  }
18245
18339
  writeFileSync13(join28(this.profileDir, "profile.json"), JSON.stringify({ ...this.info, preferences: this.preferences }, null, 2), "utf-8");
18246
18340
  }
18247
18341
  load() {
18248
18342
  const path = join28(this.profileDir, "profile.json");
18249
- if (!existsSync34(path))
18343
+ if (!existsSync35(path))
18250
18344
  return null;
18251
18345
  try {
18252
18346
  const data = JSON.parse(readFileSync20(path, "utf-8"));
@@ -18284,12 +18378,12 @@ class UserProfile {
18284
18378
  var init_profile = () => {};
18285
18379
 
18286
18380
  // src/modules/skills/loader.ts
18287
- import { readdirSync as readdirSync13, readFileSync as readFileSync21, existsSync as existsSync35, statSync as statSync6 } from "fs";
18381
+ import { readdirSync as readdirSync13, readFileSync as readFileSync21, existsSync as existsSync36, statSync as statSync6 } from "fs";
18288
18382
  import { join as join29 } from "path";
18289
18383
 
18290
18384
  class SkillsLoader {
18291
18385
  loadFromDir(dirPath) {
18292
- if (!existsSync35(dirPath))
18386
+ if (!existsSync36(dirPath))
18293
18387
  return [];
18294
18388
  const skills = [];
18295
18389
  this.scanDir(dirPath, skills);
@@ -18543,7 +18637,7 @@ var init_browser2 = __esm(() => {
18543
18637
 
18544
18638
  // src/modules/lsp/command.ts
18545
18639
  import { delimiter, join as join30 } from "path";
18546
- import { existsSync as existsSync36 } from "fs";
18640
+ import { existsSync as existsSync37 } from "fs";
18547
18641
  import { platform as platform6 } from "os";
18548
18642
  function resolveSpawnCommand(command, platformName = platform6(), pathEnv = process.env.PATH ?? "") {
18549
18643
  if (platformName !== "win32")
@@ -18555,7 +18649,7 @@ function resolveSpawnCommand(command, platformName = platform6(), pathEnv = proc
18555
18649
  for (const dir of dirs) {
18556
18650
  for (const ext of WIN_EXTS) {
18557
18651
  const candidate = join30(dir, `${command}${ext}`);
18558
- if (existsSync36(candidate))
18652
+ if (existsSync37(candidate))
18559
18653
  return `${command}${ext}`;
18560
18654
  }
18561
18655
  }
@@ -18823,18 +18917,70 @@ var init_client2 = __esm(() => {
18823
18917
  init_command();
18824
18918
  });
18825
18919
 
18920
+ // src/modules/lsp/check-tool.ts
18921
+ import { readdir, stat } from "fs/promises";
18922
+ import { resolve as resolve21, relative as relative2 } from "path";
18923
+ async function collectCheckFiles(resolved, config) {
18924
+ const info = await stat(resolved).catch(() => null);
18925
+ if (!info)
18926
+ return [];
18927
+ if (info.isFile())
18928
+ return [resolved];
18929
+ const out = [];
18930
+ await walkDir(resolved, 0, out, config);
18931
+ return out;
18932
+ }
18933
+ async function walkDir(dir, depth, out, config) {
18934
+ if (depth > MAX_LSP_WALK_DEPTH || out.length >= MAX_LSP_CHECK_FILES)
18935
+ return;
18936
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
18937
+ for (const entry of entries) {
18938
+ if (out.length >= MAX_LSP_CHECK_FILES)
18939
+ break;
18940
+ if (entry.isDirectory()) {
18941
+ if (SKIP_DIRS2.has(entry.name))
18942
+ continue;
18943
+ await walkDir(resolve21(dir, entry.name), depth + 1, out, config);
18944
+ } else if (entry.isFile() && getServerForFile(resolve21(dir, entry.name), config)) {
18945
+ out.push(resolve21(dir, entry.name));
18946
+ }
18947
+ }
18948
+ }
18949
+ function formatCheckDiagnostics(items, baseDir) {
18950
+ return items.slice(0, 40).map(({ file, diag }) => {
18951
+ const rel = relative2(baseDir, file).replace(/\\/g, "/");
18952
+ const line = diag.range.start.line + 1;
18953
+ const col = diag.range.start.character + 1;
18954
+ const sev = severityLabels[diag.severity] ?? "unknown";
18955
+ return ` ${rel}(${line},${col}): ${sev}: ${diag.message}`;
18956
+ }).join(`
18957
+ `);
18958
+ }
18959
+ var MAX_LSP_CHECK_FILES = 15, MAX_LSP_WALK_DEPTH = 4, SKIP_DIRS2, severityLabels;
18960
+ var init_check_tool = __esm(() => {
18961
+ init_config();
18962
+ SKIP_DIRS2 = new Set(["node_modules", ".git", ".mma", "dist"]);
18963
+ severityLabels = {
18964
+ 1: "error",
18965
+ 2: "warning",
18966
+ 3: "info",
18967
+ 4: "hint"
18968
+ };
18969
+ });
18970
+
18826
18971
  // src/modules/lsp/module.ts
18827
- import { existsSync as existsSync37 } from "fs";
18828
- import { resolve as resolve21 } from "path";
18972
+ import { existsSync as existsSync38 } from "fs";
18973
+ import { resolve as resolve22 } from "path";
18829
18974
 
18830
18975
  class LspModule {
18831
18976
  name = "lsp";
18832
18977
  config;
18833
- client = new LspClient;
18978
+ client;
18834
18979
  failuresByServer = new Map;
18835
18980
  disabledServers = new Set;
18836
- constructor(config) {
18981
+ constructor(config, client) {
18837
18982
  this.config = { ...DEFAULT_LSP_CONFIG, ...config };
18983
+ this.client = client ?? new LspClient;
18838
18984
  }
18839
18985
  isLspDisabled() {
18840
18986
  return this.disabledServers.size > 0;
@@ -18860,8 +19006,8 @@ class LspModule {
18860
19006
  const filePath = String(call.arguments.path ?? "");
18861
19007
  if (!filePath)
18862
19008
  return;
18863
- const fullPath = resolve21(_ctx.baseDir, filePath);
18864
- if (!existsSync37(fullPath))
19009
+ const fullPath = resolve22(_ctx.baseDir, filePath);
19010
+ if (!existsSync38(fullPath))
18865
19011
  return;
18866
19012
  const serverConfig = getServerForFile(fullPath, self.config);
18867
19013
  if (!serverConfig)
@@ -18910,6 +19056,110 @@ ${items}`;
18910
19056
  }
18911
19057
  };
18912
19058
  }
19059
+ getToolDefinitions() {
19060
+ const self = this;
19061
+ return [
19062
+ {
19063
+ name: "lsp_check",
19064
+ description: 'Run static checks (LSP diagnostics) on a file or directory. For "check for errors" tasks: pass a file to check it, or a directory to check its source files (capped at 15). Returns [LSP errors] / [LSP warnings] for files with a configured LSP server.',
19065
+ tags: ["code", "check"],
19066
+ boundedOutput: true,
19067
+ timeoutMs: 90000,
19068
+ parameters: {
19069
+ type: "object",
19070
+ properties: {
19071
+ path: {
19072
+ type: "string",
19073
+ description: "File or directory to check"
19074
+ }
19075
+ },
19076
+ required: ["path"]
19077
+ },
19078
+ handler: async (ctx, args) => self.runCheck(ctx, args)
19079
+ }
19080
+ ];
19081
+ }
19082
+ async runCheck(ctx, args) {
19083
+ if (!this.config.enabled) {
19084
+ return { success: false, output: t("lsp.check_disabled") };
19085
+ }
19086
+ const path = String(args.path ?? "");
19087
+ if (!path)
19088
+ return { success: false, output: t("lsp.check_no_path") };
19089
+ const resolved = safeResolvePath(ctx.baseDir, path);
19090
+ const securityPaths = ctx.config?.security?.paths || DEFAULT_SECURITY_CONFIG.paths;
19091
+ const scopeCheck = isPathInScope(ctx.baseDir, resolved, ctx.scope, securityPaths);
19092
+ if (!scopeCheck.allowed) {
19093
+ return {
19094
+ success: false,
19095
+ output: t("file.path_not_allowed", {
19096
+ path: `${path} — ${scopeCheck.reason}`
19097
+ })
19098
+ };
19099
+ }
19100
+ if (!existsSync38(resolved)) {
19101
+ return { success: false, output: t("lsp.check_notfound", { path }) };
19102
+ }
19103
+ const files = await collectCheckFiles(resolved, this.config);
19104
+ const errors = [];
19105
+ const warnings = [];
19106
+ const notes = [];
19107
+ let checked = 0;
19108
+ for (const file of files) {
19109
+ const serverConfig = getServerForFile(file, this.config);
19110
+ if (!serverConfig)
19111
+ continue;
19112
+ const key = this.serverKey(serverConfig);
19113
+ if (this.disabledServers.has(key))
19114
+ continue;
19115
+ const projectRoot = findProjectRoot(file, ctx.baseDir, serverConfig.workspaceMarkers ?? []);
19116
+ try {
19117
+ const diags = await this.client.checkFile(file, ctx.baseDir, serverConfig, projectRoot);
19118
+ this.failuresByServer.set(key, 0);
19119
+ checked++;
19120
+ for (const d of diags) {
19121
+ if (d.severity === 1)
19122
+ errors.push({ file, diag: d });
19123
+ else if (d.severity === 2)
19124
+ warnings.push({ file, diag: d });
19125
+ }
19126
+ } catch (e) {
19127
+ const msg = e instanceof Error ? e.message : String(e);
19128
+ const failures = (this.failuresByServer.get(key) ?? 0) + 1;
19129
+ this.failuresByServer.set(key, failures);
19130
+ try {
19131
+ ctx.logger?.warn(`LSP check failed for ${file}: ${msg}`);
19132
+ } catch {}
19133
+ if (failures >= MAX_CONSECUTIVE_LSP_FAILURES) {
19134
+ this.disabledServers.add(key);
19135
+ notes.push(`[LSP disabled: ${t("lsp.unavailable")}]`);
19136
+ } else {
19137
+ notes.push(`[LSP check failed for ${file}: ${msg}]`);
19138
+ }
19139
+ }
19140
+ }
19141
+ const lines = [];
19142
+ if (errors.length > 0) {
19143
+ lines.push(`[LSP errors] (${errors.length}):`);
19144
+ lines.push(formatCheckDiagnostics(errors, ctx.baseDir));
19145
+ }
19146
+ if (warnings.length > 0) {
19147
+ lines.push(`[LSP warnings] (${warnings.length}):`);
19148
+ lines.push(formatCheckDiagnostics(warnings, ctx.baseDir));
19149
+ }
19150
+ if (checked > 0 && errors.length === 0 && warnings.length === 0) {
19151
+ lines.push(t("lsp.check_clean", { count: String(checked) }));
19152
+ }
19153
+ lines.push(...notes);
19154
+ if (lines.length === 0) {
19155
+ return {
19156
+ success: true,
19157
+ output: t("lsp.check_clean", { count: String(checked) })
19158
+ };
19159
+ }
19160
+ return { success: errors.length === 0, output: lines.join(`
19161
+ `) };
19162
+ }
18913
19163
  }
18914
19164
  function formatDiagnostics(diagnostics, filePath) {
18915
19165
  const filename = filePath.replace(/\\/g, "/").split("/").pop() ?? filePath;
@@ -18921,18 +19171,16 @@ function formatDiagnostics(diagnostics, filePath) {
18921
19171
  }).join(`
18922
19172
  `);
18923
19173
  }
18924
- var severityLabels, MAX_CONSECUTIVE_LSP_FAILURES = 3;
19174
+ var MAX_CONSECUTIVE_LSP_FAILURES = 3;
18925
19175
  var init_module5 = __esm(() => {
18926
19176
  init_client2();
18927
19177
  init_config();
18928
19178
  init_project_root();
19179
+ init_path_validator();
19180
+ init_security();
19181
+ init_path_utils();
18929
19182
  init_i18n();
18930
- severityLabels = {
18931
- 1: "error",
18932
- 2: "warning",
18933
- 3: "info",
18934
- 4: "hint"
18935
- };
19183
+ init_check_tool();
18936
19184
  });
18937
19185
 
18938
19186
  // src/modules/lsp/index.ts
@@ -18943,8 +19191,8 @@ var init_lsp = __esm(() => {
18943
19191
  });
18944
19192
 
18945
19193
  // src/modules/indexer/walker.ts
18946
- import { readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync38, watch } from "fs";
18947
- import { join as join31, relative as relative2, extname as extname5 } from "path";
19194
+ import { readdirSync as readdirSync14, readFileSync as readFileSync22, statSync as statSync7, existsSync as existsSync39, watch } from "fs";
19195
+ import { join as join31, relative as relative3, extname as extname5 } from "path";
18948
19196
 
18949
19197
  class Indexer {
18950
19198
  baseDir;
@@ -18970,8 +19218,8 @@ class Indexer {
18970
19218
  const files = [];
18971
19219
  let totalSize = 0;
18972
19220
  let count = 0;
18973
- const walkDir = (dir) => {
18974
- if (!existsSync38(dir))
19221
+ const walkDir2 = (dir) => {
19222
+ if (!existsSync39(dir))
18975
19223
  return;
18976
19224
  let entries;
18977
19225
  try {
@@ -18983,26 +19231,26 @@ class Indexer {
18983
19231
  if (count >= this.MAX_FILES)
18984
19232
  return;
18985
19233
  const fullPath = join31(dir, entry);
18986
- const relPath = relative2(this.baseDir, fullPath);
18987
- const stat = statSync7(fullPath);
18988
- if (stat.isDirectory()) {
19234
+ const relPath = relative3(this.baseDir, fullPath);
19235
+ const stat2 = statSync7(fullPath);
19236
+ if (stat2.isDirectory()) {
18989
19237
  if (!IGNORE_DIRS.has(entry)) {
18990
- walkDir(fullPath);
19238
+ walkDir2(fullPath);
18991
19239
  }
18992
- } else if (stat.isFile()) {
19240
+ } else if (stat2.isFile()) {
18993
19241
  const ext = extname5(entry).toLowerCase();
18994
19242
  const language = LANGUAGES[ext];
18995
19243
  if (language) {
18996
19244
  const content = readFileSync22(fullPath, "utf-8");
18997
19245
  const exports = this.extractExports(content, language);
18998
- files.push({ path: relPath, language, exports, size: stat.size });
18999
- totalSize += stat.size;
19246
+ files.push({ path: relPath, language, exports, size: stat2.size });
19247
+ totalSize += stat2.size;
19000
19248
  count++;
19001
19249
  }
19002
19250
  }
19003
19251
  }
19004
19252
  };
19005
- walkDir(this.baseDir);
19253
+ walkDir2(this.baseDir);
19006
19254
  return { files, totalSize };
19007
19255
  }
19008
19256
  extractExports(content, language) {
@@ -19045,7 +19293,7 @@ var init_walker = __esm(() => {
19045
19293
  });
19046
19294
 
19047
19295
  // src/modules/indexer/cache.ts
19048
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as existsSync39, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
19296
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, existsSync as existsSync40, mkdirSync as mkdirSync17, rmSync as rmSync3 } from "fs";
19049
19297
  import { join as join32 } from "path";
19050
19298
 
19051
19299
  class IndexCache {
@@ -19057,7 +19305,7 @@ class IndexCache {
19057
19305
  load() {
19058
19306
  if (this.cache)
19059
19307
  return this.cache;
19060
- if (!existsSync39(this.cachePath))
19308
+ if (!existsSync40(this.cachePath))
19061
19309
  return null;
19062
19310
  try {
19063
19311
  this.cache = JSON.parse(readFileSync23(this.cachePath, "utf-8"));
@@ -19069,13 +19317,13 @@ class IndexCache {
19069
19317
  save(result) {
19070
19318
  this.cache = result;
19071
19319
  const dir = join32(this.cachePath, "..");
19072
- if (!existsSync39(dir))
19320
+ if (!existsSync40(dir))
19073
19321
  mkdirSync17(dir, { recursive: true });
19074
19322
  writeFileSync14(this.cachePath, JSON.stringify(result), "utf-8");
19075
19323
  }
19076
19324
  invalidate() {
19077
19325
  this.cache = null;
19078
- if (existsSync39(this.cachePath)) {
19326
+ if (existsSync40(this.cachePath)) {
19079
19327
  try {
19080
19328
  rmSync3(this.cachePath);
19081
19329
  } catch {}
@@ -19085,11 +19333,11 @@ class IndexCache {
19085
19333
  var init_cache = () => {};
19086
19334
 
19087
19335
  // src/modules/indexer/project-profile.ts
19088
- import { readFileSync as readFileSync24, existsSync as existsSync40 } from "fs";
19336
+ import { readFileSync as readFileSync24, existsSync as existsSync41 } from "fs";
19089
19337
  import { join as join33 } from "path";
19090
19338
  function detectManifest(baseDir) {
19091
19339
  for (const manifest of MANIFEST_ORDER) {
19092
- if (existsSync40(join33(baseDir, manifest)))
19340
+ if (existsSync41(join33(baseDir, manifest)))
19093
19341
  return manifest;
19094
19342
  }
19095
19343
  return null;
@@ -19743,8 +19991,8 @@ __export(exports_bootstrap, {
19743
19991
  bootstrap: () => bootstrap
19744
19992
  });
19745
19993
  import { homedir as homedir11 } from "os";
19746
- import { join as join35, resolve as resolve22 } from "path";
19747
- import { existsSync as existsSync41, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "fs";
19994
+ import { join as join35, resolve as resolve23 } from "path";
19995
+ import { existsSync as existsSync42, readFileSync as readFileSync25, writeFileSync as writeFileSync15 } from "fs";
19748
19996
  function buildSystemInfo(config, baseDir, profileCompressed) {
19749
19997
  const now = new Date().toISOString().replace("T", " ").slice(0, 19);
19750
19998
  const isWin = profileCompressed.toLowerCase().includes("win32");
@@ -19757,7 +20005,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
19757
20005
  lines.push(`Windows (PowerShell): use list_dir/read_file/delete_file/create_dir tools instead of dir/type/del/mkdir. No PowerShell cmdlets (Get-Content, Select-Object, Write-Output), no head/tail/grep/cat. Use forward slashes in paths. CWD: ${baseDir}`);
19758
20006
  }
19759
20007
  lines.push(`Runtime: Bun is available — run TypeScript directly with "bun <file.ts>" and tests with "bun test" (no tsx/ts-node/npm install needed for that).`);
19760
- lines.push(`Bash: use "workdir" param instead of "cd dir && cmd". One command per call. Long-running processes: pass "background: true" (id immediately), otherwise auto-backgrounded after a few seconds — check with process_log.`, `DEVELOPMENT RULES (strict): 1) install deps BEFORE writing source (npm/pip/cargo, verify lockfile exists; never import uninstalled packages); 2) framework/toolkit init BEFORE app code; 3) follow plan sequentially, after each step verify deliverables exist with real content, then "plan update step=N status=done"; 4) verify work on disk + command output — don't assume success; 5) no premature work (no files/imports for future steps); 6) stuck after 2+ failures: STOP, try a different approach, write files directly, ask the user.`, `PLAN QUALITY: each step = CONCRETE deliverables (exact file paths with extensions, exact packages, exact commands). Vague steps ("Setup the project") forbidden. Cover init → deps → framework → code → verification. 5-8 steps.`);
20008
+ lines.push(`Bash: use "workdir" param instead of "cd dir && cmd". One command per call. Long-running processes: pass "background: true" (id immediately), otherwise auto-backgrounded after a few seconds — check with process_log.`, `DEVELOPMENT RULES (strict): 1) install deps BEFORE writing source (npm/pip/cargo, verify lockfile exists; never import uninstalled packages); 2) framework/toolkit init BEFORE app code; 3) follow plan sequentially, after each step verify deliverables exist with real content, then "plan update step=N status=done"; 4) verify work on disk + command output — don't assume success; 5) no premature work (no files/imports for future steps); 6) stuck after 2+ failures: STOP, try a different approach, write files directly, ask the user.`, `PLAN QUALITY: each step = CONCRETE deliverables (exact file paths with extensions, exact packages, exact commands). Vague steps ("Setup the project") forbidden. Cover init → deps → framework → code → verification. 5-8 steps.`, `For "check/fix errors" tasks, each verification step must name the exact command that proves the errors are gone — "bun run build", "bun test", or the "lsp_check" tool — and the step is done only after that check reports clean.`);
19761
20009
  if (config.autoPlan) {
19762
20010
  lines.push(`Plan rule (MANDATORY): any task creating files, installing packages, or requiring multiple actions MUST create a plan with the "plan" tool BEFORE starting. Each step = a concrete deliverable.`);
19763
20011
  }
@@ -19814,7 +20062,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19814
20062
  retry: config.retry,
19815
20063
  rateLimits: config.security?.rateLimits
19816
20064
  });
19817
- const baseDir = projectDir ? resolve22(projectDir) : process.cwd();
20065
+ const baseDir = projectDir ? resolve23(projectDir) : process.cwd();
19818
20066
  const projectMapCacheDir = join35(baseDir, ".mma");
19819
20067
  const indexerModule = new IndexerModule({
19820
20068
  baseDir,
@@ -19843,7 +20091,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19843
20091
  estimatedTokens: Math.ceil(systemInfoContent.length / 4)
19844
20092
  };
19845
20093
  const agentsMdGlobal = join35(dir, "AGENTS.md");
19846
- if (!existsSync41(agentsMdGlobal)) {
20094
+ if (!existsSync42(agentsMdGlobal)) {
19847
20095
  writeFileSync15(agentsMdGlobal, "", "utf-8");
19848
20096
  }
19849
20097
  const sessionDir = join35(dir, "sessions");
@@ -19979,6 +20227,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19979
20227
  timestamp: new Date().toISOString()
19980
20228
  });
19981
20229
  };
20230
+ contextManager.setPlanSummaryProvider(() => execModule.getPlanSummary());
19982
20231
  const agentsMdBlocks = [];
19983
20232
  const skipAgentsMd = noAgentsMd === true;
19984
20233
  if (!skipAgentsMd) {
@@ -19988,7 +20237,7 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete) {
19988
20237
  join35(dir, "AGENTS.md")
19989
20238
  ];
19990
20239
  for (const p of agentsMdCandidates) {
19991
- if (existsSync41(p)) {
20240
+ if (existsSync42(p)) {
19992
20241
  const content = readFileSync25(p, "utf-8").trim();
19993
20242
  if (content) {
19994
20243
  agentsMdBlocks.push({
@@ -20621,10 +20870,10 @@ function menuList(items) {
20621
20870
  console.log(l);
20622
20871
  }
20623
20872
  function ask(rl, question, defaultValue) {
20624
- return new Promise((resolve23) => {
20873
+ return new Promise((resolve24) => {
20625
20874
  const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
20626
20875
  rl.question(prompt, (answer) => {
20627
- resolve23(answer.trim() || defaultValue || "");
20876
+ resolve24(answer.trim() || defaultValue || "");
20628
20877
  });
20629
20878
  });
20630
20879
  }
@@ -20843,12 +21092,12 @@ __export(exports_manifest, {
20843
21092
  getCertMark: () => getCertMark,
20844
21093
  MANIFEST_PATH: () => MANIFEST_PATH
20845
21094
  });
20846
- import { existsSync as existsSync42, readFileSync as readFileSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
21095
+ import { existsSync as existsSync43, readFileSync as readFileSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
20847
21096
  import { homedir as homedir13 } from "os";
20848
21097
  import { join as join37 } from "path";
20849
21098
  function readManifest(path = MANIFEST_PATH) {
20850
21099
  try {
20851
- if (existsSync42(path)) {
21100
+ if (existsSync43(path)) {
20852
21101
  const raw = JSON.parse(readFileSync26(path, "utf-8"));
20853
21102
  return { version: 1, certifications: raw.certifications ?? [] };
20854
21103
  }
@@ -28014,7 +28263,7 @@ var init_scenarios = __esm(() => {
28014
28263
  });
28015
28264
 
28016
28265
  // src/modules/certification/loader.ts
28017
- import { existsSync as existsSync43, readdirSync as readdirSync15, readFileSync as readFileSync27 } from "fs";
28266
+ import { existsSync as existsSync44, readdirSync as readdirSync15, readFileSync as readFileSync27 } from "fs";
28018
28267
  import { join as join38 } from "path";
28019
28268
  function validateScenario(s) {
28020
28269
  const errors2 = [];
@@ -28064,7 +28313,7 @@ function loadScenarios(userDir) {
28064
28313
  else
28065
28314
  scenarios.push(s);
28066
28315
  }
28067
- if (userDir && existsSync43(userDir)) {
28316
+ if (userDir && existsSync44(userDir)) {
28068
28317
  for (const file of readdirSync15(userDir)) {
28069
28318
  if (!file.endsWith(".yaml") && !file.endsWith(".yml"))
28070
28319
  continue;
@@ -28122,7 +28371,7 @@ var init_loader3 = __esm(() => {
28122
28371
  });
28123
28372
 
28124
28373
  // src/modules/certification/fact-checker.ts
28125
- import { existsSync as existsSync44, readFileSync as readFileSync28, statSync as statSync8 } from "fs";
28374
+ import { existsSync as existsSync45, readFileSync as readFileSync28, statSync as statSync8 } from "fs";
28126
28375
  import { join as join39 } from "path";
28127
28376
  function checkSandbox(sandboxDir, checks, exitCode, output) {
28128
28377
  const failures = [];
@@ -28142,7 +28391,7 @@ function runCheck(sandboxDir, check, exitCode, output) {
28142
28391
  case "fileExists":
28143
28392
  return isFile(join39(sandboxDir, check.path));
28144
28393
  case "fileNotExists":
28145
- return !existsSync44(join39(sandboxDir, check.path));
28394
+ return !existsSync45(join39(sandboxDir, check.path));
28146
28395
  case "dirExists":
28147
28396
  return isDir(join39(sandboxDir, check.path));
28148
28397
  case "fileContent": {
@@ -28168,14 +28417,14 @@ function runCheck(sandboxDir, check, exitCode, output) {
28168
28417
  }
28169
28418
  function isFile(p) {
28170
28419
  try {
28171
- return existsSync44(p) && statSync8(p).isFile();
28420
+ return existsSync45(p) && statSync8(p).isFile();
28172
28421
  } catch {
28173
28422
  return false;
28174
28423
  }
28175
28424
  }
28176
28425
  function isDir(p) {
28177
28426
  try {
28178
- return existsSync44(p) && statSync8(p).isDirectory();
28427
+ return existsSync45(p) && statSync8(p).isDirectory();
28179
28428
  } catch {
28180
28429
  return false;
28181
28430
  }
@@ -28206,9 +28455,9 @@ var init_fact_checker = () => {};
28206
28455
 
28207
28456
  // src/modules/certification/runner.ts
28208
28457
  import { spawn as spawn7 } from "child_process";
28209
- import { existsSync as existsSync45, mkdirSync as mkdirSync19, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
28458
+ import { existsSync as existsSync46, mkdirSync as mkdirSync19, rmSync as rmSync4, cpSync as cpSync2 } from "fs";
28210
28459
  import { platform as platform8 } from "os";
28211
- import { join as join40, resolve as resolve23, dirname as dirname13 } from "path";
28460
+ import { join as join40, resolve as resolve24, dirname as dirname13 } from "path";
28212
28461
  async function runScenario(scenario, opts) {
28213
28462
  if (scenario.mode === "skip") {
28214
28463
  return {
@@ -28289,7 +28538,7 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
28289
28538
  mkdirSync19(sandbox, { recursive: true });
28290
28539
  for (const f of scenario.fixtures ?? []) {
28291
28540
  const src = join40(mmaRoot, f.source);
28292
- if (!existsSync45(src)) {
28541
+ if (!existsSync46(src)) {
28293
28542
  throw new Error(`fixture missing: ${f.source}`);
28294
28543
  }
28295
28544
  const dest = join40(sandbox, f.dest);
@@ -28299,17 +28548,17 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
28299
28548
  }
28300
28549
  function resolveMmaEntry(mmaRoot) {
28301
28550
  const dev = join40(mmaRoot, "src", "cli", "main.ts");
28302
- if (existsSync45(dev))
28551
+ if (existsSync46(dev))
28303
28552
  return dev;
28304
28553
  return join40(mmaRoot, "dist", "main.js");
28305
28554
  }
28306
28555
  function findMmaRoot(fromDir) {
28307
28556
  const candidates = [
28308
- resolve23(fromDir, "..", "..", ".."),
28309
- resolve23(fromDir, "..")
28557
+ resolve24(fromDir, "..", "..", ".."),
28558
+ resolve24(fromDir, "..")
28310
28559
  ];
28311
28560
  for (const c of candidates) {
28312
- if (existsSync45(join40(c, "package.json")))
28561
+ if (existsSync46(join40(c, "package.json")))
28313
28562
  return c;
28314
28563
  }
28315
28564
  return process.cwd();
@@ -28379,11 +28628,11 @@ import { rmSync as rmSync5 } from "fs";
28379
28628
  import { homedir as homedir14 } from "os";
28380
28629
  import { join as join41, dirname as dirname14 } from "path";
28381
28630
  import { fileURLToPath as fileURLToPath2 } from "url";
28382
- import { existsSync as existsSync46, readFileSync as readFileSync29 } from "fs";
28631
+ import { existsSync as existsSync47, readFileSync as readFileSync29 } from "fs";
28383
28632
  function readVersion() {
28384
28633
  const candidates = [join41(MMA_ROOT, "package.json")];
28385
28634
  for (const p of candidates) {
28386
- if (existsSync46(p)) {
28635
+ if (existsSync47(p)) {
28387
28636
  try {
28388
28637
  const raw = JSON.parse(readFileSync29(p, "utf-8"));
28389
28638
  if (raw.version)
@@ -28546,7 +28795,7 @@ __export(exports_repl_commands, {
28546
28795
  });
28547
28796
  import { join as join43, dirname as dirname16 } from "path";
28548
28797
  import { homedir as homedir16 } from "os";
28549
- import { existsSync as existsSync48, readFileSync as readFileSync31 } from "fs";
28798
+ import { existsSync as existsSync49, readFileSync as readFileSync31 } from "fs";
28550
28799
  import { fileURLToPath as fileURLToPath4 } from "url";
28551
28800
  function readVersion3() {
28552
28801
  const here = dirname16(fileURLToPath4(import.meta.url));
@@ -28555,7 +28804,7 @@ function readVersion3() {
28555
28804
  join43(here, "..", "package.json")
28556
28805
  ];
28557
28806
  for (const p of candidates) {
28558
- if (existsSync48(p)) {
28807
+ if (existsSync49(p)) {
28559
28808
  try {
28560
28809
  const raw = JSON.parse(readFileSync31(p, "utf8"));
28561
28810
  if (raw.version)
@@ -28621,8 +28870,8 @@ function registerMmaCommands(ctx) {
28621
28870
  }
28622
28871
  try {
28623
28872
  const { loadFileAsDataUrl: loadFileAsDataUrl2, loadUrlAsDataUrl: loadUrlAsDataUrl2, readClipboardImage: readClipboardImage2 } = await Promise.resolve().then(() => (init_image_utils(), exports_image_utils));
28624
- const { existsSync: existsSync49 } = await import("fs");
28625
- const { resolve: resolve24 } = await import("path");
28873
+ const { existsSync: existsSync50 } = await import("fs");
28874
+ const { resolve: resolve25 } = await import("path");
28626
28875
  let dataUrl;
28627
28876
  let label;
28628
28877
  if (source.toLowerCase() === "clipboard") {
@@ -28640,8 +28889,8 @@ function registerMmaCommands(ctx) {
28640
28889
  dataUrl = result.dataUrl;
28641
28890
  label = source;
28642
28891
  } else {
28643
- const absPath = resolve24(process.cwd(), source);
28644
- if (!existsSync49(absPath)) {
28892
+ const absPath = resolve25(process.cwd(), source);
28893
+ if (!existsSync50(absPath)) {
28645
28894
  console.log(pc2.red(t("image.not_found", { path: source })));
28646
28895
  return;
28647
28896
  }
@@ -29179,7 +29428,7 @@ init_setup();
29179
29428
  init_i18n();
29180
29429
  import { join as join42, dirname as dirname15 } from "path";
29181
29430
  import { homedir as homedir15 } from "os";
29182
- import { existsSync as existsSync47, readFileSync as readFileSync30 } from "fs";
29431
+ import { existsSync as existsSync48, readFileSync as readFileSync30 } from "fs";
29183
29432
 
29184
29433
  // src/cli/security-commands.ts
29185
29434
  init_bootstrap();
@@ -29807,7 +30056,7 @@ function readVersion2() {
29807
30056
  join42(here, "..", "package.json")
29808
30057
  ];
29809
30058
  for (const p of candidates) {
29810
- if (existsSync47(p)) {
30059
+ if (existsSync48(p)) {
29811
30060
  try {
29812
30061
  const raw = JSON.parse(readFileSync30(p, "utf8"));
29813
30062
  if (raw.version)
@@ -30791,7 +31040,7 @@ class LineEditor {
30791
31040
  }
30792
31041
 
30793
31042
  // src/cli/repl.ts
30794
- import { existsSync as existsSync49, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
31043
+ import { existsSync as existsSync50, readFileSync as readFileSync32, writeFileSync as writeFileSync17 } from "fs";
30795
31044
  import { join as join44, dirname as dirname17 } from "path";
30796
31045
  import { homedir as homedir17 } from "os";
30797
31046
  import { fileURLToPath as fileURLToPath5 } from "url";
@@ -31302,7 +31551,7 @@ function readVersion4() {
31302
31551
  join44(here, "..", "package.json")
31303
31552
  ];
31304
31553
  for (const p of candidates) {
31305
- if (existsSync49(p)) {
31554
+ if (existsSync50(p)) {
31306
31555
  try {
31307
31556
  const raw = JSON.parse(readFileSync32(p, "utf8"));
31308
31557
  if (raw.version)
@@ -31396,7 +31645,7 @@ class Repl {
31396
31645
  this.setupListeners();
31397
31646
  }
31398
31647
  loadHistory() {
31399
- if (existsSync49(this.historyPath)) {
31648
+ if (existsSync50(this.historyPath)) {
31400
31649
  try {
31401
31650
  const raw = readFileSync32(this.historyPath, "utf-8");
31402
31651
  this.history = raw.split(`
@@ -31759,7 +32008,7 @@ ${t("image.clipboard_empty")}`));
31759
32008
  join44(this.baseDir, ".mma", "AGENTS.md"),
31760
32009
  join44(this.configDir, "AGENTS.md")
31761
32010
  ];
31762
- const foundAgents = agentsMdCandidates.filter((p) => existsSync49(p));
32011
+ const foundAgents = agentsMdCandidates.filter((p) => existsSync50(p));
31763
32012
  if (foundAgents.length > 0) {
31764
32013
  for (const p of foundAgents) {
31765
32014
  row(t("repl.agents_label"), pc2.dim(p));
@@ -31811,7 +32060,7 @@ init_setup();
31811
32060
  init_config2();
31812
32061
  init_i18n();
31813
32062
  init_colors();
31814
- import { existsSync as existsSync50, readFileSync as readFileSync33 } from "fs";
32063
+ import { existsSync as existsSync51, readFileSync as readFileSync33 } from "fs";
31815
32064
  import { join as join45, dirname as dirname18 } from "path";
31816
32065
  import { homedir as homedir18 } from "os";
31817
32066
  import { fileURLToPath as fileURLToPath6 } from "url";
@@ -31832,8 +32081,8 @@ function semverGt(a, b) {
31832
32081
  }
31833
32082
  var defaultRunner2 = async (command, args, options) => {
31834
32083
  const { execFile } = await import("child_process");
31835
- return new Promise((resolve24) => {
31836
- execFile(command, args, options, (err) => resolve24({ error: err?.message }));
32084
+ return new Promise((resolve25) => {
32085
+ execFile(command, args, options, (err) => resolve25({ error: err?.message }));
31837
32086
  });
31838
32087
  };
31839
32088
 
@@ -31998,7 +32247,7 @@ function readVersion5() {
31998
32247
  join45(here, "..", "package.json")
31999
32248
  ];
32000
32249
  for (const p of candidates) {
32001
- if (existsSync50(p)) {
32250
+ if (existsSync51(p)) {
32002
32251
  try {
32003
32252
  const raw = JSON.parse(readFileSync33(p, "utf8"));
32004
32253
  if (raw.version)
@@ -32084,7 +32333,7 @@ async function main() {
32084
32333
  process.exit(exitCode);
32085
32334
  } else {
32086
32335
  const configPath = join45(homedir18(), ".mma", "config.json");
32087
- if (!existsSync50(configPath)) {
32336
+ if (!existsSync51(configPath)) {
32088
32337
  console.log(pc2.yellow(`
32089
32338
  ` + t("cli.first_run") + `
32090
32339
  `));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "micro-models-agent",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "Micro Models Agent (MMA) — LLM agent harness for small models (Qwen3.5-9B, 32K-64K context)",
5
5
  "type": "module",
6
6
  "bin": {