capman 0.6.0 → 0.6.2

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 (64) hide show
  1. package/CODEBASE.md +6 -5
  2. package/dist/cjs/cache.d.ts +9 -0
  3. package/dist/cjs/cache.d.ts.map +1 -1
  4. package/dist/cjs/cache.js +37 -7
  5. package/dist/cjs/cache.js.map +1 -1
  6. package/dist/cjs/concurrent.d.ts +53 -0
  7. package/dist/cjs/concurrent.d.ts.map +1 -0
  8. package/dist/cjs/concurrent.js +71 -0
  9. package/dist/cjs/concurrent.js.map +1 -0
  10. package/dist/cjs/engine.d.ts +92 -7
  11. package/dist/cjs/engine.d.ts.map +1 -1
  12. package/dist/cjs/engine.js +269 -57
  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 +28 -6
  16. package/dist/cjs/generator.js.map +1 -1
  17. package/dist/cjs/index.d.ts +3 -1
  18. package/dist/cjs/index.d.ts.map +1 -1
  19. package/dist/cjs/index.js +5 -1
  20. package/dist/cjs/index.js.map +1 -1
  21. package/dist/cjs/learning.d.ts +16 -1
  22. package/dist/cjs/learning.d.ts.map +1 -1
  23. package/dist/cjs/learning.js +95 -14
  24. package/dist/cjs/learning.js.map +1 -1
  25. package/dist/cjs/matcher.d.ts +51 -2
  26. package/dist/cjs/matcher.d.ts.map +1 -1
  27. package/dist/cjs/matcher.js +173 -33
  28. package/dist/cjs/matcher.js.map +1 -1
  29. package/dist/cjs/parser.js +27 -9
  30. package/dist/cjs/parser.js.map +1 -1
  31. package/dist/cjs/resolver.d.ts +2 -2
  32. package/dist/cjs/resolver.d.ts.map +1 -1
  33. package/dist/cjs/resolver.js +66 -26
  34. package/dist/cjs/resolver.js.map +1 -1
  35. package/dist/cjs/schema.d.ts +821 -68
  36. package/dist/cjs/schema.d.ts.map +1 -1
  37. package/dist/cjs/schema.js +62 -13
  38. package/dist/cjs/schema.js.map +1 -1
  39. package/dist/cjs/types.d.ts +156 -9
  40. package/dist/cjs/types.d.ts.map +1 -1
  41. package/dist/cjs/version.d.ts +1 -1
  42. package/dist/cjs/version.js +1 -1
  43. package/dist/esm/cache.d.ts +9 -0
  44. package/dist/esm/cache.js +37 -7
  45. package/dist/esm/concurrent.d.ts +52 -0
  46. package/dist/esm/concurrent.js +66 -0
  47. package/dist/esm/engine.d.ts +92 -7
  48. package/dist/esm/engine.js +270 -58
  49. package/dist/esm/generator.js +28 -6
  50. package/dist/esm/index.d.ts +3 -1
  51. package/dist/esm/index.js +2 -0
  52. package/dist/esm/learning.d.ts +16 -1
  53. package/dist/esm/learning.js +95 -14
  54. package/dist/esm/matcher.d.ts +51 -2
  55. package/dist/esm/matcher.js +170 -33
  56. package/dist/esm/parser.js +27 -9
  57. package/dist/esm/resolver.d.ts +2 -2
  58. package/dist/esm/resolver.js +66 -26
  59. package/dist/esm/schema.d.ts +821 -68
  60. package/dist/esm/schema.js +62 -13
  61. package/dist/esm/types.d.ts +156 -9
  62. package/dist/esm/version.d.ts +1 -1
  63. package/dist/esm/version.js +1 -1
  64. package/package.json +1 -1
@@ -5,10 +5,14 @@ import { validateConfig, validateManifest } from './schema';
5
5
  import { logger } from './logger';
