mu-harness 0.16.19 → 0.16.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.
@@ -4,7 +4,6 @@ export interface Candidate {
4
4
  kind: 'file' | 'agent';
5
5
  }
6
6
  export declare function collectCandidates(cwd: string, agentNames: string[]): Candidate[];
7
- export declare function invalidateCandidates(): void;
8
7
  export declare function rank(query: string, candidates: Candidate[], limit?: number): Candidate[];
9
8
  export interface ActiveMention {
10
9
  start: number;
@@ -1,3 +1,4 @@
1
+ import { execFileSync } from 'node:child_process';
1
2
  import { readdirSync, statSync } from 'node:fs';
2
3
  import { join, relative } from 'node:path';
3
4
  const IGNORED = new Set([
@@ -19,7 +20,21 @@ const IGNORED = new Set([
19
20
  ]);
20
21
  const MAX_ENTRIES = 5000;
21
22
  const MAX_DEPTH = 6;
22
- let cache;
23
+ function gitFiles(cwd) {
24
+ try {
25
+ const out = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {
26
+ cwd,
27
+ encoding: 'utf8',
28
+ maxBuffer: 64 * 1024 * 1024,
29
+ stdio: ['ignore', 'pipe', 'ignore'],
30
+ });
31
+ const files = out.split('\0').filter((f) => f.length > 0);
32
+ return files.length > 0 ? files.slice(0, MAX_ENTRIES).sort() : undefined;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
23
38
  function walk(cwd) {
24
39
  const out = [];
25
40
  const stack = [{ dir: cwd, depth: 0 }];
@@ -59,30 +74,41 @@ function walk(cwd) {
59
74
  return out.sort();
60
75
  }
61
76
  export function collectCandidates(cwd, agentNames) {
62
- if (!cache || cache.cwd !== cwd)
63
- cache = { cwd, files: walk(cwd) };
77
+ const paths = gitFiles(cwd) ?? walk(cwd);
64
78
  const agents = agentNames.map((name) => ({ label: `@${name}`, insert: name, kind: 'agent' }));
65
- const files = cache.files.map((path) => ({ label: path, insert: path, kind: 'file' }));
79
+ const files = paths.map((path) => ({ label: path, insert: path, kind: 'file' }));
66
80
  return [...agents, ...files];
67
81
  }
68
- export function invalidateCandidates() {
69
- cache = undefined;
82
+ function isBoundary(target, i) {
83
+ if (i === 0)
84
+ return true;
85
+ const prev = target[i - 1] ?? '';
86
+ if (/[/\\_\-. ]/.test(prev))
87
+ return true;
88
+ const cur = target[i] ?? '';
89
+ return prev === prev.toLowerCase() && prev !== prev.toUpperCase() && cur === cur.toUpperCase() &&
90
+ cur !== cur.toLowerCase();
70
91
  }
71
- function score(query, target) {
92
+ function fuzzyScore(query, target) {
72
93
  if (query.length === 0)
73
94
  return 0;
74
95
  const q = query.toLowerCase();
75
- const t = target.toLowerCase();
76
96
  let qi = 0;
77
97
  let total = 0;
78
98
  let prev = -2;
79
- for (let i = 0; i < t.length && qi < q.length; i++) {
80
- if (t[i] === q[qi]) {
99
+ let run = 0;
100
+ for (let i = 0; i < target.length && qi < q.length; i++) {
101
+ if (target[i].toLowerCase() === q[qi]) {
81
102
  let s = 1;
82
- if (i === prev + 1)
83
- s += 2;
84
- if (i === 0 || /[/_\-. ]/.test(t[i - 1] ?? ''))
85
- s += 3;
103
+ if (i === prev + 1) {
104
+ run += 1;
105
+ s += 2 + run;
106
+ }
107
+ else {
108
+ run = 0;
109
+ }
110
+ if (isBoundary(target, i))
111
+ s += 4;
86
112
  total += s;
87
113
  prev = i;
88
114
  qi += 1;
@@ -90,14 +116,40 @@ function score(query, target) {
90
116
  }
91
117
  if (qi < q.length)
92
118
  return undefined;
93
- return total - t.length * 0.01;
119
+ return total;
120
+ }
121
+ function basename(path) {
122
+ const cut = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
123
+ return cut === -1 ? path : path.slice(cut + 1);
124
+ }
125
+ function score(query, candidate) {
126
+ if (query.length === 0)
127
+ return 0;
128
+ const pathScore = fuzzyScore(query, candidate.label);
129
+ if (pathScore === undefined)
130
+ return undefined;
131
+ let total = pathScore;
132
+ if (candidate.kind === 'file') {
133
+ const base = basename(candidate.label);
134
+ const baseScore = fuzzyScore(query, base);
135
+ if (baseScore !== undefined) {
136
+ total += baseScore * 2;
137
+ const lb = base.toLowerCase();
138
+ const lq = query.toLowerCase();
139
+ if (lb === lq)
140
+ total += 100;
141
+ else if (lb.startsWith(lq))
142
+ total += 40;
143
+ }
144
+ }
145
+ return total - candidate.label.length * 0.05;
94
146
  }
95
147
  export function rank(query, candidates, limit = 8) {
96
148
  if (!query)
97
149
  return candidates.slice(0, limit);
98
150
  const scored = [];
99
151
  for (const candidate of candidates) {
100
- const s = score(query, candidate.label);
152
+ const s = score(query, candidate);
101
153
  if (s !== undefined)
102
154
  scored.push({ candidate, score: s });
103
155
  }
@@ -176,7 +176,7 @@ export function formatToolArgs(name, input, max = 120) {
176
176
  if (input === null || typeof input !== 'object')
177
177
  return truncateText(String(input ?? ''), max);
178
178
  const args = input;
179
- if (name === 'edit' || name === 'write' || name === 'read' || name === 'list_dir') {
179
+ if (name === 'edit' || name === 'write' || name === 'read' || name === 'list') {
180
180
  return truncateText(stringifyArg(args.path), max);
181
181
  }
182
182
  if (name === 'bash')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-harness",
3
- "version": "0.16.19",
3
+ "version": "0.16.20",
4
4
  "description": "Agent harness: createHarness wires mu-core into a host — XDG paths, model registry, plugins, disk-loaded agents & skills, sub-agents, sessions (JSONL + SQLite catalog), slash commands, permission/approval hooks, an optional scheduler, and a composable TUI chat app",
5
5
  "license": "MIT",
6
6
  "main": "./script/index.js",
@@ -23,8 +23,8 @@
23
23
  "@swc/wasm-typescript": "^1.15.0",
24
24
  "cli-highlight": "^2.1.11",
25
25
  "croner": "^9.0.0",
26
- "mu-core": "^0.16.19",
27
- "mu-tui": "^0.16.19"
26
+ "mu-core": "^0.16.20",
27
+ "mu-tui": "^0.16.20"
28
28
  },
29
29
  "_generatedBy": "dnt@dev",
30
30
  "types": "./esm/index.d.ts"
@@ -4,7 +4,6 @@ export interface Candidate {
4
4
  kind: 'file' | 'agent';
5
5
  }
6
6
  export declare function collectCandidates(cwd: string, agentNames: string[]): Candidate[];
7
- export declare function invalidateCandidates(): void;
8
7
  export declare function rank(query: string, candidates: Candidate[], limit?: number): Candidate[];
9
8
  export interface ActiveMention {
10
9
  start: number;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.collectCandidates = collectCandidates;
4
- exports.invalidateCandidates = invalidateCandidates;
5
4
  exports.rank = rank;
6
5
  exports.activeMention = activeMention;
6
+ const node_child_process_1 = require("node:child_process");
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = require("node:path");
9
9
  const IGNORED = new Set([
@@ -25,7 +25,21 @@ const IGNORED = new Set([
25
25
  ]);
26
26
  const MAX_ENTRIES = 5000;
27
27
  const MAX_DEPTH = 6;
28
- let cache;
28
+ function gitFiles(cwd) {
29
+ try {
30
+ const out = (0, node_child_process_1.execFileSync)('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {
31
+ cwd,
32
+ encoding: 'utf8',
33
+ maxBuffer: 64 * 1024 * 1024,
34
+ stdio: ['ignore', 'pipe', 'ignore'],
35
+ });
36
+ const files = out.split('\0').filter((f) => f.length > 0);
37
+ return files.length > 0 ? files.slice(0, MAX_ENTRIES).sort() : undefined;
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
29
43
  function walk(cwd) {
30
44
  const out = [];
31
45
  const stack = [{ dir: cwd, depth: 0 }];
@@ -65,30 +79,41 @@ function walk(cwd) {
65
79
  return out.sort();
66
80
  }
67
81
  function collectCandidates(cwd, agentNames) {
68
- if (!cache || cache.cwd !== cwd)
69
- cache = { cwd, files: walk(cwd) };
82
+ const paths = gitFiles(cwd) ?? walk(cwd);
70
83
  const agents = agentNames.map((name) => ({ label: `@${name}`, insert: name, kind: 'agent' }));
71
- const files = cache.files.map((path) => ({ label: path, insert: path, kind: 'file' }));
84
+ const files = paths.map((path) => ({ label: path, insert: path, kind: 'file' }));
72
85
  return [...agents, ...files];
73
86
  }
74
- function invalidateCandidates() {
75
- cache = undefined;
87
+ function isBoundary(target, i) {
88
+ if (i === 0)
89
+ return true;
90
+ const prev = target[i - 1] ?? '';
91
+ if (/[/\\_\-. ]/.test(prev))
92
+ return true;
93
+ const cur = target[i] ?? '';
94
+ return prev === prev.toLowerCase() && prev !== prev.toUpperCase() && cur === cur.toUpperCase() &&
95
+ cur !== cur.toLowerCase();
76
96
  }
77
- function score(query, target) {
97
+ function fuzzyScore(query, target) {
78
98
  if (query.length === 0)
79
99
  return 0;
80
100
  const q = query.toLowerCase();
81
- const t = target.toLowerCase();
82
101
  let qi = 0;
83
102
  let total = 0;
84
103
  let prev = -2;
85
- for (let i = 0; i < t.length && qi < q.length; i++) {
86
- if (t[i] === q[qi]) {
104
+ let run = 0;
105
+ for (let i = 0; i < target.length && qi < q.length; i++) {
106
+ if (target[i].toLowerCase() === q[qi]) {
87
107
  let s = 1;
88
- if (i === prev + 1)
89
- s += 2;
90
- if (i === 0 || /[/_\-. ]/.test(t[i - 1] ?? ''))
91
- s += 3;
108
+ if (i === prev + 1) {
109
+ run += 1;
110
+ s += 2 + run;
111
+ }
112
+ else {
113
+ run = 0;
114
+ }
115
+ if (isBoundary(target, i))
116
+ s += 4;
92
117
  total += s;
93
118
  prev = i;
94
119
  qi += 1;
@@ -96,14 +121,40 @@ function score(query, target) {
96
121
  }
97
122
  if (qi < q.length)
98
123
  return undefined;
99
- return total - t.length * 0.01;
124
+ return total;
125
+ }
126
+ function basename(path) {
127
+ const cut = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
128
+ return cut === -1 ? path : path.slice(cut + 1);
129
+ }
130
+ function score(query, candidate) {
131
+ if (query.length === 0)
132
+ return 0;
133
+ const pathScore = fuzzyScore(query, candidate.label);
134
+ if (pathScore === undefined)
135
+ return undefined;
136
+ let total = pathScore;
137
+ if (candidate.kind === 'file') {
138
+ const base = basename(candidate.label);
139
+ const baseScore = fuzzyScore(query, base);
140
+ if (baseScore !== undefined) {
141
+ total += baseScore * 2;
142
+ const lb = base.toLowerCase();
143
+ const lq = query.toLowerCase();
144
+ if (lb === lq)
145
+ total += 100;
146
+ else if (lb.startsWith(lq))
147
+ total += 40;
148
+ }
149
+ }
150
+ return total - candidate.label.length * 0.05;
100
151
  }
101
152
  function rank(query, candidates, limit = 8) {
102
153
  if (!query)
103
154
  return candidates.slice(0, limit);
104
155
  const scored = [];
105
156
  for (const candidate of candidates) {
106
- const s = score(query, candidate.label);
157
+ const s = score(query, candidate);
107
158
  if (s !== undefined)
108
159
  scored.push({ candidate, score: s });
109
160
  }
@@ -183,7 +183,7 @@ function formatToolArgs(name, input, max = 120) {
183
183
  if (input === null || typeof input !== 'object')
184
184
  return truncateText(String(input ?? ''), max);
185
185
  const args = input;
186
- if (name === 'edit' || name === 'write' || name === 'read' || name === 'list_dir') {
186
+ if (name === 'edit' || name === 'write' || name === 'read' || name === 'list') {
187
187
  return truncateText(stringifyArg(args.path), max);
188
188
  }
189
189
  if (name === 'bash')