@4da/mcp-server 5.0.2 → 5.0.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 (96) hide show
  1. package/README.md +3 -3
  2. package/dist/auth-context.d.ts +0 -1
  3. package/dist/auth-context.js +0 -1
  4. package/dist/auth.d.ts +0 -1
  5. package/dist/auth.js +0 -1
  6. package/dist/db.d.ts +17 -2
  7. package/dist/db.js +104 -67
  8. package/dist/doctor.d.ts +0 -1
  9. package/dist/doctor.js +17 -15
  10. package/dist/embeddings.d.ts +0 -1
  11. package/dist/embeddings.js +0 -1
  12. package/dist/http-transport.d.ts +0 -1
  13. package/dist/http-transport.js +0 -1
  14. package/dist/index.d.ts +0 -1
  15. package/dist/index.js +17 -5
  16. package/dist/live/cache.d.ts +0 -1
  17. package/dist/live/cache.js +0 -1
  18. package/dist/live/crates-registry.d.ts +0 -1
  19. package/dist/live/crates-registry.js +5 -8
  20. package/dist/live/cvss.d.ts +0 -1
  21. package/dist/live/cvss.js +0 -1
  22. package/dist/live/go-registry.d.ts +0 -1
  23. package/dist/live/go-registry.js +4 -9
  24. package/dist/live/hn-fetcher.d.ts +0 -1
  25. package/dist/live/hn-fetcher.js +23 -11
  26. package/dist/live/http-utils.d.ts +0 -1
  27. package/dist/live/http-utils.js +0 -1
  28. package/dist/live/index.d.ts +0 -1
  29. package/dist/live/index.js +0 -1
  30. package/dist/live/npm-registry.d.ts +0 -1
  31. package/dist/live/npm-registry.js +4 -8
  32. package/dist/live/osv-scanner.d.ts +20 -2
  33. package/dist/live/osv-scanner.js +82 -9
  34. package/dist/live/platform.d.ts +0 -1
  35. package/dist/live/platform.js +0 -1
  36. package/dist/live/pypi-registry.d.ts +0 -1
  37. package/dist/live/pypi-registry.js +7 -10
  38. package/dist/live/rate-limiter.d.ts +0 -1
  39. package/dist/live/rate-limiter.js +0 -1
  40. package/dist/live/semver-utils.d.ts +11 -1
  41. package/dist/live/semver-utils.js +14 -1
  42. package/dist/live/types.d.ts +0 -1
  43. package/dist/live/types.js +0 -1
  44. package/dist/live/version-resolver.d.ts +0 -1
  45. package/dist/live/version-resolver.js +0 -1
  46. package/dist/live-singleton.d.ts +0 -1
  47. package/dist/live-singleton.js +0 -1
  48. package/dist/project-scanner.d.ts +0 -1
  49. package/dist/project-scanner.js +0 -1
  50. package/dist/schema-registry.d.ts +17 -8
  51. package/dist/schema-registry.js +54 -9
  52. package/dist/schemas/developer-dna.json +1 -1
  53. package/dist/schemas/get-context.json +2 -2
  54. package/dist/schemas/get-relevant-content.json +1 -1
  55. package/dist/schemas/record-feedback.json +1 -1
  56. package/dist/setup.d.ts +0 -1
  57. package/dist/setup.js +0 -1
  58. package/dist/tool-dispatch.d.ts +0 -1
  59. package/dist/tool-dispatch.js +21 -2
  60. package/dist/tools/agent-memory.d.ts +0 -1
  61. package/dist/tools/agent-memory.js +0 -1
  62. package/dist/tools/decision-enforcement.d.ts +0 -1
  63. package/dist/tools/decision-enforcement.js +0 -1
  64. package/dist/tools/decision-memory.d.ts +0 -1
  65. package/dist/tools/decision-memory.js +0 -1
  66. package/dist/tools/decision-recall.d.ts +0 -1
  67. package/dist/tools/decision-recall.js +0 -1
  68. package/dist/tools/dependency-health.d.ts +0 -1
  69. package/dist/tools/dependency-health.js +31 -5
  70. package/dist/tools/developer-dna.d.ts +3 -3
  71. package/dist/tools/developer-dna.js +63 -47
  72. package/dist/tools/ecosystem-pulse.d.ts +7 -2
  73. package/dist/tools/ecosystem-pulse.js +39 -3
  74. package/dist/tools/get-actionable-signals.d.ts +0 -1
  75. package/dist/tools/get-actionable-signals.js +0 -1
  76. package/dist/tools/get-context.d.ts +0 -1
  77. package/dist/tools/get-context.js +2 -3
  78. package/dist/tools/get-relevant-content.d.ts +0 -1
  79. package/dist/tools/get-relevant-content.js +1 -3
  80. package/dist/tools/index.d.ts +0 -1
  81. package/dist/tools/index.js +0 -1
  82. package/dist/tools/knowledge-gaps.d.ts +0 -1
  83. package/dist/tools/knowledge-gaps.js +79 -17
  84. package/dist/tools/recall.d.ts +0 -1
  85. package/dist/tools/recall.js +0 -1
  86. package/dist/tools/record-feedback.d.ts +1 -2
  87. package/dist/tools/record-feedback.js +7 -8
  88. package/dist/tools/upgrade-planner.d.ts +0 -1
  89. package/dist/tools/upgrade-planner.js +15 -3
  90. package/dist/tools/vulnerability-scan.d.ts +0 -1
  91. package/dist/tools/vulnerability-scan.js +0 -1
  92. package/dist/tools/what-should-i-know.d.ts +0 -1
  93. package/dist/tools/what-should-i-know.js +6 -3
  94. package/dist/types.d.ts +2 -3
  95. package/dist/types.js +0 -1
  96. package/package.json +37 -11
