@4da/mcp-server 5.0.3 → 6.0.0
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.
- package/CHANGELOG.md +667 -0
- package/README.md +66 -35
- package/dist/build-staleness.d.ts +35 -0
- package/dist/build-staleness.js +95 -0
- package/dist/db.d.ts +53 -2
- package/dist/db.js +242 -36
- package/dist/doctor.js +27 -7
- package/dist/embeddings.js +7 -1
- package/dist/freshness-markers.d.ts +34 -0
- package/dist/freshness-markers.js +74 -0
- package/dist/gitignore.d.ts +37 -0
- package/dist/gitignore.js +130 -0
- package/dist/index.js +127 -44
- package/dist/live/bun-lockfile.d.ts +26 -0
- package/dist/live/bun-lockfile.js +123 -0
- package/dist/live/cargo-platform.d.ts +38 -0
- package/dist/live/cargo-platform.js +178 -0
- package/dist/live/changelog-classify.d.ts +41 -0
- package/dist/live/changelog-classify.js +207 -0
- package/dist/live/changelog.d.ts +64 -0
- package/dist/live/changelog.js +318 -0
- package/dist/live/crates-registry.js +3 -1
- package/dist/live/crates-versions.d.ts +47 -0
- package/dist/live/crates-versions.js +201 -0
- package/dist/live/dependency-set.d.ts +13 -0
- package/dist/live/dependency-set.js +95 -0
- package/dist/live/dev-scope.d.ts +32 -0
- package/dist/live/dev-scope.js +102 -0
- package/dist/live/file-signature.d.ts +18 -0
- package/dist/live/file-signature.js +43 -0
- package/dist/live/go-registry.js +3 -1
- package/dist/live/hn-fetcher.js +9 -3
- package/dist/live/index.d.ts +105 -2
- package/dist/live/index.js +266 -139
- package/dist/live/install-state.d.ts +43 -0
- package/dist/live/install-state.js +150 -0
- package/dist/live/js-lockfile-readers.d.ts +46 -0
- package/dist/live/js-lockfile-readers.js +181 -0
- package/dist/live/lockfile-parsers-pygo.d.ts +60 -0
- package/dist/live/lockfile-parsers-pygo.js +274 -0
- package/dist/live/lockfile-parsers.d.ts +69 -0
- package/dist/live/lockfile-parsers.js +311 -0
- package/dist/live/lockfile-types.d.ts +34 -0
- package/dist/live/lockfile-types.js +42 -0
- package/dist/live/maintenance.d.ts +33 -0
- package/dist/live/maintenance.js +37 -0
- package/dist/live/npm-packument.d.ts +47 -0
- package/dist/live/npm-packument.js +150 -0
- package/dist/live/npm-registry.js +67 -53
- package/dist/live/osv-scanner.d.ts +31 -1
- package/dist/live/osv-scanner.js +119 -10
- package/dist/live/package-archive.d.ts +49 -0
- package/dist/live/package-archive.js +186 -0
- package/dist/live/pypi-registry.js +3 -1
- package/dist/live/registry-health.d.ts +16 -0
- package/dist/live/registry-health.js +74 -0
- package/dist/live/release-metadata.d.ts +67 -0
- package/dist/live/release-metadata.js +16 -0
- package/dist/live/resolution.d.ts +43 -0
- package/dist/live/resolution.js +62 -0
- package/dist/live/semver-precedence.d.ts +30 -0
- package/dist/live/semver-precedence.js +83 -0
- package/dist/live/severity-scope.d.ts +57 -0
- package/dist/live/severity-scope.js +89 -0
- package/dist/live/types.d.ts +83 -0
- package/dist/live/upgrade-sources.d.ts +71 -0
- package/dist/live/upgrade-sources.js +198 -0
- package/dist/live/version-compare.d.ts +39 -0
- package/dist/live/version-compare.js +130 -0
- package/dist/live/version-resolver.d.ts +14 -1
- package/dist/live/version-resolver.js +93 -303
- package/dist/native-bindings.d.ts +19 -0
- package/dist/native-bindings.js +39 -0
- package/dist/project-manifests.d.ts +40 -0
- package/dist/project-manifests.js +193 -0
- package/dist/project-scanner.js +38 -48
- package/dist/project-tree.d.ts +41 -0
- package/dist/project-tree.js +110 -0
- package/dist/prompts.d.ts +28 -0
- package/dist/prompts.js +54 -0
- package/dist/schema-registry.d.ts +41 -29
- package/dist/schema-registry.js +98 -105
- package/dist/server-instructions.d.ts +11 -0
- package/dist/server-instructions.js +36 -0
- package/dist/tool-args.d.ts +28 -0
- package/dist/tool-args.js +93 -0
- package/dist/tool-dispatch.d.ts +1 -1
- package/dist/tool-dispatch.js +42 -8
- package/dist/tools/app-plan.d.ts +92 -0
- package/dist/tools/app-plan.js +155 -0
- package/dist/tools/briefing-stored-advisories.d.ts +20 -0
- package/dist/tools/briefing-stored-advisories.js +58 -0
- package/dist/tools/briefing-task-scope.d.ts +73 -0
- package/dist/tools/briefing-task-scope.js +197 -0
- package/dist/tools/briefing-wisdom.d.ts +31 -0
- package/dist/tools/briefing-wisdom.js +148 -0
- package/dist/tools/decision-enforcement.d.ts +0 -5
- package/dist/tools/decision-enforcement.js +0 -4
- package/dist/tools/decision-memory.js +18 -66
- package/dist/tools/dependency-check-signals.d.ts +74 -0
- package/dist/tools/dependency-check-signals.js +264 -0
- package/dist/tools/dependency-check.d.ts +98 -0
- package/dist/tools/dependency-check.js +295 -0
- package/dist/tools/dependency-health.js +74 -21
- package/dist/tools/developer-dna.d.ts +3 -0
- package/dist/tools/developer-dna.js +16 -15
- package/dist/tools/ecosystem-pulse.d.ts +6 -2
- package/dist/tools/ecosystem-pulse.js +53 -14
- package/dist/tools/get-actionable-signals.d.ts +26 -4
- package/dist/tools/get-actionable-signals.js +214 -251
- package/dist/tools/get-context.d.ts +2 -0
- package/dist/tools/get-context.js +9 -1
- package/dist/tools/get-relevant-content.d.ts +3 -1
- package/dist/tools/get-relevant-content.js +34 -3
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js +4 -2
- package/dist/tools/install-drift-notes.d.ts +51 -0
- package/dist/tools/install-drift-notes.js +172 -0
- package/dist/tools/knowledge-gap-citations.d.ts +55 -0
- package/dist/tools/knowledge-gap-citations.js +132 -0
- package/dist/tools/knowledge-gap-exposure.d.ts +72 -0
- package/dist/tools/knowledge-gap-exposure.js +160 -0
- package/dist/tools/knowledge-gap-grading.d.ts +91 -0
- package/dist/tools/knowledge-gap-grading.js +157 -0
- package/dist/tools/knowledge-gap-installs.d.ts +65 -0
- package/dist/tools/knowledge-gap-installs.js +134 -0
- package/dist/tools/knowledge-gap-ranges.d.ts +51 -0
- package/dist/tools/knowledge-gap-ranges.js +146 -0
- package/dist/tools/knowledge-gap-scope.d.ts +63 -0
- package/dist/tools/knowledge-gap-scope.js +118 -0
- package/dist/tools/knowledge-gaps.d.ts +12 -3
- package/dist/tools/knowledge-gaps.js +172 -121
- package/dist/tools/package-presence.d.ts +24 -0
- package/dist/tools/package-presence.js +35 -0
- package/dist/tools/record-feedback.d.ts +0 -1
- package/dist/tools/record-feedback.js +7 -8
- package/dist/tools/signal-classifier.d.ts +25 -0
- package/dist/tools/signal-classifier.js +40 -0
- package/dist/tools/upgrade-impact-callsites.d.ts +52 -0
- package/dist/tools/upgrade-impact-callsites.js +226 -0
- package/dist/tools/upgrade-impact-literals.d.ts +45 -0
- package/dist/tools/upgrade-impact-literals.js +118 -0
- package/dist/tools/upgrade-impact-report.d.ts +80 -0
- package/dist/tools/upgrade-impact-report.js +178 -0
- package/dist/tools/upgrade-impact.d.ts +84 -0
- package/dist/tools/upgrade-impact.js +305 -0
- package/dist/tools/upgrade-planner.d.ts +41 -8
- package/dist/tools/upgrade-planner.js +201 -53
- package/dist/tools/vulnerability-scan-concise.d.ts +72 -0
- package/dist/tools/vulnerability-scan-concise.js +97 -0
- package/dist/tools/vulnerability-scan-format.d.ts +137 -0
- package/dist/tools/vulnerability-scan-format.js +276 -0
- package/dist/tools/vulnerability-scan.d.ts +338 -18
- package/dist/tools/vulnerability-scan.js +84 -125
- package/dist/tools/what-should-i-know.d.ts +86 -19
- package/dist/tools/what-should-i-know.js +277 -283
- package/dist/types.d.ts +4 -1
- package/package.json +27 -30
- package/dist/schemas/agent-memory.json +0 -49
- package/dist/schemas/decision-enforcement.json +0 -22
- package/dist/schemas/decision-memory.json +0 -54
- package/dist/schemas/dependency-health.json +0 -27
- package/dist/schemas/developer-dna.json +0 -219
- package/dist/schemas/ecosystem-pulse.json +0 -17
- package/dist/schemas/get-actionable-signals.json +0 -29
- package/dist/schemas/get-context.json +0 -17
- package/dist/schemas/get-relevant-content.json +0 -26
- package/dist/schemas/knowledge-gaps.json +0 -14
- package/dist/schemas/record-feedback.json +0 -24
- package/dist/schemas/upgrade-planner.json +0 -22
- package/dist/schemas/vulnerability-scan.json +0 -26
- package/dist/schemas/what-should-i-know.json +0 -19
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge-gap scope: which projects count as active, and which items the
|
|
3
|
+
* dependency linker bound to which packages. Mirrors the desktop app's
|
|
4
|
+
* `temporal::{active_repo_roots, dep_within_active_root, shares_active_root}`
|
|
5
|
+
* and `knowledge_decay::{get_active_project_paths, linked_to}`.
|
|
6
|
+
*/
|
|
7
|
+
import type BetterSqlite3 from "better-sqlite3";
|
|
8
|
+
import type { FourDADatabase } from "../db.js";
|
|
9
|
+
/** `stmt.all(...)`, or [] when the statement cannot run: a read here never throws out of the tool. */
|
|
10
|
+
export declare function allRows<T>(stmt: BetterSqlite3.Statement, ...args: unknown[]): T[];
|
|
11
|
+
/** Lowercase, forward slashes, no trailing slash (`knowledge_decay::normalize_project_path`). */
|
|
12
|
+
export declare function normPath(p: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Path-BOUNDARY match in either direction: `d:/4da` covers `d:/4da/src-tauri`
|
|
15
|
+
* (and a manifest recorded above its repo root still counts), but never
|
|
16
|
+
* `d:/4da-experiments` (`temporal::dep_within_active_root`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function withinRoots(path: string, roots: readonly string[]): boolean;
|
|
19
|
+
/** Do two project paths sit under one common active root (`temporal::shares_active_root`)? */
|
|
20
|
+
export declare function sharesRoot(a: string, b: string, roots: readonly string[]): boolean;
|
|
21
|
+
/** The app's two activity windows, loaded once per call. */
|
|
22
|
+
export interface ActiveScope {
|
|
23
|
+
/** `temporal::active_repo_roots`: commits in the last 60 days. Scopes each project ROW. */
|
|
24
|
+
rowRoots: string[];
|
|
25
|
+
/** `knowledge_decay::get_active_project_paths`: activity in the last 30 days. Scopes each DEPENDENCY. */
|
|
26
|
+
dependencyRoots: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function loadActiveScope(db: FourDADatabase): ActiveScope;
|
|
29
|
+
/**
|
|
30
|
+
* The project rows the app's dependency funnel keeps before knowledge decay
|
|
31
|
+
* ever sees them (`temporal::get_all_dependencies`): rows inside an active repo
|
|
32
|
+
* root. Without it a gap for a dependency the active repo shares with a
|
|
33
|
+
* dormant one is judged on the dormant copy too — vitest in `d:/4da` (4.1.11)
|
|
34
|
+
* beside navcal's untouched 3.2.4 would name navcal under a critical the app
|
|
35
|
+
* never shows. No recorded git activity means unscoped, and a scope that
|
|
36
|
+
* matches no row at all keeps every row rather than blanking the tool.
|
|
37
|
+
*/
|
|
38
|
+
export declare function scopeRows<T extends {
|
|
39
|
+
project_path: string;
|
|
40
|
+
}>(rows: T[], scope: ActiveScope): T[];
|
|
41
|
+
/**
|
|
42
|
+
* `knowledge_decay::detect_knowledge_gaps` active-project scoping: a
|
|
43
|
+
* dependency whose declaring projects all sit outside every recently active
|
|
44
|
+
* path is skipped. Measured 2026-09-11: axios and socket.io were reported
|
|
45
|
+
* critical from `kairos-mvp/backend`, a project untouched since 2025-10.
|
|
46
|
+
*/
|
|
47
|
+
export declare function dependencyIsActive(rows: ReadonlyArray<{
|
|
48
|
+
project_path: string;
|
|
49
|
+
}>, scope: ActiveScope): boolean;
|
|
50
|
+
/** Items the dependency linker bound to a package with STRUCTURED proof. */
|
|
51
|
+
export interface LinkerIndex {
|
|
52
|
+
/** Ids of the items bound to this package (case- and `-`/`_`-folded). */
|
|
53
|
+
itemsFor(packageName: string): ReadonlySet<number>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `source_item_dependencies` rows carrying structured proof: `advisory` (the
|
|
57
|
+
* advisory's own affected-package list) or `exact_registry`. A registry
|
|
58
|
+
* advisory cites a dependency only through this proof, never through a title
|
|
59
|
+
* word (#618; `knowledge_decay::linked_to`). Null when the table or its
|
|
60
|
+
* `match_type` column is absent: callers then fall back to the advisory's
|
|
61
|
+
* subject package, since heuristic rows cannot be told apart from proof.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadLinkerIndex(db: FourDADatabase): LinkerIndex | null;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Knowledge-gap scope: which projects count as active, and which items the
|
|
4
|
+
* dependency linker bound to which packages. Mirrors the desktop app's
|
|
5
|
+
* `temporal::{active_repo_roots, dep_within_active_root, shares_active_root}`
|
|
6
|
+
* and `knowledge_decay::{get_active_project_paths, linked_to}`.
|
|
7
|
+
*/
|
|
8
|
+
/** `stmt.all(...)`, or [] when the statement cannot run: a read here never throws out of the tool. */
|
|
9
|
+
export function allRows(stmt, ...args) {
|
|
10
|
+
try {
|
|
11
|
+
return stmt.all(...args);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Lowercase, forward slashes, no trailing slash (`knowledge_decay::normalize_project_path`). */
|
|
18
|
+
export function normPath(p) {
|
|
19
|
+
return p.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Repo roots with git activity in the last `days` days, normalized. The
|
|
23
|
+
* `git_signals.repo_path` column is stored raw ("D:\4DA"). Empty when the
|
|
24
|
+
* table is absent or silent, which every caller reads as "unscoped".
|
|
25
|
+
*/
|
|
26
|
+
function activeRoots(db, days, requireCommit) {
|
|
27
|
+
if (!db.hasColumn("git_signals", "repo_path") || !db.hasColumn("git_signals", "timestamp"))
|
|
28
|
+
return [];
|
|
29
|
+
const commit = requireCommit && db.hasColumn("git_signals", "commit_hash")
|
|
30
|
+
? "AND commit_hash IS NOT NULL AND commit_hash != ''"
|
|
31
|
+
: "";
|
|
32
|
+
try {
|
|
33
|
+
const rows = db
|
|
34
|
+
.getRawDb()
|
|
35
|
+
.prepare(`SELECT DISTINCT repo_path FROM git_signals WHERE timestamp > datetime('now', ?) ${commit}`)
|
|
36
|
+
.all(`-${days} days`);
|
|
37
|
+
return [...new Set(rows.map((r) => normPath(r.repo_path ?? "")).filter((p) => p !== ""))];
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Path-BOUNDARY match in either direction: `d:/4da` covers `d:/4da/src-tauri`
|
|
45
|
+
* (and a manifest recorded above its repo root still counts), but never
|
|
46
|
+
* `d:/4da-experiments` (`temporal::dep_within_active_root`).
|
|
47
|
+
*/
|
|
48
|
+
export function withinRoots(path, roots) {
|
|
49
|
+
const p = normPath(path);
|
|
50
|
+
if (p === "")
|
|
51
|
+
return false;
|
|
52
|
+
return roots.some((root) => p === root || p.startsWith(`${root}/`) || root.startsWith(`${p}/`));
|
|
53
|
+
}
|
|
54
|
+
/** Do two project paths sit under one common active root (`temporal::shares_active_root`)? */
|
|
55
|
+
export function sharesRoot(a, b, roots) {
|
|
56
|
+
return roots.some((root) => withinRoots(a, [root]) && withinRoots(b, [root]));
|
|
57
|
+
}
|
|
58
|
+
export function loadActiveScope(db) {
|
|
59
|
+
return { rowRoots: activeRoots(db, 60, true), dependencyRoots: activeRoots(db, 30, false) };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The project rows the app's dependency funnel keeps before knowledge decay
|
|
63
|
+
* ever sees them (`temporal::get_all_dependencies`): rows inside an active repo
|
|
64
|
+
* root. Without it a gap for a dependency the active repo shares with a
|
|
65
|
+
* dormant one is judged on the dormant copy too — vitest in `d:/4da` (4.1.11)
|
|
66
|
+
* beside navcal's untouched 3.2.4 would name navcal under a critical the app
|
|
67
|
+
* never shows. No recorded git activity means unscoped, and a scope that
|
|
68
|
+
* matches no row at all keeps every row rather than blanking the tool.
|
|
69
|
+
*/
|
|
70
|
+
export function scopeRows(rows, scope) {
|
|
71
|
+
if (scope.rowRoots.length === 0)
|
|
72
|
+
return rows;
|
|
73
|
+
const kept = rows.filter((r) => withinRoots(r.project_path, scope.rowRoots));
|
|
74
|
+
return kept.length > 0 ? kept : rows;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* `knowledge_decay::detect_knowledge_gaps` active-project scoping: a
|
|
78
|
+
* dependency whose declaring projects all sit outside every recently active
|
|
79
|
+
* path is skipped. Measured 2026-09-11: axios and socket.io were reported
|
|
80
|
+
* critical from `kairos-mvp/backend`, a project untouched since 2025-10.
|
|
81
|
+
*/
|
|
82
|
+
export function dependencyIsActive(rows, scope) {
|
|
83
|
+
return scope.dependencyRoots.length === 0 || rows.some((r) => withinRoots(r.project_path, scope.dependencyRoots));
|
|
84
|
+
}
|
|
85
|
+
const foldName = (name) => name.toLowerCase().replace(/_/g, "-");
|
|
86
|
+
const NO_ITEMS = new Set();
|
|
87
|
+
/**
|
|
88
|
+
* `source_item_dependencies` rows carrying structured proof: `advisory` (the
|
|
89
|
+
* advisory's own affected-package list) or `exact_registry`. A registry
|
|
90
|
+
* advisory cites a dependency only through this proof, never through a title
|
|
91
|
+
* word (#618; `knowledge_decay::linked_to`). Null when the table or its
|
|
92
|
+
* `match_type` column is absent: callers then fall back to the advisory's
|
|
93
|
+
* subject package, since heuristic rows cannot be told apart from proof.
|
|
94
|
+
*/
|
|
95
|
+
export function loadLinkerIndex(db) {
|
|
96
|
+
const has = (column) => db.hasColumn("source_item_dependencies", column);
|
|
97
|
+
if (!has("source_item_id") || !has("package_name") || !has("match_type"))
|
|
98
|
+
return null;
|
|
99
|
+
const byPackage = new Map();
|
|
100
|
+
try {
|
|
101
|
+
const rows = db
|
|
102
|
+
.getRawDb()
|
|
103
|
+
.prepare("SELECT source_item_id, package_name FROM source_item_dependencies WHERE match_type IN ('advisory', 'exact_registry')")
|
|
104
|
+
.all();
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
if (!row.package_name)
|
|
107
|
+
continue;
|
|
108
|
+
const key = foldName(row.package_name);
|
|
109
|
+
const ids = byPackage.get(key) ?? new Set();
|
|
110
|
+
ids.add(row.source_item_id);
|
|
111
|
+
byPackage.set(key, ids);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return { itemsFor: (name) => byPackage.get(foldName(name)) ?? NO_ITEMS };
|
|
118
|
+
}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* knowledge_gaps tool
|
|
3
3
|
*
|
|
4
|
-
* Detect knowledge gaps
|
|
4
|
+
* Detect knowledge gaps: direct dependencies with unread, consequential
|
|
5
|
+
* intelligence. The desktop app computes the same concept in
|
|
6
|
+
* `src-tauri/src/knowledge_decay.rs::detect_knowledge_gaps`, and every rule
|
|
7
|
+
* here names the function it mirrors, because two implementations disagreeing
|
|
8
|
+
* is the failure this tool keeps being audited for. Measured 2026-09-11 on the
|
|
9
|
+
* founder machine: five `critical` gaps, four false or inflated, every one
|
|
10
|
+
* contradicting the app.
|
|
5
11
|
*/
|
|
6
12
|
import type { FourDADatabase } from "../db.js";
|
|
13
|
+
import type { LiveIntelligence } from "../live/index.js";
|
|
7
14
|
import type { SourceItemBriefRow } from "../types.js";
|
|
15
|
+
export { advisorySubject, gradeGap, registryVersionFromTitle, type Exposure, type GapSeverity, type GradableItem, } from "./knowledge-gap-grading.js";
|
|
16
|
+
export { versionInAnyRange, type AdvisoryRangeEvent } from "./knowledge-gap-ranges.js";
|
|
8
17
|
export interface KnowledgeGapsParams {
|
|
9
18
|
min_severity?: string;
|
|
10
19
|
limit?: number;
|
|
@@ -31,14 +40,14 @@ export declare const knowledgeGapsTool: {
|
|
|
31
40
|
};
|
|
32
41
|
export interface KnowledgeGap {
|
|
33
42
|
dependency: string;
|
|
34
|
-
version: string;
|
|
43
|
+
version: string | null;
|
|
35
44
|
project_path: string;
|
|
36
45
|
language: string;
|
|
37
46
|
missed_items: SourceItemBriefRow[];
|
|
38
47
|
gap_severity: string;
|
|
39
48
|
missed_count: number;
|
|
40
49
|
}
|
|
41
|
-
export declare function executeKnowledgeGaps(db: FourDADatabase, params: KnowledgeGapsParams): {
|
|
50
|
+
export declare function executeKnowledgeGaps(db: FourDADatabase, params: KnowledgeGapsParams, liveIntel?: Pick<LiveIntelligence, "getResolvedDeps" | "isInitialized"> | null): {
|
|
42
51
|
gaps: never[];
|
|
43
52
|
summary: string;
|
|
44
53
|
total_dependencies?: undefined;
|
|
@@ -2,29 +2,23 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* knowledge_gaps tool
|
|
4
4
|
*
|
|
5
|
-
* Detect knowledge gaps
|
|
5
|
+
* Detect knowledge gaps: direct dependencies with unread, consequential
|
|
6
|
+
* intelligence. The desktop app computes the same concept in
|
|
7
|
+
* `src-tauri/src/knowledge_decay.rs::detect_knowledge_gaps`, and every rule
|
|
8
|
+
* here names the function it mirrors, because two implementations disagreeing
|
|
9
|
+
* is the failure this tool keeps being audited for. Measured 2026-09-11 on the
|
|
10
|
+
* founder machine: five `critical` gaps, four false or inflated, every one
|
|
11
|
+
* contradicting the app.
|
|
6
12
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
// True when `text` mentions the package name as a whole word. Word characters
|
|
17
|
-
// for this purpose include - and _ (so dep "hono" does NOT match "hono-shim",
|
|
18
|
-
// and never matches "in HONOr of"). @scope/name matches as the full literal.
|
|
19
|
-
function mentionsPackage(text, pkg) {
|
|
20
|
-
const regex = new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegExp(pkg)}($|[^A-Za-z0-9_-])`, "i");
|
|
21
|
-
return regex.test(text);
|
|
22
|
-
}
|
|
23
|
-
// Escape SQL LIKE wildcards so a package name containing % or _ (npm names may
|
|
24
|
-
// contain _) is matched literally rather than as a pattern. Paired with ESCAPE '\'.
|
|
25
|
-
function escapeLike(s) {
|
|
26
|
-
return s.replace(/[\\%_]/g, (c) => "\\" + c);
|
|
27
|
-
}
|
|
13
|
+
import { citesDependency, loadCandidates, normalizeGapTitle } from "./knowledge-gap-citations.js";
|
|
14
|
+
import { AdvisoryStore, gapExposure, installExposure, reachingTier, rowVerdict, } from "./knowledge-gap-exposure.js";
|
|
15
|
+
import { InstallResolver } from "./knowledge-gap-installs.js";
|
|
16
|
+
import { gradeGap, isAdvisoryRow, parsePublishedAt, registryVersionFromTitle, } from "./knowledge-gap-grading.js";
|
|
17
|
+
import { compareVersions } from "../live/version-compare.js";
|
|
18
|
+
import { dependencyIsActive, loadActiveScope, loadLinkerIndex, scopeRows } from "./knowledge-gap-scope.js";
|
|
19
|
+
// The grading and range rules live in their own modules; re-exported for existing importers.
|
|
20
|
+
export { advisorySubject, gradeGap, registryVersionFromTitle, } from "./knowledge-gap-grading.js";
|
|
21
|
+
export { versionInAnyRange } from "./knowledge-gap-ranges.js";
|
|
28
22
|
export const knowledgeGapsTool = {
|
|
29
23
|
name: "knowledge_gaps",
|
|
30
24
|
description: `Detect knowledge gaps by cross-referencing your project dependencies with source items you haven't engaged with. Identifies things you should know about but might have missed.`,
|
|
@@ -45,18 +39,132 @@ export const knowledgeGapsTool = {
|
|
|
45
39
|
},
|
|
46
40
|
},
|
|
47
41
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
const SEVERITY_ORDER = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
43
|
+
const MAX_MISSED_ITEMS = 5;
|
|
44
|
+
/**
|
|
45
|
+
* published_at guard: OSV/CVE backfills ingest decades-old advisories whose
|
|
46
|
+
* created_at (discovery) is days old but whose published_at is ancient. A 2021
|
|
47
|
+
* advisory is not "missed intelligence" in 2026 — unless an install is
|
|
48
|
+
* positively inside it: a still-applying advisory is missed intelligence
|
|
49
|
+
* however old (live: the jsonwebtoken crate advisory, published February,
|
|
50
|
+
* still open against relay/'s 9.3.1 in September). NULL passes (many sources
|
|
51
|
+
* never set it).
|
|
52
|
+
*/
|
|
53
|
+
const PUBLISHED_WINDOW_MS = 90 * 24 * 60 * 60 * 1000;
|
|
54
|
+
function passesPublishedCut(e, exposure, cutoff) {
|
|
55
|
+
const publishedAt = parsePublishedAt(e.item.published_at);
|
|
56
|
+
if (publishedAt === null || publishedAt >= cutoff)
|
|
57
|
+
return true;
|
|
58
|
+
return e.verdict === "reached" || (e.verdict === "unresolvable" && exposure === "exposed");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Does this evidence keep a security signal alive? A resolved row that reaches
|
|
62
|
+
* an install does; so, conservatively, does a row whose install cannot be read,
|
|
63
|
+
* and a row the mirror cannot resolve while the package is not positively
|
|
64
|
+
* patched (AD-040 rule 3: only exposed installs are findings, and an unknown
|
|
65
|
+
* installed version stays conservatively exposed).
|
|
66
|
+
*/
|
|
67
|
+
function stillReaches(e, exposure) {
|
|
68
|
+
return e.verdict === "reached" || e.verdict === "unknown" || (e.verdict === "unresolvable" && exposure !== "safe");
|
|
69
|
+
}
|
|
70
|
+
function advisoryVerdict(item, name, installs, store) {
|
|
71
|
+
const entries = store.forRow(item.source_id, name);
|
|
72
|
+
return entries === null ? "unresolvable" : rowVerdict(entries, installs);
|
|
73
|
+
}
|
|
74
|
+
const REGISTRY_SOURCES = new Set(["crates_io", "npm_registry", "pypi", "go_modules"]);
|
|
75
|
+
/** A registry row's version is above every readable installed version (unknown versions keep it). */
|
|
76
|
+
function newerThanEveryInstall(item, installs) {
|
|
77
|
+
const released = registryVersionFromTitle(item.title || "");
|
|
78
|
+
if (!released)
|
|
79
|
+
return true;
|
|
80
|
+
const known = installs.filter((i) => i.version && i.ecosystem);
|
|
81
|
+
if (known.length === 0)
|
|
82
|
+
return true;
|
|
83
|
+
return known.every((i) => compareVersions(released, i.version, i.ecosystem) > 0);
|
|
84
|
+
}
|
|
85
|
+
function brief(item) {
|
|
86
|
+
return {
|
|
87
|
+
id: item.id,
|
|
88
|
+
title: item.title && item.title.length > 120 ? `${item.title.substring(0, 120)}...` : item.title,
|
|
89
|
+
url: item.url,
|
|
90
|
+
source_type: item.source_type,
|
|
91
|
+
created_at: item.created_at,
|
|
92
|
+
relevance_score: item.relevance_score,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** One dependency's gap, judged on every project that declares it, or null when nothing is missed. */
|
|
96
|
+
function buildGap(name, rows, ctx) {
|
|
97
|
+
const cited = ctx.candidates.filter((item) => citesDependency(item, name, ctx.linker));
|
|
98
|
+
if (cited.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
const installs = ctx.installs.installsFor(rows);
|
|
101
|
+
const stored = ctx.advisories.forPackage(name);
|
|
102
|
+
const exposure = gapExposure(installs, stored);
|
|
103
|
+
// An advisory row is judged LIVE on its own advisories against the installs
|
|
104
|
+
// this gap names (`osv::exposure::advisory_row_reaches`, AD-045): a row every
|
|
105
|
+
// install is outside of is not a gap at all. A row the mirror cannot resolve
|
|
106
|
+
// stays in, conservatively. Reposts of one story count once, the newest
|
|
107
|
+
// (`keyword_misses_from`'s normalized-title dedup).
|
|
108
|
+
const evidence = [];
|
|
109
|
+
const seenTitles = new Set();
|
|
110
|
+
for (const item of cited) {
|
|
111
|
+
// A registry release you already run (or are past) was not missed: live
|
|
112
|
+
// 2026-10-01, "npm: react v19.3.0" was a gap for an installed react 19.3.0.
|
|
113
|
+
if (REGISTRY_SOURCES.has(item.source_type ?? "") && !newerThanEveryInstall(item, installs))
|
|
114
|
+
continue;
|
|
115
|
+
const verdict = isAdvisoryRow(item) ? advisoryVerdict(item, name, installs, ctx.advisories) : null;
|
|
116
|
+
if (verdict === "clear")
|
|
117
|
+
continue;
|
|
118
|
+
const e = { item, verdict };
|
|
119
|
+
if (!passesPublishedCut(e, exposure, ctx.publishedCutoff))
|
|
120
|
+
continue;
|
|
121
|
+
const titleKey = normalizeGapTitle(item.title || "");
|
|
122
|
+
if (seenTitles.has(titleKey))
|
|
123
|
+
continue;
|
|
124
|
+
seenTitles.add(titleKey);
|
|
125
|
+
evidence.push(e);
|
|
126
|
+
}
|
|
127
|
+
if (evidence.length === 0)
|
|
128
|
+
return null;
|
|
129
|
+
const vulnerable = evidence.some((e) => stillReaches(e, exposure));
|
|
130
|
+
const tier = vulnerable ? reachingTier(stored, installs) : null;
|
|
131
|
+
const versions = installs.map((i) => i.version).filter((v) => v !== null);
|
|
132
|
+
// Each item graded on its own: the gap's grade is the best of them, and the
|
|
133
|
+
// items shown lead with the ones that earn it.
|
|
134
|
+
const graded = evidence
|
|
135
|
+
.map((e, order) => ({
|
|
136
|
+
e,
|
|
137
|
+
order,
|
|
138
|
+
severity: gradeGap([{ ...e.item, cites: true }], name, stillReaches(e, exposure), versions, tier),
|
|
139
|
+
}))
|
|
140
|
+
.sort((a, b) => SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity] || a.order - b.order);
|
|
141
|
+
const severity = graded[0].severity;
|
|
142
|
+
const shown = graded.slice(0, MAX_MISSED_ITEMS);
|
|
143
|
+
// AD-044: the row is true for every project it names. When the exposure is
|
|
144
|
+
// what makes this a gap, name the exposed projects with the lead one's own
|
|
145
|
+
// version (`affected_project_paths`); otherwise every declaring project.
|
|
146
|
+
const exposed = vulnerable ? installs.filter((i) => installExposure(i, stored) === "exposed") : [];
|
|
147
|
+
const named = exposed.length > 0 ? exposed : installs;
|
|
148
|
+
const lead = named[0];
|
|
149
|
+
return {
|
|
150
|
+
dependency: name,
|
|
151
|
+
version: lead.version,
|
|
152
|
+
project_path: named.length > 1 ? `${lead.projectPath} (+${named.length - 1} more)` : lead.projectPath,
|
|
153
|
+
language: lead.language,
|
|
154
|
+
missed_items: shown.map(({ e }) => brief(e.item)),
|
|
155
|
+
gap_severity: severity,
|
|
156
|
+
missed_count: shown.length,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export function executeKnowledgeGaps(db, params, liveIntel) {
|
|
56
160
|
// Direct dependencies only — a transitive dep's news is not the user's
|
|
57
161
|
// reading backlog. Dev deps stay in: a vitest or eslint advisory is real.
|
|
58
|
-
|
|
59
|
-
|
|
162
|
+
// Every direct dependency is scanned: a `LIMIT 100` here once left 43 of
|
|
163
|
+
// 143 unexamined.
|
|
164
|
+
const hasIsDirect = db.hasColumn("project_dependencies", "is_direct");
|
|
165
|
+
const deps = db
|
|
166
|
+
.getRawDb()
|
|
167
|
+
.prepare(`SELECT package_name, version, project_path, language FROM project_dependencies ${hasIsDirect ? "WHERE is_direct = 1" : ""}`)
|
|
60
168
|
.all();
|
|
61
169
|
if (deps.length === 0) {
|
|
62
170
|
return {
|
|
@@ -64,100 +172,43 @@ export function executeKnowledgeGaps(db, params) {
|
|
|
64
172
|
summary: "No project dependencies tracked. Add context directories to enable knowledge gap detection.",
|
|
65
173
|
};
|
|
66
174
|
}
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
175
|
+
const scope = loadActiveScope(db);
|
|
176
|
+
const ctx = {
|
|
177
|
+
candidates: loadCandidates(db),
|
|
178
|
+
linker: loadLinkerIndex(db),
|
|
179
|
+
advisories: new AdvisoryStore(db),
|
|
180
|
+
installs: new InstallResolver(db, liveIntel, scope.rowRoots),
|
|
181
|
+
publishedCutoff: Date.now() - PUBLISHED_WINDOW_MS,
|
|
182
|
+
};
|
|
183
|
+
// One gap per package however many projects declare it; each project's own
|
|
184
|
+
// install is judged inside the gap. Names shorter than 3 characters ("c",
|
|
185
|
+
// "go", "ws") match too much unrelated text to be evidence.
|
|
186
|
+
const byPackage = new Map();
|
|
187
|
+
for (const row of scopeRows(deps, scope)) {
|
|
188
|
+
if (!row.package_name || row.package_name.length < 3)
|
|
73
189
|
continue;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
190
|
+
const key = row.package_name.toLowerCase();
|
|
191
|
+
const group = byPackage.get(key);
|
|
192
|
+
if (group)
|
|
193
|
+
group.push(row);
|
|
194
|
+
else
|
|
195
|
+
byPackage.set(key, [row]);
|
|
196
|
+
}
|
|
197
|
+
const gaps = [];
|
|
198
|
+
for (const rows of byPackage.values()) {
|
|
199
|
+
if (!dependencyIsActive(rows, scope))
|
|
78
200
|
continue;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// source_item_id / action columns are unused), so the NOT-IN suppression must
|
|
83
|
-
// read those or it silently never fires.
|
|
84
|
-
//
|
|
85
|
-
// Grounding (each guard killed an observed false-positive class):
|
|
86
|
-
// - relevance_score >= 0.2: the scoring pipeline's above-noise band — drops
|
|
87
|
-
// off-topic chatter that merely contains the string (chocolate-bar class).
|
|
88
|
-
// - 30-day window: a "gap" is something you MISSED, not archaeology — a
|
|
89
|
-
// 2014 StackOverflow post is not missed intelligence.
|
|
90
|
-
// - LIKE is only a cheap candidate pre-filter; the real test is the
|
|
91
|
-
// word-boundary check below ("invite" must never evidence a vite gap).
|
|
92
|
-
// - feed_relevant is deliberately NOT filtered here: an item the feed gate
|
|
93
|
-
// rejected can still be a legitimate unread dep mention — surfacing those
|
|
94
|
-
// is this tool's niche. The relevance floor already excludes noise.
|
|
95
|
-
const pattern = `%${escapeLike(dep.package_name)}%`;
|
|
96
|
-
const candidates = rawDb
|
|
97
|
-
.prepare(`SELECT si.id, si.title, si.url, si.source_type, ${hasContentType ? "si.content_type" : "NULL AS content_type"}, si.created_at,
|
|
98
|
-
${hasRelevance ? "si.relevance_score" : "NULL AS relevance_score"}, substr(COALESCE(si.content, ''), 1, 2000) AS content_head
|
|
99
|
-
FROM source_items si
|
|
100
|
-
WHERE (si.title LIKE ? ESCAPE '\\' OR si.content LIKE ? ESCAPE '\\')
|
|
101
|
-
${hasRelevance ? "AND si.relevance_score IS NOT NULL AND si.relevance_score >= 0.2" : ""}
|
|
102
|
-
AND si.created_at >= datetime('now', '-30 days')
|
|
103
|
-
AND si.id NOT IN (SELECT item_id FROM interactions WHERE action_type IN ('click', 'save'))
|
|
104
|
-
ORDER BY si.created_at DESC LIMIT 25`)
|
|
105
|
-
.all(pattern, pattern);
|
|
106
|
-
// Word-boundary verification: the mention must be the package name as a
|
|
107
|
-
// whole word in the title or the content head, not a substring.
|
|
108
|
-
const mentionedItems = candidates
|
|
109
|
-
.filter((item) => mentionsPackage(item.title || "", dep.package_name) ||
|
|
110
|
-
mentionsPackage(item.content_head || "", dep.package_name))
|
|
111
|
-
.slice(0, 5);
|
|
112
|
-
if (mentionedItems.length > 0) {
|
|
113
|
-
const isAdvisory = (item) => item.source_type === "cve" ||
|
|
114
|
-
item.source_type === "osv" ||
|
|
115
|
-
item.content_type === "security_advisory";
|
|
116
|
-
const securityKeywords = (title) => hasWordBoundary(title, "cve") ||
|
|
117
|
-
hasWordBoundary(title, "security") ||
|
|
118
|
-
hasWordBoundary(title, "vulnerability");
|
|
119
|
-
// Graded honestly:
|
|
120
|
-
// - critical: an actual advisory whose TITLE names this dependency — the
|
|
121
|
-
// advisory is about the dep, not merely co-mentioning it in a body.
|
|
122
|
-
// - high: a security-keyword item whose title names the dep.
|
|
123
|
-
// - medium: 3+ recent unread mentions; low: 1-2.
|
|
124
|
-
const advisoryAboutDep = mentionedItems.some((item) => isAdvisory(item) && mentionsPackage(item.title || "", dep.package_name));
|
|
125
|
-
const securityTitleMention = mentionedItems.some((item) => securityKeywords(item.title || "") &&
|
|
126
|
-
mentionsPackage(item.title || "", dep.package_name));
|
|
127
|
-
const severity = advisoryAboutDep
|
|
128
|
-
? "critical"
|
|
129
|
-
: securityTitleMention
|
|
130
|
-
? "high"
|
|
131
|
-
: mentionedItems.length >= 3
|
|
132
|
-
? "medium"
|
|
133
|
-
: "low";
|
|
134
|
-
gaps.push({
|
|
135
|
-
dependency: dep.package_name,
|
|
136
|
-
version: dep.version,
|
|
137
|
-
project_path: dep.project_path,
|
|
138
|
-
language: dep.language,
|
|
139
|
-
missed_items: mentionedItems.map((item) => ({
|
|
140
|
-
id: item.id,
|
|
141
|
-
title: item.title && item.title.length > 120
|
|
142
|
-
? item.title.substring(0, 120) + "..."
|
|
143
|
-
: item.title,
|
|
144
|
-
url: item.url,
|
|
145
|
-
source_type: item.source_type,
|
|
146
|
-
created_at: item.created_at,
|
|
147
|
-
relevance_score: item.relevance_score,
|
|
148
|
-
})),
|
|
149
|
-
gap_severity: severity,
|
|
150
|
-
missed_count: mentionedItems.length,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
201
|
+
const gap = buildGap(rows[0].package_name, rows, ctx);
|
|
202
|
+
if (gap)
|
|
203
|
+
gaps.push(gap);
|
|
153
204
|
}
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
const minLevel = severityOrder[params.min_severity || "medium"] || 2;
|
|
157
|
-
const filtered = gaps.filter((g) => (severityOrder[g.gap_severity] || 0) >= minLevel);
|
|
205
|
+
const minLevel = SEVERITY_ORDER[params.min_severity || "medium"] || 2;
|
|
206
|
+
const filtered = gaps.filter((g) => (SEVERITY_ORDER[g.gap_severity] || 0) >= minLevel);
|
|
158
207
|
const maxGaps = Math.min(Math.max(1, params.limit || 15), 50);
|
|
159
208
|
return {
|
|
160
|
-
gaps: filtered
|
|
209
|
+
gaps: filtered
|
|
210
|
+
.sort((a, b) => (SEVERITY_ORDER[b.gap_severity] || 0) - (SEVERITY_ORDER[a.gap_severity] || 0))
|
|
211
|
+
.slice(0, maxGaps),
|
|
161
212
|
total_dependencies: deps.length,
|
|
162
213
|
gaps_found: filtered.length,
|
|
163
214
|
gaps_returned: Math.min(filtered.length, maxGaps),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this project's lockfiles hold for a package someone asked about by name.
|
|
3
|
+
*
|
|
4
|
+
* `vulnerability_scan` and `upgrade_planner` both take `package`. When either
|
|
5
|
+
* has nothing to show, "no findings" must not read as "not a dependency": in
|
|
6
|
+
* the 2026-10-03 agent eval the planner told an agent that `diff`, a
|
|
7
|
+
* transitive devDependency with two advisories, was "not a dependency of this
|
|
8
|
+
* project (or is a devDependency: pass include_dev)" while include_dev was set.
|
|
9
|
+
*/
|
|
10
|
+
import type { LiveIntelligence } from "../live/index.js";
|
|
11
|
+
export interface PackagePresence {
|
|
12
|
+
/** The registry name as the lockfile spells it, or null when no lockfile holds it. */
|
|
13
|
+
name: string | null;
|
|
14
|
+
versions: string[];
|
|
15
|
+
scope: "direct" | "transitive" | null;
|
|
16
|
+
/** Every copy is a direct devDependency (left out of runtime-scope scans and plans). */
|
|
17
|
+
directDevOnly: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function packagePresence(query: string, liveIntel: LiveIntelligence): PackagePresence;
|
|
20
|
+
/**
|
|
21
|
+
* One sentence for a `package` query that produced no rows.
|
|
22
|
+
* `checked` says what an empty answer means for this tool ("no known advisories", "nothing to upgrade").
|
|
23
|
+
*/
|
|
24
|
+
export declare function emptyAnswerNote(query: string, liveIntel: LiveIntelligence, includeDev: boolean, checked: string): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* What this project's lockfiles hold for a package someone asked about by name.
|
|
4
|
+
*
|
|
5
|
+
* `vulnerability_scan` and `upgrade_planner` both take `package`. When either
|
|
6
|
+
* has nothing to show, "no findings" must not read as "not a dependency": in
|
|
7
|
+
* the 2026-10-03 agent eval the planner told an agent that `diff`, a
|
|
8
|
+
* transitive devDependency with two advisories, was "not a dependency of this
|
|
9
|
+
* project (or is a devDependency: pass include_dev)" while include_dev was set.
|
|
10
|
+
*/
|
|
11
|
+
import { namesPackage } from "../live/version-compare.js";
|
|
12
|
+
export function packagePresence(query, liveIntel) {
|
|
13
|
+
const copies = liveIntel.getAuditDeps().filter((d) => namesPackage(query, d.name, d.ecosystem));
|
|
14
|
+
if (copies.length === 0)
|
|
15
|
+
return { name: null, versions: [], scope: null, directDevOnly: false };
|
|
16
|
+
return {
|
|
17
|
+
name: copies[0].name,
|
|
18
|
+
versions: [...new Set(copies.map((d) => d.version ?? "unknown"))],
|
|
19
|
+
scope: copies.some((d) => d.isDirect) ? "direct" : "transitive",
|
|
20
|
+
directDevOnly: copies.every((d) => d.isDirect && d.isDev),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One sentence for a `package` query that produced no rows.
|
|
25
|
+
* `checked` says what an empty answer means for this tool ("no known advisories", "nothing to upgrade").
|
|
26
|
+
*/
|
|
27
|
+
export function emptyAnswerNote(query, liveIntel, includeDev, checked) {
|
|
28
|
+
const p = packagePresence(query, liveIntel);
|
|
29
|
+
if (!p.name)
|
|
30
|
+
return `"${query}" is not in this project's lockfiles`;
|
|
31
|
+
const held = `${p.name} ${p.versions.join(", ")} (${p.scope})`;
|
|
32
|
+
if (!includeDev && p.directDevOnly)
|
|
33
|
+
return `${held} is a direct devDependency, left out at this scope: pass include_dev: true`;
|
|
34
|
+
return `${held}: ${checked}`;
|
|
35
|
+
}
|