@4da/mcp-server 5.0.1 → 5.0.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 (108) hide show
  1. package/README.md +5 -5
  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 +93 -66
  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 +0 -1
  33. package/dist/live/osv-scanner.js +0 -1
  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 +16 -5
  97. package/dist/llm.d.ts +0 -162
  98. package/dist/llm.js +0 -502
  99. package/dist/output-manager.d.ts +0 -156
  100. package/dist/output-manager.js +0 -157
  101. package/dist/scheduler/core.d.ts +0 -25
  102. package/dist/scheduler/core.js +0 -61
  103. package/dist/scheduler/freshness.d.ts +0 -17
  104. package/dist/scheduler/freshness.js +0 -41
  105. package/dist/scheduler/index.d.ts +0 -21
  106. package/dist/scheduler/index.js +0 -32
  107. package/dist/scheduler/refreshers.d.ts +0 -21
  108. package/dist/scheduler/refreshers.js +0 -21
@@ -17,4 +17,3 @@ export async function fetchJson(url, options = {}, timeoutMs = DEFAULT_TIMEOUT_M
17
17
  }
18
18
  return (await response.json());
19
19
  }
20
- //# sourceMappingURL=http-utils.js.map
@@ -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
@@ -28,4 +28,3 @@ 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
@@ -275,4 +275,3 @@ function extractFixedVersion(affected, packageName, ecosystem) {
275
275
  }
276
276
  return null;
277
277
  }
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
@@ -210,4 +210,3 @@ export function executeCheckDecisionAlignment(db, params) {
210
210
  // Provider configured -> hybrid retrieval (returns a Promise; dispatch awaits).
211
211
  return hybridDecisionRecall(db, query, candidates, 50, config).then(({ ranked, semanticById, recall_mode }) => assembleAlignment(params, ranked, semanticById, recall_mode));
212
212
  }
213
- //# sourceMappingURL=decision-enforcement.js.map
@@ -127,4 +127,3 @@ export declare const decisionMemoryTool: {
127
127
  };
128
128
  };
129
129
  export declare function executeDecisionMemory(db: FourDADatabase, params: DecisionMemoryParams): object | Promise<object>;
130
- //# sourceMappingURL=decision-memory.d.ts.map
@@ -293,4 +293,3 @@ export function executeDecisionMemory(db, params) {
293
293
  return { error: `Unknown action: ${params.action}` };
294
294
  }
295
295
  }
296
- //# sourceMappingURL=decision-memory.js.map
@@ -56,4 +56,3 @@ export declare function decisionEmbedText(row: DecisionLike): string;
56
56
  * be embedded.
57
57
  */
58
58
  export declare function hybridDecisionRecall<T extends DecisionLike>(db: FourDADatabase, query: string, rows: T[], limit: number, config: EmbeddingConfig): Promise<DecisionRecallResult<T>>;
59
- //# sourceMappingURL=decision-recall.d.ts.map
@@ -84,4 +84,3 @@ export async function hybridDecisionRecall(db, query, rows, limit, config) {
84
84
  .map((x) => x.row);
85
85
  return { ranked, semanticById: sem.semanticById, recall_mode: "hybrid" };
86
86
  }
87
- //# sourceMappingURL=decision-recall.js.map
@@ -42,4 +42,3 @@ export declare const dependencyHealthTool: {
42
42
  };
43
43
  };
44
44
  export declare function executeDependencyHealth(_db: FourDADatabase, params: DependencyHealthParams, liveIntel: LiveIntelligence | null): Promise<DependencyHealthResult>;
45
- //# sourceMappingURL=dependency-health.d.ts.map
@@ -112,11 +112,38 @@ export async function executeDependencyHealth(_db, params, liveIntel) {
112
112
  });
113
113
  const limit = params.limit ?? 50;
114
114
  const limited = sorted.slice(0, limit);
115
- // Health score: 100 = perfect, 0 = everything is on fire
115
+ // Health score: 100 = perfect. Proportional and severity-weighted — the old
116
+ // absolute-count formula (15 points per vulnerable package, 2 per outdated,
117
+ // regardless of severity, degree, or project size) pinned any real-sized
118
+ // project at 0/100: a patch-behind dep cost the same as a critical CVE and
119
+ // the number informed nothing.
116
120
  const total = registryData.length || 1;
117
- const vulnPenalty = Math.min(vulnerable * 15, 50);
118
- const deprecatedPenalty = Math.min(deprecated * 10, 30);
119
- const outdatedPenalty = Math.min(outdated * 2, 40);
121
+ // Vulnerabilities: severity-weighted; transitive findings at one-third
122
+ // weight (their fix arrives via a parent bump — the direct surface is what
123
+ // the user controls directly).
124
+ const sevWeight = { critical: 20, high: 10, medium: 4, low: 1 };
125
+ let vulnPenaltyRaw = 0;
126
+ if (vulnResult) {
127
+ for (const v of vulnResult.vulnerabilities) {
128
+ const w = sevWeight[v.severity] ?? 2; // "unknown" (mostly unmaintained-notices) counts low
129
+ vulnPenaltyRaw += v.isDirect ? w : w / 3;
130
+ }
131
+ }
132
+ const vulnPenalty = Math.min(Math.round(vulnPenaltyRaw), 55);
133
+ // Staleness: degree-weighted and proportional to the tracked set — a major
134
+ // behind is a real liability, a patch behind is routine drift.
135
+ let stalenessWeight = 0;
136
+ for (const dep of registryData) {
137
+ const label = dep.versionsBehind?.label;
138
+ if (label === "major")
139
+ stalenessWeight += 1;
140
+ else if (label === "minor")
141
+ stalenessWeight += 0.35;
142
+ else if (label === "patch")
143
+ stalenessWeight += 0.1;
144
+ }
145
+ const outdatedPenalty = Math.min(30, Math.round((stalenessWeight / total) * 60));
146
+ const deprecatedPenalty = Math.min(deprecated * 8, 15);
120
147
  const healthScore = Math.max(0, 100 - vulnPenalty - deprecatedPenalty - outdatedPenalty);
121
148
  const vulnSummary = vulnResult ? {
122
149
  critical: vulnResult.bySeverity.critical,
@@ -151,4 +178,3 @@ export async function executeDependencyHealth(_db, params, liveIntel) {
151
178
  cached: false,
152
179
  };
153
180
  }
154
- //# sourceMappingURL=dependency-health.js.map
@@ -2,7 +2,7 @@
2
2
  * developer_dna tool
3
3
  *
4
4
  * Export a comprehensive Developer DNA profile built from project scans,
5
- * engagement data, and learned preferences.
5
+ * explicit interests, and local interaction history.
6
6
  */
7
7
  import type { FourDADatabase } from "../db.js";
8
8
  export interface DeveloperDnaParams {
@@ -46,7 +46,8 @@ interface EngagedTopic {
46
46
  interface BlindSpotEntry {
47
47
  dependency: string;
48
48
  severity: string;
49
- days_stale: number;
49
+ /** Days since last engagement with content about this dep; null = never engaged. */
50
+ days_stale: number | null;
50
51
  }
51
52
  interface SourceEngagement {
52
53
  source_type: string;
@@ -76,4 +77,3 @@ interface DeveloperDna {
76
77
  }
77
78
  export declare function executeDeveloperDna(db: FourDADatabase, params: DeveloperDnaParams): DeveloperDna;
78
79
  export {};
79
- //# sourceMappingURL=developer-dna.d.ts.map