capman 0.5.1 → 0.5.3

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 (60) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/CODEBASE.md +2 -1
  3. package/bin/lib/cmd-demo.js +2 -2
  4. package/dist/cjs/cache.d.ts +4 -1
  5. package/dist/cjs/cache.d.ts.map +1 -1
  6. package/dist/cjs/cache.js +42 -20
  7. package/dist/cjs/cache.js.map +1 -1
  8. package/dist/cjs/engine.d.ts +3 -1
  9. package/dist/cjs/engine.d.ts.map +1 -1
  10. package/dist/cjs/engine.js +103 -34
  11. package/dist/cjs/engine.js.map +1 -1
  12. package/dist/cjs/generator.d.ts.map +1 -1
  13. package/dist/cjs/generator.js +16 -1
  14. package/dist/cjs/generator.js.map +1 -1
  15. package/dist/cjs/index.d.ts +3 -2
  16. package/dist/cjs/index.d.ts.map +1 -1
  17. package/dist/cjs/index.js +3 -1
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/learning.d.ts +20 -11
  20. package/dist/cjs/learning.d.ts.map +1 -1
  21. package/dist/cjs/learning.js +169 -135
  22. package/dist/cjs/learning.js.map +1 -1
  23. package/dist/cjs/matcher.d.ts +4 -1
  24. package/dist/cjs/matcher.d.ts.map +1 -1
  25. package/dist/cjs/matcher.js +33 -13
  26. package/dist/cjs/matcher.js.map +1 -1
  27. package/dist/cjs/parser.js +10 -4
  28. package/dist/cjs/parser.js.map +1 -1
  29. package/dist/cjs/resolver.d.ts +7 -0
  30. package/dist/cjs/resolver.d.ts.map +1 -1
  31. package/dist/cjs/resolver.js +63 -19
  32. package/dist/cjs/resolver.js.map +1 -1
  33. package/dist/cjs/schema.d.ts +106 -14
  34. package/dist/cjs/schema.d.ts.map +1 -1
  35. package/dist/cjs/schema.js +9 -4
  36. package/dist/cjs/schema.js.map +1 -1
  37. package/dist/cjs/types.d.ts +1 -0
  38. package/dist/cjs/types.d.ts.map +1 -1
  39. package/dist/cjs/version.d.ts +1 -1
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/esm/cache.d.ts +4 -1
  42. package/dist/esm/cache.js +42 -20
  43. package/dist/esm/engine.d.ts +3 -1
  44. package/dist/esm/engine.js +104 -35
  45. package/dist/esm/generator.js +16 -1
  46. package/dist/esm/index.d.ts +3 -2
  47. package/dist/esm/index.js +1 -0
  48. package/dist/esm/learning.d.ts +20 -11
  49. package/dist/esm/learning.js +169 -135
  50. package/dist/esm/matcher.d.ts +4 -1
  51. package/dist/esm/matcher.js +31 -12
  52. package/dist/esm/parser.js +10 -4
  53. package/dist/esm/resolver.d.ts +7 -0
  54. package/dist/esm/resolver.js +63 -19
  55. package/dist/esm/schema.d.ts +106 -14
  56. package/dist/esm/schema.js +9 -4
  57. package/dist/esm/types.d.ts +1 -0
  58. package/dist/esm/version.d.ts +1 -1
  59. package/dist/esm/version.js +1 -1
  60. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,63 @@ All notable changes to capman are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.5.3] — 2026-04-25
