@menteeai/menteeswe 0.1.18 → 0.1.20

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/cli.js +140 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -694,7 +694,7 @@ var init_prompts = __esm({
694
694
  // src/agent/conversation.ts
695
695
  import fs9 from "fs";
696
696
  import path8 from "path";
697
- import crypto2 from "crypto";
697
+ import crypto3 from "crypto";
698
698
  import os3 from "os";
699
699
  function sessionsDir() {
700
700
  const dir = path8.join(os3.homedir(), ".mentee", "sessions");
@@ -702,7 +702,7 @@ function sessionsDir() {
702
702
  return dir;
703
703
  }
704
704
  function sessionFile(cwd) {
705
- const hash = crypto2.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
705
+ const hash = crypto3.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
706
706
  return path8.join(sessionsDir(), `${hash}.json`);
707
707
  }
708
708
  function loadConversation(cwd, limit = 12) {
@@ -747,6 +747,97 @@ Assistant: ${t.response.trim()}
747
747
  }
748
748
  return [header, ...blocks, ""].join("\n");
749
749
  }
750
+ function recentFile(cwd) {
751
+ const hash = crypto3.createHash("sha1").update(cwd).digest("hex").slice(0, 16);
752
+ return path8.join(sessionsDir(), `${hash}.files.json`);
753
+ }
754
+ function fileHash(content) {
755
+ return crypto3.createHash("sha1").update(content).digest("hex").slice(0, 12);
756
+ }
757
+ function normalizeRecords(value) {
758
+ if (!Array.isArray(value)) return [];
759
+ const out = [];
760
+ for (const item of value) {
761
+ if (typeof item === "string") out.push({ path: item });
762
+ else if (item && typeof item === "object" && typeof item.path === "string") {
763
+ out.push(item);
764
+ }
765
+ }
766
+ return out;
767
+ }
768
+ function loadRecentFiles(cwd) {
769
+ try {
770
+ const parsed = JSON.parse(fs9.readFileSync(recentFile(cwd), "utf8"));
771
+ return { reads: normalizeRecords(parsed.reads), writes: normalizeRecords(parsed.writes) };
772
+ } catch {
773
+ return { reads: [], writes: [] };
774
+ }
775
+ }
776
+ function saveRecentFiles(cwd, reads, writes, hashes) {
777
+ try {
778
+ const prev = loadRecentFiles(cwd);
779
+ const absHash = (rel) => hashes?.get(path8.resolve(cwd, rel));
780
+ const merge = (known, fresh) => {
781
+ const byPath = /* @__PURE__ */ new Map();
782
+ for (const rel of fresh) {
783
+ byPath.set(rel, { path: rel, hash: absHash(rel) });
784
+ }
785
+ for (const rec of known) {
786
+ if (!byPath.has(rec.path)) byPath.set(rec.path, rec);
787
+ }
788
+ return [...byPath.values()].slice(0, 15);
789
+ };
790
+ fs9.writeFileSync(
791
+ recentFile(cwd),
792
+ JSON.stringify({ reads: merge(prev.reads, reads), writes: merge(prev.writes, writes) }, null, 2)
793
+ );
794
+ } catch {
795
+ }
796
+ }
797
+ function formatRecentFiles(cwd, prewarm) {
798
+ const stored = loadRecentFiles(cwd);
799
+ if (stored.reads.length === 0 && stored.writes.length === 0) return "";
800
+ const verify = (records) => {
801
+ const unchanged = [];
802
+ const changed = [];
803
+ const legacy = [];
804
+ for (const rec of records.slice(0, 15)) {
805
+ if (!rec.hash) {
806
+ legacy.push(rec);
807
+ continue;
808
+ }
809
+ try {
810
+ const content = fs9.readFileSync(path8.resolve(cwd, rec.path), "utf8");
811
+ const current = fileHash(content);
812
+ if (current === rec.hash) {
813
+ unchanged.push(rec);
814
+ prewarm?.set(path8.resolve(cwd, rec.path), current);
815
+ } else {
816
+ changed.push(rec);
817
+ }
818
+ } catch {
819
+ changed.push(rec);
820
+ }
821
+ }
822
+ return { unchanged, changed, legacy };
823
+ };
824
+ const w = verify(stored.writes);
825
+ const writePaths = new Set(stored.writes.map((r2) => r2.path));
826
+ const r = verify(stored.reads.filter((rec) => !writePaths.has(rec.path)));
827
+ const lines = ["# Known files from earlier tasks in this project"];
828
+ const join = (list) => list.map((rec) => rec.path).join(", ");
829
+ if (w.unchanged.length > 0) lines.push(`Modified recently, verified unchanged on disk: ${join(w.unchanged)}`);
830
+ if (r.unchanged.length > 0) lines.push(`Read recently, verified unchanged on disk: ${join(r.unchanged)}`);
831
+ const stale = [...w.changed, ...r.changed];
832
+ if (stale.length > 0) lines.push(`Changed (or missing) on disk since then \u2014 re-read before editing: ${join(stale)}`);
833
+ for (const rec of [...w.legacy, ...r.legacy]) {
834
+ lines.push(`Known from earlier tasks (state unknown): ${rec.path}`);
835
+ }
836
+ lines.push(
837
+ "For files verified unchanged you may apply_patch directly without re-reading; the harness validates the hash and will tell you if the file changed. Do not re-search or re-read verified files unless needed."
838
+ );
839
+ return lines.join("\n");
840
+ }
750
841
  var init_conversation = __esm({
751
842
  "src/agent/conversation.ts"() {
752
843
  "use strict";
@@ -761,6 +852,7 @@ function createAgentState(task, maxIterations) {
761
852
  iteration: 0,
762
853
  maxIterations,
763
854
  modifiedFiles: [],
855
+ readFiles: [],
764
856
  commandsRun: [],
765
857
  errors: [],
766
858
  lastErrorSignature: null,
@@ -897,8 +989,12 @@ async function runAgent(options) {
897
989
  signal
898
990
  } = options;
899
991
  const state = createAgentState(task, maxIterations);
992
+ const fileState = /* @__PURE__ */ new Map();
900
993
  const prior = formatConversationContext(cwd);
901
- const system = buildSystemPrompt(cwd) + (prior ? `
994
+ const knownFiles = formatRecentFiles(cwd, fileState);
995
+ const system = buildSystemPrompt(cwd) + (knownFiles ? `
996
+
997
+ ${knownFiles}` : "") + (prior ? `
902
998
 
903
999
  ${prior}
904
1000
 
@@ -910,6 +1006,7 @@ ${systemExtra}` : "");
910
1006
  const toolCtx = {
911
1007
  cwd,
912
1008
  approval,
1009
+ fileState,
913
1010
  emit: (type, message, data) => {
914
1011
  bus.emit(type, message, data);
915
1012
  }
@@ -961,6 +1058,7 @@ ${systemExtra}` : "");
961
1058
  bus.emit("error", message + hint);
962
1059
  finalText = `Model request failed: ${message}`;
963
1060
  appendTurn(cwd, task, finalText);
1061
+ saveRecentFiles(cwd, state.readFiles, state.modifiedFiles, fileState);
964
1062
  bus.emit("task_completed", finalText.slice(0, 400), {
965
1063
  success: false,
966
1064
  finalText,
@@ -1121,7 +1219,10 @@ Investigate the root cause, fix it, then finish with a final answer.`
1121
1219
  }
1122
1220
  const readHeader = result.success && tool.name === "read_file" ? result.output.split("\n")[0] ?? "" : "";
1123
1221
  const readRel = readHeader ? normalizeRel(readHeader.replace(/\s+\(\d+ lines\)\s*$/, "").trim()) : "";
1124
- if (readRel) readMsgIndex.set(readRel, messages.length);
1222
+ if (readRel) {
1223
+ readMsgIndex.set(readRel, messages.length);
1224
+ if (!state.readFiles.includes(readRel) && state.readFiles.length < 20) state.readFiles.push(readRel);
1225
+ }
1125
1226
  if ((tool.name === "apply_patch" || tool.name === "write_file") && result.success) {
1126
1227
  const patchedRel = normalizeRel(
1127
1228
  typeof result.data?.path === "string" && result.data.path ? result.data.path : typeof args.path === "string" ? args.path : ""
@@ -1160,7 +1261,10 @@ Investigate the root cause, fix it, then finish with a final answer.`
1160
1261
  bus.emit("warning", finalText);
1161
1262
  }
1162
1263
  const success = sawFinish && state.errors.length === 0;
1163
- if (finalText && !signal?.aborted) appendTurn(cwd, task, finalText);
1264
+ if (finalText && !signal?.aborted) {
1265
+ appendTurn(cwd, task, finalText);
1266
+ saveRecentFiles(cwd, state.readFiles, state.modifiedFiles, fileState);
1267
+ }
1164
1268
  bus.emit("task_completed", finalText.slice(0, 400), {
1165
1269
  success,
1166
1270
  finalText,
@@ -1195,7 +1299,7 @@ var init_loop = __esm({
1195
1299
  var version;
1196
1300
  var init_package = __esm({
1197
1301
  "package.json"() {
1198
- version = "0.1.18";
1302
+ version = "0.1.20";
1199
1303
  }
1200
1304
  });
1201
1305
 
@@ -1558,7 +1662,7 @@ function App(props) {
1558
1662
  const [toolHistory, setToolHistory] = useState3([]);
1559
1663
  const [inspectorIndex, setInspectorIndex] = useState3(null);
1560
1664
  const [detailsOpen, setDetailsOpen] = useState3(false);
1561
- const [showDetails, setShowDetails] = useState3(false);
1665
+ const [showDetails, setShowDetails] = useState3(true);
1562
1666
  const [thinkingText, setThinkingText] = useState3("");
1563
1667
  const [phase, setPhase] = useState3("");
1564
1668
  const [showEdits, setShowEdits] = useState3(true);
@@ -2369,6 +2473,10 @@ function createProvider(provider, config, modelOverride) {
2369
2473
  init_base();
2370
2474
  import fs2 from "fs";
2371
2475
  import path2 from "path";
2476
+ import crypto from "crypto";
2477
+ function contentHash(content) {
2478
+ return crypto.createHash("sha1").update(content).digest("hex").slice(0, 12);
2479
+ }
2372
2480
  function resolveWithinWorkspace(cwd, target) {
2373
2481
  const resolved = path2.resolve(cwd, target);
2374
2482
  const normalizedCwd = path2.resolve(cwd);
@@ -2503,9 +2611,16 @@ var readFile = {
2503
2611
  if (start > lines.length) {
2504
2612
  return {
2505
2613
  success: false,
2506
- output: `start_line ${start} is beyond end of file (${lines.length} lines)`
2614
+ output: `READ_RANGE_INVALID: start_line ${start} is beyond end of file (total_lines ${lines.length}). Valid range: 1-${lines.length}. Recovery: retry read_file with start_line <= ${Math.max(1, lines.length)} or omit the range.`
2615
+ };
2616
+ }
2617
+ if (Number.isNaN(end) || end < start) {
2618
+ return {
2619
+ success: false,
2620
+ output: `READ_RANGE_INVALID: end_line (${args.end_line}) is before start_line (${start}). Valid range: 1-${lines.length}. Recovery: retry with end_line >= start_line.`
2507
2621
  };
2508
2622
  }
2623
+ ctx.fileState?.set(filePath, contentHash(content));
2509
2624
  const slice = lines.slice(start - 1, end).map((line, i) => `${start + i}: ${line}`);
2510
2625
  const { text, truncated } = truncateOutput(slice.join("\n"), 12e3);
2511
2626
  const note = end < lines.length ? `
@@ -2535,6 +2650,7 @@ var writeFile = {
2535
2650
  const content = requireString(args, "content");
2536
2651
  fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
2537
2652
  fs2.writeFileSync(filePath, content, "utf8");
2653
+ ctx.fileState?.set(filePath, contentHash(content));
2538
2654
  return {
2539
2655
  success: true,
2540
2656
  output: `Wrote ${Buffer.byteLength(content, "utf8")} bytes to ${relativeToWorkspace(ctx.cwd, filePath)}`
@@ -2567,6 +2683,16 @@ var applyPatch = {
2567
2683
  if (oldString === newString) {
2568
2684
  return { success: false, output: "old_string and new_string are identical; nothing to change." };
2569
2685
  }
2686
+ const relPath = relativeToWorkspace(ctx.cwd, filePath);
2687
+ const totalLines = content.split(/\r?\n/).length;
2688
+ const currentHash = contentHash(content);
2689
+ const knownHash = ctx.fileState?.get(filePath);
2690
+ if (knownHash && knownHash !== currentHash) {
2691
+ return {
2692
+ success: false,
2693
+ output: `FILE_CHANGED: ${relPath} changed since it was last read (expected hash ${knownHash}, current ${currentHash}, total_lines ${totalLines}). Recovery: read_file ${relPath} again, then retry apply_patch against the current text.`
2694
+ };
2695
+ }
2570
2696
  const tryMatch = (c, o, n) => {
2571
2697
  const occ = c.split(o).length - 1;
2572
2698
  return occ === 1 ? c.replace(o, n) : null;
@@ -2596,15 +2722,16 @@ var applyPatch = {
2596
2722
  if (occ === 0) {
2597
2723
  return {
2598
2724
  success: false,
2599
- output: "old_string not found in file. Read the file again and copy the exact text, including whitespace."
2725
+ output: `PATCH_FAILED: old_string not found in ${relPath} (total_lines ${totalLines}, hash ${currentHash}). Recovery: read_file ${relPath} (or the relevant line range) and copy the exact text including whitespace, then retry. Do not guess.`
2600
2726
  };
2601
2727
  }
2602
2728
  return {
2603
2729
  success: false,
2604
- output: `old_string occurs ${occ} times. Include more surrounding lines to make it unique.`
2730
+ output: `PATCH_FAILED: old_string occurs ${occ} times in ${relPath}. Recovery: include more surrounding lines to make it unique, then retry.`
2605
2731
  };
2606
2732
  }
2607
2733
  fs2.writeFileSync(filePath, updated, "utf8");
2734
+ ctx.fileState?.set(filePath, contentHash(updated));
2608
2735
  const removed = oldString.split(/\r?\n/).length;
2609
2736
  const added = newString.split(/\r?\n/).length;
2610
2737
  return {
@@ -3175,12 +3302,12 @@ init_config();
3175
3302
  init_base();
3176
3303
  import fs5 from "fs";
3177
3304
  import path5 from "path";
3178
- import crypto from "crypto";
3305
+ import crypto2 from "crypto";
3179
3306
  function memoryDir() {
3180
3307
  return path5.join(configDir(), "memory");
3181
3308
  }
3182
3309
  function memoryFile(cwd) {
3183
- const key = crypto.createHash("sha1").update(path5.resolve(cwd)).digest("hex").slice(0, 16);
3310
+ const key = crypto2.createHash("sha1").update(path5.resolve(cwd)).digest("hex").slice(0, 16);
3184
3311
  return path5.join(memoryDir(), `${key}.json`);
3185
3312
  }
3186
3313
  function load(cwd) {
@@ -3222,7 +3349,7 @@ var memoryTool = {
3222
3349
  }
3223
3350
  const topic = firstString(args, "topic") ?? "";
3224
3351
  const entry = {
3225
- id: crypto.randomBytes(4).toString("hex"),
3352
+ id: crypto2.randomBytes(4).toString("hex"),
3226
3353
  topic,
3227
3354
  content: content.trim(),
3228
3355
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@menteeai/menteeswe",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "MenteE SWE — a model-agnostic autonomous software-engineering agent CLI. Bring your own intelligence: Kimi, GLM/Z.ai, and more.",
5
5
  "type": "module",
6
6
  "license": "MIT",