@4da/mcp-server 6.0.2 → 6.2.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 +433 -0
- package/README.md +26 -18
- package/dist/db.d.ts +73 -8
- package/dist/db.js +180 -43
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +15 -9
- package/dist/embeddings.js +19 -7
- package/dist/gitignore.js +9 -3
- package/dist/index.js +43 -4
- package/dist/live/cache.d.ts +31 -2
- package/dist/live/cache.js +113 -1
- package/dist/live/changelog-classify.d.ts +9 -3
- package/dist/live/changelog-classify.js +125 -12
- package/dist/live/changelog.d.ts +22 -3
- package/dist/live/changelog.js +132 -38
- package/dist/live/dev-scope.d.ts +2 -2
- package/dist/live/fix-paths.d.ts +118 -0
- package/dist/live/fix-paths.js +498 -0
- package/dist/live/github-releases.d.ts +69 -0
- package/dist/live/github-releases.js +252 -0
- package/dist/live/go-registry.d.ts +5 -0
- package/dist/live/go-registry.js +1 -1
- package/dist/live/go-releases.d.ts +48 -0
- package/dist/live/go-releases.js +196 -0
- package/dist/live/index.d.ts +34 -8
- package/dist/live/index.js +66 -7
- package/dist/live/lockfile-parents.d.ts +61 -0
- package/dist/live/lockfile-parents.js +360 -0
- package/dist/live/lockfile-parsers-pygo.d.ts +31 -0
- package/dist/live/lockfile-parsers-pygo.js +178 -28
- package/dist/live/lockfile-parsers.d.ts +4 -1
- package/dist/live/lockfile-parsers.js +57 -24
- package/dist/live/lockfile-types.d.ts +12 -3
- package/dist/live/lockfile-types.js +2 -1
- package/dist/live/osv-ranges.d.ts +85 -0
- package/dist/live/osv-ranges.js +136 -0
- package/dist/live/osv-scanner.d.ts +1 -0
- package/dist/live/osv-scanner.js +85 -78
- package/dist/live/package-archive.d.ts +2 -0
- package/dist/live/package-archive.js +7 -4
- package/dist/live/pypi-releases.d.ts +44 -0
- package/dist/live/pypi-releases.js +103 -0
- package/dist/live/range-satisfies.d.ts +21 -0
- package/dist/live/range-satisfies.js +190 -0
- package/dist/live/rate-limiter.d.ts +6 -0
- package/dist/live/rate-limiter.js +5 -0
- package/dist/live/registry-health.js +2 -0
- package/dist/live/release-metadata.d.ts +17 -8
- package/dist/live/release-metadata.js +6 -3
- package/dist/live/resolution.d.ts +11 -1
- package/dist/live/resolution.js +16 -6
- package/dist/live/semver-utils.d.ts +8 -0
- package/dist/live/semver-utils.js +10 -2
- package/dist/live/types.d.ts +17 -1
- package/dist/live/upgrade-go.d.ts +62 -0
- package/dist/live/upgrade-go.js +130 -0
- package/dist/live/upgrade-pypi.d.ts +42 -0
- package/dist/live/upgrade-pypi.js +85 -0
- package/dist/live/upgrade-sources.d.ts +30 -11
- package/dist/live/upgrade-sources.js +93 -21
- package/dist/live/zip-archive.d.ts +22 -0
- package/dist/live/zip-archive.js +98 -0
- package/dist/memory-store.d.ts +19 -0
- package/dist/memory-store.js +8 -0
- package/dist/native-bindings.d.ts +7 -2
- package/dist/native-bindings.js +4 -0
- package/dist/project-tree.d.ts +50 -8
- package/dist/project-tree.js +121 -29
- package/dist/schema-registry.js +5 -5
- package/dist/server-instructions.d.ts +6 -0
- package/dist/server-instructions.js +20 -15
- package/dist/sqlite-driver.d.ts +134 -0
- package/dist/sqlite-driver.js +359 -0
- package/dist/tool-dispatch.d.ts +8 -1
- package/dist/tool-dispatch.js +17 -2
- package/dist/tools/agent-memory.js +10 -2
- package/dist/tools/app-plan.d.ts +11 -1
- package/dist/tools/app-plan.js +32 -6
- package/dist/tools/briefing-task-scope.d.ts +21 -2
- package/dist/tools/briefing-task-scope.js +97 -4
- package/dist/tools/decision-memory.js +9 -3
- package/dist/tools/dependency-check-signals.d.ts +23 -5
- package/dist/tools/dependency-check-signals.js +62 -19
- package/dist/tools/dependency-check-versions.d.ts +94 -0
- package/dist/tools/dependency-check-versions.js +213 -0
- package/dist/tools/dependency-check.d.ts +27 -11
- package/dist/tools/dependency-check.js +321 -74
- package/dist/tools/dependency-health.d.ts +1 -1
- package/dist/tools/dependency-health.js +5 -0
- package/dist/tools/knowledge-gap-ranges.d.ts +1 -1
- package/dist/tools/knowledge-gap-ranges.js +1 -1
- package/dist/tools/knowledge-gap-scope.d.ts +2 -2
- package/dist/tools/project-scope.d.ts +45 -0
- package/dist/tools/project-scope.js +119 -0
- package/dist/tools/record-feedback.js +1 -1
- package/dist/tools/upgrade-impact-callsites.d.ts +1 -0
- package/dist/tools/upgrade-impact-callsites.js +23 -5
- package/dist/tools/upgrade-impact-changelog.d.ts +41 -0
- package/dist/tools/upgrade-impact-changelog.js +79 -0
- package/dist/tools/upgrade-impact-imports.d.ts +42 -0
- package/dist/tools/upgrade-impact-imports.js +260 -0
- package/dist/tools/upgrade-impact-literals.js +1 -1
- package/dist/tools/upgrade-impact-report.d.ts +23 -0
- package/dist/tools/upgrade-impact-report.js +45 -6
- package/dist/tools/upgrade-impact.d.ts +13 -4
- package/dist/tools/upgrade-impact.js +236 -85
- package/dist/tools/upgrade-planner-fixpaths.d.ts +40 -0
- package/dist/tools/upgrade-planner-fixpaths.js +0 -0
- package/dist/tools/upgrade-planner.d.ts +31 -3
- package/dist/tools/upgrade-planner.js +74 -19
- package/dist/tools/vulnerability-scan-concise.d.ts +13 -1
- package/dist/tools/vulnerability-scan-concise.js +15 -3
- package/dist/tools/vulnerability-scan-format.d.ts +58 -1
- package/dist/tools/vulnerability-scan-format.js +107 -11
- package/dist/tools/vulnerability-scan.d.ts +104 -13
- package/dist/tools/vulnerability-scan.js +27 -3
- package/dist/tools/what-should-i-know.d.ts +22 -2
- package/dist/tools/what-should-i-know.js +122 -12
- package/package.json +10 -32
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Cross-agent persistent memory. What Claude Code learns, Cursor can access.
|
|
6
6
|
* Enables AI agents to store and recall memories across sessions and tools.
|
|
7
7
|
*/
|
|
8
|
+
import { writeMeta, writerOf } from "../memory-store.js";
|
|
8
9
|
import { rankRowsByRecall } from "./recall.js";
|
|
9
10
|
import { getEmbeddingConfig, semanticScores, } from "../embeddings.js";
|
|
10
11
|
// ============================================================================
|
|
@@ -149,8 +150,14 @@ export function executeAgentMemory(db, params) {
|
|
|
149
150
|
}
|
|
150
151
|
// --- Storage quota: max 1000 entries per agent ---
|
|
151
152
|
const agentType = params.agent_type || "unknown";
|
|
153
|
+
// The app's own agent_memory table (separate write connection), or the
|
|
154
|
+
// server's store when the app's schema cannot hold the row.
|
|
155
|
+
const to = writerOf(db, "agent_memory", [
|
|
156
|
+
"session_id", "agent_type", "memory_type", "subject", "content", "context_tags", "expires_at",
|
|
157
|
+
]);
|
|
152
158
|
try {
|
|
153
|
-
|
|
159
|
+
// Counted where the row will go.
|
|
160
|
+
const countRow = (to.target === "server_store" ? to.raw : rawDb)
|
|
154
161
|
.prepare(`SELECT COUNT(*) as cnt FROM agent_memory WHERE agent_type = ?`)
|
|
155
162
|
.get(agentType);
|
|
156
163
|
const currentCount = countRow?.cnt ?? 0;
|
|
@@ -166,7 +173,7 @@ export function executeAgentMemory(db, params) {
|
|
|
166
173
|
};
|
|
167
174
|
}
|
|
168
175
|
try {
|
|
169
|
-
const stmt =
|
|
176
|
+
const stmt = to.raw.prepare(`INSERT INTO agent_memory
|
|
170
177
|
(session_id, agent_type, memory_type, subject, content, context_tags, expires_at)
|
|
171
178
|
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
172
179
|
const result = stmt.run(params.session_id || "unknown", params.agent_type || "unknown", params.memory_type || "context", params.subject, params.content, JSON.stringify(params.context_tags || []), params.expires_at || null);
|
|
@@ -174,6 +181,7 @@ export function executeAgentMemory(db, params) {
|
|
|
174
181
|
success: true,
|
|
175
182
|
id: result.lastInsertRowid,
|
|
176
183
|
message: `Memory stored: ${params.subject}`,
|
|
184
|
+
...writeMeta(to),
|
|
177
185
|
};
|
|
178
186
|
}
|
|
179
187
|
catch (error) {
|
package/dist/tools/app-plan.d.ts
CHANGED
|
@@ -76,12 +76,22 @@ export interface AppPlanResult {
|
|
|
76
76
|
totalSteps: number;
|
|
77
77
|
steps: AppPlanStepView[];
|
|
78
78
|
summary: string;
|
|
79
|
+
/** The project the steps were narrowed to, or null for every project in the plan. */
|
|
80
|
+
projectScope: string | null;
|
|
81
|
+
/** Steps of the full plan that only touch other projects (left out by projectScope). */
|
|
82
|
+
otherProjectSteps?: number;
|
|
79
83
|
snapshot: Omit<AppPlanSnapshot, "steps" | "generatedAt" | "expiresAt">;
|
|
80
84
|
provenance: {
|
|
81
85
|
mode: "app_plan";
|
|
82
86
|
note: string;
|
|
83
87
|
};
|
|
84
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* The steps that touch `scope` (a project directory): each step keeps only the
|
|
91
|
+
* installed lines with a site in that project, and those lines keep only
|
|
92
|
+
* those sites. Null keeps everything.
|
|
93
|
+
*/
|
|
94
|
+
export declare function scopeAppPlanSteps(steps: AppPlanStepView[], scope: string | null): AppPlanStepView[];
|
|
85
95
|
/** Shape the app's plan as the tool result; `risk_threshold` / `max_recommendations` narrow it. */
|
|
86
96
|
export declare function formatAppPlan(read: Extract<AppPlanRead, {
|
|
87
97
|
kind: "plan";
|
|
@@ -89,4 +99,4 @@ export declare function formatAppPlan(read: Extract<AppPlanRead, {
|
|
|
89
99
|
risk_threshold?: string;
|
|
90
100
|
max_recommendations?: number;
|
|
91
101
|
package?: string;
|
|
92
|
-
}): AppPlanResult;
|
|
102
|
+
}, scope?: string | null): AppPlanResult;
|
package/dist/tools/app-plan.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* APP_PLAN_SCHEMA_VERSION is treated as absent and the caller falls back to
|
|
15
15
|
* its standalone heuristic.
|
|
16
16
|
*/
|
|
17
|
+
import { isWithin } from "./project-scope.js";
|
|
17
18
|
/** The snapshot schema this reader understands (Rust `PLAN_SCHEMA_VERSION`). */
|
|
18
19
|
export const APP_PLAN_SCHEMA_VERSION = 4;
|
|
19
20
|
const PLAN_KV_KEY = "upgrade_plan_snapshot";
|
|
@@ -93,13 +94,32 @@ export function readAppPlan(db, now = new Date()) {
|
|
|
93
94
|
}
|
|
94
95
|
const URGENCY_RISK = { critical: 3, high: 2, medium: 1, watch: 0 };
|
|
95
96
|
const RISK_LEVELS = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
97
|
+
/**
|
|
98
|
+
* The steps that touch `scope` (a project directory): each step keeps only the
|
|
99
|
+
* installed lines with a site in that project, and those lines keep only
|
|
100
|
+
* those sites. Null keeps everything.
|
|
101
|
+
*/
|
|
102
|
+
export function scopeAppPlanSteps(steps, scope) {
|
|
103
|
+
if (!scope)
|
|
104
|
+
return steps;
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const step of steps) {
|
|
107
|
+
const lines = step.lines
|
|
108
|
+
.map((line) => ({ ...line, sites: line.sites.filter((s) => isWithin(s.project, scope)) }))
|
|
109
|
+
.filter((line) => line.sites.length > 0);
|
|
110
|
+
if (lines.length > 0)
|
|
111
|
+
out.push({ ...step, lines });
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
96
115
|
const APP_PROVENANCE_NOTE = "The 4DA desktop app's own cross-project, version-confirmed Upgrade Plan, read from its database " +
|
|
97
116
|
"(the same plan the app shows and `4da plan --json` prints). Each step is keyed by the app's item id.";
|
|
98
117
|
/** Shape the app's plan as the tool result; `risk_threshold` / `max_recommendations` narrow it. */
|
|
99
|
-
export function formatAppPlan(read, params) {
|
|
118
|
+
export function formatAppPlan(read, params, scope = null) {
|
|
100
119
|
const { snapshot, stale } = read;
|
|
101
120
|
const threshold = params.risk_threshold ?? "all";
|
|
102
|
-
|
|
121
|
+
const scoped = scopeAppPlanSteps(snapshot.steps, scope);
|
|
122
|
+
let steps = scoped;
|
|
103
123
|
// `package` narrows the app's plan the same way it narrows the standalone one.
|
|
104
124
|
const wanted = params.package?.trim().toLowerCase();
|
|
105
125
|
if (wanted)
|
|
@@ -113,8 +133,9 @@ export function formatAppPlan(read, params) {
|
|
|
113
133
|
const count = (m) => selected.filter((s) => s.mechanism === m).length;
|
|
114
134
|
const majors = selected.filter((s) => s.lines.some((l) => l.upgrade_type === "major")).length;
|
|
115
135
|
const parts = [
|
|
116
|
-
`${selected.length} step${selected.length !== 1 ? "s" : ""} from the 4DA app's plan (generated ${snapshot.generatedAt})`,
|
|
136
|
+
`${selected.length} step${selected.length !== 1 ? "s" : ""} from the 4DA app's plan (generated ${snapshot.generatedAt})${scope ? ` for ${scope}` : " across every project it tracks"}`,
|
|
117
137
|
];
|
|
138
|
+
const otherProjectSteps = snapshot.steps.length - scoped.length;
|
|
118
139
|
if (count("manifest_bump") > 0)
|
|
119
140
|
parts.push(`${count("manifest_bump")} manifest bump(s)`);
|
|
120
141
|
if (count("lockfile_or_parent_update") > 0) {
|
|
@@ -126,8 +147,11 @@ export function formatAppPlan(read, params) {
|
|
|
126
147
|
parts.push(`${count("no_fix")} with no fix published`);
|
|
127
148
|
if (majors > 0)
|
|
128
149
|
parts.push(`${majors} cross a major version`);
|
|
129
|
-
if (selected.length <
|
|
130
|
-
parts.push(`${
|
|
150
|
+
if (selected.length < scoped.length) {
|
|
151
|
+
parts.push(`${scoped.length} in ${scope ? "the plan for this project" : "the full plan"} (raise max_recommendations for the rest)`);
|
|
152
|
+
}
|
|
153
|
+
if (scope && otherProjectSteps > 0) {
|
|
154
|
+
parts.push(`${otherProjectSteps} step${otherProjectSteps !== 1 ? "s" : ""} only touch other projects (project_path "*" lists every project)`);
|
|
131
155
|
}
|
|
132
156
|
const staleness = stale
|
|
133
157
|
? `STALE: past its freshness horizon (${snapshot.expiresAt}). The advisories or installs may have changed since; open the 4DA app to recompute.`
|
|
@@ -139,9 +163,11 @@ export function formatAppPlan(read, params) {
|
|
|
139
163
|
expiresAt: snapshot.expiresAt,
|
|
140
164
|
stale,
|
|
141
165
|
staleness,
|
|
142
|
-
totalSteps:
|
|
166
|
+
totalSteps: scoped.length,
|
|
143
167
|
steps: selected,
|
|
144
168
|
summary: parts.join(". ") + ".",
|
|
169
|
+
projectScope: scope,
|
|
170
|
+
...(scope ? { otherProjectSteps } : {}),
|
|
145
171
|
snapshot: {
|
|
146
172
|
schemaVersion: snapshot.schemaVersion,
|
|
147
173
|
generatorVersion: snapshot.generatorVersion,
|
|
@@ -23,6 +23,8 @@ export interface TaskPackage {
|
|
|
23
23
|
/** Versions the task text names after the package ("5.x to 7.x" -> from 5.x, to 7.x). */
|
|
24
24
|
requested_from: string | null;
|
|
25
25
|
requested_to: string | null;
|
|
26
|
+
/** Set when the package was found through a family the task names ("all tauri plugins" -> "tauri"). */
|
|
27
|
+
family?: string;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Package names that are also everyday words. One of these counts as named
|
|
@@ -36,8 +38,25 @@ export declare const COMMON_WORDS: Set<string>;
|
|
|
36
38
|
* Direct dependencies first; at most `limit` packages.
|
|
37
39
|
*/
|
|
38
40
|
export declare function detectTaskPackages(task: string, files: string[], deps: ResolvedDependency[], limit?: number): TaskPackage[];
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The package families a task names, as base names: "all tauri plugins",
|
|
43
|
+
* "tauri and its plugins", "@tauri-apps packages", "tauri-plugin-*",
|
|
44
|
+
* "@tauri-apps/*". A name alone ("bump tauri") is not a family.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectFamilyBases(task: string, files?: string[]): string[];
|
|
47
|
+
/** True when `name` belongs to the family of `base`: base-*, base_*, @base/*, @base-apps/*. */
|
|
48
|
+
export declare function inFamily(name: string, base: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The direct dependencies in the families the task names, other than the
|
|
51
|
+
* packages it names exactly. Only packages this project actually declares:
|
|
52
|
+
* a family is expanded against the lockfiles, never guessed. Measured
|
|
53
|
+
* 2026-10-07: "bump tauri and all tauri plugins" matched only `tauri`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function detectFamilyPackages(task: string, files: string[], deps: ResolvedDependency[], exact: TaskPackage[], limit?: number): TaskPackage[];
|
|
56
|
+
/** True when the task asks for the newest release ("to latest", "newest major"). */
|
|
57
|
+
export declare function wantsLatest(task: string): boolean;
|
|
58
|
+
/** The caret line a version sits on: "2" for 2.12.1, "0.13" for 0.13.5. */
|
|
59
|
+
export declare function caretLine(version: string | null): string | null;
|
|
41
60
|
/** Actionable advisories on the installed copies of one package, highest presented severity first. */
|
|
42
61
|
export declare function advisoriesFor(pkg: TaskPackage, vulns: VulnerabilityEntry[]): {
|
|
43
62
|
id: string;
|
|
@@ -95,10 +95,103 @@ export function detectTaskPackages(task, files, deps, limit = 5) {
|
|
|
95
95
|
packages.sort((a, b) => Number(b.direct) - Number(a.direct));
|
|
96
96
|
return packages.slice(0, limit);
|
|
97
97
|
}
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
/** Words after a name that make it a family: "tauri plugins", "@tauri-apps packages". */
|
|
99
|
+
const FAMILY_NOUNS = /^(?:plugins?|packages?|crates?|family|families|ecosystem|deps|dependencies|libraries|libs|modules|siblings)$/i;
|
|
100
|
+
/**
|
|
101
|
+
* The package families a task names, as base names: "all tauri plugins",
|
|
102
|
+
* "tauri and its plugins", "@tauri-apps packages", "tauri-plugin-*",
|
|
103
|
+
* "@tauri-apps/*". A name alone ("bump tauri") is not a family.
|
|
104
|
+
*/
|
|
105
|
+
export function detectFamilyBases(task, files = []) {
|
|
106
|
+
const tokens = tokenize(`${task} ${files.join(" ")}`);
|
|
107
|
+
const bases = new Set();
|
|
108
|
+
let lastName = null;
|
|
109
|
+
tokens.forEach((token, i) => {
|
|
110
|
+
// Globs: "tauri-plugin-*" -> tauri, "@tauri-apps/*" -> @tauri-apps.
|
|
111
|
+
const glob = /^(@?[a-z0-9][\w.-]*?)(?:-plugin)?(?:[-_/]\*)$/.exec(token);
|
|
112
|
+
if (glob) {
|
|
113
|
+
bases.add(glob[1]);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const next = tokens[i + 1];
|
|
117
|
+
if (next && FAMILY_NOUNS.test(next) && /^@?[a-z][\w.@/-]*$/.test(token) && !FAMILY_NOUNS.test(token)) {
|
|
118
|
+
if (!/^(?:all|every|its|their|the|other|any|some|these|those|related|official|first-party|in|of|for|with|from|our|my|your|and|or|to|dev|npm|cargo|pip|go|rust|js|node|python|workspace|root|outdated|vulnerable|new|old|unused|direct|transitive)$/.test(token)) {
|
|
119
|
+
bases.add(token);
|
|
120
|
+
}
|
|
121
|
+
else if (token === "its" || token === "their") {
|
|
122
|
+
// "tauri and its plugins": the name the possessive refers to.
|
|
123
|
+
if (lastName)
|
|
124
|
+
bases.add(lastName);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (/^@?[a-z][\w.@/-]*$/.test(token) && !FAMILY_NOUNS.test(token) && !/^(?:and|or|its|their|all|every|to|the|bump|upgrade|update)$/.test(token)) {
|
|
128
|
+
lastName = token;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return [...bases];
|
|
132
|
+
}
|
|
133
|
+
/** True when `name` belongs to the family of `base`: base-*, base_*, @base/*, @base-apps/*. */
|
|
134
|
+
export function inFamily(name, base) {
|
|
135
|
+
const n = name.toLowerCase();
|
|
136
|
+
const b = base.toLowerCase();
|
|
137
|
+
if (n === b)
|
|
138
|
+
return false;
|
|
139
|
+
if (b.startsWith("@"))
|
|
140
|
+
return n.startsWith(`${b}/`);
|
|
141
|
+
return (n.startsWith(`${b}-`) ||
|
|
142
|
+
n.startsWith(`${b}_`) ||
|
|
143
|
+
n.startsWith(`@${b}/`) ||
|
|
144
|
+
n.startsWith(`@${b}-`) // @tauri-apps/api, @tauri-apps/plugin-shell
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The direct dependencies in the families the task names, other than the
|
|
149
|
+
* packages it names exactly. Only packages this project actually declares:
|
|
150
|
+
* a family is expanded against the lockfiles, never guessed. Measured
|
|
151
|
+
* 2026-10-07: "bump tauri and all tauri plugins" matched only `tauri`.
|
|
152
|
+
*/
|
|
153
|
+
export function detectFamilyPackages(task, files, deps, exact, limit = 25) {
|
|
154
|
+
const bases = detectFamilyBases(task, files);
|
|
155
|
+
if (bases.length === 0)
|
|
156
|
+
return [];
|
|
157
|
+
const taken = new Set(exact.map((p) => `${p.ecosystem}\0${p.name}`));
|
|
158
|
+
const groups = new Map();
|
|
159
|
+
for (const dep of deps) {
|
|
160
|
+
if (!dep.name || !dep.isDirect)
|
|
161
|
+
continue;
|
|
162
|
+
const key = `${dep.ecosystem}\0${dep.name}`;
|
|
163
|
+
if (taken.has(key))
|
|
164
|
+
continue;
|
|
165
|
+
const base = bases.find((b) => inFamily(dep.name, b));
|
|
166
|
+
if (!base)
|
|
167
|
+
continue;
|
|
168
|
+
const group = groups.get(key) ?? { base, copies: [] };
|
|
169
|
+
group.copies.push(dep);
|
|
170
|
+
groups.set(key, group);
|
|
171
|
+
}
|
|
172
|
+
const out = [...groups.values()].map(({ base, copies }) => ({
|
|
173
|
+
name: copies[0].name,
|
|
174
|
+
ecosystem: copies[0].ecosystem,
|
|
175
|
+
installed: [...new Set(copies.map((d) => d.version).filter((v) => Boolean(v)))],
|
|
176
|
+
direct: true,
|
|
177
|
+
dev: copies.every((d) => d.isDev),
|
|
178
|
+
requested_from: null,
|
|
179
|
+
requested_to: null,
|
|
180
|
+
family: base,
|
|
181
|
+
}));
|
|
182
|
+
out.sort((a, b) => a.ecosystem.localeCompare(b.ecosystem) || a.name.localeCompare(b.name));
|
|
183
|
+
return out.slice(0, limit);
|
|
184
|
+
}
|
|
185
|
+
/** True when the task asks for the newest release ("to latest", "newest major"). */
|
|
186
|
+
export function wantsLatest(task) {
|
|
187
|
+
return /\b(?:latest|newest|most recent)\b/i.test(task);
|
|
188
|
+
}
|
|
189
|
+
/** The caret line a version sits on: "2" for 2.12.1, "0.13" for 0.13.5. */
|
|
190
|
+
export function caretLine(version) {
|
|
191
|
+
const m = version ? /^v?(\d+)\.(\d+)/.exec(version) : null;
|
|
192
|
+
if (!m)
|
|
193
|
+
return null;
|
|
194
|
+
return m[1] === "0" ? `0.${m[2]}` : m[1];
|
|
102
195
|
}
|
|
103
196
|
/** Actionable advisories on the installed copies of one package, highest presented severity first. */
|
|
104
197
|
export function advisoriesFor(pkg, vulns) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Manage developer decisions - record, list, check alignment, update, and supersede.
|
|
6
6
|
* Decisions persist and inform signal classification, tech radar, and agent context.
|
|
7
7
|
*/
|
|
8
|
+
import { writeMeta, writerOf } from "../memory-store.js";
|
|
8
9
|
import { executeCheckDecisionAlignment } from "./decision-enforcement.js";
|
|
9
10
|
// ============================================================================
|
|
10
11
|
// Tool Definition
|
|
@@ -132,7 +133,10 @@ export function executeDecisionMemory(db, params) {
|
|
|
132
133
|
error: "subject and decision are required for record action",
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
|
-
|
|
136
|
+
// The app's own developer_decisions table (separate write connection), or
|
|
137
|
+
// the server's store when the app's schema cannot hold the row.
|
|
138
|
+
const to = writerOf(db, "developer_decisions", ["decision_type", "subject", "decision", "rationale", "alternatives_rejected", "context_tags", "confidence", "status"]);
|
|
139
|
+
const stmt = to.raw.prepare(`INSERT INTO developer_decisions
|
|
136
140
|
(decision_type, subject, decision, rationale,
|
|
137
141
|
alternatives_rejected, context_tags, confidence, status)
|
|
138
142
|
VALUES (?, ?, ?, ?, ?, ?, ?, 'active')`);
|
|
@@ -141,6 +145,7 @@ export function executeDecisionMemory(db, params) {
|
|
|
141
145
|
success: true,
|
|
142
146
|
id: result.lastInsertRowid,
|
|
143
147
|
message: `Decision recorded: ${params.subject}`,
|
|
148
|
+
...writeMeta(to),
|
|
144
149
|
};
|
|
145
150
|
}
|
|
146
151
|
case "list": {
|
|
@@ -207,7 +212,8 @@ export function executeDecisionMemory(db, params) {
|
|
|
207
212
|
}
|
|
208
213
|
sets.push("updated_at = datetime('now')");
|
|
209
214
|
values.push(params.id);
|
|
210
|
-
const
|
|
215
|
+
const updater = writerOf(db, "developer_decisions", ["id", "updated_at", ...sets.map((s) => s.split(" ")[0])]);
|
|
216
|
+
const updated = updater.raw
|
|
211
217
|
.prepare(`UPDATE developer_decisions SET ${sets.join(", ")} WHERE id = ?`)
|
|
212
218
|
.run(...values);
|
|
213
219
|
if (updated.changes === 0) {
|
|
@@ -229,7 +235,7 @@ export function executeDecisionMemory(db, params) {
|
|
|
229
235
|
if (missing.length > 0) {
|
|
230
236
|
return { error: `No decision with id ${missing.join(" or ")}. Use action "list" to find ids.` };
|
|
231
237
|
}
|
|
232
|
-
|
|
238
|
+
writerOf(db, "developer_decisions", ["id", "status", "superseded_by", "updated_at"]).raw
|
|
233
239
|
.prepare(`UPDATE developer_decisions
|
|
234
240
|
SET status = 'superseded',
|
|
235
241
|
superseded_by = ?,
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
* another source was unreachable, but a missing source never lets the
|
|
9
9
|
* verdict fall through to `proceed`.
|
|
10
10
|
*/
|
|
11
|
-
import type { PackageReleaseIndex, ReleaseRecord } from "../live/release-metadata.js";
|
|
11
|
+
import type { CheckEcosystem, PackageReleaseIndex, ReleaseRecord } from "../live/release-metadata.js";
|
|
12
12
|
export type Verdict = "proceed" | "wait" | "review" | "avoid" | "unknown";
|
|
13
13
|
export type SignalEffect = Verdict | "info";
|
|
14
14
|
export interface CheckItem {
|
|
15
|
-
ecosystem:
|
|
15
|
+
ecosystem: CheckEcosystem;
|
|
16
16
|
package: string;
|
|
17
17
|
to: string;
|
|
18
18
|
from?: string;
|
|
@@ -60,13 +60,31 @@ export declare function selectBaseline(index: PackageReleaseIndex, item: CheckIt
|
|
|
60
60
|
/** Runtime dependency names in `to` that the baseline did not have; null when either list is unknown. */
|
|
61
61
|
export declare function introducedDependencies(to: ReleaseRecord, base: ReleaseRecord | null): string[] | null;
|
|
62
62
|
export declare function advisoriesSignal(item: CheckItem, facts: AdvisoryFacts): Signal;
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Why the hold does not apply to a clean `to`: every safe release on its line
|
|
65
|
+
* is inside the hold too, and every older release on it has an advisory, so
|
|
66
|
+
* holding back would leave only vulnerable releases to write.
|
|
67
|
+
*/
|
|
68
|
+
export interface HoldWaiver {
|
|
69
|
+
/** The release line judged ("7.x", "v0", "==3.1.*"). */
|
|
70
|
+
line: string;
|
|
71
|
+
/** Releases on the line past the hold, all with a known advisory. */
|
|
72
|
+
olderVulnerable: number;
|
|
73
|
+
}
|
|
74
|
+
export declare function releaseAgeSignal(item: CheckItem, to: ReleaseRecord, advisories: Signal, now: Date, waiver?: HoldWaiver): Signal;
|
|
64
75
|
export declare function publishTrustSignal(item: CheckItem, to: ReleaseRecord, base: ReturnType<typeof selectBaseline>): Signal;
|
|
65
76
|
export declare function installScriptSignal(item: CheckItem, to: ReleaseRecord, from: ReleaseRecord | null): Signal;
|
|
66
|
-
export declare function newDependenciesSignal(to: ReleaseRecord, base: ReturnType<typeof selectBaseline>, introduced: string[] | null, created: Map<string, CreatedAtFact>): Signal;
|
|
77
|
+
export declare function newDependenciesSignal(item: CheckItem, to: ReleaseRecord, base: ReturnType<typeof selectBaseline>, introduced: string[] | null, created: Map<string, CreatedAtFact>): Signal;
|
|
67
78
|
export declare function withdrawnSignal(item: CheckItem, to: ReleaseRecord): Signal;
|
|
79
|
+
/**
|
|
80
|
+
* The bump's semver class. A breaking range (a major, or below 1.0 a minor or
|
|
81
|
+
* 0.0.x patch) is `review`: the supply-chain signals say nothing about whether
|
|
82
|
+
* the project's code survives it. Until 6.0.2 this was information only, so
|
|
83
|
+
* vitest 3 -> 5, reqwest 0.12 -> 0.13 and stripe 22 -> 23 all came back
|
|
84
|
+
* "proceed" and the summary told the agent to apply them (2026-10-07 eval).
|
|
85
|
+
*/
|
|
68
86
|
export declare function upgradeTypeSignal(item: CheckItem): Signal;
|
|
69
|
-
export declare function upgradeType(from: string | undefined, to: string): "patch" | "minor" | "major" | "downgrade" | "none" | "new_dependency" | "unknown";
|
|
87
|
+
export declare function upgradeType(from: string | undefined, to: string, ecosystem?: CheckEcosystem): "patch" | "minor" | "major" | "downgrade" | "none" | "new_dependency" | "unknown";
|
|
70
88
|
/** The item verdict and its one-line reason, from the strongest signal effect. */
|
|
71
89
|
export declare function decide(signals: Signal[]): {
|
|
72
90
|
verdict: Verdict;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* another source was unreachable, but a missing source never lets the
|
|
10
10
|
* verdict fall through to `proceed`.
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import { compareIn, isPrereleaseIn, versionParts } from "./dependency-check-versions.js";
|
|
13
13
|
/** Verdict precedence, strongest first. */
|
|
14
14
|
const PRECEDENCE = ["avoid", "review", "unknown", "wait", "proceed"];
|
|
15
15
|
export const WAIT_DAYS = 3;
|
|
@@ -25,23 +25,25 @@ export function selectBaseline(index, item) {
|
|
|
25
25
|
note: rec ? `installed ${item.from}` : `installed version ${item.from} is not listed on the registry`,
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
const
|
|
28
|
+
const eco = item.ecosystem;
|
|
29
|
+
const target = versionParts(eco, item.to);
|
|
29
30
|
if (!target)
|
|
30
|
-
return { record: null, kind: "previous_on_line", note: "target version is not readable
|
|
31
|
+
return { record: null, kind: "previous_on_line", note: "target version is not readable" };
|
|
32
|
+
const targetPre = isPrereleaseIn(eco, item.to);
|
|
31
33
|
let best = null;
|
|
32
34
|
for (const rec of Object.values(index.releases)) {
|
|
33
35
|
if (rec.withdrawn)
|
|
34
36
|
continue;
|
|
35
|
-
const p =
|
|
36
|
-
if (!p || !sameLine(p, target) ||
|
|
37
|
+
const p = versionParts(eco, rec.version);
|
|
38
|
+
if (!p || !sameLine(p, target) || compareIn(eco, rec.version, item.to) >= 0)
|
|
37
39
|
continue;
|
|
38
|
-
if (
|
|
40
|
+
if (isPrereleaseIn(eco, rec.version) && !targetPre)
|
|
39
41
|
continue;
|
|
40
|
-
if (!best ||
|
|
41
|
-
best =
|
|
42
|
+
if (!best || compareIn(eco, rec.version, best.version) > 0)
|
|
43
|
+
best = rec;
|
|
42
44
|
}
|
|
43
45
|
return best
|
|
44
|
-
? { record: best
|
|
46
|
+
? { record: best, kind: "previous_on_line", note: `previous release on the same line, ${best.version}` }
|
|
45
47
|
: { record: null, kind: "previous_on_line", note: "first release on its line; nothing earlier to compare" };
|
|
46
48
|
}
|
|
47
49
|
/** Same compatibility line: same major, or same 0.minor below 1.0. */
|
|
@@ -95,7 +97,7 @@ function list(advisories) {
|
|
|
95
97
|
const shown = advisories.slice(0, 3).map(fmt).join(", ");
|
|
96
98
|
return advisories.length > 3 ? `${shown} and ${advisories.length - 3} more` : shown;
|
|
97
99
|
}
|
|
98
|
-
export function releaseAgeSignal(item, to, advisories, now) {
|
|
100
|
+
export function releaseAgeSignal(item, to, advisories, now, waiver) {
|
|
99
101
|
if (!to.publishedAt) {
|
|
100
102
|
return { id: "release_age", value: null, evidence: `The registry gives no publish time for ${item.to}.`, effect: "unknown" };
|
|
101
103
|
}
|
|
@@ -114,6 +116,16 @@ export function releaseAgeSignal(item, to, advisories, now) {
|
|
|
114
116
|
evidence: `${ageText}, under the ${WAIT_DAYS}-day hold, but it fixes ${fixed.join(", ")} affecting ${item.from}; a security fix is not held back.`,
|
|
115
117
|
};
|
|
116
118
|
}
|
|
119
|
+
if (waiver) {
|
|
120
|
+
return {
|
|
121
|
+
id: "release_age",
|
|
122
|
+
value: { ...value, hold_waived: "no_settled_safe_release" },
|
|
123
|
+
effect: "proceed",
|
|
124
|
+
evidence: `${ageText}, under the ${WAIT_DAYS}-day hold, but every release of ${waiver.line} older than that has a known advisory ` +
|
|
125
|
+
`(${waiver.olderVulnerable} of them) and no safe one is past the hold; a vulnerable release is the worse risk, so the hold is not applied. ` +
|
|
126
|
+
`Tell the user this version is ${value.days} day${value.days === 1 ? "" : "s"} old.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
117
129
|
return {
|
|
118
130
|
id: "release_age",
|
|
119
131
|
value,
|
|
@@ -122,6 +134,9 @@ export function releaseAgeSignal(item, to, advisories, now) {
|
|
|
122
134
|
};
|
|
123
135
|
}
|
|
124
136
|
export function publishTrustSignal(item, to, base) {
|
|
137
|
+
if (item.ecosystem === "PyPI" || item.ecosystem === "Go") {
|
|
138
|
+
return { id: "publish_trust", value: "not_applicable", evidence: to.trustEvidence, effect: "info" };
|
|
139
|
+
}
|
|
125
140
|
if (to.withdrawn === "unpublished") {
|
|
126
141
|
// Its publisher record is gone with it; comparing would report a "drop" we cannot see.
|
|
127
142
|
return { id: "publish_trust", value: null, evidence: `${item.to} was unpublished; its publish metadata is gone.`, effect: "info" };
|
|
@@ -144,7 +159,7 @@ export function publishTrustSignal(item, to, base) {
|
|
|
144
159
|
}
|
|
145
160
|
export function installScriptSignal(item, to, from) {
|
|
146
161
|
if (item.ecosystem !== "npm") {
|
|
147
|
-
return { id: "install_script_added", value: "not_applicable", evidence:
|
|
162
|
+
return { id: "install_script_added", value: "not_applicable", evidence: NO_INSTALL_SCRIPTS[item.ecosystem], effect: "info" };
|
|
148
163
|
}
|
|
149
164
|
const scripts = to.installScripts ?? [];
|
|
150
165
|
if (!item.from) {
|
|
@@ -174,7 +189,20 @@ export function installScriptSignal(item, to, from) {
|
|
|
174
189
|
evidence: scripts.length > 0 ? `Install scripts unchanged (${scripts.join(", ")}).` : "Neither version declares install-time scripts.",
|
|
175
190
|
};
|
|
176
191
|
}
|
|
177
|
-
|
|
192
|
+
const NO_INSTALL_SCRIPTS = {
|
|
193
|
+
"crates.io": "not applicable: npm only (crates run build.rs at build time, which the registry does not expose).",
|
|
194
|
+
PyPI: "not applicable: PyPI's index exposes no install hooks (an sdist-only release runs its build backend at install).",
|
|
195
|
+
Go: "not applicable: Go runs no package code when a module is downloaded.",
|
|
196
|
+
};
|
|
197
|
+
export function newDependenciesSignal(item, to, base, introduced, created) {
|
|
198
|
+
if (item.ecosystem === "PyPI" || item.ecosystem === "Go") {
|
|
199
|
+
return {
|
|
200
|
+
id: "new_dependencies",
|
|
201
|
+
value: "not_applicable",
|
|
202
|
+
evidence: `not applicable: per-release dependency lists are not read for ${item.ecosystem}.`,
|
|
203
|
+
effect: "info",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
178
206
|
if (!base.record) {
|
|
179
207
|
return { id: "new_dependencies", value: null, evidence: `No baseline to diff against: ${base.note}.`, effect: "info" };
|
|
180
208
|
}
|
|
@@ -222,20 +250,35 @@ export function withdrawnSignal(item, to) {
|
|
|
222
250
|
}
|
|
223
251
|
return { id: "yanked_or_deprecated", value: null, effect: "proceed", evidence: `${item.to} is neither yanked nor deprecated.` };
|
|
224
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* The bump's semver class. A breaking range (a major, or below 1.0 a minor or
|
|
255
|
+
* 0.0.x patch) is `review`: the supply-chain signals say nothing about whether
|
|
256
|
+
* the project's code survives it. Until 6.0.2 this was information only, so
|
|
257
|
+
* vitest 3 -> 5, reqwest 0.12 -> 0.13 and stripe 22 -> 23 all came back
|
|
258
|
+
* "proceed" and the summary told the agent to apply them (2026-10-07 eval).
|
|
259
|
+
*/
|
|
225
260
|
export function upgradeTypeSignal(item) {
|
|
226
|
-
const kind = upgradeType(item.from, item.to);
|
|
261
|
+
const kind = upgradeType(item.from, item.to, item.ecosystem);
|
|
227
262
|
const text = item.from ? `${item.from} -> ${item.to}: ${kind}` : `new dependency at ${item.to}`;
|
|
228
|
-
|
|
229
|
-
|
|
263
|
+
if (kind === "major") {
|
|
264
|
+
const zeroX = /^v?0\./.test(item.from ?? "") ? " (below 1.0 a minor bump is a major one)" : "";
|
|
265
|
+
return {
|
|
266
|
+
id: "upgrade_type",
|
|
267
|
+
value: kind,
|
|
268
|
+
evidence: `${text}${zeroX}: breaking version range, run upgrade_impact to see what changes for this project.`,
|
|
269
|
+
effect: "review",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return { id: "upgrade_type", value: kind, evidence: `${text}.`, effect: "info" };
|
|
230
273
|
}
|
|
231
|
-
export function upgradeType(from, to) {
|
|
274
|
+
export function upgradeType(from, to, ecosystem = "npm") {
|
|
232
275
|
if (!from)
|
|
233
276
|
return "new_dependency";
|
|
234
|
-
const a =
|
|
235
|
-
const b =
|
|
277
|
+
const a = versionParts(ecosystem, from);
|
|
278
|
+
const b = versionParts(ecosystem, to);
|
|
236
279
|
if (!a || !b)
|
|
237
280
|
return "unknown";
|
|
238
|
-
const cmp =
|
|
281
|
+
const cmp = compareIn(ecosystem, to, from);
|
|
239
282
|
if (cmp === 0)
|
|
240
283
|
return "none";
|
|
241
284
|
if (cmp < 0)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dependency_check version handling across npm, crates.io, PyPI and Go:
|
|
3
|
+
* reading `to` as an exact version, "latest", or a constraint, and finding
|
|
4
|
+
* the safe releases a constraint admits.
|
|
5
|
+
*
|
|
6
|
+
* Why a constraint: an agent told "pin ws 7.x" writes the 7.x release it
|
|
7
|
+
* remembers. In the G7 agent evaluation (2026-10-11) that was ws 7.5.10 and
|
|
8
|
+
* body-parser 1.20.3, both affected by an advisory their next patch fixes.
|
|
9
|
+
* Asked with `to: "7.x"`, the check answers with the smallest and newest
|
|
10
|
+
* release in the constraint that no known advisory affects.
|
|
11
|
+
*
|
|
12
|
+
* Constraint syntax is the ecosystem's own (npm ranges, Cargo requirements,
|
|
13
|
+
* PEP 440 specifiers), plus the shorthand every agent writes: "7.x", "3.1.*",
|
|
14
|
+
* "v1" for a Go major. Pure functions, no I/O.
|
|
15
|
+
*/
|
|
16
|
+
import { type AdvisoryRanges } from "../live/osv-ranges.js";
|
|
17
|
+
import type { CheckEcosystem, PackageReleaseIndex } from "../live/release-metadata.js";
|
|
18
|
+
export type Target = {
|
|
19
|
+
kind: "exact";
|
|
20
|
+
version: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "latest";
|
|
23
|
+
} | {
|
|
24
|
+
kind: "range";
|
|
25
|
+
range: string;
|
|
26
|
+
}
|
|
27
|
+
/** PyPI "3.1": the release 3.1 when it is published, else the 3.1.* series. */
|
|
28
|
+
| {
|
|
29
|
+
kind: "exact_or_series";
|
|
30
|
+
version: string;
|
|
31
|
+
range: string;
|
|
32
|
+
};
|
|
33
|
+
/** Read a `to` value; null when it is neither a version nor a constraint this ecosystem understands. */
|
|
34
|
+
export declare function parseTarget(ecosystem: CheckEcosystem, raw: string): Target | null;
|
|
35
|
+
/** Whether a constraint admits a version, in the ecosystem's own syntax. Null = unreadable constraint or version. */
|
|
36
|
+
export declare function rangeAdmits(ecosystem: CheckEcosystem, range: string, version: string): boolean | null;
|
|
37
|
+
/**
|
|
38
|
+
* PEP 440 version specifiers: comma-separated clauses of `==`, `!=` (both
|
|
39
|
+
* with optional `.*` prefix matching), `>=`, `<=`, `>`, `<`, `~=` and `===`.
|
|
40
|
+
* Null when a clause or the version is unreadable.
|
|
41
|
+
*/
|
|
42
|
+
export declare function pep440SpecAdmits(spec: string, version: string): boolean | null;
|
|
43
|
+
/** Order two versions of one ecosystem. */
|
|
44
|
+
export declare function compareIn(ecosystem: CheckEcosystem, a: string, b: string): number;
|
|
45
|
+
export declare function isPrereleaseIn(ecosystem: CheckEcosystem, version: string): boolean;
|
|
46
|
+
/** MAJOR.MINOR.PATCH of a version in any of the four ecosystems (PEP 440 release parts; missing ones 0). */
|
|
47
|
+
export declare function versionParts(ecosystem: CheckEcosystem, version: string): {
|
|
48
|
+
major: number;
|
|
49
|
+
minor: number;
|
|
50
|
+
patch: number;
|
|
51
|
+
} | null;
|
|
52
|
+
/**
|
|
53
|
+
* The release line a version belongs to, as a constraint: the same major (the
|
|
54
|
+
* same 0.minor below 1.0) on npm and crates.io; the same module major on Go,
|
|
55
|
+
* where v0 is one line (`go get -u` moves freely within a module path); the
|
|
56
|
+
* same X.Y series on PyPI, where many projects do not follow semver (Django
|
|
57
|
+
* 4.2 LTS, Jinja2 3.1).
|
|
58
|
+
*/
|
|
59
|
+
export declare function lineOf(ecosystem: CheckEcosystem, version: string): string | null;
|
|
60
|
+
export interface RangeCheck {
|
|
61
|
+
/** The constraint evaluated. */
|
|
62
|
+
range: string;
|
|
63
|
+
/** "requested" (the caller's `to`) or "same_line" (the line of an affected exact `to`). */
|
|
64
|
+
basis: "requested" | "same_line";
|
|
65
|
+
/** Installable, non-prerelease releases the constraint admits. */
|
|
66
|
+
releases_in_range: number;
|
|
67
|
+
/** How many of them a known advisory affects. */
|
|
68
|
+
vulnerable_in_range: number;
|
|
69
|
+
smallest_safe_in_range: string | null;
|
|
70
|
+
newest_safe_in_range: string | null;
|
|
71
|
+
/** Up to 8 of the newest safe releases, ascending. */
|
|
72
|
+
safe_versions: string[];
|
|
73
|
+
/** Advisories that affect every release in range (no safe choice inside it); at most 10 named. */
|
|
74
|
+
affecting_all?: string[];
|
|
75
|
+
/** When nothing in range is safe: the newest release of the package that is, outside the range. */
|
|
76
|
+
newest_safe_outside_range?: string | null;
|
|
77
|
+
/** When `from` is given: the smallest safe release above it (the minimal fixing upgrade). */
|
|
78
|
+
smallest_safe_upgrade?: string | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The releases a constraint admits, judged against every advisory OSV.dev
|
|
82
|
+
* lists for the package. Withdrawn (yanked / unpublished / retracted) and
|
|
83
|
+
* deprecated releases are not candidates; neither are prereleases, unless
|
|
84
|
+
* the constraint admits nothing else. Maintenance notices do not make a
|
|
85
|
+
* release unsafe.
|
|
86
|
+
*/
|
|
87
|
+
export declare function checkRange(input: {
|
|
88
|
+
ecosystem: CheckEcosystem;
|
|
89
|
+
index: PackageReleaseIndex;
|
|
90
|
+
range: string;
|
|
91
|
+
basis: RangeCheck["basis"];
|
|
92
|
+
advisories: AdvisoryRanges[];
|
|
93
|
+
from?: string;
|
|
94
|
+
}): RangeCheck;
|