8
+ ### Fixed
9
+
10
+ **Critical:**
11
+ - `.capman/` added to `.gitignore` — cache and learning files were being committed to git, exposing internal API path structures and cached match data
12
+ - Session `userId` no longer leaks into query string on multi-endpoint capabilities — path template check was joining all endpoint paths before checking for `{param}`, causing false positives. Now checks per-endpoint inside `resolveApi()`
13
+ - File writes are now atomic — `FileCache` and `FileLearningStore` write to a `.tmp` file then rename, preventing corrupt JSON on process crash mid-write
14
+ - `ask()` now caches only after successful resolution — previously cached match result before Step 5 (resolve), permanently poisoning the cache on transient network failures
15
+
16
+ **High:**
17
+ - API path params validated against allowlist in `buildUrl()` — `encodeURIComponent` does not encode `/`, allowing path traversal via params like `../../admin`. Now mirrors the validation already applied in `resolveNav()`
18
+ - Raw query text removed from `info`-level logs — queries like `"orders for jane@corp.com"` were emitted at info level to stdout. Query text now only appears at `debug` level
19
+ - Retry logic now only retries safe/idempotent methods (GET, HEAD, OPTIONS) — previously retried POST/PUT/PATCH/DELETE which could cause duplicate orders, double charges etc. Add `retryAllMethods: true` to `ResolveOptions` to opt in to retrying writes
20
+ - `FileCache` concurrent load guard added — two simultaneous `get()` calls before `loaded = true` both read the file. Now serialized through a shared `loadPromise`
21
+
22
+ **Medium:**
23
+ - Version comparison now validates semver format before parsing — `Number("v0")` → `NaN`, `NaN !== NaN` always true, causing spurious version warnings on every startup for non-semver manifest versions
24
+ - `LearningIndex` class extracted — `updateIndex`, `subtractFromIndex`, `rebuildIndex` were copy-pasted verbatim (~80 lines) between `FileLearningStore` and `MemoryLearningStore`. Both now compose `LearningIndex`
25
+ - `computeStats()` dead code removed — was only used by old `getStats()` before incremental index was introduced in v0.5.0
26
+ - `FileLearningStore` now debounces saves — previously wrote full JSON on every `record()` call (every `ask()`). Now batches with a 5s debounce timer and flushes synchronously on `process.exit`, `SIGTERM`, `SIGINT`
27
+ - YAML catch block now distinguishes `MODULE_NOT_FOUND` from actual parse errors using `err.code` — previously swallowed real YAML syntax errors with a generic message
28
+ - `baseUrl` now required by Zod schema when any capability uses `api` or `hybrid` resolver — previously optional, producing silent relative URLs that fail with opaque connection errors
29
+ - `explain()` now asserts `resolvedVia !== 'cache'` at runtime — makes the invariant that `explain()` never reads from cache explicit and catches future regressions immediately
30
+ - ESM config files now produce an actionable error — `ERR_REQUIRE_ESM` previously surfaced as a generic "Failed to load config" message with no guidance. Now explains the issue and lists three solutions
31
+
32
+ ### Tests
33
+ - 90 tests passing
34
+
35
+ ## [0.5.2] — 2026-04-20
36
+ ### Fixed
37
+
38
+ **Critical:**
39
+ - `[from_session]` sentinel string replaced with `null` in `extractParams()` — was leaking literal `[from_session]` into API URLs and POST request bodies
40
+ - `resolveApi()` POST body now strips null params — session values that weren't injected no longer appear as `null` in request bodies
41
+ - `FileCache` and `FileLearningStore` constructors now validate path stays within working directory — prevents path traversal via caller-supplied `filePath`
42
+ - `FileCache.get()` no longer writes to disk on every cache hit — was triggering full JSON rewrite per request under any real load
43
+ - Cache hits now re-extract params from the current query — previously re-served `extractedParams` from the original cached query, leaking one user's param values to another
44
+
45
+ **High:**
46
+ - Query length and type guards added to `ask()` and `explain()` — throws `TypeError` for non-string input, `RangeError` for queries over 1000 characters
47
+ - `FileCache.load()` now normalizes keys on read — prevents duplicate entries from manual edits or older versions accumulating silently
48
+ - `matchWithLLM` now returns all capabilities as candidates with keyword scores as baseline — previously returned only the LLM winner, making learning boost unable to surface alternatives
49
+ - Manifest descriptions truncated to 200 chars and examples to 100 chars in LLM prompt — prevents context window overflow and reduces injection surface from third-party OpenAPI specs
50
+ - Zod schema now enforces `description` max 500 chars and `examples` max 200 chars per entry
51
+ - Template substitution uses `replaceAll` — `String.replace()` only replaced the first occurrence of a `{param}` placeholder, leaving duplicates unsubstituted
52
+ - TypeScript target bumped to ES2021 — enables `replaceAll`, aligns with minimum supported Node version
53
+
54
+ **Medium:**
55
+ - `MemoryLearningStore.rebuildIndex()` dead code removed — was defined but unreachable since `MemoryLearningStore` has no `load()` method
56
+ - `loadSpec()` in parser now uses `fs.promises.readFile` — was blocking event loop with synchronous `readFileSync` in an async function
57
+ - Raw query text no longer stored verbatim in learning entries — tokenized to keywords only before persisting, eliminates PII (emails, names, IDs) from `.capman/learning.json`
58
+ - `resolveApi()` JSDoc documents parallel execution and no-rollback behavior for multi-endpoint capabilities
59
+ - Version comparison now parses major/minor as integers — string comparison (`"0.10" > "0.9"`) is lexicographic and incorrect
60
+
61
+ ### Tests
62
+ - 87 tests passing
63
+
7
64
  ## [0.5.1] — 2026-04-18