@@ -93,4 +93,3 @@ export declare class LiveIntelligence {
93
93
  fetchRegistryHealth(deps: ResolvedDependency[]): Promise<RegistryPackageInfo[]>;
94
94
  getStatus(): LiveIntelligenceStatus;
95
95
  }
96
- //# sourceMappingURL=index.d.ts.map
@@ -295,4 +295,3 @@ function emptyVulnResult(projectPath, offline) {
295
295
  offline,
296
296
  };
297
297
  }
298
- //# sourceMappingURL=index.js.map
@@ -16,4 +16,3 @@ export declare class NpmRegistry {
16
16
  getPackageInfo(name: string, currentVersion: string | null, isDev: boolean): Promise<RegistryPackageInfo>;
17
17
  getBulkDownloads(names: string[]): Promise<Map<string, number>>;
18
18
  }
19
- //# sourceMappingURL=npm-registry.d.ts.map
@@ -8,7 +8,7 @@
8
8
  * Privacy: sends only package names — public manifest data.
9
9
  */
10
10
  import { fetchJson, fetchWithTimeout } from "./http-utils.js";
11
- import { computeSemverDistance, isPreRelease } from "./semver-utils.js";
11
+ import { computeSemverDistance, maxStableSemver } from "./semver-utils.js";
12
12
  const NPM_REGISTRY_URL = "https://registry.npmjs.org";
13
13
  const NPM_DOWNLOADS_URL = "https://api.npmjs.org/downloads/point/last-week";
14
14
  const NPM_TIMEOUT_MS = 8_000;
@@ -127,13 +127,10 @@ export class NpmRegistry {
127
127
  return result;
128
128
  }
129
129
  }
130
+ // Semver-max, not last-published: maintenance releases of older lines are
131
+ // published after newer lines (see maxStableSemver).
130
132
  function findLatestStable(versions) {
131
- for (let i = versions.length - 1; i >= 0; i--) {
132
- if (!isPreRelease(versions[i])) {
133
- return versions[i];
134
- }
135
- }
136
- return null;
133
+ return maxStableSemver(versions);
137
134
  }
