mixdog 0.9.121 → 0.9.123

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.121",
3
+ "version": "0.9.123",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -12,59 +12,57 @@
12
12
  (`shell.cwd` is call-local and never changes the Project);
13
13
  explicit user-requested conversation reset→`session_manage`.
14
14
  Use only named tools present in the current tool surface.
15
- - Act only on verified identities (task/cwd/project/user/tool-returned) — paths,
16
- module specifiers, symbols, data/record shapes alike; verify a guessed
17
- identity with one lookup or sample before the first call or edit that
18
- relies on it. Within the current project, pass
19
- project-relative paths and omit optional scopes equal to its root; explicit
20
- paths may be outside cwd only for targets outside the project.
15
+ - Act only on verified identities (task/cwd/project/user/tool-returned) —
16
+ paths, module specifiers, symbols, data/record shapes alike; verify a
17
+ guessed identity with one lookup or sample before the first call or edit
18
+ that relies on it. Within the current project, pass project-relative
19
+ paths and omit optional scopes equal to its root; explicit paths may be
20
+ outside cwd only for targets outside the project.
21
21
  - Plan the fewest dependent rounds, then the fewest calls. Known state —
22
22
  anything the task supplied, a tool returned (applied patches and envelope
23
- hints included), or a check already proved —
24
- is never re-found, re-derived, or re-verified; a change to its subject
25
- re-opens it. Batch calls iff none needs
26
- another's output or can change another's inputs/state; otherwise
27
- serialize. Before each batch, deduplicate the facets still required by the request,
28
- route each once to the cheapest sufficient tool with all required
29
- variants/scopesevery distinct sample/format in the same batch —
30
- and launch every independent call together never
31
- split or duplicate a facet across tools, mutate merely to widen
32
- retrieval, reserve known work, or cap fanout. Each round asks everything
33
- it can and keeps everything it gets: guesses go wide in one batch, scopes
34
- narrow only on verified cues — returned siblings/conventions or known
35
- literals — and returned output is fully mined before the next round.
36
- Symbol relations end at
23
+ hints included), or a check already proved — is never re-found,
24
+ re-derived, or re-verified; a change to its subject re-opens it. Batch
25
+ calls iff none needs another's output or can change another's
26
+ inputs/state; otherwise serialize. Before each batch, deduplicate the
27
+ facets still required by the request, route each once to the cheapest
28
+ sufficient tool with all required variants/scopes, and launch every
29
+ independent call together never split or duplicate a facet across
30
+ tools, mutate merely to widen retrieval, reserve known work, or cap
31
+ fanout. Guesses go wide in one batch, scopes narrow only on verified
32
+ cues returned siblings/conventions or known literals and returned
33
+ output is fully mined before the next round. Symbol relations end at
37
34
  `code_graph`; values/locations end at the context grep returns; `read`
38
35
  covers only what returned spans cannot, as an anchored offset/limit
39
36
  window. A conclusive result ends its facet; evidence that determines the
40
37
  answer, edit, or deliverable ends retrieval — patch if needed.
41
- - Inspect read-only; writable handles, repair, or cleanup only when the
42
- deliverable requires them, copying what they could destroy. Never clear
43
- an obstacle or unexpected state by mutating it; unrecoverably lost
44
- evidence ends its search — report best effort.
38
+ - If inspection can change evidence or durable state, use read-only means;
39
+ mutate only when the deliverable requires it, first preserving evidence
40
+ at risk. Never mutate merely to clear an obstacle or unexpected state;
41
+ unrecoverably lost evidence ends its search — report best effort.
45
42
  - Once the edit or deliverable is determined, finish in one assistant turn:
46
- before `apply_patch`, obtain every target hunk's exact current content and
47
- anchor from `grep`, `code_graph`, `read`, or a prior successful patch
48
- envelope; never infer patch context from
49
- another file, a sample, or expected text. Then issue `apply_patch` calls
50
- serially, never in parallel; use one cohesive call with one file section per
51
- target, all patches first, then their one batched verification `shell` in
52
- the same turn — the runtime runs it after every patch and only if all
53
- succeeded, so verification never needs its own turn. It runs the real
54
- required postconditions on every changed file and produced artifact,
55
- never echoing a claim; changes made through `shell` verify under the same
43
+ before `apply_patch`, obtain every target hunk's exact current content
44
+ and anchor from `grep`, `code_graph`, `read`, or a prior successful patch
45
+ envelope; never infer patch context from another file, a sample, or
46
+ expected text. Then issue `apply_patch` calls serially, never in
47
+ parallel; use one cohesive call with one file section per target, all
48
+ patches first, then their one batched verification `shell` in the same
49
+ turn — the runtime runs it after every patch and only if all succeeded,
50
+ so verification never needs its own turn. It runs the real required
51
+ postconditions on every changed file and produced artifact, never
52
+ echoing a claim; changes made through `shell` verify under the same
56
53
  one-batch contract — one script proving every postcondition, value-level