8
65
  ### Fixed
9
66
 
package/CODEBASE.md CHANGED
@@ -147,8 +147,9 @@ Usage analytics and keyword index — now incremental.
147
147
 
148
148
  Key exports:
149
149
  - `LearningStore` interface — `record(entry)`, `getStats()`, `getTopCapabilities(limit)`, `getIndex()`
150
- - `FileLearningStore` — persists to `.capman/learning.json`, caps at 10,000 entries
150
+ - `FileLearningStore` — persists to `.capman/learning.json`, caps at 10,000 entries. Saves are debounced (5s) with synchronous flush on process exit
151
151
  - `MemoryLearningStore` — in-memory only, used in tests
152
+ - `LearningIndex` — internal class shared by both stores. Maintains keyword index and stats counters incrementally. Eliminates ~80 lines of duplication
152
153
 
153
154
  `LearningEntry`:
154
155
  - `query`, `capabilityId`, `confidence`, `intent`, `extractedParams`
@@ -117,13 +117,13 @@ module.exports = function cmdDemo() {
117
117
  const endpoint = result.capability.resolver.endpoints[0]
118
118
  let p = endpoint.path
119
119
  for (const [k, v] of Object.entries(result.extractedParams)) {
120
- if (v) p = p.replace(`{${k}}`, String(v))
120
+ if (v) p = p.replaceAll(`{${k}}`, String(v))
121
121
  }
122
122
  actionLine = `${endpoint.method} ${config.baseUrl}${p}`
123
123
  } else if (result.capability.resolver.type === 'nav') {
124
124
  let dest = result.capability.resolver.destination
125
125
  for (const [k, v] of Object.entries(result.extractedParams)) {
126
- if (v) dest = dest.replace(`{${k}}`, String(v))
126
+ if (v) dest = dest.replaceAll(`{${k}}`, String(v))
127
127
  }
128
128
  actionLine = `navigate → ${dest}`
129
129
  }
@@ -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>;
@@ -47,6 +48,8 @@ export declare class ComboCache implements CacheStore {
47
48
  get(key: string, ttlMs?: number): Promise<CacheEntry | null>;
48
49
  set(key: string, result: MatchResult): Promise<void>;
49
50
  clear(): Promise<void>;
51
+ /** Returns the file-side entry count, not total unique entries across both stores.
52
+ * Memory may have additional promoted entries not reflected here. */
50
53
  size(): Promise<number>;
51
54
  }
