capman 0.5.2 → 0.5.4

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 (53) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/CODEBASE.md +15 -9
  3. package/bin/lib/cmd-explain.js +2 -2
  4. package/bin/lib/cmd-run.js +2 -2
  5. package/bin/lib/shared.js +8 -2
  6. package/dist/cjs/cache.d.ts +2 -1
  7. package/dist/cjs/cache.d.ts.map +1 -1
  8. package/dist/cjs/cache.js +11 -6
  9. package/dist/cjs/cache.js.map +1 -1
  10. package/dist/cjs/engine.d.ts +30 -0
  11. package/dist/cjs/engine.d.ts.map +1 -1
  12. package/dist/cjs/engine.js +69 -25
  13. package/dist/cjs/engine.js.map +1 -1
  14. package/dist/cjs/generator.d.ts.map +1 -1
  15. package/dist/cjs/generator.js +16 -1
  16. package/dist/cjs/generator.js.map +1 -1
  17. package/dist/cjs/learning.d.ts +20 -10
  18. package/dist/cjs/learning.d.ts.map +1 -1
  19. package/dist/cjs/learning.js +146 -129
  20. package/dist/cjs/learning.js.map +1 -1
  21. package/dist/cjs/matcher.d.ts +5 -2
  22. package/dist/cjs/matcher.d.ts.map +1 -1
  23. package/dist/cjs/matcher.js +73 -10
  24. package/dist/cjs/matcher.js.map +1 -1
  25. package/dist/cjs/parser.js +8 -2
  26. package/dist/cjs/parser.js.map +1 -1
  27. package/dist/cjs/resolver.d.ts +7 -0
  28. package/dist/cjs/resolver.d.ts.map +1 -1
  29. package/dist/cjs/resolver.js +47 -23
  30. package/dist/cjs/resolver.js.map +1 -1
  31. package/dist/cjs/schema.d.ts +93 -1
  32. package/dist/cjs/schema.d.ts.map +1 -1
  33. package/dist/cjs/schema.js +5 -2
  34. package/dist/cjs/schema.js.map +1 -1
  35. package/dist/cjs/version.d.ts +1 -1
  36. package/dist/cjs/version.js +1 -1
  37. package/dist/esm/cache.d.ts +2 -1
  38. package/dist/esm/cache.js +11 -6
  39. package/dist/esm/engine.d.ts +30 -0
  40. package/dist/esm/engine.js +69 -25
  41. package/dist/esm/generator.js +16 -1
  42. package/dist/esm/learning.d.ts +20 -10
  43. package/dist/esm/learning.js +146 -129
  44. package/dist/esm/matcher.d.ts +5 -2
  45. package/dist/esm/matcher.js +70 -10
  46. package/dist/esm/parser.js +8 -2
  47. package/dist/esm/resolver.d.ts +7 -0
  48. package/dist/esm/resolver.js +47 -23
  49. package/dist/esm/schema.d.ts +93 -1
  50. package/dist/esm/schema.js +5 -2
  51. package/dist/esm/version.d.ts +1 -1
  52. package/dist/esm/version.js +1 -1
  53. package/package.json +11 -10
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- export declare const CapmanConfigSchema: z.ZodObject<{
2
+ export declare const CapmanConfigSchema: z.ZodEffects<z.ZodObject<{
3
3
  app: z.ZodString;
4
4
  baseUrl: z.ZodOptional<z.ZodString>;
5
5
  capabilities: z.ZodEffects<z.ZodArray<z.ZodObject<{
@@ -405,6 +405,98 @@ export declare const CapmanConfigSchema: z.ZodObject<{
405
405
  examples?: string[] | undefined;
406
406
  }[];
407
407
  baseUrl?: string | undefined;
408
+ }>, {
409
+ app: string;
410
+ capabilities: {
411
+ name: string;
412
+ id: string;
413
+ params: {
414
+ name: string;
415
+ required: boolean;
416
+ description: string;
417
+ source: "user_query" | "session" | "context" | "static";
418
+ default?: string | number | boolean | undefined;
419
+ }[];
420
+ description: string;
421
+ returns: string[];
422
+ resolver: {
423
+ type: "api";
424
+ endpoints: {
425
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
426
+ path: string;
427
+ params?: string[] | undefined;
428
+ }[];
429
+ } | {
430
+ type: "nav";
431
+ destination: string;
432
+ hint?: string | undefined;
433
+ } | {
434
+ type: "hybrid";
435
+ api: {
436
+ endpoints: {
437
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
438
+ path: string;
439
+ params?: string[] | undefined;
440
+ }[];
441
+ };
442
+ nav: {
443
+ destination: string;
444
+ hint?: string | undefined;
445
+ };
446
+ };
447
+ privacy: {
448
+ level: "public" | "user_owned" | "admin";
449
+ note?: string | undefined;
450
+ };
451
+ examples?: string[] | undefined;
452
+ }[];
453
+ baseUrl?: string | undefined;
454
+ }, {
455
+ app: string;
456
+ capabilities: {
457
+ name: string;
458
+ id: string;
459
+ params: {
460
+ name: string;
461
+ required: boolean;
462
+ description: string;
463
+ source: "user_query" | "session" | "context" | "static";
464
+ default?: string | number | boolean | undefined;
465
+ }[];
466
+ description: string;
467
+ returns: string[];
468
+ resolver: {
469
+ type: "api";
470
+ endpoints: {
471
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
472
+ path: string;
473
+ params?: string[] | undefined;
474
+ }[];
475
+ } | {
476
+ type: "nav";
477
+ destination: string;
478
+ hint?: string | undefined;
479
+ } | {
480
+ type: "hybrid";
481
+ api: {
482
+ endpoints: {
483
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
484
+ path: string;
485
+ params?: string[] | undefined;
486
+ }[];
487
+ };
488
+ nav: {
489
+ destination: string;
490
+ hint?: string | undefined;
491
+ };
492
+ };
493
+ privacy: {
494
+ level: "public" | "user_owned" | "admin";
495
+ note?: string | undefined;
496
+ };
497
+ examples?: string[] | undefined;
498
+ }[];
499
+ baseUrl?: string | undefined;
408
500
  }>;
409
501
  export declare const ManifestSchema: z.ZodObject<{
410
502
  version: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA2EvB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS7B,CAAA;AAIF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKzB,CAAA;AAIF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,mBAAmB,CAQnE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,mBAAmB,CAQvE"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA2EvB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB9B,CAAA;AAID,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKzB,CAAA;AAIF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,mBAAmB,CAQnE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,mBAAmB,CAQvE"}
@@ -67,11 +67,14 @@ const CapabilitySchema = zod_1.z.object({
67
67
  // ─── Config Schema ────────────────────────────────────────────────────────────
68
68
  exports.CapmanConfigSchema = zod_1.z.object({
69
69
  app: zod_1.z.string().min(1, 'app name is required'),
70
- baseUrl: zod_1.z.string().url('baseUrl must be a valid URL').optional(),
70
+ baseUrl: zod_1.z.string().url().optional(),
71
71
  capabilities: zod_1.z.array(CapabilitySchema)
72
72
  .min(1, 'at least one capability is required')
73
73
  .refine(caps => new Set(caps.map(c => c.id)).size === caps.length, 'capability ids must be unique'),
74
- });
74
+ }).refine(cfg => {
75
+ const needsBaseUrl = cfg.capabilities.some(c => c.resolver.type === 'api' || c.resolver.type === 'hybrid');
76
+ return !needsBaseUrl || !!cfg.baseUrl;
77
+ }, { message: 'baseUrl is required when any capability uses an api or hybrid resolver' });
75
78
  // ─── Manifest Schema ──────────────────────────────────────────────────────────
76
79
  exports.ManifestSchema = zod_1.z.object({
77
80
  version: zod_1.z.string(),
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.5.2";
1
+ export declare const VERSION = "0.5.3";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // Auto-generated by scripts/version.js — do not edit manually
5
- exports.VERSION = '0.5.2';
5
+ exports.VERSION = '0.5.3';
6
6
  //# sourceMappingURL=version.js.map
@@ -29,10 +29,11 @@ export declare class MemoryCache implements CacheStore {
29
29
  export declare class FileCache implements CacheStore {
30
30
  private filePath;
31
31
  private store;
32
- private loaded;
32
+ private loadPromise;
33
33
  private saveQueue;
34
34
  constructor(filePath?: string);
35
35
  private load;
36
+ private _doLoad;
36
37
  private save;
37
38
  private _doSave;
38
39
  get(key: string, ttlMs?: number): Promise<CacheEntry | null>;
package/dist/esm/cache.js CHANGED
@@ -66,7 +66,7 @@ const FILE_CACHE_MAX = 2048;
66
66
  export class FileCache {
67
67
  constructor(filePath = '.capman/cache.json') {
68
68
  this.store = new Map();
69
- this.loaded = false;
69
+ this.loadPromise = null;
70
70
  this.saveQueue = Promise.resolve();
71
71
  const cwd = process.cwd();
72
72
  const resolved = path.resolve(cwd, filePath);
@@ -78,9 +78,13 @@ export class FileCache {
78
78
  this.filePath = resolved;
79
79
  logger.info(`FileCache initialized — writing to: ${this.filePath}`);
80
80
  }
81
- async load() {
82
- if (this.loaded)
83
- return;
81
+ load() {
82
+ if (!this.loadPromise) {
83
+ this.loadPromise = this._doLoad();
84
+ }
85
+ return this.loadPromise;
86
+ }
87
+ async _doLoad() {
84
88
  try {
85
89
  const raw = await fs.promises.readFile(this.filePath, 'utf-8');
86
90
  const parsed = JSON.parse(raw);
@@ -102,7 +106,6 @@ export class FileCache {
102
106
  catch {
103
107
  // File doesn't exist yet — start fresh
104
108
  }
105
- this.loaded = true;
106
109
  }
107
110
  save() {
108
111
  this.saveQueue = this.saveQueue.then(() => this._doSave());
@@ -112,7 +115,9 @@ export class FileCache {
112
115
  try {
113
116
  const dir = path.dirname(this.filePath);
114
117
  await fs.promises.mkdir(dir, { recursive: true });
115
- await fs.promises.writeFile(this.filePath, JSON.stringify(Object.fromEntries(this.store), null, 2));
118
+ const tmp = `${this.filePath}.tmp`;
119
+ await fs.promises.writeFile(tmp, JSON.stringify(Object.fromEntries(this.store), null, 2));
120
+ await fs.promises.rename(tmp, this.filePath);
116
121
  }
117
122
  catch (err) {
118
123
  logger.warn(`Failed to save file cache to ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`);
@@ -83,6 +83,20 @@ export interface EngineOptions {
83
83
  * @default 60000
84
84
  */
85
85
  llmCircuitBreakerResetMs?: number;
86
+ /**
87
+ * Enable fuzzy matching using Fuse.js — catches paraphrases, typos,
88
+ * and morphological variants that exact keyword matching misses.
89
+ * Example: "cancel my booking" matches a capability with "abort reservation" examples.
90
+ * Only applies in balanced and accurate modes — never in cheap mode.
91
+ * @default false
92
+ */
93
+ fuzzyMatch?: boolean;
94
+ /**
95
+ * Fuse.js threshold for fuzzy matching. 0.0 = exact match only, 1.0 = match anything.
96
+ * Lower values are stricter. Only used when fuzzyMatch is true.
97
+ * @default 0.4
98
+ */
99
+ fuzzyThreshold?: number;
86
100
  }
87
101
  export interface EngineResult {
88
102
  match: MatchResult;
@@ -105,6 +119,8 @@ export declare class CapmanEngine {
105
119
  private headers?;
106
120
  private threshold;
107
121
  private cacheTtlMs;
122
+ private fuzzyMatch;
123
+ private fuzzyThreshold;
108
124
  private maxLLMCallsPerMinute;
109
125
  private llmCooldownMs;
110
126
  private llmCircuitBreakerThreshold;
@@ -143,6 +159,20 @@ export declare class CapmanEngine {
143
159
  * Clear the cache.
144
160
  */
145
161
  clearCache(): Promise<void>;
162
+ private checkManifestVersion;
163
+ /**
164
+ * Replaces the active manifest without creating a new engine instance.
165
+ * Useful for hot-reloading manifests in long-running servers without
166
+ * losing cache, learning history, or rate limiter state.
167
+ *
168
+ * Note: clears the cache automatically — cached results from the old
169
+ * manifest are no longer valid after the manifest changes.
170
+ *
171
+ * @example
172
+ * const newManifest = generate(updatedConfig)
173
+ * await engine.loadManifest(newManifest)
174
+ */
175
+ loadManifest(manifest: Manifest): Promise<void>;
146
176
  /**
147
177
  * Explain what would happen for a query — without executing it.
148
178
  * Shows matched capability, all candidate scores with reasoning,
@@ -25,6 +25,8 @@ export class CapmanEngine {
25
25
  this.llmCooldownMs = options.llmCooldownMs ?? 0;
26
26
  this.llmCircuitBreakerThreshold = options.llmCircuitBreakerThreshold ?? 3;
27
27
  this.llmCircuitBreakerResetMs = options.llmCircuitBreakerResetMs ?? 60_000;
28
+ this.fuzzyMatch = options.fuzzyMatch ?? false;
29
+ this.fuzzyThreshold = options.fuzzyThreshold ?? 0.4;
28
30
  // Cache — default MemoryCache (no filesystem writes), or disabled with false
29
31
  // Use FileCache or ComboCache explicitly for persistence across restarts
30
32
  this.cache = options.cache === false
@@ -37,15 +39,7 @@ export class CapmanEngine {
37
39
  : (options.learning ?? new MemoryLearningStore());
38
40
  logger.info(`CapmanEngine initialized — mode: ${this.mode}, cache: ${this.cache ? 'enabled' : 'disabled'}, learning: ${this.learning ? 'enabled' : 'disabled'}`);
39
41
  // ── Manifest version compatibility check ─────────────────────────────────
40
- if (options.manifest.version) {
41
- const [mMaj, mMin] = options.manifest.version.split('.').map(Number);
42
- const [eMaj, eMin] = VERSION.split('.').map(Number);
43
- if (mMaj !== eMaj || mMin !== eMin) {
44
- console.warn(`[capman] Manifest version "${options.manifest.version}" was generated with a ` +
45
- `different engine version than "${VERSION}". This is usually fine across patch versions. ` +
46
- `If you experience unexpected matching issues, regenerate with: npx capman generate`);
47
- }
48
- }
42
+ this.checkManifestVersion(options.manifest);
49
43
  }
50
44
  /**
51
45
  * Ask the engine a natural language query.
@@ -74,7 +68,8 @@ export class CapmanEngine {
74
68
  const cached = await this.cache.get(queryKey, this.cacheTtlMs ?? undefined);
75
69
  if (cached) {
76
70
  steps.push({ type: 'cache_check', status: 'hit', durationMs: Date.now() - cacheStart, detail: 'Served from cache' });
77
- logger.info(`Cache hit for: "${query}"`);
71
+ logger.info(`Cache hit — capability: "${cached.result.capability?.id ?? 'none'}"`);
72
+ logger.debug(`Cache hit for query: "${query}"`);
78
73
  // Re-extract params from the current query — never re-use cached params.
79
74
  // Cached params belong to the original query (potentially from a different user).
80
75
  // e.g. User A: "show orders for john" → cached with { customer: 'john' }
@@ -125,15 +120,7 @@ export class CapmanEngine {
125
120
  detail: `level: ${privacyLevel}`,
126
121
  });
127
122
  }
128
- // ── Step 4: Cache the match result (public capabilities only) ─────────────
129
- // Non-public capabilities are never cached — prevents auth bypass where
130
- // User A's cached match is served to User B without privacy enforcement.
131
- if (this.cache && matchResult.capability
132
- && matchResult.capability.privacy.level === 'public') {
133
- const queryKey = normalizeQuery(query);
134
- await this.cache.set(queryKey, matchResult);
135
- }
136
- // ── Step 5: Resolve ──────────────────────────────────────────────────────
123
+ // ── Step 4: Resolve ──────────────────────────────────────────────────────
137
124
  const resolveStart = Date.now();
138
125
  const resolution = await _resolve(matchResult, matchResult.extractedParams, this.resolveOptions(overrides));
139
126
  steps.push({
@@ -142,6 +129,16 @@ export class CapmanEngine {
142
129
  durationMs: Date.now() - resolveStart,
143
130
  detail: resolution.error ?? `via ${resolution.resolverType}`,
144
131
  });
132
+ // ── Step 5: Cache after successful resolution ────────────────────────────
133
+ // Only cache when resolution succeeded — a failed resolution (network error,
134
+ // auth failure, bad params) must not poison the cache. A cached failed match
135
+ // would cause every subsequent cache hit to attempt the same failing resolution
136
+ // until TTL expires.
137
+ if (this.cache && resolution.success && matchResult.capability
138
+ && matchResult.capability.privacy.level === 'public') {
139
+ const queryKey = normalizeQuery(query);
140
+ await this.cache.set(queryKey, matchResult);
141
+ }
145
142
  // ── Step 6: Build reasoning array ────────────────────────────────────────
146
143
  const reasoning = [];
147
144
  if (matchResult.candidates.length) {
@@ -212,6 +209,41 @@ export class CapmanEngine {
212
209
  if (this.cache)
213
210
  await this.cache.clear();
214
211
  }
212
+ checkManifestVersion(manifest) {
213
+ if (!manifest.version)
214
+ return;
215
+ const SEMVER_RE = /^\d+\.\d+\.\d+$/;
216
+ if (SEMVER_RE.test(manifest.version) && SEMVER_RE.test(VERSION)) {
217
+ const [mMaj, mMin] = manifest.version.split('.').map(Number);
218
+ const [eMaj, eMin] = VERSION.split('.').map(Number);
219
+ if (mMaj !== eMaj || mMin !== eMin) {
220
+ console.warn(`[capman] Manifest version "${manifest.version}" was generated with a ` +
221
+ `different engine version than "${VERSION}". This is usually fine across patch versions. ` +
222
+ `If you experience unexpected matching issues, regenerate with: npx capman generate`);
223
+ }
224
+ }
225
+ else if (manifest.version !== VERSION) {
226
+ console.warn(`[capman] Manifest version "${manifest.version}" could not be compared ` +
227
+ `to engine version "${VERSION}" — version strings are not valid semver.`);
228
+ }
229
+ }
230
+ /**
231
+ * Replaces the active manifest without creating a new engine instance.
232
+ * Useful for hot-reloading manifests in long-running servers without
233
+ * losing cache, learning history, or rate limiter state.
234
+ *
235
+ * Note: clears the cache automatically — cached results from the old
236
+ * manifest are no longer valid after the manifest changes.
237
+ *
238
+ * @example
239
+ * const newManifest = generate(updatedConfig)
240
+ * await engine.loadManifest(newManifest)
241
+ */
242
+ async loadManifest(manifest) {
243
+ this.checkManifestVersion(manifest);
244
+ this.manifest = manifest;
245
+ await this.clearCache();
246
+ }
215
247
  /**
216
248
  * Explain what would happen for a query — without executing it.
217
249
  * Shows matched capability, all candidate scores with reasoning,
@@ -239,6 +271,12 @@ export class CapmanEngine {
239
271
  const start = Date.now();
240
272
  // ── Match — shared with ask() via _runMatch() ─────────────────────────────
241
273
  let { matchResult, resolvedVia: _resolvedVia } = await this._runMatch(query);
274
+ // explain() never reads from cache — it always runs a fresh match.
275
+ // This assertion catches any future refactor that accidentally adds
276
+ // cache reads to _runMatch() when called from explain().
277
+ if (_resolvedVia === 'cache') {
278
+ throw new Error('Invariant violation: explain() must never resolve via cache');
279
+ }
242
280
  let resolvedVia = _resolvedVia;
243
281
  // ── Apply learning boost (same as ask()) ─────────────────────────────────
244
282
  matchResult = await this.applyBoostToMatchResult(query, matchResult);
@@ -438,6 +476,11 @@ export class CapmanEngine {
438
476
  async _runMatch(query, steps) {
439
477
  let matchResult;
440
478
  let resolvedVia = 'keyword';
479
+ // Fuzzy options — never applied in cheap mode
480
+ const fuzzyOpts = {
481
+ fuzzyMatch: this.fuzzyMatch,
482
+ fuzzyThreshold: this.fuzzyThreshold,
483
+ };
441
484
  switch (this.mode) {
442
485
  case 'cheap': {
443
486
  const t = Date.now();
@@ -451,7 +494,7 @@ export class CapmanEngine {
451
494
  if (skipReason) {
452
495
  logger.warn(`LLM skipped — ${skipReason} — falling back to keyword`);
453
496
  const t = Date.now();
454
- matchResult = _match(query, this.manifest);
497
+ matchResult = _match(query, this.manifest, fuzzyOpts);
455
498
  steps?.push({ type: 'keyword_match', status: 'pass', durationMs: Date.now() - t, detail: `llm skipped: ${skipReason}` });
456
499
  }
457
500
  else {
@@ -461,7 +504,7 @@ export class CapmanEngine {
461
504
  this.recordLLMSuccess();
462
505
  resolvedVia = 'llm';
463
506
  // Merge keyword scores into LLM candidates so boost has real signal for alternatives
464
- const kwResult = _match(query, this.manifest);
507
+ const kwResult = _match(query, this.manifest, fuzzyOpts);
465
508
  matchResult = {
466
509
  ...matchResult,
467
510
  candidates: matchResult.candidates.map(c => ({
@@ -479,7 +522,7 @@ export class CapmanEngine {
479
522
  this.recordLLMFailure();
480
523
  logger.warn(`LLM call failed — falling back to keyword: ${err instanceof Error ? err.message : String(err)}`);
481
524
  const t2 = Date.now();
482
- matchResult = _match(query, this.manifest);
525
+ matchResult = _match(query, this.manifest, fuzzyOpts);
483
526
  steps?.push({ type: 'llm_match', status: 'fail', durationMs: Date.now() - t, detail: String(err) });
484
527
  steps?.push({ type: 'keyword_match', status: 'pass', durationMs: Date.now() - t2, detail: 'fallback after llm failure' });
485
528
  }
@@ -488,7 +531,7 @@ export class CapmanEngine {
488
531
  else {
489
532
  logger.warn('accurate mode requires llm — falling back to keyword');
490
533
  const t = Date.now();
491
- matchResult = _match(query, this.manifest);
534
+ matchResult = _match(query, this.manifest, fuzzyOpts);
492
535
  steps?.push({ type: 'keyword_match', status: 'pass', durationMs: Date.now() - t, detail: 'llm not provided, used keyword' });
493
536
  }
494
537
  break;
@@ -496,7 +539,7 @@ export class CapmanEngine {
496
539
  case 'balanced':
497
540
  default: {
498
541
  const t1 = Date.now();
499
- const keywordResult = _match(query, this.manifest);
542
+ const keywordResult = _match(query, this.manifest, fuzzyOpts);
500
543
  steps?.push({ type: 'keyword_match', status: 'pass', durationMs: Date.now() - t1, detail: `confidence: ${keywordResult.confidence}%` });
501
544
  if (keywordResult.confidence >= this.threshold || !this.llm) {
502
545
  matchResult = keywordResult;
@@ -509,7 +552,8 @@ export class CapmanEngine {
509
552
  matchResult = keywordResult;
510
553
  }
511
554
  else {
512
- logger.info(`Low confidence (${keywordResult.confidence}%) — escalating to LLM`);
555
+ logger.info(`Low keyword confidence (${keywordResult.confidence}%) — escalating to LLM`);
556
+ logger.debug(`Query escalated to LLM: "${query}"`);
513
557
  const t2 = Date.now();
514
558
  try {
515
559
  matchResult = await _matchWithLLM(query, this.manifest, { llm: this.llm });
@@ -37,8 +37,23 @@ export function loadConfig(configPath) {
37
37
  raw = mod.default ?? mod;
38
38
  }
39
39
  catch (err) {
40
+ const code = err.code;
41
+ const message = err instanceof Error ? err.message : String(err);
42
+ // ERR_REQUIRE_ESM — file is an ES module (Node v12–v21)
43
+ // On Node v22+, the error message changed but code remains ERR_REQUIRE_ESM
44
+ // for .mjs files; .js files in ESM packages may show a different message.
45
+ const isESM = code === 'ERR_REQUIRE_ESM' ||
46
+ message.includes('require() of ES Module') ||
47
+ message.includes('must use import to load ES Module');
48
+ if (isESM) {
49
+ throw new Error(`Config file "${resolved}" is an ES module but capman requires CommonJS.\n` +
50
+ `Solutions:\n` +
51
+ ` 1. Rename to capman.config.cjs\n` +
52
+ ` 2. Change to: module.exports = { ... }\n` +
53
+ ` 3. Remove "type": "module" from your package.json`);
54
+ }
40
55
  throw new Error(`Failed to load config at ${resolved}:\n` +
41
- ` ${err instanceof Error ? err.message : String(err)}\n\n` +
56
+ ` ${message}\n\n` +
42
57
  `Check your config file for syntax errors.`);
43
58
  }
44
59
  // Catch invalid config structure
@@ -28,19 +28,31 @@ export interface LearningStore {
28
28
  }>>;
29
29
  /** Returns the live keyword index without rebuilding — O(1) */
30
30
  getIndex(): Promise<Record<string, Record<string, number>>>;
31
+ /**
32
+ * Removes this store from the exit flush registry and flushes any pending data.
33
+ * Call when the store is no longer needed to prevent memory leaks.
34
+ * Must be awaited — final flush is async.
35
+ */
36
+ destroy(): Promise<void>;
31
37
  }
32
38
  export declare class FileLearningStore implements LearningStore {
33
39
  private filePath;
34
40
  private entries;
35
- private loaded;
41
+ private loadPromise;
36
42
  private saveQueue;
37
- private index;
38
- private statsCounter;
43
+ private learningIndex;
44
+ private dirty;
45
+ private saveTimer;
39
46
  constructor(filePath?: string);
40
- private updateIndex;
41
- private subtractFromIndex;
42
- private rebuildIndex;
47
+ flushSync(): void;
48
+ /**
49
+ * Removes this store from the exit flush registry and cancels any pending save timer.
50
+ * Call when the store is no longer needed to prevent memory leaks in long-running servers.
51
+ */
52
+ destroy(): Promise<void>;
43
53
  private load;
54
+ private _doLoad;
55
+ private scheduleSave;
44
56
  private save;
45
57
  private _doSave;
46
58
  record(entry: LearningEntry): Promise<void>;
@@ -54,16 +66,14 @@ export declare class FileLearningStore implements LearningStore {
54
66
  }
55
67
  export declare class MemoryLearningStore implements LearningStore {
56
68
  private entries;
57
- private index;
58
- private statsCounter;
69
+ private learningIndex;
59
70
  record(entry: LearningEntry): Promise<void>;
60
71
  getStats(): Promise<KeywordStats>;
61
72
  getIndex(): Promise<Record<string, Record<string, number>>>;
62
- private updateIndex;
63
- private subtractFromIndex;
64
73
  getTopCapabilities(limit?: number): Promise<Array<{
65
74
  id: string;
66
75
  hits: number;
67
76
  }>>;
68
77
  clear(): Promise<void>;
78
+ destroy(): Promise<void>;
69
79
  }