mindweave 3.4.1 → 3.4.2

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 (72) hide show
  1. package/README.md +26 -3
  2. package/dist/cli/App.js +52 -8
  3. package/dist/cli/App.js.map +1 -1
  4. package/dist/cli/commands.js +1 -0
  5. package/dist/cli/commands.js.map +1 -1
  6. package/dist/cli/components/ToolLine.js +5 -3
  7. package/dist/cli/components/ToolLine.js.map +1 -1
  8. package/dist/cli/components/WorkGroup.js +82 -64
  9. package/dist/cli/components/WorkGroup.js.map +1 -1
  10. package/dist/cli/startupArgs.js +24 -4
  11. package/dist/cli/startupArgs.js.map +1 -1
  12. package/dist/cli/transcript.js +14 -0
  13. package/dist/cli/transcript.js.map +1 -1
  14. package/dist/cli/workLines.js +40 -0
  15. package/dist/cli/workLines.js.map +1 -1
  16. package/dist/core/turnRunner.js +35 -23
  17. package/dist/core/turnRunner.js.map +1 -1
  18. package/dist/dynamo/contextWindow.js +28 -36
  19. package/dist/dynamo/contextWindow.js.map +1 -1
  20. package/dist/dynamo/engine.js +51 -9
  21. package/dist/dynamo/engine.js.map +1 -1
  22. package/dist/dynamo/guard.js +1 -1
  23. package/dist/dynamo/guard.js.map +1 -1
  24. package/dist/dynamo/prompt.js +1 -1
  25. package/dist/headless/args.js +82 -0
  26. package/dist/headless/args.js.map +1 -0
  27. package/dist/headless/main.js +87 -0
  28. package/dist/headless/main.js.map +1 -0
  29. package/dist/headless/output.js +150 -0
  30. package/dist/headless/output.js.map +1 -0
  31. package/dist/headless/run.js +188 -0
  32. package/dist/headless/run.js.map +1 -0
  33. package/dist/index.js +8 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/main.js +5 -0
  36. package/dist/main.js.map +1 -1
  37. package/dist/memory/compaction.js +94 -2
  38. package/dist/memory/compaction.js.map +1 -1
  39. package/dist/memory/rewind.js +10 -6
  40. package/dist/memory/rewind.js.map +1 -1
  41. package/dist/memory/session.js +2 -2
  42. package/dist/memory/session.js.map +1 -1
  43. package/dist/memory/sessionMemory.js +270 -69
  44. package/dist/memory/sessionMemory.js.map +1 -1
  45. package/dist/memory/types.js.map +1 -1
  46. package/dist/project/context.js +34 -6
  47. package/dist/project/context.js.map +1 -1
  48. package/dist/tools/coauthor.js +154 -0
  49. package/dist/tools/coauthor.js.map +1 -0
  50. package/dist/tools/codeIntel.js +38 -10
  51. package/dist/tools/codeIntel.js.map +1 -1
  52. package/dist/tools/docOutline.js +250 -0
  53. package/dist/tools/docOutline.js.map +1 -0
  54. package/dist/tools/editRipple.js +29 -2
  55. package/dist/tools/editRipple.js.map +1 -1
  56. package/dist/tools/emptyResult.js +24 -0
  57. package/dist/tools/emptyResult.js.map +1 -0
  58. package/dist/tools/glob.js +11 -2
  59. package/dist/tools/glob.js.map +1 -1
  60. package/dist/tools/grep.js +4 -3
  61. package/dist/tools/grep.js.map +1 -1
  62. package/dist/tools/missingPath.js +90 -0
  63. package/dist/tools/missingPath.js.map +1 -0
  64. package/dist/tools/readFile.js +3 -1
  65. package/dist/tools/readFile.js.map +1 -1
  66. package/dist/tools/rebuildDetail.js +9 -5
  67. package/dist/tools/rebuildDetail.js.map +1 -1
  68. package/dist/tools/runCommand.js +5 -1
  69. package/dist/tools/runCommand.js.map +1 -1
  70. package/dist/tools/webSearch.js +3 -1
  71. package/dist/tools/webSearch.js.map +1 -1
  72. package/package.json +2 -2
package/README.md CHANGED
@@ -21,8 +21,9 @@ ever go to the model provider you picked.
21
21
  Prompts are kept thin on purpose, so the context goes on your code rather than on
