incur 0.3.10 → 0.3.11

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.
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/internal/helpers.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,iDAAiD;AACjD,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAcxD;AAED,uGAAuG;AACvG,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS,CAgBvF"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/internal/helpers.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,iDAAiD;AACjD,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAcxD;AAED,uGAAuG;AACvG,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS,CA8BvF"}
@@ -25,17 +25,27 @@ export function levenshtein(a, b) {
25
25
  /** Suggests the closest command name from a set, returning it if within a reasonable edit distance. */
26
26
  export function suggest(input, candidates) {
27
27
  const threshold = input.length <= 4 ? 2 : Math.floor(input.length / 2);
28
- let best;
29
- let bestDist = threshold + 1;
28
+ const lower = input.toLowerCase();
30
29
  const all = Array.isArray(candidates) ? candidates : [...candidates];
31
- // unambiguous prefix match
32
- const prefixMatches = all.filter((c) => c.startsWith(input) && c !== input);
33
- if (prefixMatches.length === 1)
34
- return prefixMatches[0];
30
+ let best;
31
+ let bestScore = Infinity;
35
32
  for (const c of all) {
36
- const d = levenshtein(input, c);
37
- if (d < bestDist) {
38
- bestDist = d;
33
+ const lc = c.toLowerCase();
34
+ const dist = levenshtein(lower, lc);
35
+ let score;
36
+ if (lc.startsWith(lower) && lc !== lower)
37
+ // prefix match — best tier
38
+ score = dist;
39
+ else if (lc.includes(lower))
40
+ // contains match — middle tier
41
+ score = 100 + dist;
42
+ else if (dist <= threshold)
43
+ // fuzzy match — last tier
44
+ score = 200 + dist;
45
+ else
46
+ continue;
47
+ if (score < bestScore) {
48
+ bestScore = score;
39
49
  best = c;
40
50
  }
41
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/internal/helpers.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AAC9D,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IAClB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IAClB,MAAM,EAAE,GAAa,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IAC/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAE,CAAA;QACjB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACT,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CAAE,CAAA;YAClB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAA;YAC7E,IAAI,GAAG,GAAG,CAAA;QACZ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC,CAAC,CAAE,CAAA;AACf,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,UAA4B;IACjE,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACtE,IAAI,IAAwB,CAAA;IAC5B,IAAI,QAAQ,GAAG,SAAS,GAAG,CAAC,CAAA;IAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAA;IACpE,2BAA2B;IAC3B,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAA;IAC3E,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC,CAAC,CAAC,CAAA;IACvD,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QAC/B,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACjB,QAAQ,GAAG,CAAC,CAAA;YACZ,IAAI,GAAG,CAAC,CAAA;QACV,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/internal/helpers.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "[!start-pkg]": "",
17
17
  "name": "incur",
18
18
  "type": "module",
19
- "version": "0.3.10",
19
+ "version": "0.3.11",
20
20
  "license": "MIT",
21
21
  "repository": {
22
22
  "type": "git",
@@ -57,7 +57,19 @@ describe('suggest', () => {
57
57
  expect(suggest('ski', ['mcp', 'skills', 'completions'])).toBe('skills')
58
58
  })
59
59
 
60
- test('falls back to levenshtein for ambiguous prefix', () => {
60
+ test('prefers shorter prefix match among ambiguous prefixes', () => {
61
61
  expect(suggest('ski', ['skills', 'skip'])).toBe('skip')
62
62
  })
63
+
64
+ test('prefix match beats fuzzy match', () => {
65
+ expect(suggest('iss', ['issue', 'is', 'miss'])).toBe('issue')
66
+ })
67
+
68
+ test('contains match beats fuzzy match', () => {
69
+ expect(suggest('sue', ['issue', 'use', 'sum'])).toBe('issue')
70
+ })
71
+
72
+ test('case-insensitive matching', () => {
73
+ expect(suggest('Deploy', ['deploy', 'list'])).toBe('deploy')
74
+ })
63
75
  })
@@ -28,16 +28,30 @@ export function levenshtein(a: string, b: string): number {
28
28
  /** Suggests the closest command name from a set, returning it if within a reasonable edit distance. */
29
29
  export function suggest(input: string, candidates: Iterable<string>): string | undefined {
30
30
  const threshold = input.length <= 4 ? 2 : Math.floor(input.length / 2)
31
- let best: string | undefined
32
- let bestDist = threshold + 1
31
+ const lower = input.toLowerCase()
33
32
  const all = Array.isArray(candidates) ? candidates : [...candidates]
34
- // unambiguous prefix match
35
- const prefixMatches = all.filter((c) => c.startsWith(input) && c !== input)
36
- if (prefixMatches.length === 1) return prefixMatches[0]
33
+
34
+ let best: string | undefined
35
+ let bestScore = Infinity
36
+
37
37
  for (const c of all) {
38
- const d = levenshtein(input, c)
39
- if (d < bestDist) {
40
- bestDist = d
38
+ const lc = c.toLowerCase()
39
+ const dist = levenshtein(lower, lc)
40
+
41
+ let score: number
42
+ if (lc.startsWith(lower) && lc !== lower)
43
+ // prefix match — best tier
44
+ score = dist
45
+ else if (lc.includes(lower))
46
+ // contains match — middle tier
47
+ score = 100 + dist
48
+ else if (dist <= threshold)
49
+ // fuzzy match — last tier
50
+ score = 200 + dist
51
+ else continue
52
+
53
+ if (score < bestScore) {
54
+ bestScore = score
41
55
  best = c
42
56
  }
43
57
  }