138
135
  function errorResult(name, currentVersion, isDev, fetchError) {
139
136
  return {
@@ -152,4 +149,3 @@ function errorResult(name, currentVersion, isDev, fetchError) {
152
149
  fetchError,
153
150
  };
154
151
  }
155
- //# sourceMappingURL=npm-registry.js.map
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import type { LiveCache } from "./cache.js";
10
10
  import type { RateLimiter } from "./rate-limiter.js";
11
- import type { ResolvedDependency, VulnerabilityScanResult } from "./types.js";
11
+ import type { ResolvedDependency, OsvVulnerability, VulnerabilityScanResult } from "./types.js";
12
12
  export declare class OsvScanner {
13
13
  private cache;
14
14
  private rateLimiter;
@@ -28,4 +28,22 @@ export declare function extractCvssScore(severity: Array<{
28
28
  type: string;
29
29
  score: string;
30
30
  }> | undefined): number | null;
31
- //# sourceMappingURL=osv-scanner.d.ts.map
31
+ /**
32
+ * Pick the fix version for the OSV range that actually CONTAINS the installed
33
+ * version — never another release line's fix. The old code returned the first
34
+ * range's first `fixed` event, so a multi-range advisory (e.g. undici fixed at
35
+ * 6.x on the 6 line and 7.x on the 7 line) recommended a DOWNGRADE to 6.x for
36
+ * an installed 7.x.
37
+ *
38
+ * Selection order:
39
+ * 1. The `fixed` of the interval containing installed (introduced <= installed
40
+ * < fixed).
41
+ * 2. If that interval has no fix (or none contains installed), the smallest
42
+ * published fix strictly ABOVE installed across all ranges.
43
+ * 3. If the installed version is unparseable, the highest published fix (it
44
+ * resolves the advisory regardless of release line).
45
+ * 4. Otherwise null — omitting the recommendation beats emitting a downgrade.
46
+ *
47
+ * Hard guard: the returned version is never below the installed version.
48
+ */
49
+ export declare function extractFixedVersion(affected: OsvVulnerability["affected"] | undefined, packageName: string, ecosystem: string, installedVersion: string | null): string | null;
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { fetchWithTimeout, fetchJson } from "./http-utils.js";
11
11
  import { cvssBaseScore } from "./cvss.js";
12
+ import { compareSemver, maxSemver, parseSemver } from "./semver-utils.js";
12
13
  const OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch";
13
14
  const OSV_VULN_URL = "https://api.osv.dev/v1/vulns/";
14
15
  const OSV_TIMEOUT_MS = 15_000;
@@ -174,7 +175,7 @@ export class OsvScanner {
174
175
  }
175
176
  function mapVulnerability(vuln, dep) {
176
177
  const { severity, cvssScore } = deriveSeverity(vuln);
177
- const fixedVersion = extractFixedVersion(vuln.affected, dep.name, dep.ecosystem);
178
+ const fixedVersion = extractFixedVersion(vuln.affected, dep.name, dep.ecosystem, dep.version);
178
179
  return {
179
180
  package: dep.name,
180
181
  currentVersion: dep.version || "unknown",
@@ -260,19 +261,91 @@ function cvssToSeverity(score) {
260
261
  return "low";
261
262
  return "unknown";
262
263
  }
263
- function extractFixedVersion(affected, packageName, ecosystem) {
264
+ /**
265
+ * Pick the fix version for the OSV range that actually CONTAINS the installed
266
+ * version — never another release line's fix. The old code returned the first
267
+ * range's first `fixed` event, so a multi-range advisory (e.g. undici fixed at
268
+ * 6.x on the 6 line and 7.x on the 7 line) recommended a DOWNGRADE to 6.x for
269
+ * an installed 7.x.
270
+ *
271
+ * Selection order:
272
+ * 1. The `fixed` of the interval containing installed (introduced <= installed
273
+ * < fixed).
274
+ * 2. If that interval has no fix (or none contains installed), the smallest
275
+ * published fix strictly ABOVE installed across all ranges.
276
+ * 3. If the installed version is unparseable, the highest published fix (it
277
+ * resolves the advisory regardless of release line).
278
+ * 4. Otherwise null — omitting the recommendation beats emitting a downgrade.
279
+ *
280
+ * Hard guard: the returned version is never below the installed version.
281
+ */
282
+ export function extractFixedVersion(affected, packageName, ecosystem, installedVersion) {
264
283
  if (!affected)
265
284
  return null;
285
+ const intervals = collectVulnerableIntervals(affected, packageName, ecosystem);
286
+ const fixes = intervals
287
+ .map((i) => i.fixed)
288
+ .filter((f) => f !== null);
289
+ if (fixes.length === 0)
290
+ return null;
291
+ if (installedVersion === null || parseSemver(installedVersion) === null) {
292
+ // Can't place the installed version on a release line — recommend the
293
+ // highest published fix so the upgrade resolves the advisory either way.
294
+ return maxSemver(fixes);
295
+ }
296
+ // The interval whose release line the installed version sits on. `compareSemver`
297
+ // returns 0 for unparseable bounds (e.g. introduced "0" = since inception),
298
+ // which keeps those intervals eligible on the introduced side only.
299
+ const containing = intervals.find((i) => compareSemver(installedVersion, i.introduced) >= 0 &&
300
+ (i.fixed === null || compareSemver(installedVersion, i.fixed) < 0));
301
+ if (containing?.fixed &&
302
+ compareSemver(containing.fixed, installedVersion) > 0) {
303
+ return containing.fixed;
304
+ }
305
+ // No fix on the installed line (open interval, or installed already past
306
+ // every containing range): smallest fix strictly above installed, or nothing.
307
+ const above = fixes.filter((f) => compareSemver(f, installedVersion) > 0);
308
+ if (above.length === 0)
309
+ return null;
310
+ return above.reduce((min, v) => (compareSemver(v, min) < 0 ? v : min), above[0]);
311
+ }
312
+ /**
313
+ * Flatten the matching package's version ranges into [introduced, fixed)
314
+ * intervals. OSV sorts events within a range; each `introduced` opens an
315
+ * interval and the next `fixed` closes it. GIT ranges hold commit hashes,
316
+ * not versions, and are skipped.
317
+ */
318
+ function collectVulnerableIntervals(affected, packageName, ecosystem) {
319
+ const intervals = [];
266
320
  for (const a of affected) {
267
- if (a.package.name === packageName && a.package.ecosystem === ecosystem) {
268
- for (const range of a.ranges || []) {
269
- for (const event of range.events || []) {
270
- if (event.fixed)
271
- return event.fixed;
321
+ if (a.package.name !== packageName || a.package.ecosystem !== ecosystem)
322
+ continue;
323
+ for (const range of a.ranges || []) {
324
+ if (range.type === "GIT")
325
+ continue;
326
+ let open = null;
327
+ for (const event of range.events || []) {
328
+ if (event.introduced !== undefined) {
329
+ if (open)
330
+ intervals.push(open);
331
+ open = { introduced: event.introduced, fixed: null };
332
+ }
333
+ else if (event.fixed !== undefined) {
334
+ if (open) {
335
+ open.fixed = event.fixed;
336
+ intervals.push(open);
337
+ open = null;
338
+ }
339
+ else {
340
+ // `fixed` with no preceding `introduced` — treat as vulnerable
341
+ // since inception ("0" = OSV's since-the-beginning sentinel).
342
+ intervals.push({ introduced: "0", fixed: event.fixed });
343
+ }
272
344
  }
273
345
  }
346
+ if (open)
347
+ intervals.push(open);
274
348
  }
275
349
  }
276
- return null;
350
+ return intervals;
277
351
  }
278
- //# sourceMappingURL=osv-scanner.js.map
@@ -29,4 +29,3 @@ export declare function hostTarget(): HostTarget;
29
29
  export declare function targetActiveOnHost(target: string | null | undefined, host?: HostTarget): boolean;
30
30
  /** Evaluate a cfg predicate body (already unwrapped from `cfg( ... )`). */
31
31
  export declare function evalCfg(expr: string, host?: HostTarget): boolean;
32
- //# sourceMappingURL=platform.d.ts.map
@@ -153,4 +153,3 @@ function tripleActiveOnHost(triple, host) {
153
153
  return false;
154
154
  return true;
155
155
  }
156
- //# sourceMappingURL=platform.js.map
@@ -15,4 +15,3 @@ export declare class PyPIRegistry {
15
15
  constructor(cache: LiveCache, rateLimiter: RateLimiter);
16
16
  getPackageInfo(name: string, currentVersion: string | null, isDev: boolean): Promise<RegistryPackageInfo>;
17
17
  }
18
- //# sourceMappingURL=pypi-registry.d.ts.map
@@ -8,7 +8,7 @@
8
8
  * Privacy: sends only package names -- public manifest data.
9
9
  */
10
10
  import { fetchJson } from "./http-utils.js";
11
- import { computeSemverDistance, isPreRelease } from "./semver-utils.js";
11
+ import { computeSemverDistance, isPreRelease, maxStableSemver } from "./semver-utils.js";
12
12
  const PYPI_BASE_URL = "https://pypi.org/pypi";
13
13
  const PYPI_TIMEOUT_MS = 8_000;
14
14
  const PYPI_CACHE_TTL = 86_400; // 24 hours
@@ -83,17 +83,15 @@ export class PyPIRegistry {
83
83
  }
84
84
  }
85
85
  function findLatestStable(versions, releases) {
86
- for (let i = versions.length - 1; i >= 0; i--) {
87
- const v = versions[i];
86
+ // Semver-max among eligible versions, not last-listed (see maxStableSemver).
87
+ const eligible = versions.filter((v) => {
88
88
  if (isPreRelease(v))
89
- continue;
89
+ return false;
90
90
  // Skip if all files for this version are yanked
91
91
  const files = releases[v];
92
- if (files && files.length > 0 && files.every((f) => f.yanked))
93
- continue;
94
- return v;
95
- }
96
- return null;
92
+ return !(files && files.length > 0 && files.every((f) => f.yanked));
93
+ });
94
+ return maxStableSemver(eligible);
97
95
  }
98
96
  function getLastPublished(files) {
99
97
  if (!files || files.length === 0)
@@ -139,4 +137,3 @@ function errorResult(name, currentVersion, isDev, fetchError) {
139
137
  fetchError,
140
138
  };
141
139
  }
142
- //# sourceMappingURL=pypi-registry.js.map
@@ -28,4 +28,3 @@ export declare const DEFAULT_RATE_LIMITS: {
28
28
  maxPerMinute: number;
29
29
  };
30
30
  };
31
- //# sourceMappingURL=rate-limiter.d.ts.map
@@ -53,4 +53,3 @@ export const DEFAULT_RATE_LIMITS = {
53
53
  pypi: { maxPerMinute: 60 },
54
54
  go: { maxPerMinute: 60 },
55
55
  };
56
- //# sourceMappingURL=rate-limiter.js.map
@@ -10,4 +10,14 @@ export declare function isPreRelease(version: string): boolean;
10
10
  export declare function compareSemver(a: string, b: string): number;
11
11
  /** Highest version (by MAJOR.MINOR.PATCH) from a list, or null if empty. */
12
12
  export declare function maxSemver(versions: string[]): string | null;
13
- //# sourceMappingURL=semver-utils.d.ts.map
13
+ /**
14
+ * Highest NON-prerelease version by semver from a list.
15
+ *
16
+ * Registries list versions in publish order, and maintenance releases for an
17
+ * older line land AFTER newer lines (React published 19.0.8 after 19.2.x; rsa
18
+ * published 0.9.10 after 0.10.0-rc.*). Taking the "last stable entry" therefore
19
+ * reported an older line as the latest stable — which surfaced as a bogus
20
+ * stable version in dependency_health and a literal DOWNGRADE recommendation
21
+ * in upgrade_planner. Order-independent max fixes the class.
22
+ */
23
+ export declare function maxStableSemver(versions: string[]): string | null;
@@ -49,4 +49,17 @@ export function maxSemver(versions) {
49
49
  return null;
50
50
  return versions.reduce((max, v) => (compareSemver(v, max) > 0 ? v : max), versions[0]);
51
51
  }
52
- //# sourceMappingURL=semver-utils.js.map
52
+ /**
53
+ * Highest NON-prerelease version by semver from a list.
54
+ *
55
+ * Registries list versions in publish order, and maintenance releases for an
56
+ * older line land AFTER newer lines (React published 19.0.8 after 19.2.x; rsa
57
+ * published 0.9.10 after 0.10.0-rc.*). Taking the "last stable entry" therefore
58
+ * reported an older line as the latest stable — which surfaced as a bogus
59
+ * stable version in dependency_health and a literal DOWNGRADE recommendation
60
+ * in upgrade_planner. Order-independent max fixes the class.
61
+ */
62
+ export function maxStableSemver(versions) {
63
+ const stable = versions.filter((v) => parseSemver(v) !== null && !isPreRelease(v));
64
+ return maxSemver(stable);
65
+ }
@@ -153,4 +153,3 @@ export interface DependencyHealthResult {
153
153
  scanDurationMs: number;
154
154
  cached: boolean;
155
155
  }
156
- //# sourceMappingURL=types.d.ts.map
@@ -1,3 +1,2 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  export {};
3
- //# sourceMappingURL=types.js.map
@@ -23,4 +23,3 @@ export declare function resolveVersions(cwd: string, deps: string[], devDeps: st
23
23
  * Direct dependency health and upgrade planning continue to use resolveVersions.
24
24
  */
25
25
  export declare function resolveAuditVersions(cwd: string, deps: string[], devDeps: string[], language: string, targets?: Record<string, string>): ResolvedDependency[];
26
- //# sourceMappingURL=version-resolver.d.ts.map
@@ -411,4 +411,3 @@ function extractVersionFromSpec(spec) {
411
411
  const match = spec.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
412
412
  return match ? match[1] : null;
413
413
  }
414
- //# sourceMappingURL=version-resolver.js.map
@@ -6,4 +6,3 @@
6
6
  import type { LiveIntelligence } from "./live/index.js";
7
7
  export declare function setLiveIntelligence(li: LiveIntelligence): void;
8
8
  export declare function getLiveIntelligence(): LiveIntelligence | null;
9
- //# sourceMappingURL=live-singleton.d.ts.map
@@ -11,4 +11,3 @@ export function setLiveIntelligence(li) {
11
11
  export function getLiveIntelligence() {
12
12
  return instance;
13
13
  }
14
- //# sourceMappingURL=live-singleton.js.map
@@ -31,4 +31,3 @@ export interface ProjectScanResult {
31
31
  * Only reads manifest files at the root level — no recursion.
32
32
  */
33
33
  export declare function scanCurrentProject(cwd: string): ProjectScanResult;
34
- //# sourceMappingURL=project-scanner.d.ts.map
@@ -453,4 +453,3 @@ function isNotableTopic(name) {
453
453
  ]);
454
454
  return notable.has(name);
455
455
  }
456
- //# sourceMappingURL=project-scanner.js.map
@@ -1,8 +1,14 @@
1
1
  /**
2
2
  * Schema Registry for Dynamic Context Discovery
3
3
  *
4
- * Reduces tool listing from ~4500 tokens to ~500 tokens by:
5
- * - Returning only one-liner summaries in list_tools
4
+ * Reduces tool listing from ~4500 tokens to ~1300 tokens by:
5
+ * - Returning one-liner summaries in list_tools
6
+ * - Serving the REAL inputSchema only for tools with REQUIRED parameters
7
+ * (AD-032): a client cannot construct a valid call to those without the
8
+ * schema, and most clients never read MCP Resources — so a slim
9
+ * `{type:"object"}` made required-param tools effectively uncallable.
10
+ * All-optional tools stay slim (`{}` is a valid call) with the full
11
+ * schema still available as a Resource.
6
12
  * - Storing full schemas as MCP Resources (lazy-loaded)
7
13
  *
8
14
  * Also provides category/tag metadata for tool discovery and filtering.
@@ -39,16 +45,20 @@ export interface ToolRegistryEntry {
39
45
  * 14 tools total: 9 standalone + 5 full-mode.
40
46
  */
41
47
  export declare const TOOL_REGISTRY: Record<string, ToolRegistryEntry>;
48
+ /** An inputSchema as tools/list serves it — the MCP Tool contract requires
49
+ * the literal `type: "object"` at the root. */
50
+ export type ToolInputSchema = {
51
+ type: "object";
52
+ } & Record<string, unknown>;
42
53
  /**
43
- * Get slim tool list for list_tools response
44
- * Returns minimal schema (just type: object) — full schema via resources
54
+ * Get the tool list for the tools/list response: slim summaries throughout,
55
+ * real inputSchema for required-param tools, `{type:"object"}` otherwise
56
+ * (full schema via resources).
45
57
  */
46
58
  export declare function getSlimToolList(standaloneOnly?: boolean): Array<{
47
59
  name: string;
48
60
  description: string;
49
- inputSchema: {
50
- type: "object";
51
- };
61
+ inputSchema: ToolInputSchema;
52
62
  annotations: ToolAnnotations;
53
63
  }>;
54
64
  /**
@@ -77,4 +87,3 @@ export declare function getCategoryManifest(): {
77
87
  };
78
88
  /** Find tools matching any of the given tags */
79
89
  export declare function getToolsByTags(tags: string[]): string[];
80
- //# sourceMappingURL=schema-registry.d.ts.map
@@ -2,8 +2,14 @@
2
2
  /**
3
3
  * Schema Registry for Dynamic Context Discovery
4
4
  *
5
- * Reduces tool listing from ~4500 tokens to ~500 tokens by:
6
- * - Returning only one-liner summaries in list_tools
5
+ * Reduces tool listing from ~4500 tokens to ~1300 tokens by:
6
+ * - Returning one-liner summaries in list_tools
7
+ * - Serving the REAL inputSchema only for tools with REQUIRED parameters
8
+ * (AD-032): a client cannot construct a valid call to those without the
9
+ * schema, and most clients never read MCP Resources — so a slim
10
+ * `{type:"object"}` made required-param tools effectively uncallable.
11
+ * All-optional tools stay slim (`{}` is a valid call) with the full
12
+ * schema still available as a Resource.
7
13
  * - Storing full schemas as MCP Resources (lazy-loaded)
8
14
  *
9
15
  * Also provides category/tag metadata for tool discovery and filtering.
@@ -11,6 +17,9 @@
11
17
  * Full schemas available at: 4da://schema/{tool_name}
12
18
  * Category manifest at: 4da://categories
13
19
  */
20
+ import { readFileSync } from "node:fs";
21
+ import { fileURLToPath } from "node:url";
22
+ import { dirname, join } from "node:path";
14
23
  /**
15
24
  * Slim tool registry — one-liner descriptions + category/tag metadata.
16
25
  * Full schemas are stored in schemas/*.json and exposed as MCP Resources.
@@ -61,7 +70,7 @@ export const TOOL_REGISTRY = {
61
70
  annotations: { readOnlyHint: true, openWorldHint: true },
62
71
  },
63
72
  get_context: {
64
- summary: "What 4DA knows about the user: role, tech stack, interests, and learned affinities. Call FIRST when you need to know what the user works on before answering or recommending.",
73
+ summary: "What 4DA knows about the user: role, tech stack, interests, exclusions, and ACE-detected context. Call FIRST when you need to know what the user works on before answering or recommending.",
65
74
  schemaFile: "get-context.json",
66
75
  category: "intelligence",
67
76
  tags: ["context", "interests", "tech-stack", "profile"],
@@ -93,10 +102,10 @@ export const TOOL_REGISTRY = {
93
102
  annotations: { readOnlyHint: true, openWorldHint: false },
94
103
  },
95
104
  record_feedback: {
96
- summary: "Record click/save/dismiss on a content item to sharpen future scoring. Call AFTER the user reacts to a surfaced item (opens, saves, or dismisses it).",
105
+ summary: "Record click/save/dismiss on a content item as explicit interaction history. Call AFTER the user reacts to a surfaced item (opens, saves, or dismisses it).",
97
106
  schemaFile: "record-feedback.json",
98
107
  category: "intelligence",
99
- tags: ["feedback", "learning", "save", "dismiss"],
108
+ tags: ["feedback", "history", "save", "dismiss"],
100
109
  standalone: false,
101
110
  annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false, idempotentHint: false },
102
111
  },
@@ -136,9 +145,46 @@ export const TOOL_REGISTRY = {
136
145
  annotations: { readOnlyHint: true, openWorldHint: false },
137
146
  },
138
147
  };
148
+ /** Schemas live beside this module in both layouts: src/schemas (vitest) and
149
+ * dist/schemas (compiled — populated by the copy-schemas build step). */
150
+ const SCHEMAS_DIR = join(dirname(fileURLToPath(import.meta.url)), "schemas");
151
+ /** Lazy cache: tool name → its real inputSchema when the schema declares
152
+ * required parameters, else null (tool stays slim). */
153
+ const requiredParamSchemas = new Map();
139
154
  /**
140
- * Get slim tool list for list_tools response
141
- * Returns minimal schema (just type: object) — full schema via resources
155
+ * The tools/list discoverability line (AD-032): a tool whose schema declares
156
+ * REQUIRED parameters serves its real inputSchema — `{type:"object"}` hid the
157
+ * required params from every client that never reads MCP Resources, making
158
+ * those tools fail on first call (GPT adversarial audit, finding 7). Tools
159
+ * whose parameters are all optional keep the slim schema: `{}` is a valid
160
+ * call, and the full schema stays one Resource read away. Fail-open: an
161
+ * unreadable or malformed schema file falls back to slim rather than
162
+ * breaking tools/list.
163
+ */
164
+ function inputSchemaIfRequired(name, schemaFile) {
165
+ if (!requiredParamSchemas.has(name)) {
166
+ let loaded = null;
167
+ try {
168
+ const parsed = JSON.parse(readFileSync(join(SCHEMAS_DIR, schemaFile), "utf-8"));
169
+ const schema = parsed.inputSchema;
170
+ if (schema &&
171
+ schema.type === "object" &&
172
+ Array.isArray(schema.required) &&
173
+ schema.required.length > 0) {
174
+ loaded = schema;
175
+ }
176
+ }
177
+ catch {
178
+ loaded = null;
179
+ }
180
+ requiredParamSchemas.set(name, loaded);
181
+ }
182
+ return requiredParamSchemas.get(name) ?? null;
183
+ }
184
+ /**
185
+ * Get the tool list for the tools/list response: slim summaries throughout,
186
+ * real inputSchema for required-param tools, `{type:"object"}` otherwise
187
+ * (full schema via resources).
142
188
  */
143
189
  export function getSlimToolList(standaloneOnly) {
144
190
  return Object.entries(TOOL_REGISTRY)
@@ -146,7 +192,7 @@ export function getSlimToolList(standaloneOnly) {
146
192
  .map(([name, info]) => ({
147
193
  name,
148
194
  description: info.summary,
149
- inputSchema: { type: "object" },
195
+ inputSchema: inputSchemaIfRequired(name, info.schemaFile) ?? { type: "object" },
150
196
  annotations: info.annotations,
151
197
  }));
152
198
  }
@@ -200,4 +246,3 @@ export function getToolsByTags(tags) {
200
246
  .filter(([, entry]) => entry.tags.some((t) => tagSet.has(t.toLowerCase())))
201
247
  .map(([name]) => name);
202
248
  }
203
- //# sourceMappingURL=schema-registry.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "developer_dna",
3
- "description": "Export your Developer DNA — a comprehensive profile of your tech identity built from project scans, engagement data, and learned preferences. Includes primary stack, adjacent tech, top dependencies, engaged topics, blind spots, source engagement rates, and aggregate stats. Call when the user asks for a summary of their developer profile, identity, or tech fingerprint.",
3
+ "description": "Export your Developer DNA — a comprehensive profile of your tech identity built from project scans, explicit interests, and local interaction history. Includes primary stack, adjacent tech, top dependencies, engaged topics, blind spots, source engagement rates, and aggregate stats. Call when the user asks for a summary of their developer profile, identity, or tech fingerprint.",
4
4
  "inputSchema": {
5
5
  "type": "object",
6
6
  "properties": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get_context",
3
- "description": "Get the user's context - what 4DA knows about them.\n\nReturns information about:\n- Static Identity: User-declared role, tech stack, domains, interests, and exclusions\n- ACE Context (optional): Auto-detected technologies and active topics from recent activity\n- Learned Preferences (optional): Topic affinities learned from user behavior\n\nCall FIRST when you need to understand what the user works on or cares about before answering or recommending.",
3
+ "description": "Get the user's context - what 4DA knows about them.\n\nReturns information about:\n- Static Identity: User-declared role, tech stack, domains, interests, and exclusions\n- ACE Context (optional): Auto-detected technologies and active topics from recent activity\n- Learned Preferences (optional): retained compatibility field; implicit preference learning no longer contributes to scoring\n\nCall FIRST when you need to understand what the user works on or cares about before answering or recommending.",
4
4
  "inputSchema": {
5
5
  "type": "object",
6
6
  "properties": {
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "include_learned": {
12
12
  "type": "boolean",
13
- "description": "Include learned preferences (topic affinities, anti-topics). Default: true"
13
+ "description": "Include the retained learned-preferences compatibility field. Default: true"
14
14
  }
15
15
  }
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get_relevant_content",
3
- "description": "Get filtered relevant content from 4DA's personalized feed.\n\nReturns content items that match the user's interests, tech stack, and learned preferences.\nItems are scored based on:\n- Explicit interests declared by the user\n- Tech stack and domains\n- ACE-detected context (recent files, projects, git activity)\n- Learned preferences from past interactions\n\nIMPORTANT for autonomous use: each item carries an `evidence_class` describing how it is grounded. The relevance_score float is NOT comparable across classes — gate actions on evidence_class, not the number:\n- \"osv_verified\": a security advisory matching one of the user's INSTALLED dependencies — safe to act on\n- \"dependency_grounded\": matched one of the user's declared dependencies — trustworthy\n- \"semantic_only\": topical/embedding relevance with NO dependency edge — surface to the human, verify before acting\n- \"keyword_heuristic\": standalone-mode keyword score (no Rust pipeline) — weakest signal\n\nCall when the user asks what to read, what is relevant to them, or for content on a topic.",
3
+ "description": "Get filtered relevant content from 4DA's personalized feed.\n\nReturns content items that match the user's explicit interests, tech stack, and ACE-detected context.\nItems are scored based on:\n- Explicit interests declared by the user\n- Tech stack and domains\n- ACE-detected context (recent files, projects, git activity)\n\nIMPORTANT for autonomous use: each item carries an `evidence_class` describing how it is grounded. The relevance_score float is NOT comparable across classes — gate actions on evidence_class, not the number:\n- \"osv_verified\": a security advisory matching one of the user's INSTALLED dependencies — safe to act on\n- \"dependency_grounded\": matched one of the user's declared dependencies — trustworthy\n- \"semantic_only\": topical/embedding relevance with NO dependency edge — surface to the human, verify before acting\n- \"keyword_heuristic\": standalone-mode keyword score (no Rust pipeline) — weakest signal\n\nCall when the user asks what to read, what is relevant to them, or for content on a topic.",
4
4
  "inputSchema": {
5
5
  "type": "object",
6
6
  "properties": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "record_feedback",
3
- "description": "Record user feedback on a content item.\n\nFeedback actions:\n- \"click\": User clicked/opened the item (moderate positive signal)\n- \"save\": User saved/bookmarked the item (strong positive signal)\n- \"dismiss\": User dismissed the item (weak negative signal)\n- \"mark_irrelevant\": User marked item as not relevant (strong negative signal)\n\nThis feedback helps 4DA learn user preferences over time. Call after the user reacts to a surfaced item (opens, saves, or dismisses it).",
3
+ "description": "Record explicit user interaction history for a content item.\n\nFeedback actions:\n- \"click\": User clicked/opened the item\n- \"save\": User saved/bookmarked the item\n- \"dismiss\": User dismissed the item\n- \"mark_irrelevant\": User marked item as not relevant\n\nThis records what happened; it does not train content preferences (interaction counts only graduate scoring's bootstrap gate). Call after the user reacts to a surfaced item (opens, saves, or dismisses it).",
4
4
  "inputSchema": {
5
5
  "type": "object",
6
6
  "properties": {
package/dist/setup.d.ts CHANGED
@@ -15,4 +15,3 @@
15
15
  * @param dryRun If true, only report what would be written without modifying files.
16
16
  */
17
17
  export declare function runSetup(dryRun?: boolean): void;
18
- //# sourceMappingURL=setup.d.ts.map
package/dist/setup.js CHANGED
@@ -168,4 +168,3 @@ if (isDirectRun) {
168
168
  const dryRun = process.argv.includes("--dry-run");
169
169
  runSetup(dryRun);
170
170
  }
171
- //# sourceMappingURL=setup.js.map
@@ -24,4 +24,3 @@ export declare function dispatchTool(name: string, db: FourDADatabase, args: Rec
24
24
  export declare function hasDispatchTool(name: string): boolean;
25
25
  /** Get count of registered tool executors */
26
26
  export declare function getDispatchToolCount(): number;
27
- //# sourceMappingURL=tool-dispatch.d.ts.map
@@ -58,7 +58,27 @@ export async function dispatchTool(name, db, args) {
58
58
  throw new Error(`Unknown tool: ${name}`);
59
59
  }
60
60
  assertToolPermission(name);
61
- const result = await executor(db, (args || {}));
61
+ // BUSY/LOCKED retry at the single choke point every tool passes through.
62
+ // The desktop engine writes to the same database every 30 minutes; a read
63
+ // that collides with its transaction deserves one backoff retry rather than
64
+ // an immediate error JSON. (db.queryWithRetry existed for this but had no
65
+ // production caller — it is sync and the executors are async, so the retry
66
+ // lives here.)
67
+ const toolArgs = (args || {});
68
+ let result;
69
+ try {
70
+ result = await executor(db, toolArgs);
71
+ }
72
+ catch (error) {
73
+ const code = error.code;
74
+ if (code === "SQLITE_BUSY" || code === "SQLITE_LOCKED") {
75
+ await new Promise((resolve) => setTimeout(resolve, 150));
76
+ result = await executor(db, toolArgs);
77
+ }
78
+ else {
79
+ throw error;
80
+ }
81
+ }
62
82
  const payload = FRESHNESS_TOOLS.has(name) ? attachFreshness(db, result) : result;
63
83
  return {
64
84
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
@@ -94,4 +114,3 @@ export function hasDispatchTool(name) {
94
114
  export function getDispatchToolCount() {
95
115
  return Object.keys(DISPATCH_MAP).length;
96
116
  }
97
- //# sourceMappingURL=tool-dispatch.js.map
@@ -101,4 +101,3 @@ export declare function memoryEmbedText(row: {
101
101
  content: string;
102
102
  }): string;
103
103
  export declare function executeAgentMemory(db: FourDADatabase, params: AgentMemoryParams): object | Promise<object>;
104
- //# sourceMappingURL=agent-memory.d.ts.map
@@ -365,4 +365,3 @@ async function enhanceRecallWithSemantics(db, query, rows, limit, config) {
365
365
  recall_mode: sem.embeddedCount > 0 ? "hybrid" : "ranked_lexical",
366
366
  };
367
367
  }
368
- //# sourceMappingURL=agent-memory.js.map
@@ -72,4 +72,3 @@ export declare const checkDecisionAlignmentTool: {
72
72
  };
73
73
  export declare function executeCheckDecisionAlignment(db: FourDADatabase, params: CheckDecisionAlignmentParams): AlignmentResult | Promise<AlignmentResult>;
74
74
  export {};
75
- //# sourceMappingURL=decision-enforcement.d.ts.map