22
22
  scaffolding.
23
23
 
24
- Prefer a window to a terminal? [mwcode](https://github.com/mindweave-cli/mwcode) is the
25
- desktop app, built on the same engine, for Windows, macOS and Linux.
24
+ **Names.** Mindweave is the program and the name of this repository and of the npm package (`mindweave`, or `mw` for
25
+ short). [mwcode](https://github.com/mindweave-cli/mwcode) is the desktop app: a window around the same engine, for
26
+ Windows, macOS and Linux, with its own repository and its own download files. The two share sessions and settings.
26
27
 
27
28
  ## Install
28
29
 
@@ -54,7 +55,10 @@ npm link
54
55
  Qwen, Kimi, GLM, Meta, MiniMax, Tencent, OpenRouter, and local models through Ollama
55
56
  with no key at all. [PROVIDERS.md](src/drivers/PROVIDERS.md)
56
57
  - **Real tools** — file reads and edits, search, a shell with background jobs,
57
- sub-agents, web search, and a tool that tests your app by using it.
58
+ sub-agents, web search, and a tool that tests your app by using it: it opens a web page or
59
+ dev server in a browser, clicks through it and looks at the result. On Windows it can also
60
+ drive a desktop window and take a screenshot of that one window, never the whole screen. It
61
+ asks first unless you chose auto-accept.
58
62
  - **Code intelligence** — tree-sitter and language servers index the repo in the
59
63
  background, at no token cost, and every edit comes back with that file's errors.
60
64
  - **Goals that run on their own** — `/marathon` takes a goal and keeps going until it is
@@ -68,6 +72,25 @@ npm link
68
72
  `/help` lists every command. `shift+tab` switches modes. Type while it works and your
69
73
  message queues.
70
74
 
75
+ ## Run it from a script
76
+
77
+ `mindweave -p "task"` does one task with no screen and prints the answer, so it works in a shell script or a CI job.
78
+ Anything piped in is added as context, and `--output-format json` gives one result object with the session, the
79
+ tokens and the cost. The exit code says whether it finished (0), failed (1), had no key (3) or stopped before it was
80
+ done (4). Nobody can answer a question in this mode, so anything that would ask is refused. [docs/HEADLESS.md](docs/HEADLESS.md)
81
+
82
+ ```bash
83
+ git diff | mindweave -p "review this change"
84
+ mindweave -p "list the TODOs" --output-format json | jq -r .result
85
+ ```
86
+
87
+ ## Commits
88
+
89
+ When the agent makes a commit it can add one line to the message, `Co-authored-by: mwcode <...>`, so the commit
90
+ shows the agent beside you on GitHub. It asks once per project the first time, remembers the answer, and never adds
91
+ the line to a commit you make yourself. `/coauthor` shows or changes the answer, and `/coauthor off all` turns it off
92
+ everywhere.
93
+
71
94
  ## Your own rules and hooks
72
95
 
73
96
  Both live in your state folder (`~/.mindweave`, or `projects/<name>` inside it for one
package/dist/cli/App.js CHANGED
@@ -29,6 +29,7 @@ import { contextPressure, sharpContextWindow } from "../dynamo/contextWindow.js"
29
29
  import { contextBudget, formatBudget } from "../dynamo/contextBudget.js";
30
30
  import { contextView, resetContextOverride, setContextOverride } from "../core/contextSettings.js";
31
31
  import { createSession, resumeSession } from "../memory/session.js";
32
+ import { coauthorSetting, setCoauthor } from "../tools/coauthor.js";
32
33
  import { saveSession, listSessions } from "../memory/store.js";
33
34
  import { stopChassis } from "../alternator/lane.js";
34
35
  import { loadSkillBody, substituteSkillArgs } from "../governor/skills.js";
@@ -78,6 +79,7 @@ import { applyScreenMode } from "./screenShell.js";
78
79
  import { saveScreenMode } from "./screenStore.js";
79
80
  import { FULL_TAIL_BLOCKS, needsMeasure, pruneHeights, seedHeights } from "./blockHeights.js";
80
81
  import { expandableAt, hitBlock, hitItem, itemHit, revealNode } from "./expandHits.js";
82
+ import { FILE_HIT_BASE, fileRowAt } from "./workLines.js";
81
83
  import { isGrouped, nameAt } from "./editGroup.js";
82
84
  import { shellNote } from "./shellNotes.js";
83
85
  import { pressBackground } from "./commandBackground.js";
@@ -85,7 +87,7 @@ import { readFileSync, statSync } from "node:fs";
85
87
  import { compactSearchDetail, compactUiDetail, rebuildFull, recoverReadText, recoverSavedText, searchDetailFromContent, writtenFromDisk } from "../tools/rebuildDetail.js";
86
88
  import { isUserShell, runUserCommand } from "../tools/userCommand.js";
87
89
  import { isUserCommandText, parseUserCommand } from "../memory/localCommand.js";
88
- import { shownText } from "../memory/compaction.js";
90
+ import { shownText, splitExpiredImages, userAsSent } from "../memory/compaction.js";
89
91
  import { BASE_COMMANDS } from "./commands.js";
90
92
  import { attachmentBudget, dismissMarathon } from "../core/turnRunner.js";
91
93
  import { describeMarathonEvent, resumeMarathon, startMarathon } from "../dynamo/marathon.js";
@@ -728,8 +730,9 @@ export function App({ resumeSessionId, initialScreen }) {
728
730
  // but they are ours, not the person's. Replaying one draws it as a `>` prompt
729
731
  // the user never typed — seen live as "> That was 3 sentences between tool
730
732
  // calls…" sitting in their own chat history.
733
+ // The note compaction adds for the model when it drops an image is not something the person typed.
731
734
  if (!e.synthetic)
732
- dispatch({ type: "user", text: collapsePastes(stripAttachments(e.content)) });
735
+ dispatch({ type: "user", text: collapsePastes(splitExpiredImages(stripAttachments(userAsSent(e).content)).text) });
733
736
  // A command the person ran with `!` comes back as the row it was: what they ran, how it ended,
734
737
  // and the output behind the click. The screen's record is kept whole even when the model's copy
735
738
  // of the output was cleared to save context.
@@ -2069,7 +2072,19 @@ export function App({ resumeSessionId, initialScreen }) {
2069
2072
  if (block && block.kind === "work" && !block.open && block.items.length === 1)
2070
2073
  fetchRead(block.items[0]);
2071
2074
  }
2072
- if (hitItem(hit) >= 0) {
2075
+ if (hitItem(hit) >= FILE_HIT_BASE) {
2076
+ // One file of a read that took several: its text is fetched with the first click on any of them.
2077
+ const st = stateRef.current;
2078
+ const block = [...st.committed, ...st.tail].find((b) => b.id === hitBlock(hit));
2079
+ const at = block && block.kind === "work" ? fileRowAt(block.items, hitItem(hit) - FILE_HIT_BASE) : undefined;
2080
+ if (block && block.kind === "work" && at) {
2081
+ const item = block.items[at.item];
2082
+ if (!item.openFiles?.includes(at.file))
2083
+ fetchRead(item);
2084
+ dispatch({ type: "toggleFile", id: block.id, item: at.item, file: at.file });
2085
+ }
2086
+ }
2087
+ else if (hitItem(hit) >= 0) {
2073
2088
  // Before the toggle: the state is updated the moment it is dispatched, and a read already open is not fetched for.
2074
2089
  fetchReadText(hitBlock(hit), hitItem(hit));
2075
2090
  dispatch({ type: "toggleItem", id: hitBlock(hit), index: hitItem(hit) });
@@ -3127,12 +3142,14 @@ export function App({ resumeSessionId, initialScreen }) {
3127
3142
  // Hand a free-text directive to the model with an instruction wrapper, as its own
3128
3143
  // turn — used by the manual /rules and /skills commands (the model does the
3129
3144
  // "rewrite it well and save it" work via remember_rule / the skill tool).
3130
- async function runDirective(instruction, activity) {
3145
+ // `typed` is the command line the person wrote. `instruction` is the engine's wording for the model, which
3146
+ // must never be drawn as if the person had said it, so the message keeps what was typed as its original.
3147
+ async function runDirective(instruction, activity, typed) {
3131
3148
  const s = session.current;
3132
3149
  if (!s)
3133
3150
  return;
3134
3151
  note(activity);
3135
- s.transcript.push({ role: "user", content: instruction });
3152
+ s.transcript.push({ role: "user", content: instruction, ...(typed ? { original: { content: typed } } : {}) });
3136
3153
  await streamRespond(s);
3137
3154
  }
3138
3155
  async function handleCommand(raw) {
@@ -3308,7 +3325,7 @@ export function App({ resumeSessionId, initialScreen }) {
3308
3325
  `pull it in with an @-import line rather than making the root file long; if a ` +
3309
3326
  `convention is true of one folder only, write a MINDWEAVE.md inside that folder ` +
3310
3327
  `instead, which costs nothing when work is happening elsewhere. ` +
3311
- `changed. Finish by confirming in one line what you wrote and why.`, "writing MINDWEAVE.md…");
3328
+ `changed. Finish by confirming in one line what you wrote and why.`, "writing MINDWEAVE.md…", raw);
3312
3329
  return;
3313
3330
  }
3314
3331
  if (name === "/compact") {
@@ -3724,7 +3741,7 @@ export function App({ resumeSessionId, initialScreen }) {
3724
3741
  }
3725
3742
  await runDirective(`The user wants this saved as a standing rule for this project. Rewrite it as a clear, ` +
3726
3743
  `imperative, self-contained rule and save it with remember_rule (add globs only if it ` +
3727
- `clearly applies to specific files). Then confirm in one line. Directive: "${arg}"`, "saving a rule…");
3744
+ `clearly applies to specific files). Then confirm in one line. Directive: "${arg}"`, "saving a rule…", raw);
3728
3745
  return;
3729
3746
  }
3730
3747
  // /forbidden — list forbidden paths, or (with a path) forbid one outright.
@@ -3756,6 +3773,33 @@ export function App({ resumeSessionId, initialScreen }) {
3756
3773
  }
3757
3774
  return;
3758
3775
  }
3776
+ // /coauthor — whether commits the agent makes carry the "Co-authored-by: mwcode" line.
3777
+ if (name === "/coauthor") {
3778
+ const root = s.governance.forbidden.root;
3779
+ const words = arg.trim().toLowerCase().split(/\s+/).filter(Boolean);
3780
+ const everywhere = words.includes("all");
3781
+ const verb = words.find((w) => w !== "all");
3782
+ if (verb === "on" || verb === "off") {
3783
+ await setCoauthor(root, everywhere ? "global" : "project", verb === "on");
3784
+ const where = everywhere ? "in every project that has no answer of its own" : "in this project";
3785
+ note(verb === "on" ? `commits I make will carry the co-author line ${where}.` : `commits I make will not carry the co-author line ${where}.`);
3786
+ return;
3787
+ }
3788
+ if (verb === "reset") {
3789
+ await setCoauthor(root, "project", null);
3790
+ note("forgot this project's answer. I will ask at the next commit.");
3791
+ return;
3792
+ }
3793
+ if (verb) {
3794
+ say("Use /coauthor to see it, /coauthor on or /coauthor off to set it here, and add 'all' for every project. /coauthor reset forgets this project's answer.");
3795
+ return;
3796
+ }
3797
+ const current = await coauthorSetting(root);
3798
+ say(current
3799
+ ? `The co-author line is ${current.credit ? "on" : "off"}, set for ${current.from === "project" ? "this project" : "all projects"}.`
3800
+ : "Not set yet. I will ask the first time I commit here.");
3801
+ return;
3802
+ }
3759
3803
  // /forbid-command — list forbidden commands, or (with an argument) forbid one.
3760
3804
  if (name === "/forbid-command") {
3761
3805
  if (!arg) {
@@ -3790,7 +3834,7 @@ export function App({ resumeSessionId, initialScreen }) {
3790
3834
  if (arg) {
3791
3835
  await runDirective(`The user wants a new reusable skill for this project: "${arg}". Design it and save it ` +
3792
3836
  `with the skill tool — a short invocation name, a one-line description, and the procedure ` +
3793
- `as a clear markdown checklist (use $ARGUMENTS/$1 if it should take input). Then confirm.`, "creating a skill…");
3837
+ `as a clear markdown checklist (use $ARGUMENTS/$1 if it should take input). Then confirm.`, "creating a skill…", raw);
3794
3838
  return;
3795
3839
  }
3796
3840
  const skills = s.governance.skills;