57
- included, never one check per round; a postcondition that did not actually run is
58
- unresolved, not passed. Retry only failed envelopes; rerun a failed check only
59
- after a change that can alter its outcome — commands alike; else switch
60
- route or report it unresolved.
54
+ included, never one check per round; a postcondition that did not
55
+ actually run is unresolved, not passed. Retry only failed envelopes;
56
+ rerun a failed check only after a change that can alter its outcome —
57
+ commands alike; else switch route or report it unresolved.
61
58
  Hand-authored text is edited only with `apply_patch`; computed artifacts
62
59
  (data/reports/derived values) come from `shell` computation, never
63
60
  hand-transcribed numbers. Earlier `shell` is only for runtime/state
64
- evidence unavailable to file tools—an independent facet, batched with
65
- the rest; independent probes are parallel shell calls, never one
66
- serial script per round.
67
- - A long or uncertain command runs async never nohup and its
68
- `task_id` ends the turn; completion resumes work. Never poll in any
69
- form sleep/status probes included; task control is for recovery or a
70
- required blocking result only.
61
+ evidence unavailable to file tools an independent facet, batched with
62
+ the rest; independent probes are parallel shell calls, never one serial
63
+ script per round.
64
+ - Run long/uncertain commands async—never nohup—and omit timeout unless a real
65
+ total deadline is required; explicit timeout kills even async jobs. Do not
66
+ poll a notified `task_id`; completion resumes. Else poll only when requested
67
+ or no notification exists, with cadence/stop condition; task control only
68
+ serves recovery/blocking.
@@ -62,7 +62,7 @@ export const BUILTIN_TOOLS = [
62
62
  minItems: 1,
63
63
  },
64
64
  ],
65
- description: 'Project-relative file path, string[] files, [path,offset,limit?] range, or range[]; absolute only outside the project.',
65
+ description: 'File path, string[] files, [path,offset,limit?] range, or range[].',
66
66
  },
67
67
  offset: { type: 'number', minimum: 0, description: 'Lines to skip.' },
68
68
  limit: { type: 'number', minimum: 1, description: 'Max lines; default 2000.' },
@@ -83,7 +83,7 @@ export const BUILTIN_TOOLS = [
83
83
  cwd: { type: 'string', description: 'Omit to use the current Project root; use a project-relative subdir or explicit external path for this call only.' },
84
84
  timeout: {
85
85
  type: 'number',
86
- description: `Timeout ms; default ${_shellDefaultTimeoutMs()}. Explicit values are deadlines; sync may return task_id.`,
86
+ description: `Total deadline ms. Omit for sync default ${_shellDefaultTimeoutMs()}; omit for unlimited async. Explicit values kill at deadline; sync may return task_id.`,
87
87
  },
88
88
  mode: { type: 'string', enum: ['sync', 'async'], description: executionModeSchemaDescription('sync') },
89
89
  shell: { type: 'string', enum: ['bash', 'powershell'], description: 'Force shell.' },
@@ -128,7 +128,7 @@ export const BUILTIN_TOOLS = [
128
128
  { type: 'string' },
129
129
  { type: 'array', items: { type: 'string' }, minItems: 1 },
130
130
  ],
131
- description: 'Project-relative file/dir scope(s); omit for project root; absolute only outside.',
131
+ description: 'File/dir scope(s).',
132
132
  },
133
133
  glob: {
134
134
  anyOf: [
@@ -169,7 +169,7 @@ export const BUILTIN_TOOLS = [
169
169
  { type: 'string' },
170
170
  { type: 'array', items: { type: 'string' }, minItems: 1 },
171
171
  ],
172
- description: 'Project-relative base dir(s); omit for project root; path[] batches; absolute only outside.',
172
+ description: 'Base dir(s); path[] batches.',
173
173
  },
174
174
  limit: { type: 'number', description: 'Max entries; default 100; 0 unlimited.' },
175
175
  offset: { type: 'number', minimum: 0, description: 'Entry offset.' },
@@ -193,7 +193,7 @@ export const BUILTIN_TOOLS = [
193
193
  ],
194
194
  description: 'Filename or directory path fragments matched against path strings; query[] batches.',
195
195
  },