52
55
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AAEH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,MAAM,CAQR;AAOD,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAiB5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B;AAMD,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAyC;IACtD,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,SAAS,CAA6C;gBAElD,QAAQ,SAAuB;YAK7B,IAAI;IAiBlB,OAAO,CAAC,IAAI;YAKE,OAAO;IAaf,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAmB5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAI9B;AAID,qBAAa,UAAW,YAAW,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,IAAI,CAAW;gBAEX,QAAQ,SAAuB;IAKrC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAY5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAG9B"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AAEH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,MAAM,CAQR;AAOD,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAiB5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B;AAMD,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,SAAS,CAA+C;gBAEpD,QAAQ,SAAuB;IAcvC,OAAO,CAAC,IAAI;YAOE,OAAO;IAsBzB,OAAO,CAAC,IAAI;YAKE,OAAO;IAYf,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAqB5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAI9B;AAID,qBAAa,UAAW,YAAW,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,IAAI,CAAW;gBAEX,QAAQ,SAAuB;IAKrC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAY5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;0EACsE;IAChE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAG9B"}
package/dist/cjs/cache.js CHANGED
@@ -105,19 +105,37 @@ const FILE_CACHE_MAX = 2048;
105
105
  class FileCache {
106
106
  constructor(filePath = '.capman/cache.json') {
107
107
  this.store = new Map();
108
- this.loaded = false;
108
+ this.loadPromise = null;
109
109
  this.saveQueue = Promise.resolve();
110
- this.filePath = path.resolve(process.cwd(), filePath);
110
+ const cwd = process.cwd();
111
+ const resolved = path.resolve(cwd, filePath);
112
+ const allowedPrefix = cwd === '/' ? '/' : cwd + path.sep;
113
+ if (!resolved.startsWith(allowedPrefix)) {
114
+ throw new Error(`FileCache path "${filePath}" resolves outside the working directory.\n` +
115
+ `Resolved: ${resolved}\nAllowed: ${cwd}`);
116
+ }
117
+ this.filePath = resolved;
111
118
  logger_1.logger.info(`FileCache initialized — writing to: ${this.filePath}`);
112
119
  }
113
- async load() {
114
- if (this.loaded)
115
- return;
120
+ load() {
121
+ if (!this.loadPromise) {
122
+ this.loadPromise = this._doLoad();
123
+ }
124
+ return this.loadPromise;
125
+ }
126
+ async _doLoad() {
116
127
  try {
117
128
  const raw = await fs.promises.readFile(this.filePath, 'utf-8');
118
129
  const parsed = JSON.parse(raw);
119
130
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
120
- this.store = new Map(Object.entries(parsed));
131
+ // Normalize keys on load — prevents duplicate entries from older versions,
132
+ // manual edits, or any path that bypassed normalizeQuery() on write.
133
+ // e.g. "Show me articles" and "show me articles" collapse to the same key.
134
+ const normalized = new Map();
135
+ for (const [k, v] of Object.entries(parsed)) {
136
+ normalized.set(normalizeQuery(k), v);
137
+ }
138
+ this.store = normalized;
121
139
  logger_1.logger.debug(`File cache loaded: ${this.store.size} entries`);
122
140
  }
123
141
  else {
@@ -127,7 +145,6 @@ class FileCache {
127
145
  catch {
128
146
  // File doesn't exist yet — start fresh
129
147
  }
130
- this.loaded = true;
131
148
  }
132
149
  save() {
133
150
  this.saveQueue = this.saveQueue.then(() => this._doSave());
@@ -137,7 +154,9 @@ class FileCache {
137
154
  try {
138
155
  const dir = path.dirname(this.filePath);
139
156
  await fs.promises.mkdir(dir, { recursive: true });
140
- await fs.promises.writeFile(this.filePath, JSON.stringify(Object.fromEntries(this.store), null, 2));
157
+ const tmp = `${this.filePath}.tmp`;
158
+ await fs.promises.writeFile(tmp, JSON.stringify(Object.fromEntries(this.store), null, 2));
159
+ await fs.promises.rename(tmp, this.filePath);
141
160
  }
142
161
  catch (err) {
143
162
  logger_1.logger.warn(`Failed to save file cache to ${this.filePath}: ${err instanceof Error ? err.message : String(err)}`);
@@ -146,20 +165,21 @@ class FileCache {
146
165
  async get(key, ttlMs) {
147
166
  await this.load();
148
167
  const entry = this.store.get(key);
149
- if (entry) {
150
- if (ttlMs && Date.now() - new Date(entry.cachedAt).getTime() > ttlMs) {
151
- this.store.delete(key);
152
- await this.save();
153
- logger_1.logger.debug(`Cache entry expired (file): "${key}"`);
154
- return null;
155
- }
156
- entry.hits++;
168
+ if (!entry)
169
+ return null;
170
+ if (ttlMs && Date.now() - new Date(entry.cachedAt).getTime() > ttlMs) {
157
171
  this.store.delete(key);
158
- this.store.set(key, entry);
159
- logger_1.logger.debug(`Cache hit (file): "${key}"`);
160
- await this.save();
172
+ await this.save(); // eviction must be persisted
173
+ logger_1.logger.debug(`Cache entry expired (file): "${key}"`);
174
+ return null;
161
175
  }
162
- return entry ?? null;
176
+ entry.hits++;
177
+ this.store.delete(key); // reinsert at end for LRU ordering
178
+ this.store.set(key, entry);
179
+ // hits counter is in-memory only — not saved on read
180
+ // saves only happen on set() and eviction to avoid full file rewrite per request
181
+ logger_1.logger.debug(`Cache hit (file): "${key}"`);
182
+ return entry;
163
183
  }
164
184
  async set(key, result) {
165
185
  await this.load();
@@ -218,6 +238,8 @@ class ComboCache {
218
238
  this.file.clear(),
219
239
  ]);
220
240
  }
241
+ /** Returns the file-side entry count, not total unique entries across both stores.
242
+ * Memory may have additional promoted entries not reflected here. */
221
243
  async size() {
222
244
  return this.file.size();
223
245
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import type { LLMMatcherOptions } from './matcher';
3
3
  import type { ResolveOptions, AuthContext } from './resolver';
4
4
  import type { CacheStore } from './cache';
5
5
  import type { LearningStore } from './learning';
6
- import type { MatchMode } from './index';
6
+ import type { MatchMode } from './types';
7
7
  /**
8
8
  * Options for constructing a CapmanEngine instance.
9
9
  *
@@ -93,6 +93,8 @@ export interface EngineResult {
93
93
  trace: ExecutionTrace;
94
94
  }
95
95
  export declare class CapmanEngine {
96
+ /** Maximum allowed query length in characters. Queries exceeding this throw RangeError. */
97
+ static readonly MAX_QUERY_LENGTH = 1000;
96
98
  private manifest;
97
99
  private mode;
98
100
  private llm?;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/engine.ts"],"names":[],"mappings":"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"}
@@ -27,7 +27,7 @@ class CapmanEngine {
27
27
  this.maxLLMCallsPerMinute = options.maxLLMCallsPerMinute ?? 60;
28
28
  this.llmCooldownMs = options.llmCooldownMs ?? 0;
29
29
  this.llmCircuitBreakerThreshold = options.llmCircuitBreakerThreshold ?? 3;
30
- this.llmCircuitBreakerResetMs = options.llmCircuitBreakerResetMs ?? 60000;
30
+ this.llmCircuitBreakerResetMs = options.llmCircuitBreakerResetMs ?? 60_000;
31
31
  // Cache — default MemoryCache (no filesystem writes), or disabled with false
32
32
  // Use FileCache or ComboCache explicitly for persistence across restarts
33
33
  this.cache = options.cache === false
@@ -40,14 +40,22 @@ class CapmanEngine {
40
40
  : (options.learning ?? new learning_1.MemoryLearningStore());
41
41
  logger_1.logger.info(`CapmanEngine initialized — mode: ${this.mode}, cache: ${this.cache ? 'enabled' : 'disabled'}, learning: ${this.learning ? 'enabled' : 'disabled'}`);
42
42
  // ── Manifest version compatibility check ─────────────────────────────────
43
- const manifestMajorMinor = options.manifest.version?.split('.').slice(0, 2).join('.');
44
- const engineMajorMinor = version_1.VERSION.split('.').slice(0, 2).join('.');
45
- if (manifestMajorMinor && manifestMajorMinor !== engineMajorMinor) {
46
- // Use console.warn directly — must be visible regardless of logger level
47
- // Default log level is 'silent' so logger.warn would never be seen
48
- console.warn(`[capman] Manifest version "${options.manifest.version}" was generated with a ` +
49
- `different engine version than "${version_1.VERSION}". If you experience matching issues, ` +
50
- `regenerate with: npx capman generate`);
43
+ if (options.manifest.version) {
44
+ const SEMVER_RE = /^\d+\.\d+\.\d+$/;
45
+ if (SEMVER_RE.test(options.manifest.version) && SEMVER_RE.test(version_1.VERSION)) {
46
+ const [mMaj, mMin] = options.manifest.version.split('.').map(Number);
47
+ const [eMaj, eMin] = version_1.VERSION.split('.').map(Number);
48
+ if (mMaj !== eMaj || mMin !== eMin) {
49
+ console.warn(`[capman] Manifest version "${options.manifest.version}" was generated with a ` +
50
+ `different engine version than "${version_1.VERSION}". This is usually fine across patch versions. ` +
51
+ `If you experience unexpected matching issues, regenerate with: npx capman generate`);
52
+ }
53
+ }
54
+ else if (options.manifest.version !== version_1.VERSION) {
55
+ //console.warn is used instead of logger.warn to avoid the warning being logged to the console
56
+ console.warn(`[capman] Manifest version "${options.manifest.version}" could not be compared ` +
57
+ `to engine version "${version_1.VERSION}" — version strings are not valid semver.`);
58
+ }
51
59
  }
52
60
  }
53
61
  /**
@@ -62,6 +70,12 @@ class CapmanEngine {
62
70
  * console.log(result.resolvedVia) // 'keyword' | 'llm' | 'cache'
63
71
  */
64
72
  async ask(query, overrides = {}) {
73
+ if (!query || typeof query !== 'string') {
74
+ throw new TypeError('query must be a non-empty string');
75
+ }
76
+ if (query.length > CapmanEngine.MAX_QUERY_LENGTH) {
77
+ throw new RangeError(`query exceeds maximum length of ${CapmanEngine.MAX_QUERY_LENGTH} characters`);
78
+ }
65
79
  const start = Date.now();
66
80
  const steps = [];
67
81
  // ── Step 1: Check cache ──────────────────────────────────────────────────
@@ -71,8 +85,20 @@ class CapmanEngine {
71
85
  const cached = await this.cache.get(queryKey, this.cacheTtlMs ?? undefined);
72
86
  if (cached) {
73
87
  steps.push({ type: 'cache_check', status: 'hit', durationMs: Date.now() - cacheStart, detail: 'Served from cache' });
74
- logger_1.logger.info(`Cache hit for: "${query}"`);
75
- const resolution = await (0, resolver_1.resolve)(cached.result, cached.result.extractedParams, this.resolveOptions(overrides));
88
+ logger_1.logger.info(`Cache hit — capability: "${cached.result.capability?.id ?? 'none'}"`);
89
+ logger_1.logger.debug(`Cache hit for query: "${query}"`);
90
+ // Re-extract params from the current query — never re-use cached params.
91
+ // Cached params belong to the original query (potentially from a different user).
92
+ // e.g. User A: "show orders for john" → cached with { customer: 'john' }
93
+ // User B: "show orders for jane" → must get { customer: 'jane' }, not john's
94
+ const freshParams = cached.result.capability
95
+ ? (0, matcher_1.extractParams)(query, cached.result.capability)
96
+ : {};
97
+ const matchWithFreshParams = {
98
+ ...cached.result,
99
+ extractedParams: freshParams,
100
+ };
101
+ const resolution = await (0, resolver_1.resolve)(matchWithFreshParams, freshParams, this.resolveOptions(overrides));
76
102
  const trace = {
77
103
  query,
78
104
  candidates: cached.result.candidates,
@@ -82,13 +108,13 @@ class CapmanEngine {
82
108
  totalMs: Date.now() - start,
83
109
  };
84
110
  const result = {
85
- match: cached.result,
111
+ match: matchWithFreshParams,
86
112
  resolution,
87
113
  resolvedVia: 'cache',
88
114
  durationMs: Date.now() - start,
89
115
  trace,
90
116
  };
91
- await this.recordLearning(query, cached.result, 'cache');
117
+ await this.recordLearning(query, matchWithFreshParams, 'cache');
92
118
  return result;
93
119
  }
94
120
  steps.push({ type: 'cache_check', status: 'miss', durationMs: Date.now() - cacheStart });
@@ -111,15 +137,7 @@ class CapmanEngine {
111
137
  detail: `level: ${privacyLevel}`,
112
138
  });
113
139
  }
114
- // ── Step 4: Cache the match result (public capabilities only) ─────────────
115
- // Non-public capabilities are never cached — prevents auth bypass where
116
- // User A's cached match is served to User B without privacy enforcement.
117
- if (this.cache && matchResult.capability
118
- && matchResult.capability.privacy.level === 'public') {
119
- const queryKey = (0, cache_1.normalizeQuery)(query);
120
- await this.cache.set(queryKey, matchResult);
121
- }
122
- // ── Step 5: Resolve ──────────────────────────────────────────────────────
140
+ // ── Step 4: Resolve ──────────────────────────────────────────────────────
123
141
  const resolveStart = Date.now();
124
142
  const resolution = await (0, resolver_1.resolve)(matchResult, matchResult.extractedParams, this.resolveOptions(overrides));
125
143
  steps.push({
@@ -128,6 +146,16 @@ class CapmanEngine {
128
146
  durationMs: Date.now() - resolveStart,
129
147
  detail: resolution.error ?? `via ${resolution.resolverType}`,
130
148
  });
149
+ // ── Step 5: Cache after successful resolution ────────────────────────────
150
+ // Only cache when resolution succeeded — a failed resolution (network error,
151
+ // auth failure, bad params) must not poison the cache. A cached failed match
152
+ // would cause every subsequent cache hit to attempt the same failing resolution
153
+ // until TTL expires.
154
+ if (this.cache && resolution.success && matchResult.capability
155
+ && matchResult.capability.privacy.level === 'public') {
156
+ const queryKey = (0, cache_1.normalizeQuery)(query);
157
+ await this.cache.set(queryKey, matchResult);
158
+ }
131
159
  // ── Step 6: Build reasoning array ────────────────────────────────────────
132
160
  const reasoning = [];
133
161
  if (matchResult.candidates.length) {
@@ -216,9 +244,21 @@ class CapmanEngine {
216
244
  * console.log(explanation.candidates)
217
245
  */
218
246
  async explain(query) {
247
+ if (!query || typeof query !== 'string') {
248
+ throw new TypeError('query must be a non-empty string');
249
+ }
250
+ if (query.length > CapmanEngine.MAX_QUERY_LENGTH) {
251
+ throw new RangeError(`query exceeds maximum length of ${CapmanEngine.MAX_QUERY_LENGTH} characters`);
252
+ }
219
253
  const start = Date.now();
220
254
  // ── Match — shared with ask() via _runMatch() ─────────────────────────────
221
255
  let { matchResult, resolvedVia: _resolvedVia } = await this._runMatch(query);
256
+ // explain() never reads from cache — it always runs a fresh match.
257
+ // This assertion catches any future refactor that accidentally adds
258
+ // cache reads to _runMatch() when called from explain().
259
+ if (_resolvedVia === 'cache') {
260
+ throw new Error('Invariant violation: explain() must never resolve via cache');
261
+ }
222
262
  let resolvedVia = _resolvedVia;
223
263
  // ── Apply learning boost (same as ask()) ─────────────────────────────────
224
264
  matchResult = await this.applyBoostToMatchResult(query, matchResult);
@@ -264,12 +304,17 @@ class CapmanEngine {
264
304
  }
265
305
  reasoning.push(`Resolved via: ${resolvedVia}`);
266
306
  if (matchResult.extractedParams && Object.keys(matchResult.extractedParams).length) {
267
- const params = Object.entries(matchResult.extractedParams)
307
+ const extracted = Object.entries(matchResult.extractedParams)
268
308
  .filter(([, v]) => v !== null)
269
309
  .map(([k, v]) => `${k}=${v}`)
270
310
  .join(', ');
271
- if (params)
272
- reasoning.push(`Would extract params: ${params}`);
311
+ const session = matchResult.capability?.params
312
+ .filter(p => p.source === 'session')
313
+ .map(p => `${p.name}=[from auth]`)
314
+ .join(', ');
315
+ const parts = [extracted, session].filter(Boolean).join(', ');
316
+ if (parts)
317
+ reasoning.push(`Would extract params: ${parts}`);
273
318
  }
274
319
  // ── Build wouldExecute ───────────────────────────────────────────────────
275
320
  const cap = matchResult.capability;
@@ -302,7 +347,7 @@ class CapmanEngine {
302
347
  let path = endpoint.path;
303
348
  for (const [k, v] of Object.entries(params)) {
304
349
  if (v)
305
- path = path.replace(`{${k}}`, v);
350
+ path = path.replaceAll(`{${k}}`, v);
306
351
  }
307
352
  const base = this.baseUrl ?? '';
308
353
  action = `${endpoint.method} ${base}${path}`;
@@ -311,7 +356,7 @@ class CapmanEngine {
311
356
  let dest = cap.resolver.destination;
312
357
  for (const [k, v] of Object.entries(params)) {
313
358
  if (v)
314
- dest = dest.replace(`{${k}}`, v);
359
+ dest = dest.replaceAll(`{${k}}`, v);
315
360
  }
316
361
  action = `navigate → ${dest}`;
317
362
  }
@@ -321,12 +366,12 @@ class CapmanEngine {
321
366
  let path = endpoint.path;
322
367
  for (const [k, v] of Object.entries(params)) {
323
368
  if (v)
324
- path = path.replace(`{${k}}`, v);
369
+ path = path.replaceAll(`{${k}}`, v);
325
370
  }
326
371
  let dest = hybrid.nav.destination;
327
372
  for (const [k, v] of Object.entries(params)) {
328
373
  if (v)
329
- dest = dest.replace(`{${k}}`, v);
374
+ dest = dest.replaceAll(`{${k}}`, v);
330
375
  }
331
376
  const base = this.baseUrl ?? '';
332
377
  action = `${endpoint.method} ${base}${path} + navigate → ${dest}`;
@@ -375,13 +420,13 @@ class CapmanEngine {
375
420
  }
376
421
  // ── Per-minute rate limit ────────────────────────────────────────────────
377
422
  const windowElapsed = now - this.llmWindowStart;
378
- if (windowElapsed >= 60000) {
423
+ if (windowElapsed >= 60_000) {
379
424
  this.llmCallsThisMinute = 0;
380
425
  this.llmWindowStart = now;
381
426
  }
382
427
  if (this.llmCallsThisMinute >= this.maxLLMCallsPerMinute) {
383
428
  // Recalculate elapsed after possible window reset above
384
- const resetIn = Math.ceil((60000 - (now - this.llmWindowStart)) / 1000);
429
+ const resetIn = Math.ceil((60_000 - (now - this.llmWindowStart)) / 1000);
385
430
  return `rate limit reached (${this.maxLLMCallsPerMinute}/min) — resets in ${Math.max(0, resetIn)}s`;
386
431
  }
387
432
  // Reserve the slot atomically before the call happens
@@ -435,10 +480,21 @@ class CapmanEngine {
435
480
  matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
436
481
  this.recordLLMSuccess();
437
482
  resolvedVia = 'llm';
483
+ // Merge keyword scores into LLM candidates so boost has real signal for alternatives
484
+ const kwResult = (0, matcher_1.match)(query, this.manifest);
485
+ matchResult = {
486
+ ...matchResult,
487
+ candidates: matchResult.candidates.map(c => ({
488
+ ...c,
489
+ score: c.matched
490
+ ? c.score // keep LLM confidence for winner
491
+ : (kwResult.candidates.find(kc => kc.capabilityId === c.capabilityId)?.score ?? 0),
492
+ })),
493
+ };
438
494
  steps?.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t, detail: `confidence: ${matchResult.confidence}%` });
439
495
  }
440
496
  catch (err) {
441
- const isParseError = String(err).startsWith('LLM_PARSE_ERROR');
497
+ const isParseError = err instanceof matcher_1.LLMParseError;
442
498
  if (!isParseError)
443
499
  this.recordLLMFailure();
444
500
  logger_1.logger.warn(`LLM call failed — falling back to keyword: ${err instanceof Error ? err.message : String(err)}`);
@@ -473,16 +529,27 @@ class CapmanEngine {
473
529
  matchResult = keywordResult;
474
530
  }
475
531
  else {
476
- logger_1.logger.info(`Low confidence (${keywordResult.confidence}%) — escalating to LLM`);
532
+ logger_1.logger.info(`Low keyword confidence (${keywordResult.confidence}%) — escalating to LLM`);
533
+ logger_1.logger.debug(`Query escalated to LLM: "${query}"`);
477
534
  const t2 = Date.now();
478
535
  try {
479
536
  matchResult = await (0, matcher_1.matchWithLLM)(query, this.manifest, { llm: this.llm });
480
537
  this.recordLLMSuccess();
481
538
  resolvedVia = 'llm';
539
+ // keywordResult already computed above in balanced mode — merge scores
540
+ matchResult = {
541
+ ...matchResult,
542
+ candidates: matchResult.candidates.map(c => ({
543
+ ...c,
544
+ score: c.matched
545
+ ? c.score
546
+ : (keywordResult.candidates.find(kc => kc.capabilityId === c.capabilityId)?.score ?? 0),
547
+ })),
548
+ };
482
549
  steps?.push({ type: 'llm_match', status: 'pass', durationMs: Date.now() - t2, detail: `confidence: ${matchResult.confidence}%` });
483
550
  }
484
551
  catch (err) {
485
- const isParseError = String(err).startsWith('LLM_PARSE_ERROR');
552
+ const isParseError = err instanceof matcher_1.LLMParseError;
486
553
  if (!isParseError)
487
554
  this.recordLLMFailure();
488
555
  logger_1.logger.warn(`LLM call failed — falling back to keyword: ${err instanceof Error ? err.message : String(err)}`);
@@ -598,4 +665,6 @@ class CapmanEngine {
598
665
  }
599
666
  }
600
667
  exports.CapmanEngine = CapmanEngine;
668
+ /** Maximum allowed query length in characters. Queries exceeding this throw RangeError. */
669
+ CapmanEngine.MAX_QUERY_LENGTH = 1000;
601
670
  //# sourceMappingURL=engine.js.map