@codyswann/lisa 2.279.2 → 2.280.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/dist/cli/doctor-readiness-audit-allowlist.d.ts +7 -0
- package/dist/cli/doctor-readiness-audit-allowlist.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-audit-allowlist.js +177 -0
- package/dist/cli/doctor-readiness-audit-allowlist.js.map +1 -0
- package/dist/cli/doctor-readiness-context-claims.d.ts +14 -0
- package/dist/cli/doctor-readiness-context-claims.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-context-claims.js +117 -0
- package/dist/cli/doctor-readiness-context-claims.js.map +1 -0
- package/dist/cli/doctor-readiness-context-mechanisms.d.ts +46 -0
- package/dist/cli/doctor-readiness-context-mechanisms.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-context-mechanisms.js +116 -0
- package/dist/cli/doctor-readiness-context-mechanisms.js.map +1 -0
- package/dist/cli/doctor-readiness-context.d.ts +16 -0
- package/dist/cli/doctor-readiness-context.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-context.js +244 -0
- package/dist/cli/doctor-readiness-context.js.map +1 -0
- package/dist/cli/doctor-readiness-delivery.d.ts.map +1 -1
- package/dist/cli/doctor-readiness-delivery.js +1 -10
- package/dist/cli/doctor-readiness-delivery.js.map +1 -1
- package/dist/cli/doctor-readiness-shared.d.ts +47 -0
- package/dist/cli/doctor-readiness-shared.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-shared.js +88 -0
- package/dist/cli/doctor-readiness-shared.js.map +1 -0
- package/dist/cli/doctor-readiness-supply-chain-scan.d.ts +67 -0
- package/dist/cli/doctor-readiness-supply-chain-scan.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-supply-chain-scan.js +316 -0
- package/dist/cli/doctor-readiness-supply-chain-scan.js.map +1 -0
- package/dist/cli/doctor-readiness-supply-chain.d.ts +14 -0
- package/dist/cli/doctor-readiness-supply-chain.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-supply-chain.js +231 -0
- package/dist/cli/doctor-readiness-supply-chain.js.map +1 -0
- package/dist/cli/doctor-readiness-workspaces.d.ts +22 -0
- package/dist/cli/doctor-readiness-workspaces.d.ts.map +1 -0
- package/dist/cli/doctor-readiness-workspaces.js +98 -0
- package/dist/cli/doctor-readiness-workspaces.js.map +1 -0
- package/dist/cli/doctor-readiness.d.ts.map +1 -1
- package/dist/cli/doctor-readiness.js +7 -3
- package/dist/cli/doctor-readiness.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +13 -0
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline supply-chain scanning for the dependencies/supply-chain readiness
|
|
3
|
+
* producer (B5, PRD #1739, #1896).
|
|
4
|
+
*
|
|
5
|
+
* The B5 question — does belief that the owned surface still works rest on more
|
|
6
|
+
* than hope — is answered from four repository facts: a committed lockfile, the
|
|
7
|
+
* dependency specs the manifest names, whether anything anywhere audits the
|
|
8
|
+
* tree, and whether each audit exception carries the decision behind it. This
|
|
9
|
+
* module gathers those facts; its sibling turns them into the rubric-shaped
|
|
10
|
+
* dimension record. Splitting them keeps each file inside the repository's
|
|
11
|
+
* file-size budget and keeps the gathering independently testable.
|
|
12
|
+
*
|
|
13
|
+
* Reading is offline and best-effort by construction: an unreadable file
|
|
14
|
+
* establishes nothing and is passed over, never reported as a violation.
|
|
15
|
+
* @module cli/doctor-readiness-supply-chain-scan
|
|
16
|
+
*/
|
|
17
|
+
import { readdir } from "node:fs/promises";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { isRecord, readFileOrNull } from "./doctor-readiness-shared.js";
|
|
20
|
+
/**
|
|
21
|
+
* Every lockfile spelling a JavaScript package manager writes. One of these
|
|
22
|
+
* being committed is what makes two installs resolve to the same tree.
|
|
23
|
+
*/
|
|
24
|
+
export const LOCKFILES = [
|
|
25
|
+
"bun.lock",
|
|
26
|
+
"bun.lockb",
|
|
27
|
+
"package-lock.json",
|
|
28
|
+
"npm-shrinkwrap.json",
|
|
29
|
+
"yarn.lock",
|
|
30
|
+
"pnpm-lock.yaml",
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Manifest blocks whose specs must name a version. `peerDependencies` is
|
|
34
|
+
* deliberately excluded: `*` there is the conventional way to say "any host
|
|
35
|
+
* version works", not a floating install.
|
|
36
|
+
*/
|
|
37
|
+
const VERSIONED_BLOCKS = [
|
|
38
|
+
"dependencies",
|
|
39
|
+
"devDependencies",
|
|
40
|
+
"optionalDependencies",
|
|
41
|
+
"overrides",
|
|
42
|
+
"resolutions",
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Specs that resolve to whatever is newest at install time. Deliberately narrow:
|
|
46
|
+
* a `>=` floor in `overrides` is the recommended way to force a patched
|
|
47
|
+
* transitive dependency, so treating ranges as floating would fault the very
|
|
48
|
+
* remediation the audit gate asks for.
|
|
49
|
+
*/
|
|
50
|
+
const FLOATING_SPECS = new Set([
|
|
51
|
+
"",
|
|
52
|
+
"*",
|
|
53
|
+
"x",
|
|
54
|
+
"X",
|
|
55
|
+
"*.*",
|
|
56
|
+
"*.*.*",
|
|
57
|
+
"latest",
|
|
58
|
+
"next",
|
|
59
|
+
"*.x",
|
|
60
|
+
]);
|
|
61
|
+
/**
|
|
62
|
+
* Protocols that resolve to something inside this repository rather than to a
|
|
63
|
+
* published version, so there is nothing to pin and nothing to drift.
|
|
64
|
+
*/
|
|
65
|
+
const LOCAL_PROTOCOLS = [
|
|
66
|
+
"workspace:",
|
|
67
|
+
"file:",
|
|
68
|
+
"link:",
|
|
69
|
+
"portal:",
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Protocols that clone a source tree. Without a ref these resolve to whatever
|
|
73
|
+
* the default branch holds at install time, which is the drift B5 cares about.
|
|
74
|
+
* Each alternative ends in `:` or `+` on purpose — a colonless `bitbucket`
|
|
75
|
+
* alternative would match the ordinary registry package `bitbucket-client`.
|
|
76
|
+
*/
|
|
77
|
+
const GIT_FETCH_PROTOCOL = /^(github:|gitlab:|bitbucket:|git\+|git:)/i;
|
|
78
|
+
/** An `http(s)` spec, which may clone a repository or download an archive. */
|
|
79
|
+
const HTTP_SPEC = /^https?:\/\//i;
|
|
80
|
+
/**
|
|
81
|
+
* Whether a source-fetch spec is anchored to one revision by a `#<sha|tag>`
|
|
82
|
+
* suffix. Written without a regex so it cannot backtrack on a hostile spec.
|
|
83
|
+
* @param spec - The declared spec text
|
|
84
|
+
* @returns True when a non-empty ref follows a `#`
|
|
85
|
+
*/
|
|
86
|
+
function hasImmutableRef(spec) {
|
|
87
|
+
const marker = spec.indexOf("#");
|
|
88
|
+
return marker >= 0 && spec.slice(marker + 1).trim() !== "";
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Whether a spec clones a git repository.
|
|
92
|
+
*
|
|
93
|
+
* The distinction this draws is the whole point: `git+https://…/widget.git`
|
|
94
|
+
* tracks a moving branch tip, but `https://…/widget-1.2.3.tgz` names one built
|
|
95
|
+
* archive that cannot become something newer tomorrow. Demanding a `#ref` of an
|
|
96
|
+
* archive URL would fault a repository that is already perfectly pinned.
|
|
97
|
+
* @param spec - The declared spec text
|
|
98
|
+
* @returns True when the spec resolves by cloning a repository
|
|
99
|
+
*/
|
|
100
|
+
function clonesRepository(spec) {
|
|
101
|
+
const beforeRef = spec.split("#")[0] ?? "";
|
|
102
|
+
return (GIT_FETCH_PROTOCOL.test(spec) ||
|
|
103
|
+
(HTTP_SPEC.test(spec) && beforeRef.toLowerCase().endsWith(".git")));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether a dependency spec resolves to whatever is newest at install time
|
|
107
|
+
* rather than to a version something was validated against.
|
|
108
|
+
*
|
|
109
|
+
* Four families are recognized, and the narrowness of each is deliberate — every
|
|
110
|
+
* over-eager rule here fails a correctly configured repository:
|
|
111
|
+
*
|
|
112
|
+
* - **Local protocols** resolve inside the repository and never float.
|
|
113
|
+
* - **Registry aliases** (`npm:left-pad@latest`) hide the real spec behind the
|
|
114
|
+
* alias, so the part after the version separator is re-tested.
|
|
115
|
+
* - **Repository clones** (`github:acme/widget`, `git+https://…/x.git`) float
|
|
116
|
+
* unless anchored to a `#<sha|tag>` revision, because the branch tip moves
|
|
117
|
+
* under the install. An archive URL (`…/widget-1.2.3.tgz`) is NOT a clone: it
|
|
118
|
+
* names one built artifact and is treated as pinned.
|
|
119
|
+
* - **Plain ranges** float only when they name no version at all. A `>=` floor
|
|
120
|
+
* in `overrides` is the recommended way to force a patched transitive
|
|
121
|
+
* dependency, so ranges are otherwise left alone.
|
|
122
|
+
* @param spec - The declared spec text
|
|
123
|
+
* @returns True when the spec floats
|
|
124
|
+
*/
|
|
125
|
+
export function isFloatingSpec(spec) {
|
|
126
|
+
const trimmed = spec.trim();
|
|
127
|
+
if (LOCAL_PROTOCOLS.some(protocol => trimmed.startsWith(protocol))) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (/^npm:/i.test(trimmed)) {
|
|
131
|
+
const separator = trimmed.lastIndexOf("@");
|
|
132
|
+
return separator > "npm:".length
|
|
133
|
+
? isFloatingSpec(trimmed.slice(separator + 1))
|
|
134
|
+
: true;
|
|
135
|
+
}
|
|
136
|
+
if (clonesRepository(trimmed)) {
|
|
137
|
+
return !hasImmutableRef(trimmed);
|
|
138
|
+
}
|
|
139
|
+
return FLOATING_SPECS.has(trimmed.toLowerCase());
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Commands and actions that actually audit the DEPENDENCY tree. A job merely
|
|
143
|
+
* titled "Security Scan" is deliberately not enough: a secret scanner proves
|
|
144
|
+
* nothing about the dependency tree, and counting it would be a false green —
|
|
145
|
+
* the worst direction to err for a confidence model.
|
|
146
|
+
*/
|
|
147
|
+
const AUDIT_GATE_PATTERN = /\b(npm|bun|yarn|pnpm)\s+audit\b|audit-ci|osv-scanner|dependency-review-action|\bsnyk\b|\btrivy\b|\bgrype\b/i;
|
|
148
|
+
/**
|
|
149
|
+
* An update bot covering the JavaScript dependency tree. A `dependabot.yml`
|
|
150
|
+
* that only watches `github-actions` never looks at `package.json`, so its mere
|
|
151
|
+
* presence is not a confidence model for the dependencies.
|
|
152
|
+
*/
|
|
153
|
+
const JS_ECOSYSTEM_PATTERN = /package-ecosystem:\s*["']?(npm|bun|yarn|pnpm)\b/i;
|
|
154
|
+
/** Files that may declare an audit gate in their text. */
|
|
155
|
+
const GATE_DIRECTORIES = [
|
|
156
|
+
path.join(".github", "workflows"),
|
|
157
|
+
".husky",
|
|
158
|
+
];
|
|
159
|
+
/** Single-file gate declarations checked by text match. */
|
|
160
|
+
const GATE_FILES = [
|
|
161
|
+
"lefthook.yml",
|
|
162
|
+
"lefthook.yaml",
|
|
163
|
+
".lefthook.yml",
|
|
164
|
+
];
|
|
165
|
+
/** Update bots whose mere presence is a standing confidence model. */
|
|
166
|
+
const UPDATE_BOT_FILES = [
|
|
167
|
+
path.join(".github", "dependabot.yml"),
|
|
168
|
+
path.join(".github", "dependabot.yaml"),
|
|
169
|
+
path.join(".github", "renovate.json"),
|
|
170
|
+
"renovate.json",
|
|
171
|
+
".renovaterc",
|
|
172
|
+
".renovaterc.json",
|
|
173
|
+
];
|
|
174
|
+
/**
|
|
175
|
+
* Whether a repository-relative path exists.
|
|
176
|
+
* @param root - Repository root
|
|
177
|
+
* @param relativePath - Repo-relative path
|
|
178
|
+
* @returns True when the file could be read
|
|
179
|
+
*/
|
|
180
|
+
async function fileExists(root, relativePath) {
|
|
181
|
+
return (await readFileOrNull(root, relativePath)) !== null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* List the files directly inside a repository-relative directory.
|
|
185
|
+
* @param root - Repository root
|
|
186
|
+
* @param relativeDir - Repo-relative directory
|
|
187
|
+
* @returns Repo-relative file paths (empty when the directory is absent)
|
|
188
|
+
*/
|
|
189
|
+
async function listDirectory(root, relativeDir) {
|
|
190
|
+
try {
|
|
191
|
+
const entries = await readdir(path.join(root, relativeDir), {
|
|
192
|
+
withFileTypes: true,
|
|
193
|
+
});
|
|
194
|
+
return entries
|
|
195
|
+
.filter(entry => entry.isFile())
|
|
196
|
+
.map(entry => path.join(relativeDir, entry.name))
|
|
197
|
+
.sort((a, b) => a.localeCompare(b));
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Read and parse `package.json`, distinguishing "absent" from "unparseable" so
|
|
205
|
+
* the SKIP reason can say which one happened.
|
|
206
|
+
* @param root - Repository root
|
|
207
|
+
* @returns The parsed manifest, or the reason it cannot be assessed
|
|
208
|
+
*/
|
|
209
|
+
export async function readManifest(root) {
|
|
210
|
+
const source = await readFileOrNull(root, "package.json");
|
|
211
|
+
if (source === null) {
|
|
212
|
+
return {
|
|
213
|
+
kind: "unassessable",
|
|
214
|
+
reason: "no `package.json` was found, so this repository declares no " +
|
|
215
|
+
"JavaScript dependency surface to build a confidence model for; " +
|
|
216
|
+
"supply-chain confidence is not established either way",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const parsed = JSON.parse(source);
|
|
221
|
+
if (!isRecord(parsed)) {
|
|
222
|
+
throw new Error("manifest is not a JSON object");
|
|
223
|
+
}
|
|
224
|
+
return { kind: "ok", manifest: parsed };
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
return {
|
|
228
|
+
kind: "unassessable",
|
|
229
|
+
reason: "`package.json` could not be parsed, so its dependency specs were " +
|
|
230
|
+
`never read and nothing about them is established: ${error instanceof Error ? error.message : String(error)}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Flatten one manifest block into `name → spec` pairs. `overrides` nests, so a
|
|
236
|
+
* nested map is walked rather than skipped — a floating spec one level down
|
|
237
|
+
* installs exactly as loosely as one at the top.
|
|
238
|
+
* @param block - Block name, e.g. `dependencies`
|
|
239
|
+
* @param value - The block's raw value
|
|
240
|
+
* @param prefix - Accumulated name path for nested overrides
|
|
241
|
+
* @returns Flattened specs
|
|
242
|
+
*/
|
|
243
|
+
function flattenBlock(block, value, prefix = "") {
|
|
244
|
+
if (!isRecord(value)) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
return Object.entries(value).flatMap(([name, spec]) => {
|
|
248
|
+
const qualified = prefix === "" ? name : `${prefix}.${name}`;
|
|
249
|
+
if (typeof spec === "string") {
|
|
250
|
+
return [{ block, name: qualified, spec }];
|
|
251
|
+
}
|
|
252
|
+
return flattenBlock(block, spec, qualified);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Flatten every versioned manifest block into one list of specs.
|
|
257
|
+
* @param manifest - Parsed `package.json`
|
|
258
|
+
* @returns Every declared spec across the versioned blocks
|
|
259
|
+
*/
|
|
260
|
+
export function collectSpecs(manifest) {
|
|
261
|
+
return VERSIONED_BLOCKS.flatMap(block => flattenBlock(block, manifest[block]));
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Find the committed lockfile, if any.
|
|
265
|
+
* @param root - Repository root
|
|
266
|
+
* @returns The lockfile name, or null when none is committed
|
|
267
|
+
*/
|
|
268
|
+
export async function findLockfile(root) {
|
|
269
|
+
for (const candidate of LOCKFILES) {
|
|
270
|
+
if (await fileExists(root, candidate)) {
|
|
271
|
+
return candidate;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Whether an update-bot config actually watches the JavaScript dependency tree.
|
|
278
|
+
* Dependabot declares an ecosystem per entry, so a config covering only
|
|
279
|
+
* `github-actions` is not a confidence model for `package.json`. Renovate has no
|
|
280
|
+
* such per-file declaration and defaults to every manager it detects, so its
|
|
281
|
+
* presence is taken at face value.
|
|
282
|
+
* @param botFile - Repo-relative bot config path
|
|
283
|
+
* @param source - The config's text
|
|
284
|
+
* @returns True when the bot covers the JavaScript dependencies
|
|
285
|
+
*/
|
|
286
|
+
function coversJavaScriptTree(botFile, source) {
|
|
287
|
+
return botFile.includes("dependabot")
|
|
288
|
+
? JS_ECOSYSTEM_PATTERN.test(source)
|
|
289
|
+
: true;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Find where the repository audits its dependency tree: a CI job, a git hook, a
|
|
293
|
+
* lefthook declaration, or an update bot.
|
|
294
|
+
* @param root - Repository root
|
|
295
|
+
* @returns The repo-relative path of the first gate found, or null
|
|
296
|
+
*/
|
|
297
|
+
export async function findAuditGate(root) {
|
|
298
|
+
for (const botFile of UPDATE_BOT_FILES) {
|
|
299
|
+
const source = await readFileOrNull(root, botFile);
|
|
300
|
+
if (source !== null && coversJavaScriptTree(botFile, source)) {
|
|
301
|
+
return botFile.split(path.sep).join("/");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const scanned = [
|
|
305
|
+
...(await Promise.all(GATE_DIRECTORIES.map(dir => listDirectory(root, dir)))).flat(),
|
|
306
|
+
...GATE_FILES,
|
|
307
|
+
];
|
|
308
|
+
for (const file of scanned) {
|
|
309
|
+
const source = await readFileOrNull(root, file);
|
|
310
|
+
if (source !== null && AUDIT_GATE_PATTERN.test(source)) {
|
|
311
|
+
return file.split(path.sep).join("/");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=doctor-readiness-supply-chain-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-readiness-supply-chain-scan.js","sourceRoot":"","sources":["../../src/cli/doctor-readiness-supply-chain-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReadinessDimensionRecord } from "./doctor-readiness-types.js";
|
|
2
|
+
/** The dependencies/supply-chain readiness dimension id (readiness-rubric). */
|
|
3
|
+
export declare const DEPENDENCIES_SUPPLY_CHAIN_DIMENSION_ID = "dependencies-supply-chain";
|
|
4
|
+
/**
|
|
5
|
+
* Assess the dependencies/supply-chain dimension: B5, "an owned compatibility or
|
|
6
|
+
* security surface has no confidence model". Offline by construction — it reads
|
|
7
|
+
* only the repository's manifest, lockfiles, CI declarations, git hooks, and
|
|
8
|
+
* audit allowlists, and degrades to a stated-reason SKIP wherever those files
|
|
9
|
+
* cannot settle the question.
|
|
10
|
+
* @param root - Project root to assess
|
|
11
|
+
* @returns The dependencies/supply-chain dimension record
|
|
12
|
+
*/
|
|
13
|
+
export declare function assessDependenciesSupplyChainDimension(root: string): Promise<ReadinessDimensionRecord>;
|
|
14
|
+
//# sourceMappingURL=doctor-readiness-supply-chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-readiness-supply-chain.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-readiness-supply-chain.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAE5E,+EAA+E;AAC/E,eAAO,MAAM,sCAAsC,8BACtB,CAAC;AAsL9B;;;;;;;;GAQG;AACH,wBAAsB,sCAAsC,CAC1D,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,wBAAwB,CAAC,CA8CnC"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dependencies/supply-chain readiness producer — ship blocker B5
|
|
3
|
+
* (PRD #1739, #1896).
|
|
4
|
+
*
|
|
5
|
+
* Dimension 6 of the `readiness-rubric` asks whether belief that the owned
|
|
6
|
+
* surface still works rests on more than hope. Four things, read offline from
|
|
7
|
+
* the repository's own files, decide that: the dependency tree resolves to the
|
|
8
|
+
* same versions twice (a lockfile), the manifest names versions rather than
|
|
9
|
+
* "whatever is newest" (no floating specs), something somewhere actually audits
|
|
10
|
+
* the tree (a CI scan, a git hook, or an update bot), and every audit exception
|
|
11
|
+
* carries the decision that made it acceptable.
|
|
12
|
+
*
|
|
13
|
+
* Two disciplines are load-bearing rather than stylistic:
|
|
14
|
+
*
|
|
15
|
+
* 1. **A finding names a `blocker` id ONLY on an actual violation.** The blocker
|
|
16
|
+
* engine stands a blocker up on any finding that names an id and carries
|
|
17
|
+
* evidence, regardless of the finding's status — so a clean repository's PASS
|
|
18
|
+
* finding must carry no `blocker` key, or a healthy repository reports
|
|
19
|
+
* NOT_READY.
|
|
20
|
+
* The scanning half — reading the manifest, the lockfile, the audit gates, and
|
|
21
|
+
* the audit allowlists — lives in `doctor-readiness-supply-chain-scan`; this
|
|
22
|
+
* module turns what it found into the rubric-shaped dimension record.
|
|
23
|
+
*
|
|
24
|
+
* 2. **Never manufacture RED or GREEN from absence.** A repository with no
|
|
25
|
+
* manifest, an unparseable manifest, or no declared dependencies owns no
|
|
26
|
+
* dependency surface this producer can speak to, so it renders a
|
|
27
|
+
* stated-reason SKIP — never a violation, and never a false-green PASS.
|
|
28
|
+
* @module cli/doctor-readiness-supply-chain
|
|
29
|
+
*/
|
|
30
|
+
import { informationalFindings } from "./doctor-readiness-shared.js";
|
|
31
|
+
import { auditExceptionViolations } from "./doctor-readiness-audit-allowlist.js";
|
|
32
|
+
import { collectSpecs, findAuditGate, findLockfile, isFloatingSpec, LOCKFILES, readManifest, } from "./doctor-readiness-supply-chain-scan.js";
|
|
33
|
+
import { resolveWorkspaceMembers, } from "./doctor-readiness-workspaces.js";
|
|
34
|
+
/** The dependencies/supply-chain readiness dimension id (readiness-rubric). */
|
|
35
|
+
export const DEPENDENCIES_SUPPLY_CHAIN_DIMENSION_ID = "dependencies-supply-chain";
|
|
36
|
+
/** The ship blocker for an owned surface with no confidence model. */
|
|
37
|
+
const SUPPLY_CHAIN_BLOCKER_ID = "B5";
|
|
38
|
+
/** Most evidence lines carried into a single finding, to keep it readable. */
|
|
39
|
+
const MAX_EVIDENCE_LINES = 12;
|
|
40
|
+
/**
|
|
41
|
+
* Build the evidence line for a manifest with no committed lockfile.
|
|
42
|
+
* @param specCount - How many specs the manifest declares
|
|
43
|
+
* @returns One evidence line
|
|
44
|
+
*/
|
|
45
|
+
function lockfileEvidence(specCount) {
|
|
46
|
+
return (`\`package.json\` declares ${specCount} dependency spec(s) but no lockfile ` +
|
|
47
|
+
`is committed (looked for ${LOCKFILES.join(", ")}) — two installs can ` +
|
|
48
|
+
"resolve to different trees, so what was validated is not provably what " +
|
|
49
|
+
"gets installed");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether a floating-looking spec is really a link to a package in this
|
|
53
|
+
* repository. `"@acme/utils": "*"` against a workspace member resolves to the
|
|
54
|
+
* local package, which is the workspace idiom rather than a floating install —
|
|
55
|
+
* so faulting it would fail every correctly configured monorepo.
|
|
56
|
+
*
|
|
57
|
+
* When workspaces are declared but no member name could be resolved (an
|
|
58
|
+
* unreadable child manifest, an unsupported glob), a bare `*` is exempted
|
|
59
|
+
* anyway: the repository has told us it links locally, and absence of proof is
|
|
60
|
+
* not proof of a violation.
|
|
61
|
+
* @param spec - The declared spec
|
|
62
|
+
* @param workspaces - What resolving the workspace members established
|
|
63
|
+
* @returns True when the spec links to a workspace member
|
|
64
|
+
*/
|
|
65
|
+
function linksWorkspaceMember(spec, workspaces) {
|
|
66
|
+
if (!workspaces.declared) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return (workspaces.names.has(spec.name) ||
|
|
70
|
+
(workspaces.names.size === 0 && spec.spec.trim() === "*"));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Assess a repository's dependency confidence model.
|
|
74
|
+
* @param root - Repository root
|
|
75
|
+
* @param specs - Declared dependency specs
|
|
76
|
+
* @param workspaces - What resolving the workspace members established
|
|
77
|
+
* @returns Evidence lines for violations, and non-blocking observations
|
|
78
|
+
*/
|
|
79
|
+
async function collectFindings(root, specs, workspaces) {
|
|
80
|
+
const lockfile = await findLockfile(root);
|
|
81
|
+
const auditGate = await findAuditGate(root);
|
|
82
|
+
const floating = specs.filter(spec => isFloatingSpec(spec.spec) && !linksWorkspaceMember(spec, workspaces));
|
|
83
|
+
return {
|
|
84
|
+
violations: [
|
|
85
|
+
...(lockfile === null ? [lockfileEvidence(specs.length)] : []),
|
|
86
|
+
...floating.map(spec => `\`package.json\` \`${spec.block}.${spec.name}\` is \`${spec.spec}\`, ` +
|
|
87
|
+
"which resolves to whatever is newest at install time rather than to " +
|
|
88
|
+
"a version anything was ever validated against"),
|
|
89
|
+
...(auditGate === null
|
|
90
|
+
? [
|
|
91
|
+
"no dependency-audit gate covering the JavaScript tree was found " +
|
|
92
|
+
"anywhere — no `npm`/`bun` audit step in `.github/workflows/*.yml`, " +
|
|
93
|
+
"none in a git hook, and no `dependabot.yml` npm entry or " +
|
|
94
|
+
"`renovate.json` — so a newly disclosed advisory in this tree " +
|
|
95
|
+
"would never be noticed by anything",
|
|
96
|
+
]
|
|
97
|
+
: []),
|
|
98
|
+
...(await auditExceptionViolations(root)),
|
|
99
|
+
],
|
|
100
|
+
observations: [
|
|
101
|
+
...(lockfile === null ? [] : [`Lockfile in use: \`${lockfile}\`.`]),
|
|
102
|
+
...(auditGate === null
|
|
103
|
+
? []
|
|
104
|
+
: [`Dependency-audit gate declared in \`${auditGate}\`.`]),
|
|
105
|
+
...(workspaces.declared ? [workspaceObservation(workspaces)] : []),
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* State what the workspace exemption actually did.
|
|
111
|
+
*
|
|
112
|
+
* The two cases must not be described in the same words. When member manifests
|
|
113
|
+
* resolved, named packages were exempted because they provably link locally.
|
|
114
|
+
* When none resolved, a bare `*` was exempted on the strength of the
|
|
115
|
+
* `workspaces` declaration alone — a deliberate refusal to manufacture a
|
|
116
|
+
* violation from absence, but NOT a resolved link, and reporting it as
|
|
117
|
+
* "0 package name(s) were exempted" would describe the opposite of what
|
|
118
|
+
* happened.
|
|
119
|
+
* @param workspaces - What resolving the workspace members established
|
|
120
|
+
* @returns One operator-language observation
|
|
121
|
+
*/
|
|
122
|
+
function workspaceObservation(workspaces) {
|
|
123
|
+
return workspaces.names.size > 0
|
|
124
|
+
? `Workspaces are declared, so ${workspaces.names.size} locally linked ` +
|
|
125
|
+
"package name(s) were exempted from the floating-spec check."
|
|
126
|
+
: "Workspaces are declared but no member manifests resolved offline, so " +
|
|
127
|
+
"bare `*` specs were treated as workspace links rather than as " +
|
|
128
|
+
"floating installs. Whether they really link locally is not established.";
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Build the rubric-shaped B5 finding from its evidence lines.
|
|
132
|
+
* @param violations - Evidence lines
|
|
133
|
+
* @returns The B5 finding
|
|
134
|
+
*/
|
|
135
|
+
function supplyChainFinding(violations) {
|
|
136
|
+
const shown = violations.slice(0, MAX_EVIDENCE_LINES);
|
|
137
|
+
const overflow = violations.length - shown.length;
|
|
138
|
+
return {
|
|
139
|
+
blocker: SUPPLY_CHAIN_BLOCKER_ID,
|
|
140
|
+
invariant_violated: "belief that the owned surface still works rests on a repeatable install " +
|
|
141
|
+
"and a standing audit, not on hope",
|
|
142
|
+
evidence: shown.join(" | ") +
|
|
143
|
+
(overflow > 0
|
|
144
|
+
? ` | (+${overflow} further finding(s) of the same kind)`
|
|
145
|
+
: ""),
|
|
146
|
+
why_proof_missed: "the test suite proves things about the code in the tree it happened to " +
|
|
147
|
+
"install, so a tree that drifts — or one carrying a known advisory nobody " +
|
|
148
|
+
"checks for — still reports green",
|
|
149
|
+
root_correction: "commit a lockfile, name a version for every dependency spec, run a " +
|
|
150
|
+
"dependency audit on every push, and record the decision behind each " +
|
|
151
|
+
"audit exception next to the exception itself",
|
|
152
|
+
machinery_to_remove: [
|
|
153
|
+
"audit exceptions kept alive with no written decision, which cost review " +
|
|
154
|
+
"attention every cycle and prove nothing",
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Describe what the spec check established, without overstating the bare-`*`
|
|
160
|
+
* workspace fallback as a resolved link.
|
|
161
|
+
* @param workspaces - What resolving the workspace members established
|
|
162
|
+
* @returns One clause for the PASS evidence
|
|
163
|
+
*/
|
|
164
|
+
function describeSpecCleanliness(workspaces) {
|
|
165
|
+
return workspaces.declared && workspaces.names.size === 0
|
|
166
|
+
? "every one names a version, except bare `*` specs exempted because " +
|
|
167
|
+
"workspaces are declared but no member manifests resolved offline"
|
|
168
|
+
: "every one names a version or links a workspace member";
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build the stated-reason SKIP for a repository with no assessable manifest.
|
|
172
|
+
* @param reason - Why the dimension was not assessed
|
|
173
|
+
* @returns The SKIP dimension record
|
|
174
|
+
*/
|
|
175
|
+
function skipRecord(reason) {
|
|
176
|
+
return {
|
|
177
|
+
id: DEPENDENCIES_SUPPLY_CHAIN_DIMENSION_ID,
|
|
178
|
+
status: "SKIP",
|
|
179
|
+
findings: [{ reason, skip: true }],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Assess the dependencies/supply-chain dimension: B5, "an owned compatibility or
|
|
184
|
+
* security surface has no confidence model". Offline by construction — it reads
|
|
185
|
+
* only the repository's manifest, lockfiles, CI declarations, git hooks, and
|
|
186
|
+
* audit allowlists, and degrades to a stated-reason SKIP wherever those files
|
|
187
|
+
* cannot settle the question.
|
|
188
|
+
* @param root - Project root to assess
|
|
189
|
+
* @returns The dependencies/supply-chain dimension record
|
|
190
|
+
*/
|
|
191
|
+
export async function assessDependenciesSupplyChainDimension(root) {
|
|
192
|
+
const outcome = await readManifest(root);
|
|
193
|
+
if (outcome.kind === "unassessable") {
|
|
194
|
+
return skipRecord(outcome.reason);
|
|
195
|
+
}
|
|
196
|
+
const specs = collectSpecs(outcome.manifest);
|
|
197
|
+
if (specs.length === 0) {
|
|
198
|
+
return skipRecord("`package.json` declares no dependencies, so this repository owns no " +
|
|
199
|
+
"third-party surface a confidence model could cover; supply-chain " +
|
|
200
|
+
"confidence is not established either way");
|
|
201
|
+
}
|
|
202
|
+
const workspaces = await resolveWorkspaceMembers(root, outcome.manifest);
|
|
203
|
+
const { violations, observations } = await collectFindings(root, specs, workspaces);
|
|
204
|
+
if (violations.length > 0) {
|
|
205
|
+
return {
|
|
206
|
+
id: DEPENDENCIES_SUPPLY_CHAIN_DIMENSION_ID,
|
|
207
|
+
status: "FAIL",
|
|
208
|
+
findings: [
|
|
209
|
+
supplyChainFinding(violations),
|
|
210
|
+
...informationalFindings(observations),
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
id: DEPENDENCIES_SUPPLY_CHAIN_DIMENSION_ID,
|
|
216
|
+
status: "PASS",
|
|
217
|
+
findings: [
|
|
218
|
+
{
|
|
219
|
+
evidence: `Inspected ${specs.length} dependency spec(s) in the root ` +
|
|
220
|
+
`\`package.json\`: ${describeSpecCleanliness(workspaces)}, a ` +
|
|
221
|
+
"lockfile is committed, a dependency-audit gate covering the " +
|
|
222
|
+
"JavaScript tree is declared, and every active audit exception " +
|
|
223
|
+
"carries a written decision. Workspace child manifests are not " +
|
|
224
|
+
"walked, so this speaks only to the root manifest.",
|
|
225
|
+
checked: [SUPPLY_CHAIN_BLOCKER_ID],
|
|
226
|
+
},
|
|
227
|
+
...informationalFindings(observations),
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=doctor-readiness-supply-chain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-readiness-supply-chain.js","sourceRoot":"","sources":["../../src/cli/doctor-readiness-supply-chain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** What resolving the repository's workspace members established. */
|
|
2
|
+
export interface WorkspaceMembers {
|
|
3
|
+
/** Whether the repository declares workspaces at all. */
|
|
4
|
+
readonly declared: boolean;
|
|
5
|
+
/** Names of the local packages the workspaces resolve to. */
|
|
6
|
+
readonly names: ReadonlySet<string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the local package names a repository's workspace globs expand to.
|
|
10
|
+
*
|
|
11
|
+
* This exists because `"@acme/utils": "*"` against a workspace member is the
|
|
12
|
+
* workspace-link idiom, not a floating install: it resolves to the package in
|
|
13
|
+
* this repository. Faulting it would fail every correctly configured monorepo.
|
|
14
|
+
* Glob support is deliberately shallow (a single trailing `*`), and an
|
|
15
|
+
* unresolvable glob simply contributes no names — absence never manufactures a
|
|
16
|
+
* violation.
|
|
17
|
+
* @param root - Repository root
|
|
18
|
+
* @param manifest - Parsed root `package.json`
|
|
19
|
+
* @returns Whether workspaces are declared, and the member names resolved
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveWorkspaceMembers(root: string, manifest: Record<string, unknown>): Promise<WorkspaceMembers>;
|
|
22
|
+
//# sourceMappingURL=doctor-readiness-workspaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-readiness-workspaces.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-readiness-workspaces.ts"],"names":[],"mappings":"AAwBA,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACrC;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAwB3B"}
|