196
- path: { type: 'string', description: 'Project-relative base; omit for project root; absolute only outside.' },
196
+ path: { type: 'string', description: 'Base path.' },
197
197
  limit: { type: 'number', description: 'Max paths; default 25; 0 unlimited.' },
198
198
  include_noise: { type: 'boolean', description: 'Also search gitignored/dependency trees.' },
199
199
  },
@@ -214,7 +214,7 @@ export const BUILTIN_TOOLS = [
214
214
  { type: 'string' },
215
215
  { type: 'array', items: { type: 'string' }, minItems: 1 },
216
216
  ],
217
- description: 'Project-relative directory; omit for project root; path[] batches; absolute only outside.',
217
+ description: 'Directory; path[] batches.',
218
218
  },
219
219
  hidden: { type: 'boolean', description: 'Include dotfiles.' },
220
220
  meta: { type: 'boolean', description: 'Per-entry size bytes, UTC mtime, octal mode.' },
@@ -447,6 +447,29 @@ function invalidateFindEnumerationRoot(root) {
447
447
  if (findEnumerationRootFromKey(key) === root) FIND_ENUM_INFLIGHT.delete(key);
448
448
  }
449
449
  }
450
+ // Trailing-edge prewarm throttle: sustained write bursts (builds, installs)
451
+ // fire the watcher continuously; re-running the two full sweeps on every
452
+ // flush kept the serve bulk queue permanently busy. Invalidation stays
453
+ // immediate — only the warm rebuild is coalesced to one run per interval
454
+ // per root, scheduled at the trailing edge so the cache re-arms after the
455
+ // burst settles.
456
+ const FIND_ENUM_PREWARM_STATE = new Map(); // root -> { lastAt, timer }
457
+ const FIND_ENUM_PREWARM_MIN_INTERVAL_MS = 15_000;
458
+ function scheduleFindEnumerationPrewarm(root, delayMs = 100) {
459
+ let state = FIND_ENUM_PREWARM_STATE.get(root);
460
+ if (!state) {
461
+ state = { lastAt: 0, timer: null };
462
+ FIND_ENUM_PREWARM_STATE.set(root, state);
463
+ }
464
+ if (state.timer) return;
465
+ const wait = Math.max(delayMs, state.lastAt + FIND_ENUM_PREWARM_MIN_INTERVAL_MS - Date.now());
466
+ state.timer = setTimeout(() => {
467
+ state.timer = null;
468
+ state.lastAt = Date.now();
469
+ void prewarmFindEnumeration(root);
470
+ }, wait);
471
+ state.timer.unref?.();
472
+ }
450
473
  function ensureFindEnumerationWatcher(root) {
451
474
  const existing = FIND_ENUM_WATCHERS.get(root);
452
475
  if (existing) {
@@ -461,8 +484,7 @@ function ensureFindEnumerationWatcher(root) {
461
484
  const paths = [...pending];
462
485
  pending.clear();
463
486
  invalidateBuiltinResultCache(paths.length ? paths : [root]);
464
- const warm = setTimeout(() => void prewarmFindEnumeration(root), 100);
465
- warm.unref?.();
487
+ scheduleFindEnumerationPrewarm(root);
466
488
  };
467
489
  record.watcher = watch(root, { recursive: true, persistent: false }, (_event, filename) => {
468
490
  const rel = filename == null ? '' : String(filename);
@@ -649,6 +671,21 @@ async function getBroadEnumeration({ root, hidden, depth, includeNoise, ignoreMo
649
671
  return subscribeToFindEnumeration(key, entry, signal);
650
672
  }
651
673
 
674
+ // Cache-only peek: returns the SHARED files array of a still-valid broad
675
+ // sweep for these dimensions, or null. Never spawns, joins, or waits — the
676
+ // targeted find path below must not block behind a full-tree enumeration.
677
+ function peekBroadEnumeration({ root, hidden, depth, includeNoise, ignoreMode }) {
678
+ if (findEnumTtlMs() <= 0) return null;
679
+ const key = findEnumKey({ root, hidden, depth, includeNoise, ignoreMode });
680
+ const hit = FIND_ENUM_CACHE.get(key);
681
+ if (hit && hit.gen === FIND_ENUM_GEN
682
+ && hit.rootGen === findEnumerationRootGeneration(root)
683
+ && hit.expiresAt > Date.now()) {
684
+ return hit.files;
685
+ }
686
+ return null;
687
+ }
688
+
652
689
  // Best-effort warm of the broad enumeration for a root using the `find` tool's
653
690
  // DEFAULT flags (hidden:true, includeNoise:false, depth:unbounded). Swallows
654
691
  // all errors — a failed prewarm must never surface or block the caller.
@@ -695,38 +732,27 @@ async function getTargetedFindEnumeration({
695
732
  const key = JSON.stringify([root, hidden, depth ?? '', includeNoise, terms]);
696
733
  const runs = context?.targetedRuns;
697
734
  if (runs?.has(key)) return runs.get(key);
698
- try {
699
- const inventoryArgs = ['--files', '--no-ignore'];
700
- if (hidden) inventoryArgs.push('--hidden');
701
- if (depth != null) inventoryArgs.push('--max-depth', String(depth));
702
- if (!includeNoise) {
703
- for (const ex of DEFAULT_IGNORE_GLOBS) inventoryArgs.push('--glob', ex);
704
- }
705
- inventoryArgs.push('.');
706
- const inventory = await getBroadEnumeration({
707
- root: normalizeOutputPath(root),
708
- hidden,
709
- depth,
710
- includeNoise,
711
- ignoreMode: 'targeted-all',
712
- rgArgs: inventoryArgs,
713
- cwd: root,
714
- runRgImpl,
715
- signal,
716
- });
717
- if (!inventory.truncated && !inventory.partial) {
718
- const lowerTerms = terms.map((term) => term.toLowerCase());
719
- return {
720
- files: inventory.files.filter((path) => {
721
- const lower = path.toLowerCase();
722
- return lowerTerms.some((term) => lower.includes(term));
723
- }),
724
- truncated: false,
725
- partial: false,
726
- };
727
- }
728
- } catch {
729
- if (signal?.aborted) throw findEnumerationAbortError(signal);
735
+ // Reuse a WARM broad inventory only (cache peek). A cold cache goes
736
+ // straight to the cheap targeted --iglob batch below: front-running the
737
+ // full `--no-ignore` sweep made cold finds stall behind the serve bulk
738
+ // queue for tens of seconds.
739
+ const inventory = peekBroadEnumeration({
740
+ root: normalizeOutputPath(root),
741
+ hidden,
742
+ depth,
743
+ includeNoise,
744
+ ignoreMode: 'targeted-all',
745
+ });
746
+ if (inventory) {
747
+ const lowerTerms = terms.map((term) => term.toLowerCase());
748
+ return {
749
+ files: inventory.filter((path) => {
750
+ const lower = path.toLowerCase();
751
+ return lowerTerms.some((term) => lower.includes(term));
752
+ }),
753
+ truncated: false,
754
+ partial: false,
755
+ };
730
756
  }
731
757
  let batches = FIND_TARGETED_BATCHES_BY_RUNNER.get(runRgImpl);
732
758
  if (!batches) {
@@ -154,6 +154,15 @@ export async function tryServeSearch(argsList, execOptions = {}, opts = {}) {
154
154
  const server = _ensureServer();
155
155
  if (!server) return null;
156
156
  const id = ++server.sequence;
157
+ // Honor the caller's rg deadline: the resident server must never wait
158
+ // LONGER than the spawn fallback it replaces (glob passes 10s while the
159
+ // serve ceiling is 20s), otherwise a queued/stuck serve request delays the
160
+ // fallback instead of accelerating it. The fixed ceiling still bounds
161
+ // callers that pass no timeout.
162
+ const callerTimeoutMs = Number(execOptions.timeout);
163
+ const deadlineMs = Number.isFinite(callerTimeoutMs) && callerTimeoutMs > 0
164
+ ? Math.min(callerTimeoutMs, REQUEST_TIMEOUT_MS)
165
+ : REQUEST_TIMEOUT_MS;
157
166
  _setServerReferenced(server, true);
158
167
  const request = {
159
168
  id,
@@ -185,7 +194,7 @@ export async function tryServeSearch(argsList, execOptions = {}, opts = {}) {
185
194
  const timer = setTimeout(() => {
186
195
  server.pending.delete(id);
187
196
  settle(null, true);
188
- }, REQUEST_TIMEOUT_MS);
197
+ }, deadlineMs);
189
198
  timer.unref?.();
190
199
  server.pending.set(id, { resolve: settle, reject: () => settle(null) });
191
200
  onAbort = () => {
@@ -76,6 +76,12 @@ export async function windowsPidDescendants(pid) {
76
76
  if (process.platform !== 'win32') return new Map();
77
77
  let rows = null;
78
78
  try { rows = await windowsProcessSnapshot({ fresh: true }); } catch { rows = null; }
79
+ if (!rows) {
80
+ // One retry before giving up: a single missed 750ms native-snapshot
81
+ // deadline (server busy with a bulk enumeration) otherwise marks
82
+ // every standby dirty and collapses the pool into cold spawns.
83
+ try { rows = await windowsProcessSnapshot({ fresh: true }); } catch { rows = null; }
84
+ }
79
85
  if (!rows) return null;
80
86
  const owned = new Set([pid]);
81
87
  const descendants = new Map();
@@ -8,7 +8,7 @@ export const CODE_GRAPH_TOOL_DEFS = [
8
8
  type: 'object',
9
9
  properties: {
10
10
  mode: { type: 'string', enum: ['overview', 'imports', 'dependents', 'related', 'impact', 'symbols', 'find_symbol', 'symbol_search', 'search', 'references', 'callers', 'callees'], description: 'File modes: overview/imports/dependents/related/impact. symbols with files[] gives a file outline; others are symbol modes.' },
11
- files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Project-relative source file path(s); absolute only outside the project; supported targets only.' },
11
+ files: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Project-relative source file path(s).' },
12
12
  symbols: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Exact identifiers or keywords (symbol-index terms); batch multiple in one symbols[] call.' },
13
13
  body: { type: 'boolean', description: 'Include body.' },
14
14
  limit: { type: 'number', minimum: 1, description: 'Max results.' },
@@ -1,26 +1,26 @@
1
1
  {
2
- "version": "0.1.6",
2
+ "version": "0.1.7",
3
3
  "_comment": "Synced from immutable graph-v release assets.",
4
4
  "assets": {
5
5
  "darwin-arm64": {
6
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.6/mixdog-graph-darwin-arm64",
7
- "sha256": "43df904a60384761e6a0f848feec93fce0d6a3db6246695475b52cf355891a35"
6
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.7/mixdog-graph-darwin-arm64",
7
+ "sha256": "a8d95602fd2beb2901dded0b1fe728adfde8075d73557442e4c540b39e64fe8d"
8
8
  },
9
9
  "darwin-x64": {
10
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.6/mixdog-graph-darwin-x64",
11
- "sha256": "1bfbf4bc4ff0a55ed04477919e3a22a30de41af7404b8895e74d5ca0216e0285"
10
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.7/mixdog-graph-darwin-x64",
11
+ "sha256": "788e6e0fb32d8d8ba3b4c22e31492dc99bba039a3186a5b3f2420df3778f45d6"
12
12
  },
13
13
  "linux-arm64": {
14
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.6/mixdog-graph-linux-arm64",
15
- "sha256": "60512d468674105997b9171b9d9cc23c341144ada555cdc21f2e9b5cfbdd7ecb"
14
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.7/mixdog-graph-linux-arm64",
15
+ "sha256": "43f723df7b452e75bde9839c2ce027982fa329c08560f6888442c65ac0ef7ddf"
16
16
  },
17
17
  "linux-x64": {
18
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.6/mixdog-graph-linux-x64",
19
- "sha256": "9bddb7e78922081c550197ad7e34044fa4486ba57372dd0b662f3805c84f1561"
18
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.7/mixdog-graph-linux-x64",
19
+ "sha256": "a7a390fb2e446fb7a9d16ac68c42c1989622beab0d6711b610a16e43360e7e84"
20
20
  },
21
21
  "win32-x64": {
22
- "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.6/mixdog-graph-win32-x64.exe",
23
- "sha256": "0ad9198a5c5cf61993cb1bf76b2e4652f06542e8cf8f22601b7ca065a96b65f1"
22
+ "url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.7/mixdog-graph-win32-x64.exe",
23
+ "sha256": "75e7dafd4df2f56c889b3c77643cfe95526c8ada1c1ffd1711539677737950c2"
24
24
  }
25
25
  }
26
26
  }
@@ -47,8 +47,7 @@ const APPLY_PATCH_JSON_DESCRIPTION = [
47
47
  'Each section starts with exactly one: *** Add File: <path> (+ lines), *** Delete File: <path> (header only), or *** Update File: <path> (optional *** Move to: <new path>).',
48
48
  'Hunks start with @@ or @@ <symbol|1-based line>; lines start space, -, or +; every Update hunk needs >=1 +/- line; optional *** End of File.',
49
49
  'Use 3 verbatim context lines from newest output (post-patch body after edits); avoid overlap; stack @@ only if ambiguous.',
50
- 'Project-relative paths; explicit absolute paths only outside the project; + every added line. Never send compacted-history markers; re-read first.',
51
- 'A same-turn shell runs after all patches succeed.',
50
+ '+ prefixes every added line. Never send compacted-history markers; re-read first.',
52
51
  ].join('\n');
53
52
 
54
53
  export const PATCH_TOOL_DEFS = [