6
6
  export function generate(config) {
7
7
  return {
8
+ schemaVersion: '1',
8
9
  version: VERSION,
9
10
  app: config.app,
10
11
  generatedAt: new Date().toISOString(),
11
- capabilities: config.capabilities.map(cap => ({ ...cap, params: [...cap.params] })),
12
+ capabilities: config.capabilities.map(cap => ({ ...cap })),
13
+ ...(config.info ? { info: config.info } : {}),
14
+ ...(config.tagRegistry ? { tagRegistry: config.tagRegistry } : {}),
15
+ ...(config.servers ? { servers: config.servers } : {}),
12
16
  };
13
17
  }
14
18
  export function loadConfig(configPath) {
@@ -33,6 +37,10 @@ export function loadConfig(configPath) {
33
37
  // Use a CJS config file or convert with: module.exports = { ... }
34
38
  // Full ESM config support is planned for v0.5.
35
39
  try {
40
+ // Bust the module cache before loading — require() caches by resolved path,
41
+ // so a second call without this returns the stale version from the first call.
42
+ // This matters in watch mode and test suites that change config between calls.
43
+ delete require.cache[require.resolve(resolved)];
36
44
  const mod = require(resolved);
37
45
  raw = mod.default ?? mod;
38
46
  }
@@ -80,7 +88,12 @@ export function writeManifest(manifest, outputPath = 'manifest.json') {
80
88
  throw new Error(`writeManifest: output path "${outputPath}" resolves outside the working directory.\n` +
81
89
  `Resolved: ${resolved}\nAllowed: ${cwd}`);
82
90
  }
83
- fs.writeFileSync(resolved, JSON.stringify(manifest, null, 2));
91
+ // Write atomically via tmp → rename — same pattern used by FileCache and
92
+ // FileLearningStore. A crash or SIGKILL mid-write leaves the .tmp file, not
93
+ // a truncated manifest.json, so the next readManifest() can still parse it.
94
+ const tmp = `${resolved}.tmp`;
95
+ fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
96
+ fs.renameSync(tmp, resolved);
84
97
  return resolved;
85
98
  }
86
99
  export function readManifest(manifestPath = 'manifest.json') {
@@ -122,14 +135,23 @@ export function validate(manifest) {
122
135
  return { valid: errors.length === 0, errors, warnings };
123
136
  }
124
137
  export function generateStarterConfig() {
125
- return `// capman.config.js
126
- // Define what your app can do for AI agents.
127
- // Replace the examples below with your own app's capabilities.
138
+ return `// capman.config.js
139
+ // Auto-generated starter config edit before use
128
140
 
129
141
  module.exports = {
130
- app: 'your-app-name',
142
+ app: 'my-app',
131
143
  baseUrl: 'https://api.your-app.com',
132
144
 
145
+ // Optional metadata block — used for documentation and provenance
146
+ info: {
147
+ title: 'My App',
148
+ description: 'Brief description of what this app does',
149
+ version: '1.0.0',
150
+ homepage: 'https://your-app.com',
151
+ contact: { name: 'Your Name', email: 'you@your-app.com' },
152
+ license: { name: 'MIT' },
153
+ },
154
+
133
155
  capabilities: [
134
156
  {
135
157
  id: 'get_resource',
@@ -1,14 +1,16 @@
1
1
  export { setLogLevel } from './logger';
2
2
  export type { LogLevel } from './logger';
3
- export type { Capability, CapabilityParam, CapmanConfig, Manifest, MatchResult, ExecutionTrace, TraceStep, MatchCandidate, ResolveResult, ApiCallResult, ValidationResult, Resolver, ApiResolver, NavResolver, HybridResolver, PrivacyScope, ResolverType, HttpMethod, ExplainResult, ExplainCandidate, } from './types';
3
+ export type { Capability, CapabilityParam, CapmanConfig, Manifest, MatchResult, ExecutionTrace, TraceStep, MatchCandidate, ResolveResult, ApiCallResult, ValidationResult, Resolver, ApiResolver, NavResolver, HybridResolver, PrivacyScope, ResolverType, HttpMethod, ExplainResult, ExplainCandidate, ManifestInfo, Server, LifecycleInfo, LifecycleStatus, CapabilityError, Endpoint, ParamType, MatchHint, EmbeddingProvider, } from './types';
4
4
  export { generate, loadConfig, writeManifest, readManifest, validate, generateStarterConfig, } from './generator';
5
5
  export { match, matchWithLLM, extractParams, } from './matcher';
6
6
  export { LLMParseError } from './matcher';
7
7
  export type { LLMMatcherOptions } from './matcher';
8
8
  export { TYPE_PATTERNS } from './matcher';
9
+ export { filterByTags } from './matcher';
9
10
  export { resolve } from './resolver';
10
11
  export type { ResolveOptions, AuthContext } from './resolver';
11
12
  export { CapmanEngine } from './engine';
13
+ export { ConcurrentCapmanEngine } from './concurrent';
12
14
  export type { EngineOptions, EngineResult } from './engine';
13
15
  export { MemoryCache, FileCache, ComboCache, buildCacheKey, normalizeQuery } from './cache';
14
16
  export type { CacheStore, CacheEntry } from './cache';
package/dist/esm/index.js CHANGED
@@ -3,9 +3,11 @@ export { generate, loadConfig, writeManifest, readManifest, validate, generateSt
3
3
  export { match, matchWithLLM, extractParams, } from './matcher';
4
4
  export { LLMParseError } from './matcher';
5
5
  export { TYPE_PATTERNS } from './matcher';
6
+ export { filterByTags } from './matcher';
6
7
  export { resolve } from './resolver';
7
8
  // ─── Engine (recommended API) ─────────────────────────────────────────────────
8
9
  export { CapmanEngine } from './engine';
10
+ export { ConcurrentCapmanEngine } from './concurrent';
9
11
  // ─── Cache ────────────────────────────────────────────────────────────────────
10
12
  export { MemoryCache, FileCache, ComboCache, buildCacheKey, normalizeQuery } from './cache';
11
13
  // ─── Learning ─────────────────────────────────────────────────────────────────
@@ -6,6 +6,20 @@ export interface LearningEntry {
6
6
  extractedParams: Record<string, string | null>;
7
7
  resolvedVia: 'keyword' | 'llm' | 'cache';
8
8
  timestamp: string;
9
+ /**
10
+ * Confidence-derived weight stored at record time (confidence / 100, floor 0.1).
11
+ * Used by subtract() to reverse the exact contribution made by update(),
12
+ * preventing index drift when high-confidence entries are pruned.
13
+ * Optional for backwards-compatibility with persisted entries written before v0.5.5.
14
+ */
15
+ weight?: number;
16
+ /**
17
+ * Unix timestamp (ms) when this entry was last updated.
18
+ * Used for time-decay — older entries contribute less learning signal.
19
+ * Optional for backwards-compatibility with persisted entries written before v0.7.0.
20
+ * Migration: FileLearningStore falls back to file mtime for entries missing this field.
21
+ */
22
+ lastUpdated?: number;
9
23
  }
10
24
  export interface KeywordStats {
11
25
  /** keyword → Map of capabilityId → hit count */
@@ -43,7 +57,7 @@ export declare class FileLearningStore implements LearningStore {
43
57
  private learningIndex;
44
58
  private dirty;
45
59
  private saveTimer;
46
- constructor(filePath?: string);
60
+ constructor(filePath?: string, halfLifeDays?: number);
47
61
  flushSync(): void;
48
62
  /**
49
63
  * Removes this store from the exit flush registry and cancels any pending save timer.
@@ -67,6 +81,7 @@ export declare class FileLearningStore implements LearningStore {
67
81
  export declare class MemoryLearningStore implements LearningStore {
68
82
  private entries;
69
83
  private learningIndex;
84
+ constructor(halfLifeDays?: number);
70
85
  record(entry: LearningEntry): Promise<void>;
71
86
  getStats(): Promise<KeywordStats>;
72
87
  getIndex(): Promise<Record<string, Record<string, number>>>;
@@ -3,6 +3,15 @@ import * as path from 'path';
3
3
  import { logger } from './logger';
4
4
  const MAX_LEARNING_ENTRIES = 10_000;
5
5
  import { tokenize } from './matcher';
6
+ /**
7
+ * Exponential decay — older entries contribute less signal.
8
+ * At exactly halfLifeDays old, a weight of 1.0 decays to 0.5.
9
+ * At 2× halfLifeDays, it decays to 0.25. And so on.
10
+ */
11
+ function decayedWeight(weight, lastUpdated, halfLifeDays) {
12
+ const ageDays = (Date.now() - lastUpdated) / (1000 * 60 * 60 * 24);
13
+ return weight * Math.pow(0.5, ageDays / halfLifeDays);
14
+ }
6
15
  // Module-level registry — tracks all active FileLearningStore instances
7
16
  // for process exit flushing. Handlers registered once to avoid accumulation.
8
17
  const activeStores = new Set();
@@ -56,11 +65,18 @@ function computeTopCapabilities(entries, limit) {
56
65
  // Both FileLearningStore and MemoryLearningStore compose this instead of
57
66
  // duplicating the same ~80 lines of index management logic.
58
67
  class LearningIndex {
59
- constructor() {
68
+ constructor(halfLifeDays = 30) {
60
69
  this.index = {};
70
+ /** Tracks when each (word, capabilityId) cell was last reinforced — used for decay */
71
+ this.lastUpdatedIndex = {};
61
72
  this.statsCounter = {
62
73
  totalQueries: 0, llmQueries: 0, cacheHits: 0, outOfScope: 0,
63
74
  };
75
+ if (halfLifeDays <= 0) {
76
+ throw new RangeError(`halfLifeDays must be a positive number — got ${halfLifeDays}. ` +
77
+ `Use a value in days e.g. 30 (1 month), 7 (1 week).`);
78
+ }
79
+ this.halfLifeDays = halfLifeDays;
64
80
  }
65
81
  update(entry) {
66
82
  this.statsCounter.totalQueries++;
@@ -75,11 +91,21 @@ class LearningIndex {
75
91
  // more signal than a 51% borderline match. Floor of 0.1 ensures
76
92
  // borderline matches still contribute, just proportionally less.
77
93
  const weight = Math.max(0.1, entry.confidence / 100);
94
+ // Respect a caller-supplied timestamp (historical replay, rebuild()).
95
+ // For brand-new real-time entries lastUpdated is undefined — default to now.
96
+ const now = entry.lastUpdated ?? Date.now();
97
+ // Store weight and timestamp on the entry so subtract() can reverse the
98
+ // exact amount and migration has an accurate record time.
99
+ entry.weight = weight;
100
+ entry.lastUpdated = now;
78
101
  const words = tokenize(entry.query);
79
102
  for (const word of words) {
80
103
  this.index[word] ??= {};
81
104
  this.index[word][entry.capabilityId] =
82
105
  (this.index[word][entry.capabilityId] ?? 0) + weight;
106
+ // Track when this (word, cap) cell was last reinforced for decay
107
+ this.lastUpdatedIndex[word] ??= {};
108
+ this.lastUpdatedIndex[word][entry.capabilityId] = now;
83
109
  }
84
110
  }
85
111
  }
@@ -99,15 +125,19 @@ class LearningIndex {
99
125
  for (const word of words) {
100
126
  if (!this.index[word])
101
127
  continue;
102
- // Subtract estimated weight (0.5 average) exact weight not stored.
103
- // Minor drift on prune is acceptable; index is rebuilt when drift matters.
128
+ // Use the weight stored at record time for exact symmetric subtraction.
129
+ // Fallback recalculates from confidence for entries persisted before the
130
+ // weight field was added (backwards-compatible with older learning.json files).
131
+ const weight = entry.weight ?? Math.max(0.1, entry.confidence / 100);
104
132
  this.index[word][entry.capabilityId] =
105
- (this.index[word][entry.capabilityId] ?? 0.5) - 0.5;
133
+ (this.index[word][entry.capabilityId] ?? weight) - weight;
106
134
  if (this.index[word][entry.capabilityId] <= 0) {
107
135
  delete this.index[word][entry.capabilityId];
136
+ delete this.lastUpdatedIndex[word]?.[entry.capabilityId];
108
137
  }
109
138
  if (Object.keys(this.index[word]).length === 0) {
110
139
  delete this.index[word];
140
+ delete this.lastUpdatedIndex[word];
111
141
  }
112
142
  }
113
143
  }
@@ -120,10 +150,25 @@ class LearningIndex {
120
150
  }
121
151
  reset() {
122
152
  this.index = {};
153
+ this.lastUpdatedIndex = {};
123
154
  this.statsCounter = { totalQueries: 0, llmQueries: 0, cacheHits: 0, outOfScope: 0 };
124
155
  }
125
156
  getStats() {
126
- return { ...this.statsCounter, index: structuredClone(this.index) };
157
+ // Apply time-decay lazily on read. The index stores accumulated weights;
158
+ // each (word, capId) cell is decayed by how long ago it was last reinforced.
159
+ // This means recently-used capabilities retain full signal while stale ones fade.
160
+ const decayed = {};
161
+ for (const [word, capMap] of Object.entries(this.index)) {
162
+ for (const [capId, weight] of Object.entries(capMap)) {
163
+ const lastUpdated = this.lastUpdatedIndex[word]?.[capId] ?? Date.now();
164
+ const dw = decayedWeight(weight, lastUpdated, this.halfLifeDays);
165
+ if (dw > 0.001) { // drop negligible signal — avoids ghost entries
166
+ decayed[word] ??= {};
167
+ decayed[word][capId] = dw;
168
+ }
169
+ }
170
+ }
171
+ return { ...this.statsCounter, index: decayed };
127
172
  }
128
173
  getIndex() {
129
174
  return structuredClone(this.index);
@@ -131,13 +176,13 @@ class LearningIndex {
131
176
  }
132
177
  // ─── File Learning Store ──────────────────────────────────────────────────────
133
178
  export class FileLearningStore {
134
- constructor(filePath = '.capman/learning.json') {
179
+ constructor(filePath = '.capman/learning.json', halfLifeDays = 30) {
135
180
  this.entries = [];
136
181
  this.loadPromise = null;
137
182
  this.saveQueue = Promise.resolve();
138
- this.learningIndex = new LearningIndex();
139
183
  this.dirty = false;
140
184
  this.saveTimer = null;
185
+ this.learningIndex = new LearningIndex(halfLifeDays);
141
186
  const cwd = process.cwd();
142
187
  const resolved = path.resolve(cwd, filePath);
143
188
  const allowedPrefix = cwd === '/' ? '/' : cwd + path.sep;
@@ -168,8 +213,10 @@ export class FileLearningStore {
168
213
  fs.writeFileSync(tmp, payload);
169
214
  fs.renameSync(tmp, this.filePath);
170
215
  }
171
- catch {
172
- // Best-effort in exit handler
216
+ catch (err) {
217
+ // Use process.stderr.write — never console.error in an exit handler,
218
+ // as stdout may already be flushed or closed at this point.
219
+ process.stderr.write(`[capman] Failed to flush learning store to ${this.filePath}: ${err}\n`);
173
220
  }
174
221
  }
175
222
  /**
@@ -199,10 +246,44 @@ export class FileLearningStore {
199
246
  }
200
247
  async _doLoad() {
201
248
  try {
249
+ // Fetch mtime once — used as lastUpdated fallback for pre-v0.7.0 entries.
250
+ // Conservative: treats all old entries as "last updated when file was written"
251
+ // rather than "infinitely old", preventing a cliff-edge decay on first upgrade.
252
+ let fileMtimeMs = Date.now();
253
+ try {
254
+ const stat = await fs.promises.stat(this.filePath);
255
+ fileMtimeMs = stat.mtimeMs;
256
+ }
257
+ catch {
258
+ // File doesn't exist yet or stat failed — Date.now() fallback is safe
259
+ }
202
260
  const raw = await fs.promises.readFile(this.filePath, 'utf-8');
203
261
  const parsed = JSON.parse(raw);
204
262
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Array.isArray(parsed.entries)) {
205
- this.entries = parsed.entries;
263
+ // Validate each entry — corrupted entries (null capability, wrong types) must
264
+ // not propagate into the engine where they cause runtime errors deep in matching.
265
+ const validEntries = [];
266
+ let skipped = 0;
267
+ for (const entry of parsed.entries) {
268
+ if (entry !== null && typeof entry === 'object' &&
269
+ typeof entry.query === 'string' &&
270
+ (entry.capabilityId === null || typeof entry.capabilityId === 'string') &&
271
+ typeof entry.confidence === 'number' &&
272
+ typeof entry.resolvedVia === 'string') {
273
+ // Migration guard: backfill lastUpdated for pre-v0.7.0 entries
274
+ validEntries.push({
275
+ ...entry,
276
+ lastUpdated: entry.lastUpdated ?? fileMtimeMs,
277
+ });
278
+ }
279
+ else {
280
+ skipped++;
281
+ }
282
+ }
283
+ if (skipped > 0) {
284
+ logger.warn(`Learning store: skipped ${skipped} invalid entries during load`);
285
+ }
286
+ this.entries = validEntries;
206
287
  this.learningIndex.rebuild(this.entries);
207
288
  logger.debug(`Learning store loaded: ${this.entries.length} entries`);
208
289
  }
@@ -299,9 +380,9 @@ export class FileLearningStore {
299
380
  }
300
381
  // ─── Memory Learning Store (for testing) ─────────────────────────────────────
301
382
  export class MemoryLearningStore {
302
- constructor() {
383
+ constructor(halfLifeDays = 30) {
303
384
  this.entries = [];
304
- this.learningIndex = new LearningIndex();
385
+ this.learningIndex = new LearningIndex(halfLifeDays);
305
386
  }
306
387
  async record(entry) {
307
388
  const sanitized = {
@@ -313,8 +394,8 @@ export class MemoryLearningStore {
313
394
  if (this.entries.length > MAX_LEARNING_ENTRIES) {
314
395
  const excess = this.entries.length - MAX_LEARNING_ENTRIES;
315
396
  const pruned = this.entries.splice(0, excess);
316
- for (const entry of pruned) {
317
- this.learningIndex.subtract(entry);
397
+ for (const staleEntry of pruned) {
398
+ this.learningIndex.subtract(staleEntry);
318
399
  }
319
400
  }
320
401
  }
@@ -34,6 +34,17 @@ export interface BM25Index {
34
34
  N: number;
35
35
  /** Bigram sets per capability — post-stopword, post-stem, examples only */
36
36
  bigrams: Record<string, Set<string>>;
37
+ /**
38
+ * Pre-computed token arrays per capability, per field.
39
+ * Avoids re-tokenizing capability text on every scoreCapability() call.
40
+ * At 50 capabilities × 100 req/s, that is 5,000 redundant tokenization
41
+ * calls per second — each involving stem() and split/filter chains.
42
+ */
43
+ capTokens: Record<string, {
44
+ examples: string[];
45
+ description: string[];
46
+ name: string[];
47
+ }>;
37
48
  }
38
49
  /** Build a BM25 index over all capabilities. Call once at manifest load. */
39
50
  export declare function buildBM25Index(capabilities: Capability[]): BM25Index;
@@ -48,11 +59,39 @@ export declare function scoreCapability(qWordSet: Set<string>, cap: Capability,
48
59
  * Input must already be post-stopword and post-stem (use tokenize() first).
49
60
  */
50
61
  export declare function extractBigrams(tokens: string[]): Set<string>;
62
+ /**
63
+ * Reciprocal Rank Fusion — fuses multiple ranked lists into a single score map.
64
+ * k=60 is the standard literature default.
65
+ */
66
+ export declare function rrf(rankings: Array<Array<{
67
+ id: string;
68
+ score: number;
69
+ }>>, k?: number): Map<string, number>;
70
+ /**
71
+ * Returns a sub-manifest containing only capabilities that match ALL provided tags.
72
+ * Capabilities without tags are excluded when tags filter is active.
73
+ * Enables token-efficient LLM prompts for large manifests:
74
+ *
75
+ * @example
76
+ * // Only send order-related capabilities to LLM
77
+ * const orderManifest = filterByTags(manifest, ['orders'])
78
+ * const result = await matchWithLLM(query, orderManifest, { llm })
79
+ *
80
+ * @example
81
+ * // Match by any of multiple tags (union) — call filterByTags per tag and merge
82
+ * const ordersOrPayments = [
83
+ * ...filterByTags(manifest, ['orders']).capabilities,
84
+ * ...filterByTags(manifest, ['payments']).capabilities,
85
+ * ]
86
+ */
87
+ export declare function filterByTags(manifest: Manifest, tags: string[]): Manifest;
51
88
  export declare function resolverToIntent(cap: Capability): MatchResult['intent'];
52
89
  /**
53
90
  * Strips characters that could break LLM prompt structure from
54
91
  * capability field values before injection into the system prompt.
55
- * Removes control characters, newlines, and delimiter-like sequences.
92
+ * Removes control characters, newlines, delimiter sequences, and braces
93
+ * anywhere in the string (not just at line starts) to resist prompt injection
94
+ * from third-party OpenAPI spec content ingested via parseOpenAPI().
56
95
  */
57
96
  export declare function sanitizeForPrompt(value: string, maxLen: number): string;
58
97
  /**
@@ -77,9 +116,19 @@ export interface MatchOptions {
77
116
  bm25K1?: number;
78
117
  bm25B?: number;
79
118
  bm25Ceiling?: number;
119
+ /** Pre-computed cosine similarity scores keyed by capability ID (0–100). Engine passes these when an EmbeddingProvider is configured. */
120
+ embeddingScores?: Map<string, number>;
80
121
  }
122
+ /**
123
+ * Calibrates a BM25 normalization ceiling from the manifest.
124
+ * Scores each capability against all of its own examples and returns the maximum.
125
+ * Call once at manifest load time — O(capabilities × examples).
126
+ */
127
+ export declare function calibrateCeiling(capabilities: Capability[], bm25Index: BM25Index, k1: number, b: number): number;
81
128
  export declare function match(query: string, manifest: Manifest, options?: MatchOptions): MatchResult;
82
129
  export interface LLMMatcherOptions {
130
+ /** App name for prompt context — passed from engine, optional for direct callers */
131
+ app?: string;
83
132
  llm: (prompt: string) => Promise<string>;
84
133
  }
85
134
  /**
@@ -93,4 +142,4 @@ export interface LLMMatcherOptions {
93
142
  * wrapper that maps the prompt to a proper system message, keeping user query
94
143
  * data in the user turn only.
95
144
  */
96
- export declare function matchWithLLM(query: string, manifest: Manifest, options: LLMMatcherOptions): Promise<MatchResult>;
145
+ export declare function matchWithLLM(query: string, topCandidates: Capability[], options: LLMMatcherOptions): Promise<MatchResult>;